Logo Search packages:      
Sourcecode: yydecode version File versions

yydecode.c

/* yydecode utility -- http://nerv.cx/liyang/
   Copyright (C) 1994, 1995 Free Software Foundation, Inc.

   This product is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This product is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this product; see the file COPYING.  If not, write to
   the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  */

#define _GNU_SOURCE

#include "system.h"
#include <fcntl.h>
#include "getopt.h"
#include "crc32.h"
#include "yydecode.h"

/* entry point */
int main(int argc, char * const *argv);

/* static prototypes */
static u_int32_t atocrc32(char *str);
static struct decoded_file *decoded_file_lookup(struct decoded_file **list, const char *filename, const char *forced_outname);
static int read_yenc(const char *inname, const char *forced_outname, struct decoded_file **decoded_list, int whinge);
static int decode(const char *inname, const char *forced_outname, struct decoded_file **decoded_list);
static void usage(int status);

/* extern variables */
const char /*@null@*/ *program_name = NULL;

/* static variables */
static int opt_force_overwrite = 0;
static int opt_write_broken = 0;
static int opt_large_parts = 0;

/* code starts here */

static u_int32_t
atocrc32(str)
      char *str;
{
      return((u_int32_t)strtoul((char *)str, NULL, 16));
}

static struct decoded_file *
decoded_file_lookup(list, filename, forced_outname)
      struct decoded_file **list;
      const char *filename;
      const char *forced_outname;
{
      struct decoded_file **last = list;
      struct decoded_file *p = *list;

      for(; p; p = p->next)
      {
            if(!strcmp(p->filename, filename))
                  return(p);
            last = &p->next;
      }

      *last = malloc(sizeof(struct decoded_file));
      memset(*last, 0, sizeof(struct decoded_file));
      (*last)->filename = strdup(filename);

      /* If it's writable, we'll say it exists. (because that's the only case
       * when we can overwrite it, and that's all we care about.) Otherwise,
       * we pretend it doesn't, and let open() whinge instead. */
      (*last)->previously_existed = access(forced_outname, W_OK) ? 0 : 1;

      return(*last);
}

