1 /* This file is part of GNU Paxutils.
2 Copyright (C) 2009, 2023 Free Software Foundation, Inc.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include "system.h"
18 #include "system-ioctl.h"
19 #include <configmake.h>
20 #include <argp.h>
21 #include <argp-version-etc.h>
22 #include <getopt.h>
23 #include <full-write.h>
24 #include <configmake.h>
25 #include <error.h>
26 #include <progname.h>
27 #include <c-ctype.h>
28 #include <safe-read.h>
29
30 #ifndef EXIT_FAILURE
31 # define EXIT_FAILURE 1
32 #endif
33 #ifndef EXIT_SUCCESS
34 # define EXIT_SUCCESS 0
35 #endif
36
37
38 int dbglev;
39 FILE *dbgout;
40
41 #define DEBUG(lev,msg) \
42 do { if (dbgout && (lev) <= dbglev) fprintf (dbgout, "%s", msg); } while (0)
43 #define DEBUG1(lev, fmt, x) \
44 do { if (dbgout && (lev) <= dbglev) fprintf (dbgout, fmt, x); } while (0)
45 #define DEBUG2(lev, fmt, x1, x2) \
46 do \
47 { \
48 if (dbgout && (lev) <= dbglev) \
49 fprintf (dbgout, fmt, x1, x2); \
50 } \
51 while (0)
52
53 #define VDEBUG(lev, pfx, fmt) \
54 do \
55 { \
56 if (dbgout && (lev) <= dbglev) \
57 { \
58 va_list aptr; \
59 va_start (aptr, fmt); \
60 fprintf (dbgout, "%s", pfx); \
61 vfprintf (dbgout, fmt, aptr); \
62 va_end (aptr); \
63 } \
64 } \
65 while (0)
66
67
68
69 static void
70 trimnl (char *str)
71 {
72 if (str)
73 {
74 size_t len = strlen (str);
75 if (len > 1 && str[len-1] == '\n')
76 str[len-1] = 0;
77 }
78 }
79
80
81
82 char *input_buf_ptr = NULL;
83 size_t input_buf_size = 0;
84
85 static char *
86 rmt_read (void)
87 {
88 ssize_t rc = getline (&input_buf_ptr, &input_buf_size, stdin);
89 if (rc > 0)
90 {
91 DEBUG1 (10, "C: %s", input_buf_ptr);
92 trimnl (input_buf_ptr);
93 return input_buf_ptr;
94 }
95 DEBUG (10, "reached EOF");
96 return NULL;
97 }
98
99 static void
100 rmt_write (const char *fmt, ...)
101 {
102 va_list ap;
103 va_start (ap, fmt);
104 vfprintf (stdout, fmt, ap);
105 va_end (ap);
106 fflush (stdout);
107 VDEBUG (10, "S: ", fmt);
108 }
109
110 static void
111 rmt_reply (uintmax_t code)
112 {
113 rmt_write ("A%ju\n", code);
114 }
115
116 static void
117 rmt_error_message (int code, const char *msg)
118 {
119 DEBUG1 (10, "S: E%d\n", code);
120 DEBUG1 (10, "S: %s\n", msg);
121 DEBUG1 (1, "error: %s\n", msg);
122 fprintf (stdout, "E%d\n%s\n", code, msg);
123 fflush (stdout);
124 }
125
126 static void
127 rmt_error (int code)
128 {
129 rmt_error_message (code, strerror (code));
130 }
131
132
133 char *record_buffer_ptr;
134 size_t record_buffer_size;
135
136 static void
137 prepare_record_buffer (size_t size)
138 {
139 if (size > record_buffer_size)
140 {
141 record_buffer_ptr = xrealloc (record_buffer_ptr, size);
142 record_buffer_size = size;
143 }
144 }
145
146
147
148 int device_fd = -1;
149
150 struct rmt_kw
151 {
152 char const *name;
153 size_t len;
154 int value;
155 };
156
157 #define RMT_KW(s,v) { #s, sizeof (#s) - 1, v }
158
159 static int
160 xlat_kw (const char *s, const char *pfx,
161 struct rmt_kw const *kw, int *valp, const char **endp)
162 {
163 size_t slen = strlen (s);
164
165 if (pfx)
166 {
167 size_t pfxlen = strlen (pfx);
168 if (slen > pfxlen && memcmp (s, pfx, pfxlen) == 0)
169 {
170 s += pfxlen;
171 slen -= pfxlen;
172 }
173 }
174
175 for (; kw->name; kw++)
176 {
177 if (slen >= kw->len
178 && memcmp (kw->name, s, kw->len) == 0
179 && !(s[kw->len] && c_isalnum (s[kw->len])))
180 {
181 *valp = kw->value;
182 *endp = s + kw->len;
183 return 0;
184 }
185 }
186 return 1;
187 }
188
189 static const char *
190 skip_ws (const char *s)
191 {
192 while (*s && c_isblank (*s))
193 s++;
194 return s;
195 }
196
197 static struct rmt_kw const open_flag_kw[] =
198 {
199 #ifdef O_APPEND
200 RMT_KW(APPEND, O_APPEND),
201 #endif
202 RMT_KW(CREAT, O_CREAT),
203 #ifdef O_DSYNC
204 RMT_KW(DSYNC, O_DSYNC),
205 #endif
206 RMT_KW(EXCL, O_EXCL),
207 #ifdef O_LARGEFILE
208 RMT_KW(LARGEFILE, O_LARGEFILE),
209 #endif
210 #ifdef O_NOCTTY
211 RMT_KW(NOCTTY, O_NOCTTY),
212 #endif
213 #if O_NONBLOCK
214 RMT_KW(NONBLOCK, O_NONBLOCK),
215 #endif
216 RMT_KW(RDONLY, O_RDONLY),
217 RMT_KW(RDWR, O_RDWR),
218 #ifdef O_RSYNC
219 RMT_KW(RSYNC, O_RSYNC),
220 #endif
221 #ifdef O_SYNC
222 RMT_KW(SYNC, O_SYNC),
223 #endif
224 RMT_KW(TRUNC, O_TRUNC),
225 RMT_KW(WRONLY, O_WRONLY),
226 { NULL }
227 };
228
229 static int
230 decode_open_flag (const char *mstr, int *pmode)
231 {
232 int numeric_mode = 0;
233 int mode = 0;
234 const char *p;
235
236 mstr = skip_ws (mstr);
237 if (c_isdigit (*mstr))
238 {
239 numeric_mode = strtol (mstr, (char**) &p, 10);
240 mstr = skip_ws (p);
241 }
242
243 if (*mstr)
244 {
245 while (mstr)
246 {
247 int v;
248
249 mstr = skip_ws (mstr);
250 if (*mstr == 0)
251 break;
252 else if (c_isdigit (*mstr))
253 v = strtol (mstr, (char**) &p, 10);
254 else if (xlat_kw (mstr, "O_", open_flag_kw, &v, &p))
255 {
256 rmt_error_message (EINVAL, "invalid open mode");
257 return 1;
258 }
259
260 mode |= v;
261
262 if (*p && c_isblank (*p))
263 p = skip_ws (p);
264 if (*p == 0)
265 break;
266 else if (*p == '|')
267 {
268 /* FIXMEL
269 if (p[1] == 0)
270 rmt_error_message (EINVAL, "invalid open mode");
271 */
272 mstr = p + 1;
273 }
274 else
275 {
276 rmt_error_message (EINVAL, "invalid open mode");
277 return 1;
278 }
279 }
280 }
281 else
282 mode = numeric_mode;
283 *pmode = mode;
284 return 0;
285 }
286
287
288 /* Syntax
289 ------
290 O<device>\n<flags>\n
291
292 Function
293 --------
294 Opens the <device> with given <flags>. If a device had already been opened,
295 it is closed before opening the new one.
296
297 Arguments
298 ---------
299 <device> - name of the device to open.
300 <flags> - flags for open(2): a decimal number, or any valid O_* constant
301 from fcntl.h (the initial O_ may be omitted), or a bitwise or (using '|')
302 of any number of these, e.g.:
303
304 576
305 64|512
306 CREAT|TRUNC
307
308 In addition, a compined form is also allowed, i.e. a decimal mode followed
309 by its symbolic representation. In this case the symbolic representation
310 is given preference.
311
312 Reply
313 -----
314 A0\n on success, E0\n<msg>\n on error.
315
316 Extensions
317 ----------
318 BSD version allows only decimal number as <flags>
319 */
320
321 static void
322 open_device (char *str)
323 {
324 char *device = xstrdup (str);
325 char *flag_str;
326 int flag;
327
328 flag_str = rmt_read ();
329 if (!flag_str)
330 {
331 DEBUG (1, "unexpected EOF");
332 exit (EXIT_FAILURE);
333 }
334 if (decode_open_flag (flag_str, &flag) == 0)
335 {
336 if (device_fd >= 0)
337 close (device_fd);
338
339 device_fd = open (device, flag, MODE_RW);
340 if (device_fd < 0)
341 rmt_error (errno);
342 else
343 rmt_reply (0);
344 }
345 free (device);
346 }
347
348 /* Syntax
349 ------
350 C[<device>]\n
351
352 Function
353 --------
354 Close the currently open device.
355
356 Arguments
357 ---------
358 Any arguments are silently ignored.
359
360 Reply
361 -----
362 A0\n on success, E0\n<msg>\n on error.
363 */
364 static void
365 close_device (void)
366 {
367 if (close (device_fd) < 0)
368 rmt_error (errno);
369 else
370 {
371 device_fd = -1;
372 rmt_reply (0);
373 }
374 }
375
376 /* Syntax
377 ------
378 L<whence>\n<offset>\n
379
380 Function
381 --------
382 Perform an lseek(2) on the currently open device with the specified
383 parameters.
384
385 Arguments
386 ---------
387 <whence> - Where to measure offset from. Valid values are:
388 0, SET, SEEK_SET to seek from the file beginning,
389 1, CUR, SEEK_CUR to seek from the current location in file,
390 2, END, SEEK_END to seek from the file end.
391 Reply
392 -----
393 A<offset>\n on success. The <offset> is the new offset in file.
394 E0\n<msg>\n on error.
395
396 Extensions
397 ----------
398 BSD version allows only 0,1,2 as <whence>.
399 */
400
401 static struct rmt_kw const seek_whence_kw[] =
402 {
403 RMT_KW(SET, SEEK_SET),
404 RMT_KW(CUR, SEEK_CUR),
405 RMT_KW(END, SEEK_END),
406 { NULL }
407 };
408
409 static void
410 lseek_device (const char *str)
411 {
412 char *p;
413 int whence;
414 off_t off;
415 uintmax_t n;
416
417 if (str[0] && str[1] == 0)
418 {
419 switch (str[0])
420 {
421 case '0':
422 whence = SEEK_SET;
423 break;
424
425 case '1':
426 whence = SEEK_CUR;
427 break;
428
429 case '2':
430 whence = SEEK_END;
431 break;
432
433 default:
434 rmt_error_message (EINVAL, N_("Seek direction out of range"));
435 return;
436 }
437 }
438 else if (xlat_kw (str, "SEEK_", seek_whence_kw, &whence, (const char **) &p))
439 {
440 rmt_error_message (EINVAL, N_("Invalid seek direction"));
441 return;
442 }
443
444 str = rmt_read ();
445 n = off = strtoumax (str, &p, 10);
446 if (*p)
447 {
448 rmt_error_message (EINVAL, N_("Invalid seek offset"));
449 return;
450 }
451
452 if (n != off || errno == ERANGE)
453 {
454 rmt_error_message (EINVAL, N_("Seek offset out of range"));
455 return;
456 }
457
458 off = lseek (device_fd, off, whence);
459 if (off < 0)
460 rmt_error (errno);
461 else
462 rmt_reply (off);
463 }
464
465 /* Syntax
466 ------
467 R<count>\n
468
469 Function
470 --------
471 Read <count> bytes of data from the current device.
472
473 Arguments
474 ---------
475 <count> - number of bytes to read.
476
477 Reply
478 -----
479 On success: A<rdcount>\n, followed by <rdcount> bytes of data read from
480 the device.
481 On error: E0\n<msg>\n
482 */
483
484 static void
485 read_device (const char *str)
486 {
487 char *p;
488 size_t size;
489 uintmax_t n;
490 size_t status;
491
492 n = size = strtoumax (str, &p, 10);
493 if (*p)
494 {
495 rmt_error_message (EINVAL, N_("Invalid byte count"));
496 return;
497 }
498
499 if (n != size || errno == ERANGE)
500 {
501 rmt_error_message (EINVAL, N_("Byte count out of range"));
502 return;
503 }
504
505 prepare_record_buffer (size);
506 status = safe_read (device_fd, record_buffer_ptr, size);
507 if (status == SAFE_READ_ERROR)
508 rmt_error (errno);
509 else
510 {
511 rmt_reply (status);
512 full_write (STDOUT_FILENO, record_buffer_ptr, status);
513 }
514 }
515
516 /* Syntax
517 ------
518 W<count>\n followed by <count> bytes of input data.
519
520 Function
521 --------
522 Write data onto the current device.
523
524 Arguments
525 ---------
526 <count> - number of bytes.
527
528 Reply
529 -----
530 On success: A<wrcount>\n, where <wrcount> is number of bytes actually
531 written.
532 On error: E0\n<msg>\n
533 */
534
535 static void
536 write_device (const char *str)
537 {
538 char *p;
539 size_t size;
540 uintmax_t n;
541 size_t status;
542
543 n = size = strtoumax (str, &p, 10);
544 if (*p)
545 {
546 rmt_error_message (EINVAL, N_("Invalid byte count"));
547 return;
548 }
549
550 if (n != size || errno == ERANGE)
551 {
552 rmt_error_message (EINVAL, N_("Byte count out of range"));
553 return;
554 }
555
556 prepare_record_buffer (size);
557 if (fread (record_buffer_ptr, size, 1, stdin) != 1)
558 {
559 if (feof (stdin))
560 rmt_error_message (EIO, N_("Premature eof"));
561 else
562 rmt_error (errno);
563 return;
564 }
565
566 status = full_write (device_fd, record_buffer_ptr, size);
567 if (status != size)
568 rmt_error (errno);
569 else
570 rmt_reply (status);
571 }
572
573 /* Syntax
574 ------
575 I<opcode>\n<count>\n
576
577 Function
578 --------
579 Perform a MTIOCOP ioctl(2) command using the specified paramedters.
580
581 Arguments
582 ---------
583 <opcode> - MTIOCOP operation code.
584 <count> - mt_count.
585
586 Reply
587 -----
588 On success: A0\n
589 On error: E0\n<msg>\n
590 */
591
592 static void
593 iocop_device (const char *str)
594 {
595 char *p;
596 long opcode;
597 off_t count;
598 uintmax_t n;
599
600 opcode = strtol (str, &p, 10);
601 if (*p)
602 {
603 rmt_error_message (EINVAL, N_("Invalid operation code"));
604 return;
605 }
606 str = rmt_read ();
607 n = count = strtoumax (str, &p, 10);
608 if (*p)
609 {
610 rmt_error_message (EINVAL, N_("Invalid byte count"));
611 return;
612 }
613
614 if (n != count || errno == ERANGE)
615 {
616 rmt_error_message (EINVAL, N_("Byte count out of range"));
617 return;
618 }
619
620 #ifdef MTIOCTOP
621 {
622 struct mtop mtop;
623
624 mtop.mt_count = count;
625 if (mtop.mt_count != count)
626 {
627 rmt_error_message (EINVAL, N_("Byte count out of range"));
628 return;
629 }
630
631 mtop.mt_op = opcode;
632 if (ioctl (device_fd, MTIOCTOP, (char *) &mtop) < 0)
633 rmt_error (errno);
634 else
635 rmt_reply (0);
636 }
637 #else
638 rmt_error_message (ENOSYS, N_("Operation not supported"));
639 #endif
640 }
641
642 /* Syntax
643 ------
644 S\n
645
646 Function
647 --------
648 Return the status of the open device, as obtained with a MTIOCGET
649 ioctl call.
650
651 Arguments
652 ---------
653 None
654
655 Reply
656 -----
657 On success: A<count>\n followed by <count> bytes of data.
658 On error: E0\n<msg>\n
659 */
660
661 static void
662 status_device (const char *str)
663 {
664 if (*str)
665 {
666 rmt_error_message (EINVAL, N_("Unexpected arguments"));
667 return;
668 }
669 #ifdef MTIOCGET
670 {
671 struct mtget mtget;
672
673 if (ioctl (device_fd, MTIOCGET, (char *) &mtget) < 0)
674 rmt_error (errno);
675 else
676 {
677 rmt_reply (sizeof (mtget));
678 full_write (STDOUT_FILENO, (char *) &mtget, sizeof (mtget));
679 }
680 }
681 #else
682 rmt_error_message (ENOSYS, N_("Operation not supported"));
683 #endif
684 }
685
686
687
688 const char *argp_program_version = "rmt (" PACKAGE_NAME ") " VERSION;
689 const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";
690
691 static char const doc[] = N_("Manipulate a tape drive, accepting commands from a remote process");
692
693 enum {
694 DEBUG_FILE_OPTION = 256
695 };
696
697 static struct argp_option options[] = {
698 { "debug", 'd', N_("NUMBER"), 0,
699 N_("set debug level"), 0 },
700 { "debug-file", DEBUG_FILE_OPTION, N_("FILE"), 0,
701 N_("set debug output file name"), 0 },
702 { NULL }
703 };
704
705 static error_t
706 parse_opt (int key, char *arg, struct argp_state *state)
707 {
708 switch (key)
709 {
710 case 'd':
711 dbglev = strtol (arg, NULL, 0);
712 break;
713
714 case DEBUG_FILE_OPTION:
715 dbgout = fopen (arg, "w");
716 if (!dbgout)
717 error (EXIT_FAILURE, errno, _("cannot open %s"), arg);
718 break;
719
720 case ARGP_KEY_FINI:
721 if (dbglev)
722 {
723 if (!dbgout)
724 dbgout = stderr;
725 }
726 else if (dbgout)
727 dbglev = 1;
728 break;
729
730 default:
731 return ARGP_ERR_UNKNOWN;
732 }
733 return 0;
734 }
735
736 static struct argp argp = {
737 options,
738 parse_opt,
739 NULL,
740 doc,
741 NULL,
742 NULL,
743 NULL
744 };
745
746 static const char *rmt_authors[] = {
747 "Sergey Poznyakoff",
748 NULL
749 };
750
751
752 void
753 xalloc_die (void)
754 {
755 rmt_error (ENOMEM);
756 exit (EXIT_FAILURE);
757 }
758
759
760 int
761 main (int argc, char **argv)
762 {
763 char *buf;
764 int idx;
765 int stop = 0;
766
767 set_program_name (argv[0]);
768 argp_version_setup ("rmt", rmt_authors);
769
770 if (isatty (STDOUT_FILENO))
771 {
772 setlocale (LC_ALL, "");
773 bindtextdomain (PACKAGE, LOCALEDIR);
774 textdomain (PACKAGE);
775 }
776
777 if (argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &idx, NULL))
778 exit (EXIT_FAILURE);
779 if (idx != argc)
780 {
781 if (idx != argc - 1)
782 error (EXIT_FAILURE, 0, _("too many arguments"));
783 dbgout = fopen (argv[idx], "w");
784 if (!dbgout)
785 error (EXIT_FAILURE, errno, _("cannot open %s"), argv[idx]);
786 dbglev = 1;
787 }
788
789 while (!stop && (buf = rmt_read ()) != NULL)
790 {
791 switch (buf[0])
792 {
793 case 'C':
794 close_device ();
795 stop = 1;
796 break;
797
798 case 'I':
799 iocop_device (buf + 1);
800 break;
801
802 case 'L':
803 lseek_device (buf + 1);
804 break;
805
806 case 'O':
807 open_device (buf + 1);
808 break;
809
810 case 'R':
811 read_device (buf + 1);
812 break;
813
814 case 'S':
815 status_device (buf + 1);
816 break;
817
818 case 'W':
819 write_device (buf + 1);
820 break;
821
822 default:
823 DEBUG1 (1, "garbage input %s\n", buf);
824 rmt_error_message (EINVAL, N_("Garbage command"));
825 return EXIT_FAILURE; /* exit status used to be 3 */
826 }
827 }
828 if (device_fd >= 0)
829 close_device ();
830 free (input_buf_ptr);
831 free (record_buffer_ptr);
832 return EXIT_SUCCESS;
833 }