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