Instead of using the non-standard conforming %+ format string,
[dragonfly.git] / contrib / cpio / rtapelib.c
1 /* Functions for communicating with a remote tape drive.
2    Copyright (C) 1988, 1992 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 2, 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, write to the Free Software
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* The man page rmt(8) for /etc/rmt documents the remote mag tape
19    protocol which rdump and rrestore use.  Unfortunately, the man
20    page is *WRONG*.  The author of the routines I'm including originally
21    wrote his code just based on the man page, and it didn't work, so he
22    went to the rdump source to figure out why.  The only thing he had to
23    change was to check for the 'F' return code in addition to the 'E',
24    and to separate the various arguments with \n instead of a space.  I
25    personally don't think that this is much of a problem, but I wanted to
26    point it out. -- Arnold Robbins
27
28    Originally written by Jeff Lee, modified some by Arnold Robbins.
29    Redone as a library that can replace open, read, write, etc., by
30    Fred Fish, with some additional work by Arnold Robbins.
31    Modified to make all rmtXXX calls into macros for speed by Jay Fenlason.
32    Use -DHAVE_NETDB_H for rexec code, courtesy of Dan Kegel, srs!dan.  */
33
34 #include <stdio.h>
35 #include <sys/types.h>
36 #include <signal.h>
37
38 #ifdef HAVE_SYS_MTIO_H
39 #include <sys/ioctl.h>
40 #include <sys/mtio.h>
41 #endif
42
43 #ifdef HAVE_NETDB_H
44 #include <netdb.h>
45 #endif
46
47 #include <errno.h>
48 #include <setjmp.h>
49 #include <sys/stat.h>
50
51 #ifndef errno
52 extern int errno;
53 #endif
54
55 #ifdef HAVE_UNISTD_H
56 #include <unistd.h>
57 #endif
58 #ifdef STDC_HEADERS
59 #include <string.h>
60 #include <stdlib.h>
61 #endif
62
63 /* Maximum size of a fully qualified host name.  */
64 #define MAXHOSTLEN 257
65
66 /* Size of buffers for reading and writing commands to rmt.
67    (An arbitrary limit.)  */
68 #define CMDBUFSIZE 64
69
70 #ifndef RETSIGTYPE
71 #define RETSIGTYPE void
72 #endif
73
74 /* Maximum number of simultaneous remote tape connections.
75    (Another arbitrary limit.)  */
76 #define MAXUNIT 4
77
78 /* Return the parent's read side of remote tape connection FILDES.  */
79 #define READ(fildes) (from_rmt[fildes][0])
80
81 /* Return the parent's write side of remote tape connection FILDES.  */
82 #define WRITE(fildes) (to_rmt[fildes][1])
83
84 /* The pipes for receiving data from remote tape drives.  */
85 static int from_rmt[MAXUNIT][2] =
86 {-1, -1, -1, -1, -1, -1, -1, -1};
87
88 /* The pipes for sending data to remote tape drives.  */
89 static int to_rmt[MAXUNIT][2] =
90 {-1, -1, -1, -1, -1, -1, -1, -1};
91
92 /* Temporary variable used by macros in rmt.h.  */
93 char *__rmt_path;
94 \f
95 /* Close remote tape connection FILDES.  */
96
97 static void
98 _rmt_shutdown (fildes)
99      int fildes;
100 {
101   close (READ (fildes));
102   close (WRITE (fildes));
103   READ (fildes) = -1;
104   WRITE (fildes) = -1;
105 }
106
107 /* Attempt to perform the remote tape command specified in BUF
108    on remote tape connection FILDES.
109    Return 0 if successful, -1 on error.  */
110
111 static int
112 command (fildes, buf)
113      int fildes;
114      char *buf;
115 {
116   register int buflen;
117   RETSIGTYPE (*pipe_handler) ();
118
119   /* Save the current pipe handler and try to make the request.  */
120
121   pipe_handler = signal (SIGPIPE, SIG_IGN);
122   buflen = strlen (buf);
123   if (write (WRITE (fildes), buf, buflen) == buflen)
124     {
125       signal (SIGPIPE, pipe_handler);
126       return 0;
127     }
128
129   /* Something went wrong.  Close down and go home.  */
130
131   signal (SIGPIPE, pipe_handler);
132   _rmt_shutdown (fildes);
133   errno = EIO;
134   return -1;
135 }
136
137 /* Read and return the status from remote tape connection FILDES.
138    If an error occurred, return -1 and set errno.  */
139
140 static int
141 status (fildes)
142      int fildes;
143 {
144   int i;
145   char c, *cp;
146   char buffer[CMDBUFSIZE];
147
148   /* Read the reply command line.  */
149
150   for (i = 0, cp = buffer; i < CMDBUFSIZE; i++, cp++)
151     {
152       if (read (READ (fildes), cp, 1) != 1)
153         {
154           _rmt_shutdown (fildes);
155           errno = EIO;
156           return -1;
157         }
158       if (*cp == '\n')
159         {
160           *cp = '\0';
161           break;
162         }
163     }
164
165   if (i == CMDBUFSIZE)
166     {
167       _rmt_shutdown (fildes);
168       errno = EIO;
169       return -1;
170     }
171
172   /* Check the return status.  */
173
174   for (cp = buffer; *cp; cp++)
175     if (*cp != ' ')
176       break;
177
178   if (*cp == 'E' || *cp == 'F')
179     {
180       errno = atoi (cp + 1);
181       /* Skip the error message line.  */
182       while (read (READ (fildes), &c, 1) == 1)
183         if (c == '\n')
184           break;
185
186       if (*cp == 'F')
187         _rmt_shutdown (fildes);
188
189       return -1;
190     }
191
192   /* Check for mis-synced pipes. */
193
194   if (*cp != 'A')
195     {
196       _rmt_shutdown (fildes);
197       errno = EIO;
198       return -1;
199     }
200
201   /* Got an `A' (success) response.  */
202   return atoi (cp + 1);
203 }
204
205 #ifdef HAVE_NETDB_H
206 /* Execute /etc/rmt as user USER on remote system HOST using rexec.
207    Return a file descriptor of a bidirectional socket for stdin and stdout.
208    If USER is NULL, or an empty string, use the current username.
209
210    By default, this code is not used, since it requires that
211    the user have a .netrc file in his/her home directory, or that the
212    application designer be willing to have rexec prompt for login and
213    password info.  This may be unacceptable, and .rhosts files for use
214    with rsh are much more common on BSD systems.  */
215
216 static int
217 _rmt_rexec (host, user)
218      char *host;
219      char *user;
220 {
221   struct servent *rexecserv;
222   int save_stdin = dup (fileno (stdin));
223   int save_stdout = dup (fileno (stdout));
224   int tape_fd;                  /* Return value. */
225
226   /* When using cpio -o < filename, stdin is no longer the tty.
227      But the rexec subroutine reads the login and the passwd on stdin,
228      to allow remote execution of the command.
229      So, reopen stdin and stdout on /dev/tty before the rexec and
230      give them back their original value after.  */
231   if (freopen ("/dev/tty", "r", stdin) == NULL)
232     freopen ("/dev/null", "r", stdin);
233   if (freopen ("/dev/tty", "w", stdout) == NULL)
234     freopen ("/dev/null", "w", stdout);
235
236   rexecserv = getservbyname ("exec", "tcp");
237   if (NULL == rexecserv)
238     {
239       fprintf (stderr, "exec/tcp: service not available");
240       exit (1);
241     }
242   if (user != NULL && *user == '\0')
243     user = NULL;
244   tape_fd = rexec (&host, rexecserv->s_port, user, NULL,
245                    "/etc/rmt", (int *) NULL);
246   fclose (stdin);
247   fdopen (save_stdin, "r");
248   fclose (stdout);
249   fdopen (save_stdout, "w");
250
251   return tape_fd;
252 }
253
254 #endif /* HAVE_NETDB_H */
255
256 /* Open a magtape device on the system specified in PATH, as the given user.
257    PATH has the form `[user@]system:/dev/????'.
258    If COMPAT is defined, it can also have the form `system[.user]:/dev/????'.
259
260    OFLAG is O_RDONLY, O_WRONLY, etc.
261    MODE is ignored; 0666 is always used.
262
263    If successful, return the remote tape pipe number plus BIAS.
264    On error, return -1.  */
265
266 int
267 __rmt_open (path, oflag, mode, bias)
268      char *path;
269      int oflag;
270      int mode;
271      int bias;
272 {
273   int i, rc;
274   char buffer[CMDBUFSIZE];      /* Command buffer.  */
275   char system[MAXHOSTLEN];      /* The remote host name.  */
276   char device[CMDBUFSIZE];      /* The remote device name.  */
277   char login[CMDBUFSIZE];       /* The remote user name.  */
278   char *sys, *dev, *user;       /* For copying into the above buffers.  */
279
280   sys = system;
281   dev = device;
282   user = login;
283
284   /* Find an unused pair of file descriptors.  */
285
286   for (i = 0; i < MAXUNIT; i++)
287     if (READ (i) == -1 && WRITE (i) == -1)
288       break;
289
290   if (i == MAXUNIT)
291     {
292       errno = EMFILE;
293       return -1;
294     }
295
296   /* Pull apart the system and device, and optional user.
297      Don't munge the original string.  */
298
299   while (*path != '@'
300 #ifdef COMPAT
301          && *path != '.'
302 #endif
303          && *path != ':')
304     {
305       *sys++ = *path++;
306     }
307   *sys = '\0';
308   path++;
309
310   if (*(path - 1) == '@')
311     {
312       /* Saw user part of user@host.  Start over. */
313       strcpy (user, system);
314       sys = system;
315       while (*path != ':')
316         {
317           *sys++ = *path++;
318         }
319       *sys = '\0';
320       path++;
321     }
322 #ifdef COMPAT
323   else if (*(path - 1) == '.')
324     {
325       while (*path != ':')
326         {
327           *user++ = *path++;
328         }
329       *user = '\0';
330       path++;
331     }
332 #endif
333   else
334     *user = '\0';
335
336   while (*path)
337     {
338       *dev++ = *path++;
339     }
340   *dev = '\0';
341
342 #ifdef HAVE_NETDB_H
343   /* Execute the remote command using rexec.  */
344   READ (i) = WRITE (i) = _rmt_rexec (system, login);
345   if (READ (i) < 0)
346     return -1;
347 #else /* !HAVE_NETDB_H */
348   /* Set up the pipes for the `rsh' command, and fork.  */
349
350   if (pipe (to_rmt[i]) == -1 || pipe (from_rmt[i]) == -1)
351     return -1;
352
353   rc = fork ();
354   if (rc == -1)
355     return -1;
356
357   if (rc == 0)
358     {
359       /* Child.  */
360       close (0);
361       dup (to_rmt[i][0]);
362       close (to_rmt[i][0]);
363       close (to_rmt[i][1]);
364
365       close (1);
366       dup (from_rmt[i][1]);
367       close (from_rmt[i][0]);
368       close (from_rmt[i][1]);
369
370       setuid (getuid ());
371       setgid (getgid ());
372
373       if (*login)
374         {
375           execl ("/usr/bin/rsh", "rsh", "-l", login, system,
376                  "/etc/rmt", (char *) 0);
377         }
378       else
379         {
380           execl ("/usr/bin/rsh", "rsh", system,
381                  "/etc/rmt", (char *) 0);
382         }
383
384       /* Bad problems if we get here.  */
385
386       perror ("cannot execute remote shell");
387       _exit (1);
388     }
389
390   /* Parent.  */
391   close (to_rmt[i][0]);
392   close (from_rmt[i][1]);
393 #endif /* !HAVE_NETDB_H */
394
395   /* Attempt to open the tape device.  */
396
397   sprintf (buffer, "O%s\n%d\n", device, oflag);
398   if (command (i, buffer) == -1 || status (i) == -1)
399     return -1;
400
401   return i + bias;
402 }
403
404 /* Close remote tape connection FILDES and shut down.
405    Return 0 if successful, -1 on error.  */
406
407 int
408 __rmt_close (fildes)
409      int fildes;
410 {
411   int rc;
412
413   if (command (fildes, "C\n") == -1)
414     return -1;
415
416   rc = status (fildes);
417   _rmt_shutdown (fildes);
418   return rc;
419 }
420
421 /* Read up to NBYTE bytes into BUF from remote tape connection FILDES.
422    Return the number of bytes read on success, -1 on error.  */
423
424 int
425 __rmt_read (fildes, buf, nbyte)
426      int fildes;
427      char *buf;
428      unsigned int nbyte;
429 {
430   int rc, i;
431   char buffer[CMDBUFSIZE];
432
433   sprintf (buffer, "R%d\n", nbyte);
434   if (command (fildes, buffer) == -1 || (rc = status (fildes)) == -1)
435     return -1;
436
437   for (i = 0; i < rc; i += nbyte, buf += nbyte)
438     {
439       nbyte = read (READ (fildes), buf, rc - i);
440       if (nbyte <= 0)
441         {
442           _rmt_shutdown (fildes);
443           errno = EIO;
444           return -1;
445         }
446     }
447
448   return rc;
449 }
450
451 /* Write NBYTE bytes from BUF to remote tape connection FILDES.
452    Return the number of bytes written on success, -1 on error.  */
453
454 int
455 __rmt_write (fildes, buf, nbyte)
456      int fildes;
457      char *buf;
458      unsigned int nbyte;
459 {
460   char buffer[CMDBUFSIZE];
461   RETSIGTYPE (*pipe_handler) ();
462
463   sprintf (buffer, "W%d\n", nbyte);
464   if (command (fildes, buffer) == -1)
465     return -1;
466
467   pipe_handler = signal (SIGPIPE, SIG_IGN);
468   if (write (WRITE (fildes), buf, nbyte) == nbyte)
469     {
470       signal (SIGPIPE, pipe_handler);
471       return status (fildes);
472     }
473
474   /* Write error.  */
475   signal (SIGPIPE, pipe_handler);
476   _rmt_shutdown (fildes);
477   errno = EIO;
478   return -1;
479 }
480
481 /* Perform an imitation lseek operation on remote tape connection FILDES.
482    Return the new file offset if successful, -1 if on error.  */
483
484 long
485 __rmt_lseek (fildes, offset, whence)
486      int fildes;
487      long offset;
488      int whence;
489 {
490   char buffer[CMDBUFSIZE];
491
492   sprintf (buffer, "L%ld\n%d\n", offset, whence);
493   if (command (fildes, buffer) == -1)
494     return -1;
495
496   return status (fildes);
497 }
498
499 /* Perform a raw tape operation on remote tape connection FILDES.
500    Return the results of the ioctl, or -1 on error.  */
501
502 #ifdef MTIOCTOP
503 int
504 __rmt_ioctl (fildes, op, arg)
505      int fildes, op;
506      char *arg;
507 {
508   char c;
509   int rc, cnt;
510   char buffer[CMDBUFSIZE];
511
512   switch (op)
513     {
514     default:
515       errno = EINVAL;
516       return -1;
517
518     case MTIOCTOP:
519       /* MTIOCTOP is the easy one.  Nothing is transfered in binary.  */
520       sprintf (buffer, "I%d\n%d\n", ((struct mtop *) arg)->mt_op,
521                ((struct mtop *) arg)->mt_count);
522       if (command (fildes, buffer) == -1)
523         return -1;
524       return status (fildes);   /* Return the count.  */
525
526     case MTIOCGET:
527       /* Grab the status and read it directly into the structure.
528          This assumes that the status buffer is not padded
529          and that 2 shorts fit in a long without any word
530          alignment problems; i.e., the whole struct is contiguous.
531          NOTE - this is probably NOT a good assumption.  */
532
533       if (command (fildes, "S") == -1 || (rc = status (fildes)) == -1)
534         return -1;
535
536       for (; rc > 0; rc -= cnt, arg += cnt)
537         {
538           cnt = read (READ (fildes), arg, rc);
539           if (cnt <= 0)
540             {
541               _rmt_shutdown (fildes);
542               errno = EIO;
543               return -1;
544             }
545         }
546
547       /* Check for byte position.  mt_type is a small integer field
548          (normally) so we will check its magnitude.  If it is larger than
549          256, we will assume that the bytes are swapped and go through
550          and reverse all the bytes.  */
551
552       if (((struct mtget *) arg)->mt_type < 256)
553         return 0;
554
555       for (cnt = 0; cnt < rc; cnt += 2)
556         {
557           c = arg[cnt];
558           arg[cnt] = arg[cnt + 1];
559           arg[cnt + 1] = c;
560         }
561
562       return 0;
563     }
564 }
565
566 #endif