1 /*
2  * Copyright (c) 1998,1999,2000
3  *	Traakan, Inc., Los Altos, CA
4  *	All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice unmodified, this list of conditions, and the following
11  *    disclaimer.
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.
15  *
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
26  * SUCH DAMAGE.
27  */
28 
29 /*
30  * Project:  NDMJOB
31  * Ident:    $Id: $
32  *
33  * Description:
34  *
35  */
36 
37 
38 #include "ndmagents.h"
39 
40 
41 #ifndef NDMOS_OPTION_NO_TAPE_AGENT
42 
43 
44 /*
45  * Initialization and Cleanup
46  ****************************************************************
47  */
48 
49 /* Initialize -- Set data structure to know value, ignore current value */
50 int
ndmta_initialize(struct ndm_session * sess)51 ndmta_initialize (struct ndm_session *sess)
52 {
53 	struct ndm_tape_agent *	ta = &sess->tape_acb;
54 	int			rc;
55 
56 	NDMOS_MACRO_ZEROFILL(ta);
57 
58 	ndmta_commission (sess);
59 
60 	rc = ndmos_tape_initialize (sess);
61 	if (rc) return rc;
62 
63 	return 0;
64 }
65 
66 /* Commission -- Get agent ready. Entire session has been initialize()d */
67 int
ndmta_commission(struct ndm_session * sess)68 ndmta_commission (struct ndm_session *sess)
69 {
70 	ndmta_init_mover_state (sess);
71 	return 0;
72 }
73 
74 /* Decommission -- Discard agent */
75 int
ndmta_decommission(struct ndm_session * sess)76 ndmta_decommission (struct ndm_session *sess)
77 {
78 	ndmis_tape_close (sess);
79 	ndmta_commission (sess);
80 
81 	return 0;
82 }
83 
84 /* helper for mover state */
85 int
ndmta_init_mover_state(struct ndm_session * sess)86 ndmta_init_mover_state (struct ndm_session *sess)
87 {
88 	struct ndm_tape_agent *	ta = &sess->tape_acb;
89 
90 	NDMOS_MACRO_ZEROFILL (&ta->mover_state);
91 
92 	ta->mover_state.state = NDMP9_MOVER_STATE_IDLE;
93 	ta->mover_state.window_offset = 0;
94 	ta->mover_state.record_num = 0; /* this should probably be -1, but spec says 0 */
95 	ta->mover_state.record_size = 20*512;	/* traditional tar default */
96 	ta->mover_state.window_length = NDMP_LENGTH_INFINITY;
97 	ta->mover_window_end = NDMP_LENGTH_INFINITY;
98 	ta->mover_want_pos = 0;
99 
100 	ta->tb_blockno = -1;
101 
102 	return 0;
103 }
104 
105 
106 
107 /*
108  * Semantic actions -- called from ndma_dispatch()
109  ****************************************************************
110  */
111 
112 void
ndmta_mover_sync_state(struct ndm_session * sess)113 ndmta_mover_sync_state (struct ndm_session *sess)
114 {
115 	ndmos_tape_sync_state (sess);
116 }
117 
118 ndmp9_error
ndmta_mover_listen(struct ndm_session * sess,ndmp9_mover_mode mover_mode)119 ndmta_mover_listen (struct ndm_session *sess, ndmp9_mover_mode mover_mode)
120 {
121 	struct ndm_tape_agent *	ta = &sess->tape_acb;
122 
123 	ta->mover_state.mode = mover_mode;
124 
125 	ta->mover_state.state = NDMP9_MOVER_STATE_LISTEN;
126 	ta->mover_state.halt_reason = NDMP9_MOVER_HALT_NA;
127 	ta->mover_state.pause_reason = NDMP9_MOVER_PAUSE_NA;
128 
129 	return NDMP9_NO_ERR;
130 }
131 
132 ndmp9_error
ndmta_mover_connect(struct ndm_session * sess,ndmp9_mover_mode mover_mode)133 ndmta_mover_connect (struct ndm_session *sess, ndmp9_mover_mode mover_mode)
134 {
135 	struct ndm_tape_agent *	ta = &sess->tape_acb;
136 
137 	ta->mover_state.mode = mover_mode;
138 
139 	ndmta_mover_start_active (sess);
140 
141 	return NDMP9_NO_ERR;
142 }
143 
144 void
ndmta_mover_halt(struct ndm_session * sess,ndmp9_mover_halt_reason reason)145 ndmta_mover_halt (struct ndm_session *sess, ndmp9_mover_halt_reason reason)
146 {
147 	struct ndm_tape_agent *	ta = &sess->tape_acb;
148 
149 	ta->mover_state.state = NDMP9_MOVER_STATE_HALTED;
150 	ta->mover_state.halt_reason = reason;
151 	ta->mover_state.pause_reason = NDMP9_MOVER_PAUSE_NA;
152 	ta->pending_change_after_drain = 0;
153 	ta->mover_notify_pending = 1;
154 
155 	ndmis_tape_close (sess);
156 }
157 
158 void
ndmta_mover_pause(struct ndm_session * sess,ndmp9_mover_pause_reason reason)159 ndmta_mover_pause (struct ndm_session *sess, ndmp9_mover_pause_reason reason)
160 {
161 	struct ndm_tape_agent *	ta = &sess->tape_acb;
162 
163 	ta->mover_state.state = NDMP9_MOVER_STATE_PAUSED;
164 	ta->mover_state.halt_reason = NDMP9_MOVER_HALT_NA;
165 	ta->mover_state.pause_reason = reason;
166 	ta->pending_change_after_drain = 0;
167 	ta->mover_notify_pending = 1;
168 }
169 
170 void
ndmta_mover_pending(struct ndm_session * sess,ndmp9_mover_state pending_state,ndmp9_mover_halt_reason halt_reason,ndmp9_mover_pause_reason pause_reason)171 ndmta_mover_pending (struct ndm_session *sess,
172   ndmp9_mover_state pending_state,
173   ndmp9_mover_halt_reason halt_reason,
174   ndmp9_mover_pause_reason pause_reason)
175 {
176 	struct ndm_tape_agent *	ta = &sess->tape_acb;
177 
178 	if (ta->pending_change_after_drain) {
179 		/* internal botch */
180 	}
181 
182 	ta->pending_mover_state = pending_state;
183 	ta->pending_mover_halt_reason = halt_reason;
184 	ta->pending_mover_pause_reason = pause_reason;
185 	ta->pending_change_after_drain = 1;
186 }
187 
188 void
ndmta_mover_apply_pending(struct ndm_session * sess)189 ndmta_mover_apply_pending (struct ndm_session *sess)
190 {
191 	struct ndm_tape_agent *	ta = &sess->tape_acb;
192 
193 	if (!ta->pending_change_after_drain) {
194 		/* internal botch */
195 	}
196 
197 	ta->mover_state.state = ta->pending_mover_state;
198 	ta->mover_state.halt_reason = ta->pending_mover_halt_reason;
199 	ta->mover_state.pause_reason = ta->pending_mover_pause_reason;
200 	ta->pending_change_after_drain = 0;
201 	ta->mover_notify_pending = 1;
202 }
203 
204 void
ndmta_mover_halt_pending(struct ndm_session * sess,ndmp9_mover_halt_reason halt_reason)205 ndmta_mover_halt_pending (struct ndm_session *sess,
206   ndmp9_mover_halt_reason halt_reason)
207 {
208 	ndmta_mover_pending (sess, NDMP9_MOVER_STATE_HALTED,
209 		halt_reason, NDMP9_MOVER_PAUSE_NA);
210 }
211 
212 void
ndmta_mover_pause_pending(struct ndm_session * sess,ndmp9_mover_pause_reason pause_reason)213 ndmta_mover_pause_pending (struct ndm_session *sess,
214   ndmp9_mover_pause_reason pause_reason)
215 {
216 	ndmta_mover_pending (sess, NDMP9_MOVER_STATE_PAUSED,
217 		NDMP9_MOVER_HALT_NA, pause_reason);
218 }
219 
220 void
ndmta_mover_active(struct ndm_session * sess)221 ndmta_mover_active (struct ndm_session *sess)
222 {
223 	struct ndm_tape_agent *	ta = &sess->tape_acb;
224 
225 	ta->mover_state.state = NDMP9_MOVER_STATE_ACTIVE;
226 	ta->mover_state.halt_reason = NDMP9_MOVER_HALT_NA;
227 	ta->mover_state.pause_reason = NDMP9_MOVER_PAUSE_NA;
228 
229 	ta->tb_blockno = -1;	/* always mistrust after activating */
230 }
231 
232 void
ndmta_mover_start_active(struct ndm_session * sess)233 ndmta_mover_start_active (struct ndm_session *sess)
234 {
235 	struct ndm_tape_agent *	ta = &sess->tape_acb;
236 
237 	ndmalogf (sess, 0, 6, "mover going active");
238 	ndma_send_logmsg(sess, NDMP9_LOG_DEBUG, sess->plumb.control,
239 		"mover going active");
240 
241 	switch (ta->mover_state.mode) {
242 	case NDMP9_MOVER_MODE_READ:
243 		ndmis_tape_start (sess, NDMCHAN_MODE_READ);
244 		ndmta_mover_active (sess);
245 		break;
246 
247 	case NDMP9_MOVER_MODE_WRITE:
248 		ndmis_tape_start (sess, NDMCHAN_MODE_WRITE);
249 		ndmta_mover_active (sess);
250 		break;
251 
252 	default:
253 		ndmalogf (sess, 0, 0, "BOTCH mover listen, unknown mode");
254 		break;
255 	}
256 }
257 
258 void
ndmta_mover_stop(struct ndm_session * sess)259 ndmta_mover_stop (struct ndm_session *sess)
260 {
261 	ndmta_init_mover_state (sess);
262 }
263 
264 void
ndmta_mover_abort(struct ndm_session * sess)265 ndmta_mover_abort (struct ndm_session *sess)
266 {
267 	ndmta_mover_halt (sess, NDMP9_MOVER_HALT_ABORTED);
268 }
269 
270 void
ndmta_mover_continue(struct ndm_session * sess)271 ndmta_mover_continue (struct ndm_session *sess)
272 {
273 	ndmta_mover_active (sess);
274 }
275 
276 void
ndmta_mover_close(struct ndm_session * sess)277 ndmta_mover_close (struct ndm_session *sess)
278 {
279 	struct ndm_tape_agent *	ta = &sess->tape_acb;
280 
281 	if (ta->mover_state.state != NDMP9_MOVER_STATE_HALTED)
282 		ndmta_mover_halt (sess, NDMP9_MOVER_HALT_CONNECT_CLOSED);
283 }
284 
285 void
ndmta_mover_read(struct ndm_session * sess,unsigned long long offset,unsigned long long length)286 ndmta_mover_read (struct ndm_session *sess,
287   unsigned long long offset, unsigned long long length)
288 {
289 	struct ndm_tape_agent *	ta = &sess->tape_acb;
290 
291 	ta->mover_state.seek_position = offset;
292 	ta->mover_state.bytes_left_to_read = length;
293 	ta->mover_want_pos = offset;
294 }
295 
296 
297 
298 /*
299  * Quantum -- get a bit of work done
300  ****************************************************************
301  */
302 
303 int
ndmta_quantum(struct ndm_session * sess)304 ndmta_quantum (struct ndm_session *sess)
305 {
306 	struct ndm_tape_agent *	ta = &sess->tape_acb;
307 	int			rc = 0;	/* did nothing */
308 
309 	switch (ta->mover_state.state) {
310 	default:
311 		ndmalogf (sess, 0, 0, "BOTCH mover state");
312 		return -1;
313 
314 	case NDMP9_MOVER_STATE_IDLE:
315 	case NDMP9_MOVER_STATE_PAUSED:
316 	case NDMP9_MOVER_STATE_HALTED:
317 		break;
318 
319 	case NDMP9_MOVER_STATE_LISTEN:
320 		switch (sess->plumb.image_stream.tape_ep.connect_status) {
321 		case NDMIS_CONN_LISTEN:		/* no connection yet */
322 			break;
323 
324 		case NDMIS_CONN_ACCEPTED:	/* we're in business */
325 			ndmta_mover_start_active (sess);
326 			rc = 1;		/* did something */
327 			break;
328 
329 		case NDMIS_CONN_BOTCHED:	/* accept() went south */
330 		default:			/* ain't suppose to happen */
331 			ndmta_mover_halt(sess,NDMP9_MOVER_HALT_CONNECT_ERROR);
332 			break;
333 		}
334 		break;
335 
336 	case NDMP9_MOVER_STATE_ACTIVE:
337 		switch (ta->mover_state.mode) {
338 		case NDMP9_MOVER_MODE_READ:
339 			rc = ndmta_read_quantum (sess);
340 			break;
341 
342 		case NDMP9_MOVER_MODE_WRITE:
343 			rc = ndmta_write_quantum (sess);
344 			break;
345 
346 		default:
347 			ndmalogf (sess, 0, 0,
348 				"BOTCH mover active, unknown mode");
349 			return -1;
350 		}
351 		break;
352 	}
353 
354 	ndmta_mover_send_notice (sess);
355 
356 	return rc;
357 }
358 
359 int
ndmta_read_quantum(struct ndm_session * sess)360 ndmta_read_quantum (struct ndm_session *sess)
361 {
362 	struct ndm_tape_agent *	ta = &sess->tape_acb;
363 	struct ndmchan *	ch = &sess->plumb.image_stream.chan;
364 	unsigned long		count = ta->mover_state.record_size;
365 	int			did_something = 0;
366 	unsigned		n_ready;
367 	char *			data;
368 	unsigned long		done_count;
369 	ndmp9_error		error;
370 
371   again:
372 	n_ready = ndmchan_n_ready (ch);
373 	if (ch->eof) {
374 		if (n_ready == 0) {
375 			/* done */
376 			if (ch->saved_errno)
377 				ndmta_mover_halt (sess,
378 					NDMP9_MOVER_HALT_CONNECT_ERROR);
379 			else
380 				ndmta_mover_halt (sess,
381 					NDMP9_MOVER_HALT_CONNECT_CLOSED);
382 
383 			did_something++;
384 
385 			return did_something;
386 		}
387 
388 		if (n_ready < count) {
389 			int		n_pad = count - n_ready;
390 			int		n_avail;
391 
392 
393 			while (n_pad > 0) {
394 				n_avail = ndmchan_n_avail (ch);
395 				if (n_avail == 0) {
396 					/* Uh-oh */
397 				}
398 				data = &ch->data[ch->end_ix];
399 				if (n_avail > n_pad)
400 					n_avail = n_pad;
401 				bzero (data, n_avail);
402 				ch->end_ix += n_avail;
403 				n_pad -= n_avail;
404 			}
405 			n_ready = ndmchan_n_ready (ch);
406 		}
407 	}
408 
409 	if (n_ready < count) {
410 		return did_something;	/* blocked */
411 	}
412 
413 	if (ta->mover_want_pos >= ta->mover_window_end) {
414 		ndmta_mover_pause (sess, NDMP9_MOVER_PAUSE_SEEK);
415 		did_something++;
416 		return did_something;
417 	}
418 
419 	data = &ch->data[ch->beg_ix];
420 	done_count = 0;
421 
422 	error = ndmos_tape_write (sess, data, count, &done_count);
423 
424 	switch (error) {
425 	case NDMP9_NO_ERR:
426 		if (done_count != count) {
427 			/* This ain't suppose to happen */
428 		}
429 		ta->mover_state.bytes_moved += count;
430 		/* note this is calculated before mover_want_pos is incremented, since
431 		 * record_num is the *last* block processed */
432 		/* ta->mover_state.record_num = ta->mover_want_pos / ta->mover_state.record_size; */
433 		/* It was a bug, set record_num after mover_want_pos */
434 		ta->mover_want_pos += count;
435 		ta->mover_state.record_num = ta->mover_want_pos / ta->mover_state.record_size;
436 		ch->beg_ix += count;
437 		did_something++;
438 		goto again;	/* write as much to tape as possible */
439 
440 	case NDMP9_EOM_ERR:
441 		ndmta_mover_pause (sess, NDMP9_MOVER_PAUSE_EOM);
442 		did_something++;
443 		break;
444 
445 	default:
446 		ndmta_mover_halt (sess, NDMP9_MOVER_HALT_MEDIA_ERROR);
447 		did_something++;
448 		break;
449 	}
450 
451 	return did_something;
452 }
453 
454 
455 int
ndmta_write_quantum(struct ndm_session * sess)456 ndmta_write_quantum (struct ndm_session *sess)
457 {
458 	struct ndm_tape_agent *	ta = &sess->tape_acb;
459 	struct ndmchan *	ch = &sess->plumb.image_stream.chan;
460 	unsigned long		count = ta->mover_state.record_size;
461 	int			did_something = 0;
462 	unsigned long long	max_read;
463 	unsigned long long	want_window_off;
464 	unsigned long		block_size;
465 	unsigned long		want_blockno;
466 	unsigned long		cur_blockno;
467 	unsigned		n_avail, n_read, record_off;
468 	char *			data;
469 	unsigned long		done_count;
470 	ndmp9_error		error;
471 
472   again:
473 	n_read = n_avail = ndmchan_n_avail_record (ch, count);
474 	if (n_avail < count) {
475 		/* allow to drain */
476 		return did_something;
477 	}
478 
479 	if (ta->pending_change_after_drain) {
480 		if (ndmchan_n_ready (ch) > 0) {
481 			/* allow to drain */
482 		} else {
483 			ndmta_mover_apply_pending (sess);
484 			did_something++;
485 		}
486 		return did_something;
487 	}
488 
489 	if (n_read > ta->mover_state.bytes_left_to_read)
490 		n_read = ta->mover_state.bytes_left_to_read;
491 
492 	if (n_read < count) {
493 		/* Active, but paused awaiting MOVER_READ request */
494 		return did_something;	/* mover blocked */
495 	}
496 
497 	if (ta->mover_want_pos < ta->mover_state.window_offset
498 	 || ta->mover_want_pos >= ta->mover_window_end) {
499 		ndmta_mover_pause_pending (sess, NDMP9_MOVER_PAUSE_SEEK);
500 		goto again;
501 	}
502 
503 	max_read = ta->mover_window_end - ta->mover_want_pos;
504 	if (n_read > max_read)
505 		n_read = max_read;
506 
507 	want_window_off = ta->mover_want_pos - ta->mover_state.window_offset;
508 
509 	/* make an estimate of the block size - the tape agent's block size, or
510 	 * if it's in variable block size mode, the mover's record size: "When
511 	 * in variable block mode, as indicated by a tape block_size value of
512 	 * zero, the mover record size defines the actual block size used by
513 	 * the tape subsystem." (NDMPv4 RFC, Section 3.6.2.1) */
514 	block_size = ta->tape_state.block_size.value;
515 	if (!block_size)
516 		block_size = ta->mover_state.record_size;
517 
518 	want_blockno = ta->mover_window_first_blockno + want_window_off / block_size;
519 
520 	if (ta->tb_blockno != want_blockno) {
521 		unsigned long	xsr_count, xsr_resid;
522 
523 		ndmos_tape_sync_state(sess);
524 		cur_blockno = ta->tape_state.blockno.value;
525 		if (cur_blockno < want_blockno) {
526 			xsr_count = want_blockno - cur_blockno;
527 			error = ndmos_tape_mtio (sess, NDMP9_MTIO_FSR,
528 						xsr_count, &xsr_resid);
529 			if (error == NDMP9_EOF_ERR) {
530 				ndmta_mover_pause_pending (sess,
531 						NDMP9_MOVER_PAUSE_EOF);
532 				goto again;
533 			}
534 			if (error != NDMP9_NO_ERR) {
535 				ndmta_mover_halt_pending (sess,
536 						NDMP9_MOVER_HALT_MEDIA_ERROR);
537 				goto again;
538 			}
539 			if (xsr_resid > 0) {
540 				ndmta_mover_pause_pending (sess,
541 						NDMP9_MOVER_PAUSE_EOF);
542 				goto again;
543 			}
544 		} else if (cur_blockno > want_blockno) {
545 			xsr_count = cur_blockno - want_blockno;
546 			error = ndmos_tape_mtio (sess, NDMP9_MTIO_BSR,
547 						xsr_count, &xsr_resid);
548 			if (error != NDMP9_NO_ERR || xsr_resid > 0) {
549 				ndmta_mover_halt_pending (sess,
550 						NDMP9_MOVER_HALT_MEDIA_ERROR);
551 				goto again;
552 			}
553 		} else {
554 			/* in position */
555 		}
556 
557 		data = ta->tape_buffer;
558 		done_count = 0;
559 		error = ndmos_tape_read (sess, data, count, &done_count);
560 		did_something++;
561 
562 		if (error == NDMP9_EOF_ERR) {
563 			ndmta_mover_pause_pending (sess,
564 						NDMP9_MOVER_PAUSE_EOF);
565 			goto again;
566 		}
567 		/* N.B. - handling of done_count = 0 here is hacked to support
568 		 * non-blocking writes to a socket in amndmjob */
569 		if (error != NDMP9_NO_ERR) {
570 			ndmta_mover_halt_pending (sess,
571 				NDMP9_MOVER_HALT_MEDIA_ERROR);
572 			goto again;
573 		}
574 		if (done_count == 0) {
575 			return did_something - 1;
576 		}
577 		if (done_count != count) {
578 			n_read = done_count;
579 			goto again;
580 		}
581 		ta->tb_blockno = want_blockno;
582 		/* re-calcluate this, since record_size may be > block_size, in which
583 		 * case the record_num may not change for each block read from tape */
584 		ta->mover_state.record_num = ta->mover_want_pos / ta->mover_state.record_size;
585 	}
586 
587 	record_off = ta->mover_want_pos % ta->mover_state.record_size;
588 
589 	n_avail = ta->mover_state.record_size - record_off;
590 	if (n_read > n_avail)
591 		n_read = n_avail;
592 	if (n_read != done_count) {
593 		dbprintf("lost %lu bytes %lu %u\n", done_count - n_read, done_count, n_read);
594 		n_read = done_count;
595 	}
596 
597 	data = &ta->tape_buffer[record_off];
598 
599 	bcopy (data, ch->data + ch->end_ix, n_read);
600 	ch->end_ix += n_read;
601 	ta->mover_state.bytes_moved += n_read;
602 	ta->mover_want_pos += n_read;
603 	ta->mover_state.bytes_left_to_read -= n_read;
604 
605 	did_something++;
606 
607 	goto again;	/* do as much as possible */
608 }
609 
610 void
ndmta_mover_send_notice(struct ndm_session * sess)611 ndmta_mover_send_notice (struct ndm_session *sess)
612 {
613 	struct ndm_tape_agent *	ta = &sess->tape_acb;
614 
615 	if (!ta->mover_notify_pending)
616 		return;
617 
618 	ta->mover_notify_pending = 0;
619 
620 	switch (ta->mover_state.state) {
621 	case NDMP9_MOVER_STATE_HALTED:
622 		ndma_notify_mover_halted (sess);
623 		break;
624 
625 	case NDMP9_MOVER_STATE_PAUSED:
626 		ndma_notify_mover_paused (sess);
627 		break;
628 
629 	default:
630 		/* Hmm. Why are we here. Race? */
631 		break;
632 	}
633 }
634 
635 #endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
636