/* Called once per part. */
static int
read_yenc(inname, forced_outname, decoded_list, whinge)
      const char *inname;
      const char *forced_outname;
      struct decoded_file **decoded_list;
      int whinge;
{
      int exit_status = EXIT_SUCCESS;
      unsigned char buf[2 * BUFSIZ];
      unsigned char *p;
      unsigned char *out = NULL;
      unsigned char *pout = NULL;
      struct decoded_file *this_file = NULL;

      /* Note that much of the logic here equates single-part articles to a
       * one-part multi-part article. */

      int part = 0;
      int total_parts = 0;
      const char *part_outname;
      int part_status = part_broken;      /* Assume broken unless we verify otherwise */
      int is_multi_part = 0;
      off_t part_begin = 0;
      off_t part_end = 0;

#define ASSERT(expr)                                        \
      if(!(expr))                                     \
      {                                               \
            error(0, 0, _("%s: Assertion `%s' failed -- output may be corrupt"),    \
                  inname, #expr);                           \
            exit_status = EXIT_FAILURE;                     \
            goto exit_fail;                                 \
      }

#define SEARCH_MARKER(marker, whinge, ret_fail)                   \
      if(!fgets((char *)buf, (int)sizeof(buf), stdin))                  \
      {                                               \
            if(whinge)                                \
                  error(0, 0, _("%s: No `%s' marker found"),      \
                        inname, (marker));                  \
            exit_status = (ret_fail);                       \
            goto exit_fail;                                 \
      }                                               \
      if(!strncmp((char *)buf, marker, lenof(marker)))            \
            break

#define PARSE_TAG_OPTIONAL(tag, convertfn, assignto)              \
      if((p = (unsigned char *)strstr((char *)buf, tag)))         \
            assignto = convertfn((char *)(p + lenof(tag)))

#define PARSE_TAG(tag, convertfn, assignto)                       \
      PARSE_TAG_OPTIONAL(tag, convertfn, assignto);               \
      else                                            \
      {                                               \
            error(0, 0, _("%s: No `%s' tag found"),               \
                  inname, tag);                             \
            exit_status = EXIT_FAILURE;                     \
            goto exit_fail;                                 \
      }

      for(;;) { SEARCH_MARKER(YMARKER_BEGIN, whinge, EXIT_EOF); }

      /* Pretend single-part articles have a single `part=1' */
      PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, part);
      PARSE_TAG_OPTIONAL(YTAG_TOTAL, (int)atol, total_parts);
      is_multi_part = part;
      if(!part)
            part++;
      ASSERT(part > 0);
      if(total_parts)
            ASSERT(part <= total_parts);
      if(total_parts < part)
            total_parts = part;

      /* parse the name tag */
      if(!(p = (unsigned char *)strstr((char*)buf, YTAG_NAME)))
      {
            error(0, 0, _("%s: No `%s' tag found"),
                  inname, YTAG_NAME);
            exit_status = EXIT_FAILURE;
            goto exit_fail;
      }
      p += lenof(YTAG_NAME);
      part_outname = (char *)p;

      p += strlen((char *)p) - 1;
      while((*p == '\r') || (*p == '\n'))
            --p;
      *++p = '\0';

      if(!forced_outname)
            forced_outname = part_outname;

      /* Once we get past this point, this_file->status is guaranteed to be
       * non-NULL *on return*. */
      this_file = decoded_file_lookup(decoded_list, part_outname, forced_outname);

      /* Don't bother decoding duplicated parts. */
      if((part <= this_file->total_parts)
            && ((this_file->status[part - 1] == part_intact)
                  || (this_file->status[part - 1] == part_duplicated)))
      {
            /* This is OK, because out == NULL */
            part_status = part_duplicated;
            goto exit_fail;
      }

      {     /* Assert that we can handle the line width.
             * sizeof(buf) should be 16kb, hope that's sufficient. */
            int line_width;
            PARSE_TAG(YTAG_LINE, (int)atol, line_width);
            ASSERT(line_width >= 0);
            ASSERT((size_t)line_width < sizeof(buf));
      }

      {
            off_t file_size = 0;

            PARSE_TAG(YTAG_SIZE, atol, file_size);
            ASSERT(file_size >= 0);

            if(this_file->total_parts && (this_file->total_size != file_size))
            {
                  error(0, 0, _("%s: warning: File size mismatch -- previous parts says %li, this part %li -- ignoring this part"),
                        inname, this_file->total_size, file_size);
                  exit_status = EXIT_WARNING;
            }
            else
                  this_file->total_size = file_size;
      }

      if(!is_multi_part)
            part_end = this_file->total_size;

      /* Not sure why we bother checking /dev/stdout -- uudecode does. */
      if(!strcmp(forced_outname, "/dev/stdout"))
            forced_outname = "-";

      if(strcmp(forced_outname, "-"))
      {
            int handle;

            /* Ask for exclusiveness if the file existed before the start
             * of this program, and we didn't force overwriting. In a way,
             * we already know that this is going to fail, but we're lazy,
             * so we let open() generate the error message for us. */

            /* Note that we truncate (overwriting in the process) a file
             * only if we're not writing broken parts. */

            handle = open(forced_outname, O_WRONLY | O_CREAT
                        | (this_file->previously_existed && !opt_force_overwrite
                              ? O_EXCL : 0)
                        | (this_file->previously_existed && opt_force_overwrite && !opt_write_broken
                              ? O_TRUNC : 0),
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
            if(handle >= 0)
                  close(handle);
            else
            {
                  /* Only print error for the first part. */
                  if(!this_file->total_parts)
                        error(0, errno, _("%s: %s"), inname, forced_outname);
                  exit_status = EXIT_FAILURE;
                  goto exit_fail;
            }

            /* If it didn't fail above, then we own this file now ... */
            this_file->previously_existed = 0;

            /* No such function as fdreopen() ... */
            if(!freopen(forced_outname, "r+", stdout))
            {
                  error(0, errno, _("%s: %s"), inname, forced_outname);
                  exit_status = EXIT_FAILURE;
                  goto exit_fail;
            }
      }

      /* Is this a multi-part article? */
      if(is_multi_part)
      {
            for(;;) { SEARCH_MARKER(YMARKER_PART, 1, EXIT_FAILURE); }

            PARSE_TAG(YTAG_BEGIN, atol, part_begin);
            part_begin--;
            ASSERT(part_begin >= 0);

            PARSE_TAG(YTAG_END, atol, part_end);
            ASSERT(part_end >= 0);
            ASSERT(part_end >= part_begin);
            ASSERT(part_end <= this_file->total_size);

            /* This fails on the real stdout, but we ignore that. */
            (void)fseek(stdout, part_begin, SEEK_SET);
      }

      /* Prepare the output buffer. */
      if((part_end - part_begin > PART_SIZE_SOFT_LIMIT) && !opt_large_parts)
      {
            error(0, 0, _("%s: not going to malloc() %lu bytes (broken header?); use --large-parts"),
                  inname, part_end - part_begin);
            exit_status = EXIT_FAILURE;
            goto exit_fail;
      }

      if(!(pout = out = malloc((size_t)(part_end - part_begin))))
      {
            error(0, 0, _("%s: Unable to malloc() %lu bytes"),
                  inname, part_end - part_begin);
            exit_status = EXIT_FAILURE;
            goto exit_fail;
      }

      /* Begin decoding! */
      for(;;)
      {
            int unrecognised_escapes[0x100];

            /* Breaks out of the for(;;) if =yend found. */
            SEARCH_MARKER(YMARKER_END, 1, EXIT_FAILURE);

            memset(unrecognised_escapes, 0, sizeof(unrecognised_escapes));
            p = buf + strlen((char *)buf) - 1;
            while((*p == '\r') || (*p == '\n'))
                  --p;
            *++p = '\0';

            for(p = buf; *p; p++)
            {
                  if(pout - out >= part_end - part_begin)
                  {
                        error(0, 0, _("%s: Part longer than expected"), inname);
                        exit_status = EXIT_FAILURE;
                        goto exit_fail;
                  }
                  if(*p != '=')
                  {
                        *pout++ = *p - 42;
                        continue;
                  }

                  switch(*++p)
                  {
                  default:    /* Be liberal in what we accept. */
                        if(!unrecognised_escapes[*p & 0xff])
                        {     /* Just once is enough. */
                              error(0, 0, _("%s: warning: Unrecognised escape code `\\%o' (allowing it anyway)"),
                                    inname, *p);
                              exit_status = EXIT_WARNING;
                        }
                        unrecognised_escapes[*p & 0xff]++;
                  /*   NUL       TAB        LF        CR */
                  case '@': case 'I': case 'J': case 'M':
                  /*    =         .  */
                  case '}': case 'n':
                        *pout++ = *p - '@' - 42;
                        break;
                  }
            }
      }

      {     /* verify the CRC32 sum */
            u_int32_t part_crc32;
            struct crc32_ctx crc32_context;

            crc32_init_ctx(&crc32_context);
            crc32_process_bytes(out, pout - out, &crc32_context);
            crc32_finish_ctx(&crc32_context);

            if(is_multi_part)
            {
                  u_int32_t file_crc32 = 0;

                  PARSE_TAG_OPTIONAL(YTAG_CRC32, atocrc32, file_crc32);
                  if(file_crc32 && this_file->total_parts
                              && this_file->crc32
                              && (this_file->crc32 != file_crc32))
                  {
                        error(0, 0, _("%s: warning: File CRC mismatch in trailer -- previous parts says %08x, this part %08x -- ignoring this part"),
                              inname, this_file->crc32, file_crc32);
                        exit_status = EXIT_WARNING;
                  }
                  else if(file_crc32)
                        this_file->crc32 = file_crc32;

                  PARSE_TAG(YTAG_PCRC32, atocrc32, part_crc32);
            }
            else
            {
                  PARSE_TAG(YTAG_CRC32, atocrc32, part_crc32);
            }

            if(part_crc32 != crc32_read_ctx(&crc32_context))
            {
                  error(0, 0, _("%s: CRC error -- calculated 0x%08x, should be 0x%08x"),
                        inname, crc32_read_ctx(&crc32_context), part_crc32);
                  exit_status = EXIT_FAILURE;
                  goto exit_fail;
            }
      }

      /* Check the trailer part number. */
      {
            int trailer_part = 0;
            PARSE_TAG_OPTIONAL(YTAG_PART, atol, trailer_part);
            if(!is_multi_part)
                  trailer_part++;
            ASSERT(trailer_part > 0);

            if(trailer_part != part)
            {
                  error(0, 0, _("%s: warning: Part number mismatch -- header says %i, trailer %i; ignoring trailer"),
                        inname, part, trailer_part);
                  exit_status = EXIT_WARNING;
            }
      }

      /* All of the following are warnings, since the CRC matched. And
       * frankly, I trust the CRC more than I trust the ability of some
       * random sample of the human population to be able to code. */
      {
            off_t part_size = 0;

            PARSE_TAG(YTAG_SIZE, atol, part_size);
            ASSERT(part_size >= 0);

            if(part_size != pout - out)
            {
                  error(0, 0, _("%s: warning: Wrong part size -- decoded %lu bytes, expected %lu"),
                        inname, pout - out, part_size);
                  exit_status = EXIT_WARNING;
            }

            if(part_size != part_end - part_begin)
            {
                  error(0, 0, _("%s: warning: Part size/range mismatch -- %lu != %lu-%lu"),
                        inname, part_size, part_end, part_begin);
                  exit_status = EXIT_WARNING;
            }
      }

      part_status = part_intact;

exit_fail:
      if(out)
      {
            /* Commit the part to file. */
            if((part_status == part_intact) || opt_write_broken)
            {
                  /* this_file always set before out */
                  this_file->bytes_written += pout - out;
                  fwrite(out, pout - out, 1, stdout);
            }
            free(out);
      }

      if(this_file)
      {
            if(total_parts > this_file->total_parts)
            {
                  enum part_status *ps;

                  ps = malloc(total_parts * sizeof(enum part_status));
                  if(this_file->total_parts) /* avoid memcpy 0 bytes */
                        memcpy(ps, this_file->status,
                              this_file->total_parts * sizeof(enum part_status));
                  memset(ps + this_file->total_parts, 0,
                        (part - this_file->total_parts) * sizeof(enum part_status));
                  if(this_file->status)
                        free(this_file->status);
                  this_file->status = ps;
                  this_file->total_parts = total_parts;
            }

            this_file->status[part - 1] = part_status;
      }
#undef PARSE_TAG
#undef SEARCH_MARKER
#undef ASSERT
      return(exit_status);
}

/* Called once per each input file. */
static int
decode(inname, forced_outname, decoded_list)
      const char *inname;
      const char *forced_outname;
      struct decoded_file **decoded_list;
{
      int exit_status = EXIT_SUCCESS;
      int first_pass = 1;

      for(; ; first_pass = 0)
      {
            switch(read_yenc(inname, forced_outname, decoded_list, first_pass))
            {
            case EXIT_EOF:
                  return(first_pass ? EXIT_FAILURE : exit_status);

            case EXIT_WARNING:      /* A failure is more severe */
                  exit_status = exit_status == EXIT_FAILURE
                        ? EXIT_FAILURE : EXIT_WARNING;
                  break;
            case EXIT_FAILURE: default:
                  exit_status = EXIT_FAILURE;
                  /* Try to decode the rest of the input regardless. */
            case EXIT_SUCCESS:
                  /* Last part was good, let's go back for more. */
                  break;
            }

      }
}

static void
usage(status)
      int status;
{
      if(status)
            fprintf(stderr, _("Try `%s --help' for more information.\n"),
                  program_name);
      else
      {
            printf(_("\
%s %s -- Copyright (C) Liyang Hu 2002, see http://nerv.cx/liyang/\n\
Usage: %s [FILE] ...\n\
  -o, --output-file=FILE   direct all output to FILE (use - for stdout)\n\
  -f, --force-overwrite    overwrite output file; truncate unless -b is used.\n\
  -b, --write-broken       write decoded part even if verified to be broken,\n\
                           and don't append .broken to the filename\n\
  -l, --large-parts        expect parts larger than %uk\n\
  -h, --help               display this help and exit\n\
  -v, --version            output version information and exit\n\
Refer to the man page for details.\n"),
                  PACKAGE, VERSION,
                  program_name,
                  PART_SIZE_SOFT_LIMIT >> 10);
      }
      exit(status);
}

int
main(argc, argv)
      int argc;
      char * const *argv;
{
      int opt;
      int exit_status;
      const char *outname;
      struct decoded_file *decoded_list = NULL;

      struct option longopts[] =
      {
            { "write-broken", no_argument, NULL, (int)'b' },
            { "force-overwrite", no_argument, NULL, (int)'f' },
            { "help", no_argument, NULL, (int)'h' },
            { "output-file", required_argument, NULL, (int)'o' },
            { "large-parts", no_argument, NULL, (int)'l' },
            { "version", no_argument, NULL, 'v' },
            { NULL, 0, NULL, 0 }
      };

      program_name = argv[0];
      outname = NULL;

      while(opt = getopt_long(argc, argv, "bfho:lv", longopts, (int *)NULL),
            opt != EOF)
      {
            switch(opt)
            {
            case 'h':
                  usage(EXIT_SUCCESS);

            case 'o':
                  outname = optarg;
                  break;

            case 'v':
                  printf("%s %s\n", PACKAGE, VERSION);
                  exit(EXIT_SUCCESS);

            case 'b':
                  opt_write_broken = 1;
                  break;

            case 'f':
                  opt_force_overwrite = 1;
                  break;

            case 'l':
                  opt_large_parts = 1;
                  break;

            case 0:
                  break;

            default:
                  usage(EXIT_FAILURE);
            }
      }

      if(optind == argc)
            exit_status = decode("stdin", outname, &decoded_list);
      else
      {
            exit_status = EXIT_SUCCESS;
            do
            {
                  int det_return;
                  if(!freopen(argv[optind], "r", stdin))
                  {
                        error(0, errno, _("%s"), argv[optind]);
                        exit_status = EXIT_FAILURE;
                        break;
                  }

                  if((det_return = decode(argv[optind], outname, &decoded_list))
                              != EXIT_SUCCESS)
                        exit_status = det_return;
            }
            while(++optind < argc);
      }

      while(decoded_list)
      {
            int rename_file = 0;
            struct decoded_file *p = decoded_list;
            int i;

            if(!p->previously_existed)
            {
                  /* Did we write to this file at all? Yes: */

                  for(i = 0; i < p->total_parts; i++)
                  {
                        switch(p->status[i])
                        {
                        case part_missing:
                              error(0, 0, _("%s: Part %i of %i is missing"),
                                    p->filename, i + 1, p->total_parts);
                              break;
                        case part_broken:
                              error(0, 0, _("%s: Part %i of %i is broken"),
                                    p->filename, i + 1, p->total_parts);
                              break;
                        case part_duplicated:
                              error(0, 0, _("%s: warning: Part %i of %i is duplicated (probably not broken)"),
                                    p->filename, i + 1, p->total_parts);
                        case part_intact:
                              continue;
                        }
                        rename_file = 1;
                  }

                  if(p->bytes_written != p->total_size)
                  {
                        error(0, 0, _("%s: wrote %li bytes; should have been %li"),
                              p->filename, p->bytes_written, p->total_size);
                        rename_file = 1;
                  }

                  if(rename_file && !opt_write_broken)
                  {
                        char *outname_broken = malloc(strlen(p->filename)
                                    + lenof(YYDEC_BROKEN_SUFFIX) + 1);
                        strcpy(outname_broken, p->filename);
                        strcat(outname_broken, YYDEC_BROKEN_SUFFIX);
                        error(0, 0, "%s: file is broken -- renamed to %s", p->filename, outname_broken);
                        rename(p->filename, outname_broken);
                        free(outname_broken);
                  }

            } /* Never even wrote to this file: do nothing. */

            decoded_list = p->next;
            if(p->filename)
                  free(p->filename);
            if(p->status)
                  free(p->status);
            free(p);
      }

      exit(exit_status);
}


Generated by  Doxygen 1.6.0   Back to index