1 /* Functions for communicating with a remote tape drive.
2
3 Copyright (C) 1988, 1992, 1994, 1996, 1997, 1999, 2000, 2001, 2004,
4 2005, 2006, 2007 Free Software Foundation, Inc.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software Foundation,
18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19
20 /* The man page rmt(8) for /etc/rmt documents the remote mag tape protocol
21 which rdump and rrestore use. Unfortunately, the man page is *WRONG*.
22 The author of the routines I'm including originally wrote his code just
23 based on the man page, and it didn't work, so he went to the rdump source
24 to figure out why. The only thing he had to change was to check for the
25 'F' return code in addition to the 'E', and to separate the various
26 arguments with \n instead of a space. I personally don't think that this
27 is much of a problem, but I wanted to point it out. -- Arnold Robbins
28
29 Originally written by Jeff Lee, modified some by Arnold Robbins. Redone
30 as a library that can replace open, read, write, etc., by Fred Fish, with
31 some additional work by Arnold Robbins. Modified to make all rmt* calls
32 into macros for speed by Jay Fenlason. Use -DWITH_REXEC for rexec
33 code, courtesy of Dan Kegel. */
34
35 #include "system.h"
36 #include "system-ioctl.h"
37
38 #include <safe-read.h>
39 #include <full-write.h>
40
41 /* Try hard to get EOPNOTSUPP defined. 486/ISC has it in net/errno.h,
42 3B2/SVR3 has it in sys/inet.h. Otherwise, like on MSDOS, use EINVAL. */
43
44 #ifndef EOPNOTSUPP
45 # if HAVE_NET_ERRNO_H
46 # include <net/errno.h>
47 # endif
48 # if HAVE_SYS_INET_H
49 # include <sys/inet.h>
50 # endif
51 # ifndef EOPNOTSUPP
52 # define EOPNOTSUPP EINVAL
53 # endif
54 #endif
55
56 #include <signal.h>
57
58 #if HAVE_NETDB_H
59 # include <netdb.h>
60 #endif
61
62 #include <rmt.h>
63 #include <rmt-command.h>
64
65 /* Exit status if exec errors. */
66 #define EXIT_ON_EXEC_ERROR 128
67
68 /* FIXME: Size of buffers for reading and writing commands to rmt. */
69 #define COMMAND_BUFFER_SIZE 64
70
71 #ifndef RETSIGTYPE
72 # define RETSIGTYPE void
73 #endif
74
75 /* FIXME: Maximum number of simultaneous remote tape connections. */
76 #define MAXUNIT 4
77
78 #define PREAD 0 /* read file descriptor from pipe() */
79 #define PWRITE 1 /* write file descriptor from pipe() */
80
81 /* Return the parent's read side of remote tape connection Fd. */
82 #define READ_SIDE(Fd) (from_remote[Fd][PREAD])
83
84 /* Return the parent's write side of remote tape connection Fd. */
85 #define WRITE_SIDE(Fd) (to_remote[Fd][PWRITE])
86
87 /* The pipes for receiving data from remote tape drives. */
88 static int from_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
89
90 /* The pipes for sending data to remote tape drives. */
91 static int to_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
92
93 char const *rmt_command = DEFAULT_RMT_COMMAND;
94
95 /* Temporary variable used by macros in rmt.h. */
96 char const *rmt_dev_name__;
97
98 /* If true, always consider file names to be local, even if they contain
99 colons */
100 bool force_local_option;
101
102
103
104 /* Close remote tape connection HANDLE, and reset errno to ERRNO_VALUE. */
105 static void
_rmt_shutdown(int handle,int errno_value)106 _rmt_shutdown (int handle, int errno_value)
107 {
108 close (READ_SIDE (handle));
109 close (WRITE_SIDE (handle));
110 READ_SIDE (handle) = -1;
111 WRITE_SIDE (handle) = -1;
112 errno = errno_value;
113 }
114
115 /* Attempt to perform the remote tape command specified in BUFFER on
116 remote tape connection HANDLE. Return 0 if successful, -1 on
117 error. */
118 static int
do_command(int handle,const char * buffer)119 do_command (int handle, const char *buffer)
120 {
121 /* Save the current pipe handler and try to make the request. */
122
123 size_t length = strlen (buffer);
124 RETSIGTYPE (*pipe_handler) (int) = signal (SIGPIPE, SIG_IGN);
125 ssize_t written = full_write (WRITE_SIDE (handle), buffer, length);
126 signal (SIGPIPE, pipe_handler);
127
128 if (written == length)
129 return 0;
130
131 /* Something went wrong. Close down and go home. */
132
133 _rmt_shutdown (handle, EIO);
134 return -1;
135 }
136
137 static char *
get_status_string(int handle,char * command_buffer)138 get_status_string (int handle, char *command_buffer)
139 {
140 char *cursor;
141 int counter;
142
143 /* Read the reply command line. */
144
145 for (counter = 0, cursor = command_buffer;
146 counter < COMMAND_BUFFER_SIZE;
147 counter++, cursor++)
148 {
149 if (safe_read (READ_SIDE (handle), cursor, 1) != 1)
150 {
151 _rmt_shutdown (handle, EIO);
152 return 0;
153 }
154 if (*cursor == '\n')
155 {
156 *cursor = '\0';
157 break;
158 }
159 }
160
161 if (counter == COMMAND_BUFFER_SIZE)
162 {
163 _rmt_shutdown (handle, EIO);
164 return 0;
165 }
166
167 /* Check the return status. */
168
169 for (cursor = command_buffer; *cursor; cursor++)
170 if (*cursor != ' ')
171 break;
172
173 if (*cursor == 'E' || *cursor == 'F')
174 {
175 /* Skip the error message line. */
176
177 /* FIXME: there is better to do than merely ignoring error messages
178 coming from the remote end. Translate them, too... */
179
180 {
181 char character;
182
183 while (safe_read (READ_SIDE (handle), &character, 1) == 1)
184 if (character == '\n')
185 break;
186 }
187
188 errno = atoi (cursor + 1);
189
190 if (*cursor == 'F')
191 _rmt_shutdown (handle, errno);
192
193 return 0;
194 }
195
196 /* Check for mis-synced pipes. */
197
198 if (*cursor != 'A')
199 {
200 _rmt_shutdown (handle, EIO);
201 return 0;
202 }
203
204 /* Got an `A' (success) response. */
205
206 return cursor + 1;
207 }
208
209 /* Read and return the status from remote tape connection HANDLE. If
210 an error occurred, return -1 and set errno. */
211 static long int
get_status(int handle)212 get_status (int handle)
213 {
214 char command_buffer[COMMAND_BUFFER_SIZE];
215 const char *status = get_status_string (handle, command_buffer);
216 if (status)
217 {
218 long int result = atol (status);
219 if (0 <= result)
220 return result;
221 errno = EIO;
222 }
223 return -1;
224 }
225
226 static off_t
get_status_off(int handle)227 get_status_off (int handle)
228 {
229 char command_buffer[COMMAND_BUFFER_SIZE];
230 const char *status = get_status_string (handle, command_buffer);
231
232 if (! status)
233 return -1;
234 else
235 {
236 /* Parse status, taking care to check for overflow.
237 We can't use standard functions,
238 since off_t might be longer than long. */
239
240 off_t count = 0;
241 int negative;
242
243 for (; *status == ' ' || *status == '\t'; status++)
244 continue;
245
246 negative = *status == '-';
247 status += negative || *status == '+';
248
249 for (;;)
250 {
251 int digit = *status++ - '0';
252 if (9 < (unsigned) digit)
253 break;
254 else
255 {
256 off_t c10 = 10 * count;
257 off_t nc = negative ? c10 - digit : c10 + digit;
258 if (c10 / 10 != count || (negative ? c10 < nc : nc < c10))
259 return -1;
260 count = nc;
261 }
262 }
263
264 return count;
265 }
266 }
267
268 #if WITH_REXEC
269
270 /* Execute /etc/rmt as user USER on remote system HOST using rexec.
271 Return a file descriptor of a bidirectional socket for stdin and
272 stdout. If USER is zero, use the current username.
273
274 By default, this code is not used, since it requires that the user
275 have a .netrc file in his/her home directory, or that the
276 application designer be willing to have rexec prompt for login and
277 password info. This may be unacceptable, and .rhosts files for use
278 with rsh are much more common on BSD systems. */
279 static int
_rmt_rexec(char * host,char * user)280 _rmt_rexec (char *host, char *user)
281 {
282 int saved_stdin = dup (STDIN_FILENO);
283 int saved_stdout = dup (STDOUT_FILENO);
284 struct servent *rexecserv;
285 int result;
286
287 /* When using cpio -o < filename, stdin is no longer the tty. But the
288 rexec subroutine reads the login and the passwd on stdin, to allow
289 remote execution of the command. So, reopen stdin and stdout on
290 /dev/tty before the rexec and give them back their original value
291 after. */
292
293 if (! freopen ("/dev/tty", "r", stdin))
294 freopen ("/dev/null", "r", stdin);
295 if (! freopen ("/dev/tty", "w", stdout))
296 freopen ("/dev/null", "w", stdout);
297
298 if (rexecserv = getservbyname ("exec", "tcp"), !rexecserv)
299 error (EXIT_ON_EXEC_ERROR, 0, _("exec/tcp: Service not available"));
300
301 result = rexec (&host, rexecserv->s_port, user, 0, rmt_command, 0);
302 if (fclose (stdin) == EOF)
303 error (0, errno, _("stdin"));
304 fdopen (saved_stdin, "r");
305 if (fclose (stdout) == EOF)
306 error (0, errno, _("stdout"));
307 fdopen (saved_stdout, "w");
308
309 return result;
310 }
311
312 #endif /* WITH_REXEC */
313
314 /* Place into BUF a string representing OFLAG, which must be suitable
315 as argument 2 of `open'. BUF must be large enough to hold the
316 result. This function should generate a string that decode_oflag
317 can parse. */
318 static void
encode_oflag(char * buf,int oflag)319 encode_oflag (char *buf, int oflag)
320 {
321 sprintf (buf, "%d ", oflag);
322
323 switch (oflag & O_ACCMODE)
324 {
325 case O_RDONLY: strcat (buf, "O_RDONLY"); break;
326 case O_RDWR: strcat (buf, "O_RDWR"); break;
327 case O_WRONLY: strcat (buf, "O_WRONLY"); break;
328 default: abort ();
329 }
330
331 #ifdef O_APPEND
332 if (oflag & O_APPEND) strcat (buf, "|O_APPEND");
333 #endif
334 if (oflag & O_CREAT) strcat (buf, "|O_CREAT");
335 #ifdef O_DSYNC
336 if (oflag & O_DSYNC) strcat (buf, "|O_DSYNC");
337 #endif
338 if (oflag & O_EXCL) strcat (buf, "|O_EXCL");
339 #ifdef O_LARGEFILE
340 if (oflag & O_LARGEFILE) strcat (buf, "|O_LARGEFILE");
341 #endif
342 #ifdef O_NOCTTY
343 if (oflag & O_NOCTTY) strcat (buf, "|O_NOCTTY");
344 #endif
345 if (oflag & O_NONBLOCK) strcat (buf, "|O_NONBLOCK");
346 #ifdef O_RSYNC
347 if (oflag & O_RSYNC) strcat (buf, "|O_RSYNC");
348 #endif
349 #ifdef O_SYNC
350 if (oflag & O_SYNC) strcat (buf, "|O_SYNC");
351 #endif
352 if (oflag & O_TRUNC) strcat (buf, "|O_TRUNC");
353 }
354
355 /* Open a file (a magnetic tape device?) on the system specified in
356 FILE_NAME, as the given user. FILE_NAME has the form `[USER@]HOST:FILE'.
357 OPEN_MODE is O_RDONLY, O_WRONLY, etc. If successful, return the
358 remote pipe number plus BIAS. REMOTE_SHELL may be overridden. On
359 error, return -1. */
360 int
rmt_open__(const char * file_name,int open_mode,int bias,const char * remote_shell)361 rmt_open__ (const char *file_name, int open_mode, int bias,
362 const char *remote_shell)
363 {
364 int remote_pipe_number; /* pseudo, biased file descriptor */
365 char *file_name_copy; /* copy of file_name string */
366 char *remote_host; /* remote host name */
367 char *remote_file; /* remote file name (often a device) */
368 char *remote_user; /* remote user name */
369
370 /* Find an unused pair of file descriptors. */
371
372 for (remote_pipe_number = 0;
373 remote_pipe_number < MAXUNIT;
374 remote_pipe_number++)
375 if (READ_SIDE (remote_pipe_number) == -1
376 && WRITE_SIDE (remote_pipe_number) == -1)
377 break;
378
379 if (remote_pipe_number == MAXUNIT)
380 {
381 errno = EMFILE;
382 return -1;
383 }
384
385 /* Pull apart the system and device, and optional user. */
386
387 {
388 char *cursor;
389
390 file_name_copy = xstrdup (file_name);
391 remote_host = file_name_copy;
392 remote_user = 0;
393 remote_file = 0;
394
395 for (cursor = file_name_copy; *cursor; cursor++)
396 switch (*cursor)
397 {
398 default:
399 break;
400
401 case '\n':
402 /* Do not allow newlines in the file_name, since the protocol
403 uses newline delimiters. */
404 free (file_name_copy);
405 errno = ENOENT;
406 return -1;
407
408 case '@':
409 if (!remote_user)
410 {
411 remote_user = remote_host;
412 *cursor = '\0';
413 remote_host = cursor + 1;
414 }
415 break;
416
417 case ':':
418 if (!remote_file)
419 {
420 *cursor = '\0';
421 remote_file = cursor + 1;
422 }
423 break;
424 }
425 }
426
427 /* FIXME: Should somewhat validate the decoding, here. */
428 if (gethostbyname (remote_host) == NULL)
429 error (EXIT_ON_EXEC_ERROR, 0, _("Cannot connect to %s: resolve failed"),
430 remote_host);
431
432 if (remote_user && *remote_user == '\0')
433 remote_user = 0;
434
435 #if WITH_REXEC
436
437 /* Execute the remote command using rexec. */
438
439 READ_SIDE (remote_pipe_number) = _rmt_rexec (remote_host, remote_user);
440 if (READ_SIDE (remote_pipe_number) < 0)
441 {
442 int e = errno;
443 free (file_name_copy);
444 errno = e;
445 return -1;
446 }
447
448 WRITE_SIDE (remote_pipe_number) = READ_SIDE (remote_pipe_number);
449
450 #else /* not WITH_REXEC */
451 {
452 const char *remote_shell_basename;
453 pid_t status;
454
455 /* Identify the remote command to be executed. */
456
457 if (!remote_shell)
458 {
459 #ifdef REMOTE_SHELL
460 remote_shell = REMOTE_SHELL;
461 #else
462 free (file_name_copy);
463 errno = EIO;
464 return -1;
465 #endif
466 }
467 remote_shell_basename = last_component (remote_shell);
468
469 /* Set up the pipes for the `rsh' command, and fork. */
470
471 if (pipe (to_remote[remote_pipe_number]) == -1
472 || pipe (from_remote[remote_pipe_number]) == -1)
473 {
474 int e = errno;
475 free (file_name_copy);
476 errno = e;
477 return -1;
478 }
479
480 status = fork ();
481 if (status == -1)
482 {
483 int e = errno;
484 free (file_name_copy);
485 errno = e;
486 return -1;
487 }
488
489 if (status == 0)
490 {
491 /* Child. */
492
493 if (dup2 (to_remote[remote_pipe_number][PREAD], STDIN_FILENO) < 0
494 || (to_remote[remote_pipe_number][PREAD] != STDIN_FILENO
495 && close (to_remote[remote_pipe_number][PREAD]) != 0)
496 || (to_remote[remote_pipe_number][PWRITE] != STDIN_FILENO
497 && close (to_remote[remote_pipe_number][PWRITE]) != 0)
498 || dup2 (from_remote[remote_pipe_number][PWRITE], STDOUT_FILENO) < 0
499 || close (from_remote[remote_pipe_number][PREAD]) != 0
500 || close (from_remote[remote_pipe_number][PWRITE]) != 0)
501 error (EXIT_ON_EXEC_ERROR, errno,
502 _("Cannot redirect files for remote shell"));
503
504 sys_reset_uid_gid ();
505
506 if (remote_user)
507 execl (remote_shell, remote_shell_basename, remote_host,
508 "-l", remote_user, rmt_command, (char *) 0);
509 else
510 execl (remote_shell, remote_shell_basename, remote_host,
511 rmt_command, (char *) 0);
512
513 /* Bad problems if we get here. */
514
515 /* In a previous version, _exit was used here instead of exit. */
516 error (EXIT_ON_EXEC_ERROR, errno, _("Cannot execute remote shell"));
517 }
518
519 /* Parent. */
520
521 close (from_remote[remote_pipe_number][PWRITE]);
522 close (to_remote[remote_pipe_number][PREAD]);
523 }
524 #endif /* not WITH_REXEC */
525
526 /* Attempt to open the tape device. */
527
528 {
529 size_t remote_file_len = strlen (remote_file);
530 char *command_buffer = xmalloc (remote_file_len + 1000);
531 sprintf (command_buffer, "O%s\n", remote_file);
532 encode_oflag (command_buffer + remote_file_len + 2, open_mode);
533 strcat (command_buffer, "\n");
534 if (do_command (remote_pipe_number, command_buffer) == -1
535 || get_status (remote_pipe_number) == -1)
536 {
537 int e = errno;
538 free (command_buffer);
539 free (file_name_copy);
540 _rmt_shutdown (remote_pipe_number, e);
541 return -1;
542 }
543 free (command_buffer);
544 }
545
546 free (file_name_copy);
547 return remote_pipe_number + bias;
548 }
549
550 /* Close remote tape connection HANDLE and shut down. Return 0 if
551 successful, -1 on error. */
552 int
rmt_close__(int handle)553 rmt_close__ (int handle)
554 {
555 long int status;
556
557 if (do_command (handle, "C\n") == -1)
558 return -1;
559
560 status = get_status (handle);
561 _rmt_shutdown (handle, errno);
562 return status;
563 }
564
565 /* Read up to LENGTH bytes into BUFFER from remote tape connection HANDLE.
566 Return the number of bytes read on success, SAFE_READ_ERROR on error. */
567 size_t
rmt_read__(int handle,char * buffer,size_t length)568 rmt_read__ (int handle, char *buffer, size_t length)
569 {
570 char command_buffer[COMMAND_BUFFER_SIZE];
571 size_t status;
572 size_t rlen;
573 size_t counter;
574
575 sprintf (command_buffer, "R%lu\n", (unsigned long) length);
576 if (do_command (handle, command_buffer) == -1
577 || (status = get_status (handle)) == SAFE_READ_ERROR
578 || status > length)
579 return SAFE_READ_ERROR;
580
581 for (counter = 0; counter < status; counter += rlen, buffer += rlen)
582 {
583 rlen = safe_read (READ_SIDE (handle), buffer, status - counter);
584 if (rlen == SAFE_READ_ERROR || rlen == 0)
585 {
586 _rmt_shutdown (handle, EIO);
587 return SAFE_READ_ERROR;
588 }
589 }
590
591 return status;
592 }
593
594 /* Write LENGTH bytes from BUFFER to remote tape connection HANDLE.
595 Return the number of bytes written. */
596 size_t
rmt_write__(int handle,char * buffer,size_t length)597 rmt_write__ (int handle, char *buffer, size_t length)
598 {
599 char command_buffer[COMMAND_BUFFER_SIZE];
600 RETSIGTYPE (*pipe_handler) (int);
601 size_t written;
602
603 sprintf (command_buffer, "W%lu\n", (unsigned long) length);
604 if (do_command (handle, command_buffer) == -1)
605 return 0;
606
607 pipe_handler = signal (SIGPIPE, SIG_IGN);
608 written = full_write (WRITE_SIDE (handle), buffer, length);
609 signal (SIGPIPE, pipe_handler);
610 if (written == length)
611 {
612 long int r = get_status (handle);
613 if (r < 0)
614 return 0;
615 if (r == length)
616 return length;
617 written = r;
618 }
619
620 /* Write error. */
621
622 _rmt_shutdown (handle, EIO);
623 return written;
624 }
625
626 /* Perform an imitation lseek operation on remote tape connection
627 HANDLE. Return the new file offset if successful, -1 if on error. */
628 off_t
rmt_lseek__(int handle,off_t offset,int whence)629 rmt_lseek__ (int handle, off_t offset, int whence)
630 {
631 char command_buffer[COMMAND_BUFFER_SIZE];
632 char operand_buffer[UINTMAX_STRSIZE_BOUND];
633 uintmax_t u = offset < 0 ? - (uintmax_t) offset : (uintmax_t) offset;
634 char *p = operand_buffer + sizeof operand_buffer;
635
636 *--p = 0;
637 do
638 *--p = '0' + (int) (u % 10);
639 while ((u /= 10) != 0);
640 if (offset < 0)
641 *--p = '-';
642
643 switch (whence)
644 {
645 case SEEK_SET: whence = 0; break;
646 case SEEK_CUR: whence = 1; break;
647 case SEEK_END: whence = 2; break;
648 default: abort ();
649 }
650
651 sprintf (command_buffer, "L%s\n%d\n", p, whence);
652
653 if (do_command (handle, command_buffer) == -1)
654 return -1;
655
656 return get_status_off (handle);
657 }
658
659 /* Perform a raw tape operation on remote tape connection HANDLE.
660 Return the results of the ioctl, or -1 on error. */
661 int
rmt_ioctl__(int handle,int operation,char * argument)662 rmt_ioctl__ (int handle, int operation, char *argument)
663 {
664 switch (operation)
665 {
666 default:
667 errno = EOPNOTSUPP;
668 return -1;
669
670 #ifdef MTIOCTOP
671 case MTIOCTOP:
672 {
673 char command_buffer[COMMAND_BUFFER_SIZE];
674 char operand_buffer[UINTMAX_STRSIZE_BOUND];
675 uintmax_t u = (((struct mtop *) argument)->mt_count < 0
676 ? - (uintmax_t) ((struct mtop *) argument)->mt_count
677 : (uintmax_t) ((struct mtop *) argument)->mt_count);
678 char *p = operand_buffer + sizeof operand_buffer;
679
680 *--p = 0;
681 do
682 *--p = '0' + (int) (u % 10);
683 while ((u /= 10) != 0);
684 if (((struct mtop *) argument)->mt_count < 0)
685 *--p = '-';
686
687 /* MTIOCTOP is the easy one. Nothing is transferred in binary. */
688
689 sprintf (command_buffer, "I%d\n%s\n",
690 ((struct mtop *) argument)->mt_op, p);
691 if (do_command (handle, command_buffer) == -1)
692 return -1;
693
694 return get_status (handle);
695 }
696 #endif /* MTIOCTOP */
697
698 #ifdef MTIOCGET
699 case MTIOCGET:
700 {
701 ssize_t status;
702 size_t counter;
703
704 /* Grab the status and read it directly into the structure. This
705 assumes that the status buffer is not padded and that 2 shorts
706 fit in a long without any word alignment problems; i.e., the
707 whole struct is contiguous. NOTE - this is probably NOT a good
708 assumption. */
709
710 if (do_command (handle, "S") == -1
711 || (status = get_status (handle), status == -1))
712 return -1;
713
714 if (status > sizeof (struct mtop))
715 {
716 errno = EOVERFLOW;
717 return -1;
718 }
719
720 for (; status > 0; status -= counter, argument += counter)
721 {
722 counter = safe_read (READ_SIDE (handle), argument, status);
723 if (counter == SAFE_READ_ERROR || counter == 0)
724 {
725 _rmt_shutdown (handle, EIO);
726 return -1;
727 }
728 }
729
730 /* Check for byte position. mt_type (or mt_model) is a small integer
731 field (normally) so we will check its magnitude. If it is larger
732 than 256, we will assume that the bytes are swapped and go through
733 and reverse all the bytes. */
734
735 if (((struct mtget *) argument)->MTIO_CHECK_FIELD < 256)
736 return 0;
737
738 for (counter = 0; counter < status; counter += 2)
739 {
740 char copy = argument[counter];
741
742 argument[counter] = argument[counter + 1];
743 argument[counter + 1] = copy;
744 }
745
746 return 0;
747 }
748 #endif /* MTIOCGET */
749
750 }
751 }
752