busybox/coreutils/dd.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini dd implementation for busybox
   4 *
   5 * Copyright (C) 2000,2001  Matt Kraai
   6 *
   7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   8 */
   9//config:config DD
  10//config:       bool "dd (7.5 kb)"
  11//config:       default y
  12//config:       help
  13//config:       dd copies a file (from standard input to standard output,
  14//config:       by default) using specific input and output blocksizes,
  15//config:       while optionally performing conversions on it.
  16//config:
  17//config:config FEATURE_DD_SIGNAL_HANDLING
  18//config:       bool "Enable signal handling for status reporting"
  19//config:       default y
  20//config:       depends on DD
  21//config:       help
  22//config:       Sending a SIGUSR1 signal to a running 'dd' process makes it
  23//config:       print to standard error the number of records read and written
  24//config:       so far, then to resume copying.
  25//config:
  26//config:       $ dd if=/dev/zero of=/dev/null &
  27//config:       $ pid=$!; kill -USR1 $pid; sleep 1; kill $pid
  28//config:       10899206+0 records in
  29//config:       10899206+0 records out
  30//config:
  31//config:config FEATURE_DD_THIRD_STATUS_LINE
  32//config:       bool "Enable the third status line upon signal"
  33//config:       default y
  34//config:       depends on DD && FEATURE_DD_SIGNAL_HANDLING
  35//config:       help
  36//config:       Displays a coreutils-like third status line with transferred bytes,
  37//config:       elapsed time and speed.
  38//config:
  39//config:config FEATURE_DD_IBS_OBS
  40//config:       bool "Enable ibs, obs, iflag, oflag and conv options"
  41//config:       default y
  42//config:       depends on DD
  43//config:       help
  44//config:       Enable support for writing a certain number of bytes in and out,
  45//config:       at a time, and performing conversions on the data stream.
  46//config:
  47//config:config FEATURE_DD_STATUS
  48//config:       bool "Enable status display options"
  49//config:       default y
  50//config:       depends on DD
  51//config:       help
  52//config:       Enable support for status=noxfer/none option.
  53
  54//applet:IF_DD(APPLET_NOEXEC(dd, dd, BB_DIR_BIN, BB_SUID_DROP, dd))
  55
  56//kbuild:lib-$(CONFIG_DD) += dd.o
  57
  58//usage:#define dd_trivial_usage
  59//usage:       "[if=FILE] [of=FILE] [" IF_FEATURE_DD_IBS_OBS("ibs=N obs=N/") "bs=N] [count=N] [skip=N] [seek=N]\n"
  60//usage:        IF_FEATURE_DD_IBS_OBS(
  61//usage:       "        [conv=notrunc|noerror|sync|fsync]\n"
  62//usage:       "        [iflag=skip_bytes|fullblock] [oflag=seek_bytes]"
  63//usage:        )
  64//usage:#define dd_full_usage "\n\n"
  65//usage:       "Copy a file with converting and formatting\n"
  66//usage:     "\n        if=FILE         Read from FILE instead of stdin"
  67//usage:     "\n        of=FILE         Write to FILE instead of stdout"
  68//usage:     "\n        bs=N            Read and write N bytes at a time"
  69//usage:        IF_FEATURE_DD_IBS_OBS(
  70//usage:     "\n        ibs=N           Read N bytes at a time"
  71//usage:        )
  72//usage:        IF_FEATURE_DD_IBS_OBS(
  73//usage:     "\n        obs=N           Write N bytes at a time"
  74//usage:        )
  75//usage:     "\n        count=N         Copy only N input blocks"
  76//usage:     "\n        skip=N          Skip N input blocks"
  77//usage:     "\n        seek=N          Skip N output blocks"
  78//usage:        IF_FEATURE_DD_IBS_OBS(
  79//usage:     "\n        conv=notrunc    Don't truncate output file"
  80//usage:     "\n        conv=noerror    Continue after read errors"
  81//usage:     "\n        conv=sync       Pad blocks with zeros"
  82//usage:     "\n        conv=fsync      Physically write data out before finishing"
  83//usage:     "\n        conv=swab       Swap every pair of bytes"
  84//usage:     "\n        iflag=skip_bytes        skip=N is in bytes"
  85//usage:     "\n        iflag=fullblock Read full blocks"
  86//usage:     "\n        oflag=seek_bytes        seek=N is in bytes"
  87//usage:        )
  88//usage:        IF_FEATURE_DD_STATUS(
  89//usage:     "\n        status=noxfer   Suppress rate output"
  90//usage:     "\n        status=none     Suppress all output"
  91//usage:        )
  92//usage:     "\n"
  93//usage:     "\nN may be suffixed by c (1), w (2), b (512), kB (1000), k (1024), MB, M, GB, G"
  94//usage:
  95//usage:#define dd_example_usage
  96//usage:       "$ dd if=/dev/zero of=/dev/ram1 bs=1M count=4\n"
  97//usage:       "4+0 records in\n"
  98//usage:       "4+0 records out\n"
  99
 100#include "libbb.h"
 101#include "common_bufsiz.h"
 102
 103/* This is a NOEXEC applet. Be very careful! */
 104
 105
 106enum {
 107        ifd = STDIN_FILENO,
 108        ofd = STDOUT_FILENO,
 109};
 110
 111struct globals {
 112        off_t out_full, out_part, in_full, in_part;
 113#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
 114        unsigned long long total_bytes;
 115        unsigned long long begin_time_us;
 116#endif
 117        int flags;
 118} FIX_ALIASING;
 119#define G (*(struct globals*)bb_common_bufsiz1)
 120#define INIT_G() do { \
 121        setup_common_bufsiz(); \
 122        /* we have to zero it out because of NOEXEC */ \
 123        memset(&G, 0, sizeof(G)); \
 124} while (0)
 125
 126enum {
 127        /* Must be in the same order as OP_conv_XXX! */
 128        /* (see "flags |= (1 << what)" below) */
 129        FLAG_NOTRUNC = (1 << 0) * ENABLE_FEATURE_DD_IBS_OBS,
 130        FLAG_SYNC    = (1 << 1) * ENABLE_FEATURE_DD_IBS_OBS,
 131        FLAG_NOERROR = (1 << 2) * ENABLE_FEATURE_DD_IBS_OBS,
 132        FLAG_FSYNC   = (1 << 3) * ENABLE_FEATURE_DD_IBS_OBS,
 133        FLAG_SWAB    = (1 << 4) * ENABLE_FEATURE_DD_IBS_OBS,
 134        /* end of conv flags */
 135        /* start of input flags */
 136        FLAG_IFLAG_SHIFT = 5,
 137        FLAG_SKIP_BYTES = (1 << 5) * ENABLE_FEATURE_DD_IBS_OBS,
 138        FLAG_FULLBLOCK = (1 << 6) * ENABLE_FEATURE_DD_IBS_OBS,
 139        /* end of input flags */
 140        /* start of output flags */
 141        FLAG_OFLAG_SHIFT = 7,
 142        FLAG_SEEK_BYTES = (1 << 7) * ENABLE_FEATURE_DD_IBS_OBS,
 143        /* end of output flags */
 144        FLAG_TWOBUFS = (1 << 8) * ENABLE_FEATURE_DD_IBS_OBS,
 145        FLAG_COUNT   = 1 << 9,
 146        FLAG_STATUS_NONE = 1 << 10,
 147        FLAG_STATUS_NOXFER = 1 << 11,
 148};
 149
 150static void dd_output_status(int UNUSED_PARAM cur_signal)
 151{
 152#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
 153        double seconds;
 154        unsigned long long bytes_sec;
 155        unsigned long long now_us = monotonic_us(); /* before fprintf */
 156#endif
 157
 158        /* Deliberately using %u, not %d */
 159        fprintf(stderr, "%"OFF_FMT"u+%"OFF_FMT"u records in\n"
 160                        "%"OFF_FMT"u+%"OFF_FMT"u records out\n",
 161                        G.in_full, G.in_part,
 162                        G.out_full, G.out_part);
 163
 164#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
 165# if ENABLE_FEATURE_DD_STATUS
 166        if (G.flags & FLAG_STATUS_NOXFER) /* status=noxfer active? */
 167                return;
 168        //TODO: should status=none make dd stop reacting to USR1 entirely?
 169        //So far we react to it (we print the stats),
 170        //status=none only suppresses final, non-USR1 generated status message.
 171# endif
 172        fprintf(stderr, "%llu bytes (%sB) copied, ",
 173                        G.total_bytes,
 174                        /* show fractional digit, use suffixes */
 175                        make_human_readable_str(G.total_bytes, 1, 0)
 176        );
 177        /* Corner cases:
 178         * ./busybox dd </dev/null >/dev/null
 179         * ./busybox dd bs=1M count=2000 </dev/zero >/dev/null
 180         * (echo DONE) | ./busybox dd >/dev/null
 181         * (sleep 1; echo DONE) | ./busybox dd >/dev/null
 182         */
 183        seconds = (now_us - G.begin_time_us) / 1000000.0;
 184        bytes_sec = G.total_bytes / seconds;
 185        fprintf(stderr, "%f seconds, %sB/s\n",
 186                        seconds,
 187                        /* show fractional digit, use suffixes */
 188                        make_human_readable_str(bytes_sec, 1, 0)
 189        );
 190#endif
 191}
 192
 193static ssize_t full_write_or_warn(const void *buf, size_t len,
 194        const char *const filename)
 195{
 196        ssize_t n = full_write(ofd, buf, len);
 197        if (n < 0)
 198                bb_perror_msg("writing '%s'", filename);
 199        return n;
 200}
 201
 202static bool write_and_stats(const void *buf, size_t len, size_t obs,
 203        const char *filename)
 204{
 205        ssize_t n = full_write_or_warn(buf, len, filename);
 206        if (n < 0)
 207                return 1;
 208#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
 209        G.total_bytes += n;
 210#endif
 211        if ((size_t)n == obs) {
 212                G.out_full++;
 213                return 0;
 214        }
 215        if ((size_t)n == len) {
 216                G.out_part++;
 217                return 0;
 218        }
 219        return 1;
 220}
 221
 222#if ENABLE_LFS
 223# define XATOU_SFX xatoull_sfx
 224#else
 225# define XATOU_SFX xatoul_sfx
 226#endif
 227
 228#if ENABLE_FEATURE_DD_IBS_OBS
 229static int parse_comma_flags(char *val, const char *words, const char *error_in)
 230{
 231        int flags = 0;
 232        while (1) {
 233                int n;
 234                char *arg;
 235                /* find ',', replace them with NUL so we can use val for
 236                 * index_in_strings() without copying.
 237                 * We rely on val being non-null, else strchr would fault.
 238                 */
 239                arg = strchr(val, ',');
 240                if (arg)
 241                        *arg = '\0';
 242                n = index_in_strings(words, val);
 243                if (n < 0)
 244                        bb_error_msg_and_die(bb_msg_invalid_arg_to, val, error_in);
 245                flags |= (1 << n);
 246                if (!arg) /* no ',' left, so this was the last specifier */
 247                        break;
 248                *arg = ','; /* to preserve ps listing */
 249                val = arg + 1; /* skip this keyword and ',' */
 250        }
 251        return flags;
 252}
 253#endif
 254
 255int dd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 256int dd_main(int argc UNUSED_PARAM, char **argv)
 257{
 258        static const char keywords[] ALIGN1 =
 259                "bs\0""count\0""seek\0""skip\0""if\0""of\0"IF_FEATURE_DD_STATUS("status\0")
 260#if ENABLE_FEATURE_DD_IBS_OBS
 261                "ibs\0""obs\0""conv\0""iflag\0""oflag\0"
 262#endif
 263                ;
 264#if ENABLE_FEATURE_DD_IBS_OBS
 265        static const char conv_words[] ALIGN1 =
 266                "notrunc\0""sync\0""noerror\0""fsync\0""swab\0";
 267        static const char iflag_words[] ALIGN1 =
 268                "skip_bytes\0""fullblock\0";
 269        static const char oflag_words[] ALIGN1 =
 270                "seek_bytes\0";
 271#endif
 272#if ENABLE_FEATURE_DD_STATUS
 273        static const char status_words[] ALIGN1 =
 274                "none\0""noxfer\0";
 275#endif
 276        enum {
 277                OP_bs = 0,
 278                OP_count,
 279                OP_seek,
 280                OP_skip,
 281                OP_if,
 282                OP_of,
 283                IF_FEATURE_DD_STATUS(OP_status,)
 284#if ENABLE_FEATURE_DD_IBS_OBS
 285                OP_ibs,
 286                OP_obs,
 287                OP_conv,
 288                OP_iflag,
 289                OP_oflag,
 290                /* Must be in the same order as FLAG_XXX! */
 291                OP_conv_notrunc = 0,
 292                OP_conv_sync,
 293                OP_conv_noerror,
 294                OP_conv_fsync,
 295                OP_conv_swab,
 296        /* Unimplemented conv=XXX: */
 297        //nocreat       do not create the output file
 298        //excl          fail if the output file already exists
 299        //fdatasync     physically write output file data before finishing
 300        //lcase         change upper case to lower case
 301        //ucase         change lower case to upper case
 302        //block         pad newline-terminated records with spaces to cbs-size
 303        //unblock       replace trailing spaces in cbs-size records with newline
 304        //ascii         from EBCDIC to ASCII
 305        //ebcdic        from ASCII to EBCDIC
 306        //ibm           from ASCII to alternate EBCDIC
 307        /* Partially implemented: */
 308        //swab          swap every pair of input bytes: will abort on non-even reads
 309                OP_iflag_skip_bytes,
 310                OP_iflag_fullblock,
 311                OP_oflag_seek_bytes,
 312#endif
 313        };
 314        smallint exitcode = EXIT_FAILURE;
 315        int i;
 316        size_t ibs = 512;
 317        char *ibuf;
 318#if ENABLE_FEATURE_DD_IBS_OBS
 319        size_t obs = 512;
 320        char *obuf;
 321#else
 322# define obs  ibs
 323# define obuf ibuf
 324#endif
 325        /* These are all zeroed at once! */
 326        struct {
 327                IF_FEATURE_DD_IBS_OBS(size_t ocount;)
 328                ssize_t prev_read_size; /* for detecting swab failure */
 329                off_t count;
 330                off_t seek, skip;
 331                const char *infile, *outfile;
 332        } Z;
 333#define ocount  (Z.ocount )
 334#define prev_read_size (Z.prev_read_size)
 335#define count   (Z.count  )
 336#define seek    (Z.seek   )
 337#define skip    (Z.skip   )
 338#define infile  (Z.infile )
 339#define outfile (Z.outfile)
 340
 341        memset(&Z, 0, sizeof(Z));
 342        INIT_G();
 343        //fflush_all(); - is this needed because of NOEXEC?
 344
 345        for (i = 1; argv[i]; i++) {
 346                int what;
 347                char *val;
 348                char *arg = argv[i];
 349
 350#if ENABLE_DESKTOP
 351                /* "dd --". NB: coreutils 6.9 will complain if they see
 352                 * more than one of them. We wouldn't. */
 353                if (arg[0] == '-' && arg[1] == '-' && arg[2] == '\0')
 354                        continue;
 355#endif
 356                val = strchr(arg, '=');
 357                if (val == NULL)
 358                        bb_show_usage();
 359                *val = '\0';
 360                what = index_in_strings(keywords, arg);
 361                if (what < 0)
 362                        bb_show_usage();
 363                /* *val = '='; - to preserve ps listing? */
 364                val++;
 365#if ENABLE_FEATURE_DD_IBS_OBS
 366                if (what == OP_ibs) {
 367                        /* Must fit into positive ssize_t */
 368                        ibs = xatoul_range_sfx(val, 1, ((size_t)-1L)/2, cwbkMG_suffixes);
 369                        /*continue;*/
 370                }
 371                if (what == OP_obs) {
 372                        obs = xatoul_range_sfx(val, 1, ((size_t)-1L)/2, cwbkMG_suffixes);
 373                        /*continue;*/
 374                }
 375                if (what == OP_conv) {
 376                        G.flags |= parse_comma_flags(val, conv_words, "conv");
 377                        /*continue;*/
 378                }
 379                if (what == OP_iflag) {
 380                        G.flags |= parse_comma_flags(val, iflag_words, "iflag") << FLAG_IFLAG_SHIFT;
 381                        /*continue;*/
 382                }
 383                if (what == OP_oflag) {
 384                        G.flags |= parse_comma_flags(val, oflag_words, "oflag") << FLAG_OFLAG_SHIFT;
 385                        /*continue;*/
 386                }
 387#endif
 388                if (what == OP_bs) {
 389                        ibs = xatoul_range_sfx(val, 1, ((size_t)-1L)/2, cwbkMG_suffixes);
 390                        obs = ibs;
 391                        /*continue;*/
 392                }
 393                /* These can be large: */
 394                if (what == OP_count) {
 395                        G.flags |= FLAG_COUNT;
 396                        count = XATOU_SFX(val, cwbkMG_suffixes);
 397                        /*continue;*/
 398                }
 399                if (what == OP_seek) {
 400                        seek = XATOU_SFX(val, cwbkMG_suffixes);
 401                        /*continue;*/
 402                }
 403                if (what == OP_skip) {
 404                        skip = XATOU_SFX(val, cwbkMG_suffixes);
 405                        /*continue;*/
 406                }
 407                if (what == OP_if) {
 408                        infile = val;
 409                        /*continue;*/
 410                }
 411                if (what == OP_of) {
 412                        outfile = val;
 413                        /*continue;*/
 414                }
 415#if ENABLE_FEATURE_DD_STATUS
 416                if (what == OP_status) {
 417                        int n;
 418                        n = index_in_strings(status_words, val);
 419                        if (n < 0)
 420                                bb_error_msg_and_die(bb_msg_invalid_arg_to, val, "status");
 421                        G.flags |= FLAG_STATUS_NONE << n;
 422                        /*continue;*/
 423                }
 424#endif
 425        } /* end of "for (argv[i])" */
 426
 427//XXX:FIXME for huge ibs or obs, malloc'ing them isn't the brightest idea ever
 428        ibuf = xmalloc(ibs);
 429        obuf = ibuf;
 430#if ENABLE_FEATURE_DD_IBS_OBS
 431        if (ibs != obs) {
 432                G.flags |= FLAG_TWOBUFS;
 433                obuf = xmalloc(obs);
 434        }
 435#endif
 436
 437#if ENABLE_FEATURE_DD_SIGNAL_HANDLING
 438        signal_SA_RESTART_empty_mask(SIGUSR1, dd_output_status);
 439#endif
 440#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
 441        G.begin_time_us = monotonic_us();
 442#endif
 443
 444        if (infile) {
 445                xmove_fd(xopen(infile, O_RDONLY), ifd);
 446        } else {
 447                infile = bb_msg_standard_input;
 448        }
 449        if (outfile) {
 450                int oflag = O_WRONLY | O_CREAT;
 451
 452                if (!seek && !(G.flags & FLAG_NOTRUNC))
 453                        oflag |= O_TRUNC;
 454
 455                xmove_fd(xopen(outfile, oflag), ofd);
 456
 457                if (seek && !(G.flags & FLAG_NOTRUNC)) {
 458                        size_t blocksz = (G.flags & FLAG_SEEK_BYTES) ? 1 : obs;
 459                        if (ftruncate(ofd, seek * blocksz) < 0) {
 460                                struct stat st;
 461
 462                                if (fstat(ofd, &st) < 0
 463                                 || S_ISREG(st.st_mode)
 464                                 || S_ISDIR(st.st_mode)
 465                                ) {
 466                                        goto die_outfile;
 467                                }
 468                        }
 469                }
 470        } else {
 471                outfile = bb_msg_standard_output;
 472        }
 473        if (skip) {
 474                size_t blocksz = (G.flags & FLAG_SKIP_BYTES) ? 1 : ibs;
 475                if (lseek(ifd, skip * blocksz, SEEK_CUR) < 0) {
 476                        do {
 477                                ssize_t n;
 478#if ENABLE_FEATURE_DD_IBS_OBS
 479                                if (G.flags & FLAG_FULLBLOCK)
 480                                        n = full_read(ifd, ibuf, blocksz);
 481                                else
 482#endif
 483                                        n = safe_read(ifd, ibuf, blocksz);
 484                                if (n < 0)
 485                                        goto die_infile;
 486                                if (n == 0)
 487                                        break;
 488                        } while (--skip != 0);
 489                }
 490        }
 491        if (seek) {
 492                size_t blocksz = (G.flags & FLAG_SEEK_BYTES) ? 1 : obs;
 493                if (lseek(ofd, seek * blocksz, SEEK_CUR) < 0)
 494                        goto die_outfile;
 495        }
 496
 497        while (!(G.flags & FLAG_COUNT) || (G.in_full + G.in_part != count)) {
 498                ssize_t n;
 499#if ENABLE_FEATURE_DD_IBS_OBS
 500                if (G.flags & FLAG_FULLBLOCK)
 501                        n = full_read(ifd, ibuf, ibs);
 502                else
 503#endif
 504                        n = safe_read(ifd, ibuf, ibs);
 505                if (n == 0)
 506                        break;
 507                if (n < 0) {
 508                        /* "Bad block" */
 509                        if (!(G.flags & FLAG_NOERROR))
 510                                goto die_infile;
 511                        bb_simple_perror_msg(infile);
 512                        /* GNU dd with conv=noerror skips over bad blocks */
 513                        xlseek(ifd, ibs, SEEK_CUR);
 514                        /* conv=noerror,sync writes NULs,
 515                         * conv=noerror just ignores input bad blocks */
 516                        n = 0;
 517                }
 518                if (G.flags & FLAG_SWAB) {
 519                        uint16_t *p16;
 520                        ssize_t n2;
 521
 522                        /* Our code allows only last read to be odd-sized */
 523                        if (prev_read_size & 1)
 524                                bb_error_msg_and_die("can't swab %lu byte buffer",
 525                                                (unsigned long)prev_read_size);
 526                        prev_read_size = n;
 527
 528                        /* If n is odd, last byte is not swapped:
 529                         *  echo -n "qwe" | dd conv=swab
 530                         * prints "wqe".
 531                         */
 532                        p16 = (void*) ibuf;
 533                        n2 = (n >> 1);
 534                        while (--n2 >= 0) {
 535                                *p16 = bswap_16(*p16);
 536                                p16++;
 537                        }
 538                }
 539                if ((size_t)n == ibs)
 540                        G.in_full++;
 541                else {
 542                        G.in_part++;
 543                        if (G.flags & FLAG_SYNC) {
 544                                memset(ibuf + n, 0, ibs - n);
 545                                n = ibs;
 546                        }
 547                }
 548#if ENABLE_FEATURE_DD_IBS_OBS
 549                if (G.flags & FLAG_TWOBUFS) {
 550                        char *tmp = ibuf;
 551                        while (n) {
 552                                size_t d = obs - ocount;
 553                                if (d > (size_t)n)
 554                                        d = n;
 555                                memcpy(obuf + ocount, tmp, d);
 556                                n -= d;
 557                                tmp += d;
 558                                ocount += d;
 559                                if (ocount == obs) {
 560                                        if (write_and_stats(obuf, obs, obs, outfile))
 561                                                goto out_status;
 562                                        ocount = 0;
 563                                }
 564                        }
 565                } else
 566#endif
 567                {
 568                        if (write_and_stats(ibuf, n, obs, outfile))
 569                                goto out_status;
 570                }
 571        }
 572
 573        if (G.flags & FLAG_FSYNC) {
 574                if (fsync(ofd) < 0)
 575                        goto die_outfile;
 576        }
 577
 578#if ENABLE_FEATURE_DD_IBS_OBS
 579        if (ocount != 0) {
 580                if (write_and_stats(obuf, ocount, obs, outfile))
 581                        goto out_status;
 582        }
 583#endif
 584        if (close(ifd) < 0) {
 585 die_infile:
 586                bb_simple_perror_msg_and_die(infile);
 587        }
 588
 589        if (close(ofd) < 0) {
 590 die_outfile:
 591                bb_simple_perror_msg_and_die(outfile);
 592        }
 593
 594        exitcode = EXIT_SUCCESS;
 595 out_status:
 596        if (!ENABLE_FEATURE_DD_STATUS || !(G.flags & FLAG_STATUS_NONE))
 597                dd_output_status(0);
 598
 599        if (ENABLE_FEATURE_CLEAN_UP) {
 600                free(obuf);
 601                if (G.flags & FLAG_TWOBUFS)
 602                        free(ibuf);
 603        }
 604
 605        return exitcode;
 606}
 607