2 * Copyright (c) 1998,1999,2000
3 * Traakan, Inc., Los Altos, CA
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice unmodified, this list of conditions, and the following
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #include "ndmagents.h"
40 #ifndef NDMOS_OPTION_NO_ROBOT_AGENT
41 #ifdef NDMOS_OPTION_ROBOT_SIMULATOR
43 #include "scsiconst.h"
45 #define ROBOT_CONTROLLER 0
54 ndmos_scsi_initialize (struct ndm_session *sess)
56 struct ndm_robot_agent * ra = &sess->robot_acb;
58 NDMOS_MACRO_ZEROFILL(&ra->sim_dir);
59 NDMOS_MACRO_ZEROFILL(&ra->scsi_state);
60 ra->scsi_state.error = NDMP9_DEV_NOT_OPEN_ERR;
61 ra->scsi_state.target_controller = ROBOT_CONTROLLER;
62 ra->scsi_state.target_id = ROBOT_ID;
63 ra->scsi_state.target_lun = ROBOT_LUN;
69 ndmos_scsi_sync_state (struct ndm_session *sess)
74 ndmos_scsi_open (struct ndm_session *sess, char *name)
77 struct ndm_robot_agent * ra = &sess->robot_acb;
79 if (!name || strlen(name) > sizeof(ra->sim_dir)-1)
80 return NDMP9_NO_DEVICE_ERR;
82 /* check that it's a directory */
83 if (stat (name, &st) < 0)
84 return NDMP9_NO_DEVICE_ERR;
85 if (!S_ISDIR(st.st_mode))
86 return NDMP9_NO_DEVICE_ERR;
88 strncpy(ra->sim_dir, name, sizeof(ra->sim_dir)-1);
89 ra->scsi_state.error = NDMP9_NO_ERR;
95 ndmos_scsi_close (struct ndm_session *sess)
97 ndmos_scsi_initialize(sess);
103 ndmos_scsi_set_target (struct ndm_session *sess)
105 return NDMP9_NOT_SUPPORTED_ERR;
110 ndmos_scsi_reset_device (struct ndm_session *sess)
112 struct ndm_robot_agent * ra = &sess->robot_acb;
115 return ra->scsi_state.error;
120 ndmos_scsi_reset_bus (struct ndm_session *sess)
122 return NDMP9_NOT_SUPPORTED_ERR;
126 * Robot state management
127 ****************************************************************
130 /* xxx_FIRST must be in order! */
135 #define DTE_FIRST 128
137 #define STORAGE_FIRST 1024
138 #define STORAGE_COUNT 10
140 #if (IE_FIRST+IE_COUNT > MTE_FIRST) \
141 || (MTE_FIRST+MTE_COUNT > DTE_FIRST) \
142 || (DTE_FIRST+MTE_COUNT > STORAGE_FIRST)
143 #error element addresses overlap or are in the wrong order
146 #define IS_IE_ADDR(a) ((a) >= IE_FIRST && (a) < IE_FIRST+IE_COUNT)
147 #define IS_MTE_ADDR(a) ((a) >= MTE_FIRST && (a) < MTE_FIRST+MTE_COUNT)
148 #define IS_DTE_ADDR(a) ((a) >= DTE_FIRST && (a) < DTE_FIRST+DTE_COUNT)
149 #define IS_STORAGE_ADDR(a) ((a) >= STORAGE_FIRST && (a) < STORAGE_FIRST+STORAGE_COUNT)
151 struct element_state {
160 struct element_state mte[MTE_COUNT];
161 struct element_state storage[STORAGE_COUNT];
162 struct element_state ie[IE_COUNT];
163 struct element_state dte[DTE_COUNT];
167 robot_state_init(struct robot_state *rs)
171 /* invent some nice data, with some nice voltags and whatnot */
173 NDMOS_API_BZERO(rs, sizeof(*rs));
175 /* (nothing to do for MTEs) */
177 for (i = 0; i < STORAGE_COUNT; i++) {
178 struct element_state *es = &rs->storage[i];
181 es->medium_type = 1; /* data */
182 es->source_element = 0;
183 snprintf(es->pvoltag, sizeof(es->pvoltag), "PTAG%02XXX ", i);
184 snprintf(es->avoltag, sizeof(es->avoltag), "ATAG%02XXX ", i);
187 /* (i/e are all empty) */
189 /* (dte's are all empty) */
193 robot_state_load(struct ndm_session *sess, struct robot_state *rs)
196 char filename[PATH_MAX];
198 /* N.B. writing a struct to disk like this isn't portable, but this
199 * is test code, so it's OK for now. */
201 snprintf(filename, sizeof filename, "%s/state", sess->robot_acb.sim_dir);
202 fd = open(filename, O_RDONLY, 0666);
204 robot_state_init(rs);
207 if (read(fd, (void *)rs, sizeof(*rs)) < sizeof(*rs)) {
208 robot_state_init(rs);
216 robot_state_save(struct ndm_session *sess, struct robot_state *rs)
219 char filename[PATH_MAX];
221 /* N.B. writing a struct to disk like this isn't portable, but this
222 * is test code, so it's OK for now. */
224 snprintf(filename, sizeof filename, "%s/state", sess->robot_acb.sim_dir);
225 fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0666);
228 if (write(fd, (void *)rs, sizeof(*rs)) < sizeof(*rs))
236 robot_state_move(struct ndm_session *sess, struct robot_state *rs, int src, int dest)
238 char src_filename[PATH_MAX];
239 struct element_state *src_elt;
240 char dest_filename[PATH_MAX];
241 struct element_state *dest_elt;
245 /* TODO: audit that the tape device is not using this volume right now */
247 ndmalogf(sess, 0, 3, "moving medium from %d to %d", src, dest);
249 if (IS_IE_ADDR(src)) {
250 src_elt = &rs->ie[src - IE_FIRST];
251 snprintf(src_filename, sizeof(src_filename), "%s/ie%d",
252 sess->robot_acb.sim_dir, src - IE_FIRST);
253 } else if (IS_DTE_ADDR(src)) {
254 src_elt = &rs->dte[src - DTE_FIRST];
255 snprintf(src_filename, sizeof(src_filename), "%s/drive%d",
256 sess->robot_acb.sim_dir, src - DTE_FIRST);
257 } else if (IS_STORAGE_ADDR(src)) {
258 src_elt = &rs->storage[src - STORAGE_FIRST];
259 snprintf(src_filename, sizeof(src_filename), "%s/slot%d",
260 sess->robot_acb.sim_dir, src - STORAGE_FIRST);
262 ndmalogf(sess, 0, 3, "invalid src address %d", src);
266 if (IS_IE_ADDR(dest)) {
267 dest_elt = &rs->ie[dest - IE_FIRST];
268 snprintf(dest_filename, sizeof(dest_filename), "%s/ie%d",
269 sess->robot_acb.sim_dir, dest - IE_FIRST);
270 } else if (IS_DTE_ADDR(dest)) {
271 dest_elt = &rs->dte[dest - DTE_FIRST];
272 snprintf(dest_filename, sizeof(dest_filename), "%s/drive%d",
273 sess->robot_acb.sim_dir, dest - DTE_FIRST);
274 } else if (IS_STORAGE_ADDR(dest)) {
275 dest_elt = &rs->storage[dest - STORAGE_FIRST];
276 snprintf(dest_filename, sizeof(dest_filename), "%s/slot%d",
277 sess->robot_acb.sim_dir, dest - STORAGE_FIRST);
279 ndmalogf(sess, 0, 3, "invalid dst address %d", src);
283 if (!src_elt->full) {
284 ndmalogf(sess, 0, 3, "src not full");
288 if (dest_elt->full) {
289 ndmalogf(sess, 0, 3, "dest full");
293 /* OK, enough checking, let's do it */
294 /* delete the destination, if it exists */
295 if (stat (dest_filename, &st) >= 0) {
296 ndmalogf(sess, 0, 3, "unlink %s", dest_filename);
297 if (unlink(dest_filename) < 0) {
298 ndmalogf(sess, 0, 0, "error unlinking: %s", strerror(errno));
303 /* and move the source if it exists */
304 if (stat (src_filename, &st) >= 0) {
305 ndmalogf(sess, 0, 3, "move %s to %s", src_filename, dest_filename);
306 if (rename(src_filename, dest_filename) < 0) {
307 ndmalogf(sess, 0, 0, "error renaming: %s", strerror(errno));
311 /* otherwise touch the destination file */
312 ndmalogf(sess, 0, 3, "touch %s", dest_filename);
313 int fd = open(dest_filename, O_CREAT | O_WRONLY, 0666);
315 ndmalogf(sess, 0, 0, "error touching: %s", strerror(errno));
321 /* blow away any tape-drive .pos files */
322 snprintf(pos, sizeof(pos), "%s.pos", src_filename);
323 unlink(pos); /* ignore errors */
324 snprintf(pos, sizeof(pos), "%s.pos", dest_filename);
325 unlink(pos); /* ignore errors */
328 *dest_elt = *src_elt;
329 ndmalogf(sess, 0, 3, "setting dest's source_element to %d", src);
330 dest_elt->source_element = src;
334 ndmalogf(sess, 0, 3, "move successful");
340 ****************************************************************
348 scsi_fail_with_sense_code(struct ndm_session *sess,
349 ndmp9_execute_cdb_reply *reply,
350 int status, int sense_key, int asq)
352 unsigned char ext_sense[] = {
353 0x72, /* current errors */
354 sense_key & SCSI_SENSE_SENSE_KEY_MASK,
362 ndmalogf(sess, 0, 3, "sending failure; status=0x%02x sense_key=0x%02x asq=0x%04x",
363 status, sense_key, asq);
365 reply->status = status;
366 reply->ext_sense.ext_sense_len = sizeof(ext_sense);
367 reply->ext_sense.ext_sense_val = NDMOS_API_MALLOC(sizeof(ext_sense));
368 NDMOS_API_BCOPY(ext_sense, reply->ext_sense.ext_sense_val, sizeof(ext_sense));
374 * Command implementations
378 execute_cdb_test_unit_ready (struct ndm_session *sess,
379 ndmp9_execute_cdb_request *request,
380 ndmp9_execute_cdb_reply *reply)
382 unsigned char *cdb = (unsigned char *)request->cdb.cdb_val;
387 if (request->cdb.cdb_len != 6)
388 return scsi_fail_with_sense_code(sess, reply,
389 SCSI_STATUS_CHECK_CONDITION,
390 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
391 ASQ_INVALID_FIELD_IN_CDB);
393 /* yep, we're ready! */
399 execute_cdb_inquiry (struct ndm_session *sess,
400 ndmp9_execute_cdb_request *request,
401 ndmp9_execute_cdb_reply *reply)
403 unsigned char *cdb = (unsigned char *)request->cdb.cdb_val;
408 /* N.B.: only page code 0 is supported */
409 if (request->cdb.cdb_len != 6
410 || request->data_dir != NDMP9_SCSI_DATA_DIR_IN
413 || request->datain_len < 96
414 || ((cdb[3] << 8) + cdb[4]) < 96)
415 return scsi_fail_with_sense_code(sess, reply,
416 SCSI_STATUS_CHECK_CONDITION,
417 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
418 ASQ_INVALID_FIELD_IN_CDB);
421 p = response = NDMOS_API_MALLOC(response_len);
422 NDMOS_API_BZERO(response, response_len);
423 *(p++) = 0x08; /* media changer */
424 *(p++) = 0; /* RMB=0 */
425 *(p++) = 6; /* VERSION=SPC-4 */
426 *(p++) = 2; /* !NORMACA, !HISUP, RESPONSE DATA FORMAT = 2 */
427 *(p++) = 92; /* remaining bytes */
428 *(p++) = 0; /* lots of flags, all 0 */
429 *(p++) = 0; /* lots of flags, all 0 */
430 *(p++) = 0; /* lots of flags, all 0 */
431 NDMOS_API_BCOPY("NDMJOB ", p, 8); p += 8;
432 NDMOS_API_BCOPY("FakeRobot ", p, 16); p += 16;
433 NDMOS_API_BCOPY("1.0 ", p, 4); p += 4;
434 /* remainder is zero */
436 reply->datain.datain_len = response_len;
437 reply->datain.datain_val = response;
443 execute_cdb_mode_sense_6 (struct ndm_session *sess,
444 ndmp9_execute_cdb_request *request,
445 ndmp9_execute_cdb_reply *reply)
447 unsigned char *cdb = (unsigned char *)request->cdb.cdb_val;
453 if (request->cdb.cdb_len != 6
454 || request->data_dir != NDMP9_SCSI_DATA_DIR_IN)
455 return scsi_fail_with_sense_code(sess, reply,
456 SCSI_STATUS_CHECK_CONDITION,
457 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
458 ASQ_INVALID_FIELD_IN_CDB);
459 page = cdb[2] & 0x3f;
462 switch ((page << 8) + subpage) {
463 case 0x1D00: /* Element Address Assignment */
464 if (request->datain_len < 20 || cdb[4] < 20)
465 return scsi_fail_with_sense_code(sess, reply,
466 SCSI_STATUS_CHECK_CONDITION,
467 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
468 ASQ_INVALID_FIELD_IN_CDB);
471 p = response = NDMOS_API_MALLOC(response_len);
472 NDMOS_API_BZERO(response, response_len);
473 *(p++) = response_len;
474 *(p++) = 0; /* reserved medium type */
475 *(p++) = 0; /* reserved device-specific parameter */
476 *(p++) = 0; /* block descriptor length (DBD = 0 above)*/
477 *(p++) = 0x1D; /* page code */
478 *(p++) = 18; /* remaining bytes */
479 *(p++) = (MTE_FIRST >> 8) & 0xff;
480 *(p++) = MTE_FIRST & 0xff;
481 *(p++) = (MTE_COUNT >> 8) & 0xff;
482 *(p++) = MTE_COUNT & 0xff;
483 *(p++) = (STORAGE_FIRST >> 8) & 0xff;
484 *(p++) = STORAGE_FIRST & 0xff;
485 *(p++) = (STORAGE_COUNT >> 8) & 0xff;
486 *(p++) = STORAGE_COUNT & 0xff;
487 *(p++) = (IE_FIRST >> 8) & 0xff;
488 *(p++) = IE_FIRST & 0xff;
489 *(p++) = (IE_COUNT >> 8) & 0xff;
490 *(p++) = IE_COUNT & 0xff;
491 *(p++) = (DTE_FIRST >> 8) & 0xff;
492 *(p++) = DTE_FIRST & 0xff;
493 *(p++) = (DTE_COUNT >> 8) & 0xff;
494 *(p++) = DTE_COUNT & 0xff;
495 /* remainder is zero */
499 return scsi_fail_with_sense_code(sess, reply,
500 SCSI_STATUS_CHECK_CONDITION,
501 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
502 ASQ_INVALID_FIELD_IN_CDB);
505 reply->datain.datain_len = response_len;
506 reply->datain.datain_val = response;
512 execute_cdb_read_element_status (struct ndm_session *sess,
513 ndmp9_execute_cdb_request *request,
514 ndmp9_execute_cdb_reply *reply)
516 unsigned char *cdb = (unsigned char *)request->cdb.cdb_val;
517 struct robot_state rs;
518 int min_addr, max_elts;
522 int num_elts = IE_COUNT + MTE_COUNT + DTE_COUNT + STORAGE_COUNT;
525 if (request->cdb.cdb_len != 12
526 || request->data_dir != NDMP9_SCSI_DATA_DIR_IN)
527 return scsi_fail_with_sense_code(sess, reply,
528 SCSI_STATUS_CHECK_CONDITION,
529 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
530 ASQ_INVALID_FIELD_IN_CDB);
531 min_addr = (cdb[2] << 8) + cdb[3];
532 max_elts = (cdb[4] << 8) + cdb[5];
533 response_len = (cdb[7] << 16) + (cdb[8] << 8) + cdb[9];
535 if (response_len < 8) {
536 return scsi_fail_with_sense_code(sess, reply,
537 SCSI_STATUS_CHECK_CONDITION,
538 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
539 ASQ_INVALID_FIELD_IN_CDB);
542 /* this is bogus, but we don't allow "partial" status requests */
543 if (min_addr > IE_FIRST || max_elts < num_elts) {
544 return scsi_fail_with_sense_code(sess, reply,
545 SCSI_STATUS_CHECK_CONDITION,
546 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
547 ASQ_INVALID_FIELD_IN_CDB);
550 robot_state_load(sess, &rs);
551 robot_state_save(sess, &rs);
553 /* calculate the total space required */
554 required_len = 8; /* element status data header */
556 required_len += 8; /* element status page header */
557 required_len += 12 * MTE_COUNT; /* element status descriptor w/o tags */
560 required_len += 8; /* element status page header */
561 required_len += 84 * STORAGE_COUNT; /* element status descriptor w/ tags */
564 required_len += 8; /* element status page header */
565 required_len += 84 * IE_COUNT; /* element status descriptor w/ tags */
568 required_len += 8; /* element status page header */
569 required_len += 84 * DTE_COUNT; /* element status descriptor w/ tags */
572 p = response = NDMOS_API_MALLOC(response_len);
573 NDMOS_API_BZERO(response, response_len);
575 /* write the element status data header */
576 *(p++) = IE_FIRST >> 8; /* first element address */
577 *(p++) = IE_FIRST & 0xff;
578 *(p++) = num_elts >> 8; /* number of elements */
579 *(p++) = num_elts & 0xff;
580 *(p++) = 0; /* reserved */
581 *(p++) = (required_len-8) >> 16; /* remaining byte count of report */
582 *(p++) = ((required_len-8) >> 8) & 0xff;
583 *(p++) = (required_len-8) & 0xff;
585 /* only fill in the rest if we have space */
586 if (required_len <= response_len) {
589 int first, count, have_voltags, eltype;
590 int empty_flags, full_flags;
591 struct element_state *es;
593 { IE_FIRST, IE_COUNT, 1, 3, 0x38, 0x39, &rs.ie[0] },
594 { MTE_FIRST, MTE_COUNT, 0, 1, 0x00, 0x01, &rs.mte[0] },
595 { DTE_FIRST, DTE_COUNT, 1, 4, 0x08, 0x81, &rs.dte[0] },
596 { STORAGE_FIRST, STORAGE_COUNT, 1, 2, 0x08, 0x09, &rs.storage[0] },
599 for (i = 0; i < 4; i++) {
600 int descr_size = page[i].have_voltags? 84 : 12;
601 int totalsize = descr_size * page[i].count;
604 if (page[i].count == 0)
607 /* write the page header */
608 *(p++) = page[i].eltype;
609 *(p++) = page[i].have_voltags? 0xc0 : 0;
612 *(p++) = 0; /* reserved */
613 *(p++) = totalsize >> 16;
614 *(p++) = (totalsize >> 8) & 0xff;
615 *(p++) = totalsize & 0xff;
617 /* and write each descriptor */
618 for (j = 0; j < page[i].count; j++) {
619 int elt_addr = page[i].first + j;
620 int src_elt = page[i].es[j].source_element;
621 unsigned char byte9 = page[i].es[j].medium_type;
623 byte9 |= 0x80; /* SVALID */
625 *(p++) = elt_addr >> 8;
626 *(p++) = elt_addr & 0xff;
627 *(p++) = page[i].es[j].full?
628 page[i].full_flags : page[i].empty_flags;
636 *(p++) = src_elt >> 8;
637 *(p++) = src_elt & 0xff;
639 if (page[i].have_voltags) {
641 if (page[i].es[j].full) {
642 for (k = 0; k < 32; k++) {
643 if (!page[i].es[j].pvoltag[k])
645 p[k] = page[i].es[j].pvoltag[k];
647 for (k = 0; k < 32; k++) {
648 if (!page[i].es[j].avoltag[k])
650 p[k+36] = page[i].es[j].avoltag[k];
653 for (k = 0; k < 32; k++) {
654 p[k] = p[k+36] = ' ';
663 reply->datain.datain_len = response_len;
664 reply->datain.datain_val = response;
670 execute_cdb_move_medium (struct ndm_session *sess,
671 ndmp9_execute_cdb_request *request,
672 ndmp9_execute_cdb_reply *reply)
674 unsigned char *cdb = (unsigned char *)request->cdb.cdb_val;
675 struct robot_state rs;
678 if (request->cdb.cdb_len != 12)
679 return scsi_fail_with_sense_code(sess, reply,
680 SCSI_STATUS_CHECK_CONDITION,
681 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
682 ASQ_INVALID_FIELD_IN_CDB);
683 mte = (cdb[2] << 8) + cdb[3];
684 src = (cdb[4] << 8) + cdb[5];
685 dest = (cdb[6] << 8) + cdb[7];
687 if (!IS_MTE_ADDR(mte))
688 return scsi_fail_with_sense_code(sess, reply,
689 SCSI_STATUS_CHECK_CONDITION,
690 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
691 ASQ_INVALID_ELEMENT_ADDRESS);
693 robot_state_load(sess, &rs);
694 if (robot_state_move(sess, &rs, src, dest) < 0)
695 return scsi_fail_with_sense_code(sess, reply,
696 SCSI_STATUS_CHECK_CONDITION,
697 SCSI_SENSE_KEY_ILLEGAL_REQUEST,
698 ASQ_INVALID_ELEMENT_ADDRESS);
699 robot_state_save(sess, &rs);
706 ndmp9_error (* execute_cdb)(
707 struct ndm_session *sess,
708 ndmp9_execute_cdb_request *request,
709 ndmp9_execute_cdb_reply *reply);
710 } cdb_executors[] = {
711 { SCSI_CMD_TEST_UNIT_READY, execute_cdb_test_unit_ready },
712 { SCSI_CMD_INQUIRY, execute_cdb_inquiry },
713 { SCSI_CMD_MODE_SENSE_6, execute_cdb_mode_sense_6 },
714 { SCSI_CMD_READ_ELEMENT_STATUS, execute_cdb_read_element_status },
715 { SCSI_CMD_MOVE_MEDIUM, execute_cdb_move_medium },
720 ndmos_scsi_execute_cdb (struct ndm_session *sess,
721 ndmp9_execute_cdb_request *request,
722 ndmp9_execute_cdb_reply *reply)
724 struct ndm_robot_agent * ra = &sess->robot_acb;
728 if (ra->scsi_state.error != NDMP9_NO_ERR)
729 return ra->scsi_state.error;
731 if (request->cdb.cdb_len < 1)
732 return NDMP9_ILLEGAL_ARGS_ERR;
734 cdb_byte = request->cdb.cdb_val[0];
735 for (i = 0; cdb_executors[i].execute_cdb; i++) {
736 if (cdb_executors[i].cdb_byte == cdb_byte)
737 return cdb_executors[i].execute_cdb(sess, request, reply);
740 return NDMP9_ILLEGAL_ARGS_ERR;
743 #endif /* NDMOS_OPTION_ROBOT_SIMULATOR */
745 #endif /* !NDMOS_OPTION_NO_ROBOT_AGENT */