root/src/punutil/cardutil.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. xstrerror_l
  2. ts_add_ns
  3. monotonic_nssleep
  4. mach_clock_nanosleep
  5. openbsd_clock_nanosleep
  6. monotonic_sleep_until
  7. monotonic_nssleep
  8. dts_trim
  9. print_help
  10. parse_options
  11. init_writer
  12. write_word12
  13. getCardLine
  14. lookup_hcode
  15. convert_card_image
  16. write_card
  17. emit_card
  18. process_stream
  19. put_buf_func
  20. daemon_loop
  21. main

   1 /*
   2  * vim: filetype=c:tabstop=2:softtabstop=2:shiftwidth=2:ai:expandtab
   3  * SPDX-License-Identifier: MIT
   4  * scspell-id: 56421d52-77b1-11f0-a8e9-80ee73e9b8e7
   5  *
   6  * ----------------------------------------------------------------------------
   7  *
   8  * Copyright (c) 2025 Eric J. Swenson
   9  * Copyright (c) 2025 Jeffrey H. Johnson
  10  * Copyright (c) 2025 The DPS8M Development Team
  11  *
  12  * Permission is hereby granted, free of charge, to any person obtaining
  13  * a copy of this software and associated documentation files (the "Software"),
  14  * to deal in the Software without restriction, including without limitation
  15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  16  * and/or sell copies of the Software, and to permit persons to whom the
  17  * Software is furnished to do so, subject to the following conditions:
  18  *
  19  * * The above copyright notice and this permission notice shall be
  20  *   included in all copies or substantial portions of the Software.
  21  *
  22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  25  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  26  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  27  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  28  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29  *
  30  * ----------------------------------------------------------------------------
  31  */
  32 
  33 #if !defined(_POSIX_C_SOURCE)
  34 # define _POSIX_C_SOURCE 200809L
  35 #endif
  36 
  37 #if !defined(_GNU_SOURCE)
  38 # define _GNU_SOURCE
  39 #endif
  40 
  41 #if !defined(_NETBSD_SOURCE)
  42 # define _NETBSD_SOURCE
  43 #endif
  44 
  45 #if !defined(_OPENBSD_SOURCE)
  46 # define _OPENBSD_SOURCE
  47 #endif
  48 
  49 #if !defined(CARDUTIL_VERSION_MAJOR)
  50 # define CARDUTIL_VERSION_MAJOR (0)
  51 #endif
  52 
  53 #if !defined(CARDUTIL_VERSION_MINOR)
  54 # define CARDUTIL_VERSION_MINOR (2)
  55 #endif
  56 
  57 #if !defined(CARDUTIL_VERSION_PATCH)
  58 # define CARDUTIL_VERSION_PATCH (1)
  59 #endif
  60 
  61 #if defined(__GLIBC__)
  62 # if !defined(_LARGEFILE64_SOURCE)
  63 #  define _LARGEFILE64_SOURCE 1
  64 # endif
  65 #endif
  66 
  67 #include <stdio.h>
  68 #include <stdlib.h>
  69 #include <string.h>
  70 #include <ctype.h>
  71 #include <unistd.h>
  72 #include <stdbool.h>
  73 #include <sys/types.h>
  74 #include <dirent.h>
  75 #include <errno.h>
  76 #include <sys/stat.h>
  77 #include <fcntl.h>
  78 #include <stddef.h>
  79 #include <stdint.h>
  80 #include <inttypes.h>
  81 #include <time.h>
  82 #include <libgen.h>
  83 #include <limits.h>
  84 #include <math.h>
  85 #include <float.h>
  86 
  87 #if !defined(NO_LOCALE)
  88 # include <locale.h>
  89 #endif
  90 
  91 #if defined(__APPLE__) && defined(__MACH__)
  92 # include <mach/mach_time.h>
  93 # include <xlocale.h>
  94 # if !defined(TIMER_ABSTIME)
  95 #  define TIMER_ABSTIME 1
  96 # endif
  97 #endif
  98 
  99 #if defined(__MINGW32__) || defined(__MINGW64__) || defined(CROSS_MINGW32) || defined(CROSS_MINGW64)
 100 # include <windows.h>
 101 #endif
 102 
 103 #if defined(_AIX) && !defined(USE_POPT)
 104 # define USE_POPT
 105 #endif /* if defined(_AIX) && !defined(USE_POPT) */
 106 #if defined(USE_POPT)
 107 # include <popt.h>
 108 #else
 109 # include <getopt.h>
 110 #endif /* if defined(USE_POPT) */
 111 
 112 #include "miniz_config.h"
 113 #include "miniz.h"
 114 
 115 #if defined(FREE)
 116 # undef FREE
 117 #endif /* if defined(FREE) */
 118 #define FREE(p) do \
 119     {              \
 120       free ((p));  \
 121       (p) = NULL;  \
 122     }              \
 123   while (0)
 124 
 125 #define CARD_COL_COUNT (80)
 126 #define DEFAULT_POLL_RATE (0.5)
 127 
 128 typedef unsigned char uint8;
 129 typedef unsigned short uint16;
 130 typedef uint16 word12;
 131 
 132 typedef struct
 133 {
 134   word12 column [CARD_COL_COUNT];
 135 } card_image_t;
 136 
 137 typedef struct
 138 {
 139   int fd;
 140   unsigned char residue;
 141   bool residue_present;
 142 } writer_state_t;
 143 
 144 enum state_t
 145 {
 146   StartDeck,
 147   ReadCards,
 148   NoMoreCards,
 149   DoneReading
 150 };
 151 
 152 #if !defined(NO_LOCALE)
 153 # define XSTR_EMAXLEN 32767
 154 
 155 static const char *
 156 xstrerror_l (int errnum)
     /* [previous][next][first][last][top][bottom][index][help] */
 157 {
 158   int saved        = errno;
 159   const char * ret = NULL;
 160   static /* __thread */ char buf [XSTR_EMAXLEN];
 161 
 162 # if defined(__APPLE__) || defined(_AIX) || defined(__MINGW32__) || \
 163      defined(__MINGW64__) || defined(CROSS_MINGW32) || defined(CROSS_MINGW64)
 164 #  if defined(__MINGW32__) || defined(__MINGW64__) || defined(CROSS_MINGW32) || defined(CROSS_MINGW64)
 165   if (0 == strerror_s (buf, sizeof (buf), errnum)) ret = buf; /*LINTOK: xstrerror_l*/
 166 #  else
 167   if (0 == strerror_r (errnum, buf, sizeof (buf))) ret = buf; /*LINTOK: xstrerror_l*/
 168 #  endif
 169 # else
 170 #  if defined(__NetBSD__)
 171   locale_t loc = LC_GLOBAL_LOCALE;
 172 #  else
 173   locale_t loc = uselocale ((locale_t)0);
 174 #  endif
 175   locale_t copy = loc;
 176   if (LC_GLOBAL_LOCALE == copy)
 177     copy = duplocale (copy);
 178 
 179   if ((locale_t)0 != copy)
 180     {
 181       ret = strerror_l (errnum, copy); /*LINTOK: xstrerror_l*/
 182       if (LC_GLOBAL_LOCALE == loc)
 183         freelocale (copy);
 184     }
 185 # endif
 186 
 187   if (! ret)
 188     {
 189       int n_buf = snprintf (buf, sizeof (buf), "Unknown error %d", errnum);
 190       if (0 > n_buf || (size_t)n_buf >= sizeof (buf))
 191         {
 192           (void)fprintf (stderr, "FATAL: snprintf buffer overflow at %s[%s:%d]\n",
 193                          __func__, __FILE__, __LINE__);
 194           exit (EXIT_FAILURE);
 195         }
 196 
 197       ret = buf;
 198     }
 199 
 200   errno = saved;
 201   return ret;
 202 }
 203 #else
 204 # define xstrerror_l strerror
 205 #endif
 206 
 207 #define MAX_POLL_RATE ((double)INT64_MAX / 1e9 - 1.0)
 208 
 209 #if !defined(CARDUTIL_MAXPATH)
 210 # if defined(PATH_MAX)
 211 #  define CARDUTIL_MAXPATH PATH_MAX
 212 # elif defined(MAXPATHLEN)
 213 #  define CARDUTIL_MAXPATH MAXPATHLEN
 214 # else
 215 #  define CARDUTIL_MAXPATH 1024
 216 # endif
 217 #endif
 218 
 219 #if !defined(__MINGW32__) && !defined(__MINGW64__) && !defined(CROSS_MINGW32) && !defined(CROSS_MINGW64)
 220 # if defined(CLOCK_UPTIME)
 221 #  define CARDUTIL_INTERVALCLOCK CLOCK_UPTIME
 222 # elif defined(CLOCK_BOOTTIME) && !defined(__EMSCRIPTEN__)
 223 #  define CARDUTIL_INTERVALCLOCK CLOCK_BOOTTIME
 224 # elif defined(CLOCK_HIGHRES)
 225 #  define CARDUTIL_INTERVALCLOCK CLOCK_HIGHRES
 226 # elif defined(CLOCK_MONOTONIC)
 227 #  define CARDUTIL_INTERVALCLOCK CLOCK_MONOTONIC
 228 # else
 229 #  define CARDUTIL_INTERVALCLOCK CLOCK_REALTIME
 230 # endif
 231 
 232 static struct timespec
 233 ts_add_ns (struct timespec a, int64_t ns)
     /* [previous][next][first][last][top][bottom][index][help] */
 234 {
 235   int64_t sec_delta = ns / 1000000000LL;
 236   long nsec_delta   = ns % 1000000000LL;
 237   long temp_nsec    = a . tv_nsec + nsec_delta;
 238   int64_t carry     = 0;
 239   long final_nsec   = temp_nsec;
 240 
 241   if (1000000000L <= temp_nsec)
 242     {
 243       carry      = temp_nsec / 1000000000L;
 244       final_nsec = temp_nsec % 1000000000L;
 245     }
 246   else if (0 > temp_nsec)
 247     {
 248       carry = temp_nsec / 1000000000L;
 249 
 250       if (0 != temp_nsec % 1000000000L)
 251         carry -= 1;
 252 
 253       final_nsec = temp_nsec - carry * 1000000000L;
 254     }
 255 
 256   int64_t total_sec_delta = sec_delta + carry;
 257   int64_t rs = (int64_t)a . tv_sec;
 258 
 259   if (0 < total_sec_delta && INT64_MAX - total_sec_delta < rs)
 260     {
 261       struct timespec ts;
 262       ts . tv_sec  = (time_t)INT64_MAX;
 263       ts . tv_nsec = 999999999L;
 264       return ts;
 265     }
 266   else if (0 > total_sec_delta && INT64_MIN - total_sec_delta > rs)
 267     {
 268       struct timespec ts;
 269       ts . tv_sec  = (time_t)INT64_MIN;
 270       ts . tv_nsec = 0L;
 271       return ts;
 272     }
 273   else
 274     {
 275       a . tv_sec  = (time_t)(rs + total_sec_delta);
 276       a . tv_nsec = final_nsec;
 277       return a;
 278   }
 279 }
 280 #endif
 281 
 282 #if defined(__MINGW32__) || defined(__MINGW64__) || defined(CROSS_MINGW32) || defined(CROSS_MINGW64)
 283 static int
 284 monotonic_nssleep (uint64_t ns)
     /* [previous][next][first][last][top][bottom][index][help] */
 285 {
 286   if ((uint64_t)INT64_MAX < ns)
 287     return EINVAL;
 288 
 289   DWORD ms = (DWORD)(ns / 1000000ULL);
 290 
 291   if (0 < ms)
 292     Sleep (ms);
 293 
 294   return 0;
 295 }
 296 #else
 297 
 298 # if defined(__APPLE__) && defined(__MACH__)
 299 static int
 300 mach_clock_nanosleep (clockid_t clock_id, int flags, const struct timespec * request, struct timespec * remain)
     /* [previous][next][first][last][top][bottom][index][help] */
 301 {
 302   mach_timebase_info_data_t tbi;
 303   mach_timebase_info (& tbi);
 304 
 305   if (TIMER_ABSTIME == flags)
 306     {
 307       struct timespec now;
 308 
 309       if (0 != clock_gettime (clock_id, & now))
 310         return errno;
 311 
 312       uint64_t now_ns    = (uint64_t)now . tv_sec * 1000000000ULL + now . tv_nsec;
 313       uint64_t target_ns = (uint64_t)request -> tv_sec * 1000000000ULL + request -> tv_nsec;
 314 
 315       if (now_ns >= target_ns)
 316         return 0;
 317 
 318       uint64_t diff_ns  = target_ns - now_ns;
 319       uint64_t diff_abs = diff_ns * tbi . denom / tbi . numer;
 320       uint64_t now_abs  = mach_absolute_time ();
 321 
 322       return mach_wait_until (now_abs + diff_abs) == KERN_SUCCESS ? 0 : errno;
 323     }
 324   else
 325     {
 326       uint64_t diff_ns  = (uint64_t)request -> tv_sec * 1000000000ULL + request -> tv_nsec;
 327       uint64_t diff_abs = diff_ns * tbi . denom / tbi . numer;
 328       uint64_t now_abs  = mach_absolute_time ();
 329 
 330       return mach_wait_until (now_abs + diff_abs) == KERN_SUCCESS ? 0 : errno;
 331     }
 332 }
 333 #  define clock_nanosleep mach_clock_nanosleep
 334 # endif
 335 
 336 # if defined(__OpenBSD__)
 337 static int
 338 openbsd_clock_nanosleep (clockid_t clk_id, int flags, const struct timespec * req, struct timespec * rem)
     /* [previous][next][first][last][top][bottom][index][help] */
 339 {
 340   if (flags & TIMER_ABSTIME)
 341     {
 342       struct timespec now;
 343 
 344       if (0 != clock_gettime (clk_id, & now))
 345         return errno;
 346 
 347       struct timespec rel;
 348       rel . tv_sec  = req -> tv_sec  - now . tv_sec;
 349       rel . tv_nsec = req -> tv_nsec - now . tv_nsec;
 350 
 351       if (0 > rel . tv_nsec)
 352         {
 353           rel . tv_nsec += 1000000000L;
 354           rel . tv_sec  -= 1;
 355         }
 356 
 357       if (0 > rel . tv_sec)
 358         return 0;
 359 
 360       return nanosleep (& rel, rem);
 361     }
 362   else
 363     return nanosleep (req, rem);
 364 }
 365 #  define clock_nanosleep openbsd_clock_nanosleep
 366 # endif
 367 
 368 static int
 369 monotonic_sleep_until (struct timespec deadline)
     /* [previous][next][first][last][top][bottom][index][help] */
 370 {
 371   for (;;)
 372     {
 373       int rc = clock_nanosleep (CARDUTIL_INTERVALCLOCK, TIMER_ABSTIME, & deadline, NULL);
 374 
 375       if (0 == rc)
 376         return 0;
 377 
 378       if (EINTR == rc)
 379         continue;
 380 
 381       return rc;
 382     }
 383 }
 384 
 385 static int
 386 monotonic_nssleep (uint64_t ns)
     /* [previous][next][first][last][top][bottom][index][help] */
 387 {
 388   if ((uint64_t)INT64_MAX < ns)
 389     return EINVAL;
 390 
 391   int64_t signed_ns = (int64_t)ns;
 392   struct timespec now;
 393 
 394   if (0 != clock_gettime (CARDUTIL_INTERVALCLOCK, & now))
 395     return errno;
 396 
 397   struct timespec deadline = ts_add_ns (now, signed_ns);
 398 
 399   return monotonic_sleep_until (deadline);
 400 }
 401 #endif
 402 
 403 #define MAX_DBL_FMTSTR (DBL_MAX_10_EXP + 19) /* 15 + 4 */
 404 #define MAX_DBL_FMTGRW 32
 405 
 406 static char *
 407 dts_trim (double value)
     /* [previous][next][first][last][top][bottom][index][help] */
 408 {
 409   size_t bufsize = MAX_DBL_FMTSTR;
 410   char  *buffer  = NULL;
 411 
 412   for (;;)
 413     {
 414       buffer = malloc (bufsize);
 415       if (! buffer)
 416         {
 417           (void)fprintf (stderr, "FATAL: Out of memory, aborting at %s[%s:%d]\n",
 418                          __func__, __FILE__, __LINE__);
 419           abort ();
 420         }
 421 
 422       int n = snprintf (buffer, bufsize, "%.15f", value);
 423       if (0 > n)
 424         {
 425           (void)fprintf (stderr, "FATAL: snprintf encoding failure at %s[%s:%d]\n",
 426                          __func__, __FILE__, __LINE__);
 427           FREE (buffer);
 428           exit (EXIT_FAILURE);
 429         }
 430 
 431       if ((size_t)n < bufsize)
 432         break;
 433 
 434       FREE (buffer);
 435       if (bufsize > SIZE_MAX - MAX_DBL_FMTGRW) //-V547
 436         {
 437           (void)fprintf (stderr, "FATAL: buffer size overflow at %s[%s:%d]\n",
 438                          __func__, __FILE__, __LINE__);
 439           exit (EXIT_FAILURE);
 440         }
 441 
 442       bufsize += MAX_DBL_FMTGRW;
 443     }
 444 
 445 #if defined(NO_LOCALE)
 446   const char * dp = ".";
 447 #else
 448   struct lconv * lc = localeconv ();
 449   const char * dp = (lc && lc -> decimal_point && * lc -> decimal_point) ? lc -> decimal_point : ".";
 450 #endif
 451 
 452   char * dot = strstr (buffer, dp);
 453   if (dot)
 454     {
 455       char * end = buffer + strlen (buffer) - 1;
 456 
 457       while (dot < end && '0' == * end)
 458         * end-- = '\0';
 459 
 460       size_t dp_len  = strlen (dp);
 461       size_t buf_len = strlen (buffer);
 462 
 463       if (buf_len >= dp_len && 0 == strcmp (buffer + buf_len - dp_len, dp))
 464         * (buffer + buf_len - dp_len) = '\0';
 465     }
 466 
 467   char * out = strdup (buffer);
 468   FREE (buffer);
 469 
 470   return out;
 471 }
 472 
 473 static void
 474 print_help (char * program, double defpoll)
     /* [previous][next][first][last][top][bottom][index][help] */
 475 {
 476   char * default_poll = dts_trim (defpoll);
 477 
 478   if (NULL == default_poll)
 479     {
 480       (void)fprintf (stderr, "FATAL: Out of memory, aborting at %s[%s:%d]\n",
 481                      __func__, __FILE__, __LINE__);
 482       abort ();
 483     }
 484 
 485   (void)fprintf (stderr, "\nMultics Card Utility Program");
 486   (void)fprintf (stderr, "\n");
 487   (void)fprintf (stderr, "\nInvoke as:\n");
 488   (void)fprintf (stderr, "  %s -m <infile >outfile\n", program);
 489   (void)fprintf (stderr, "  %s -g <infile >outfile\n", program);
 490   (void)fprintf (stderr, "  %s -d [ options ] -i indir -o outdir [ -a arcdir [ -z ] ]\n", program);
 491   (void)fprintf (stderr, "\n");
 492   (void)fprintf (stderr, "Where options are:\n");
 493   (void)fprintf (stderr, "  -h, --help       = Output this help message\n");
 494   (void)fprintf (stderr, "  -v, --version    = Output version information\n");
 495   (void)fprintf (stderr, "\n");
 496   (void)fprintf (stderr, "Data conversion options:\n");
 497   (void)fprintf (stderr, "  -u, --upper-jcl  = Enable conversion of input JCL to upper-case\n");
 498   (void)fprintf (stderr, "                     [If GBCD mode is enabled, ONLY upper-cases JCL]\n");
 499   (void)fprintf (stderr, "  -U, --upper-all  = Enable conversion of all input to upper-case\n");
 500   (void)fprintf (stderr, "                     [If GBCD mode is enabled, DISABLES upper-casing]\n");
 501   (void)fprintf (stderr, "\n");
 502   (void)fprintf (stderr, "Output mode options:\n");
 503   (void)fprintf (stderr, "  -m, --mcc        = Write the cards using MCC Punch Codes\n");
 504   (void)fprintf (stderr, "  -g, --gcos       = Write the cards using GCOS BCD Punch Codes\n");
 505   (void)fprintf (stderr, "                     [In GBCD mode, input is upper-cased by default]\n");
 506   (void)fprintf (stderr, "\n");
 507   (void)fprintf (stderr, "Daemon mode options:\n");
 508   (void)fprintf (stderr, "  -d, --daemon            = Run in daemon mode\n");
 509   (void)fprintf (stderr, "  -p, --poll <rate>       = Polling rate in seconds (default: %s)\n", default_poll);
 510   (void)fprintf (stderr, "  -i, --input <indir>     = Input directory\n");
 511   (void)fprintf (stderr, "  -o, --output <outdir>   = Output directory\n");
 512   (void)fprintf (stderr, "  -a, --archive <arcdir>  = Archive directory\n");
 513   (void)fprintf (stderr, "  -z, --compress          = Compress archived files\n");
 514   (void)fprintf (stderr, "\n");
 515   (void)fprintf (stderr, "Standard operation:\n");
 516   (void)fprintf (stderr, "  This program will convert standard input to Hollerith-coded card format\n");
 517   (void)fprintf (stderr, "  on standard output for use with the DPS8M Simulator punch card reader.\n");
 518   (void)fprintf (stderr, "\n");
 519   (void)fprintf (stderr, "Daemon mode operation:\n");
 520   (void)fprintf (stderr, "  When daemon mode ('--daemon', '-d') is specified, this program will convert\n");
 521   (void)fprintf (stderr, "  any files in the input directory (defined by '--input' or '-i') that end in\n");
 522   (void)fprintf (stderr, "  \".input\"  to  Hollerith-coded  card format  files in  the  output directory\n");
 523   (void)fprintf (stderr, "  (defined by '--output' or '-o').  Input files are  removed after conversion\n");
 524   (void)fprintf (stderr, "  unless the '--archive' (or '-a') option is specified.  Archived input files\n");
 525   (void)fprintf (stderr, "  may be  optionally compressed by specifying  the '--compress' ('-z') option.\n");
 526   (void)fprintf (stderr, "\n");
 527   (void)fprintf (stderr, "If no output mode is explicitly selected for daemon mode, then MCC is assumed.\n");
 528   (void)fprintf (stderr, "Note that only one  output mode  may be selected per execution.  If more than\n");
 529   (void)fprintf (stderr, "one  output mode  option is  specified,  then  the last  option is  effective.\n");
 530   (void)fprintf (stderr, "\n");
 531   (void)fprintf (stderr, "This software is made available under the terms of the MIT License.\n");
 532 
 533   FREE (default_poll);
 534 }
 535 
 536 static char cardImage [CARD_COL_COUNT + 1] = "";
 537 static int total_card_count                = 0;
 538 static int card_count                      = 0;
 539 static card_image_t card;
 540 
 541 #if defined(USE_POPT)
 542 static struct poptOption long_options [] = {
 543 #else
 544 static struct option long_options [] = {
 545 #endif /* if defined(USE_POPT) */
 546 #if defined(USE_POPT)
 547   { "gcos",     'g', POPT_ARG_NONE,   NULL, 'g', "gcos",      "GCOS"      },
 548   { "mcc",      'm', POPT_ARG_NONE,   NULL, 'm', "mcc",       "MCC"       },
 549   { "upper-jcl",'u', POPT_ARG_NONE,   NULL, 'u', "upper-jcl", "UPPER-JCL" },
 550   { "upper-all",'U', POPT_ARG_NONE,   NULL, 'U', "upper-all", "UPPER-ALL" },
 551   { "daemon",   'd', POPT_ARG_NONE,   NULL, 'd', "daemon",    "DAEMON"    },
 552   { "poll",     'p', POPT_ARG_STRING, NULL, 'p', "poll",      "POLL"      },
 553   { "input",    'i', POPT_ARG_STRING, NULL, 'i', "input",     "INPUT"     },
 554   { "output",   'o', POPT_ARG_STRING, NULL, 'o', "output",    "OUTPUT"    },
 555   { "archive",  'a', POPT_ARG_STRING, NULL, 'a', "archive",   "ARCHIVE"   },
 556   { "compress", 'z', POPT_ARG_NONE,   NULL, 'z', "compress",  "COMPRESS"  },
 557   { "version",  'v', POPT_ARG_NONE,   NULL, 'v', "version",   "VERSION"   },
 558   { "help",     'h', POPT_ARG_NONE,   NULL, 'h', "help",      "HELP"      }
 559 #else
 560   { "gcos",     no_argument,       0, 'g' },
 561   { "mcc",      no_argument,       0, 'm' },
 562   { "upper-jcl",no_argument,       0, 'u' },
 563   { "upper-all",no_argument,       0, 'U' },
 564   { "daemon",   no_argument,       0, 'd' },
 565   { "poll",     required_argument, 0, 'p' },
 566   { "input",    required_argument, 0, 'i' },
 567   { "output",   required_argument, 0, 'o' },
 568   { "archive",  required_argument, 0, 'a' },
 569   { "compress", no_argument,       0, 'z' },
 570   { "version",  no_argument,       0, 'v' },
 571   { "help",     no_argument,       0, 'h' },
 572   { 0,          0,                 0,  0  }
 573 #endif /* if defined(USE_POPT) */
 574 };
 575 
 576 /* Defaults */
 577 static bool use_ascii_encoding = true;
 578 static bool use_gcos_encoding  = false;
 579 static bool compress_archive   = false;
 580 static bool crlf_warned        = false;
 581 static bool daemon_mode        = false;
 582 static bool upper_jcl          = false;
 583 static bool upper_all          = false;
 584 static char * archive_dir      = NULL;
 585 static char * input_dir        = NULL;
 586 static char * output_dir       = NULL;
 587 static double poll_rate        = DEFAULT_POLL_RATE;
 588 
 589 static void
 590 parse_options (int argc, char * argv [])
     /* [previous][next][first][last][top][bottom][index][help] */
 591 {
 592   int c;
 593   char * endptr;
 594   char * poll_arg;
 595 #if !defined(USE_POPT)
 596   bool done = false;
 597 #endif /* if defined(!USE_POPT) */
 598 
 599 #if defined(USE_POPT)
 600   poptContext opt_con;
 601   opt_con = poptGetContext (NULL, argc, (const char **)argv, long_options, 0);
 602   while (0 <= (c = poptGetNextOpt (opt_con)))
 603     {
 604       optind += 1;
 605 #else
 606   while (! done)
 607     {
 608       int option_index = 0;
 609       c = getopt_long (argc, argv, "gmvuUdi:o:a:p:zh?", long_options, & option_index);
 610 #endif /* if defined(USE_POPT) */
 611 
 612       switch (c)
 613         {
 614 #if !defined(USE_POPT)
 615           case -1:
 616             done = true;
 617             break;
 618 #endif /* !defined(USE_POPT) */
 619 
 620           case 'm':
 621 #if defined(TESTING)
 622             (void)fprintf (stderr, "*** Switched to ASCII (MCC) mode\n");
 623 #endif
 624             use_ascii_encoding = true;
 625             use_gcos_encoding  = false;
 626             break;
 627 
 628           case 'g':
 629 #if defined(TESTING)
 630             (void)fprintf (stderr, "*** Switched to GCOS (GBCD) mode\n");
 631 #endif
 632             use_ascii_encoding = false;
 633             use_gcos_encoding  = true;
 634             break;
 635 
 636           case 'u':
 637 #if defined(TESTING)
 638             (void)fprintf (stderr, "*** Uppercase JCL enabled\n");
 639 #endif
 640             upper_jcl = true;
 641             break;
 642 
 643           case 'U':
 644 #if defined(TESTING)
 645             (void)fprintf (stderr, "*** Uppercase all enabled\n");
 646 #endif
 647             upper_all = true;
 648             break;
 649 
 650           case 'd':
 651 #if defined(TESTING)
 652             (void)fprintf (stderr, "*** Daemon mode enabled\n");
 653 #endif
 654             daemon_mode = true;
 655             break;
 656 
 657           case 'i':
 658 #if defined(TESTING)
 659             (void)fprintf (stderr, "*** Set input directory\n");
 660 #endif
 661 #if defined(USE_POPT)
 662             input_dir = poptGetOptArg (opt_con);
 663 #else
 664             input_dir = optarg;
 665 #endif /* if defined(USE_POPT) */
 666             break;
 667 
 668           case 'o':
 669 #if defined(TESTING)
 670             (void)fprintf (stderr, "*** Set output directory\n");
 671 #endif
 672 #if defined(USE_POPT)
 673             output_dir = poptGetOptArg (opt_con);
 674 #else
 675             output_dir = optarg;
 676 #endif /* if defined(USE_POPT) */
 677             break;
 678 
 679           case 'a':
 680 #if defined(TESTING)
 681             (void)fprintf (stderr, "*** Set archive directory\n");
 682 #endif
 683 #if defined(USE_POPT)
 684             archive_dir = poptGetOptArg (opt_con);
 685 #else
 686             archive_dir = optarg;
 687 #endif /* if defined(USE_POPT) */
 688             break;
 689 
 690           case 'z':
 691             compress_archive = true;
 692             break;
 693 
 694           case 'p':
 695             {
 696               poll_arg =
 697 #if defined(USE_POPT)
 698                 poptGetOptArg (opt_con);
 699 #else
 700                 optarg;
 701 #endif /* if defined(USE_POPT) */
 702               if (! poll_arg || '\0' == * poll_arg)
 703                 {
 704                   (void)fprintf (stderr, "ERROR: Empty polling rate specified.\n");
 705                   exit (EXIT_FAILURE);
 706                 }
 707 
 708               errno = 0;
 709               double parsed_rate = strtod (poll_arg, & endptr);
 710 
 711               if (ERANGE == errno)
 712                 {
 713                   (void)fprintf (stderr, "ERROR: Polling rate out of range.\n");
 714                   exit (EXIT_FAILURE);
 715                 }
 716 
 717               if ('\0' != * endptr)
 718                 {
 719                   (void)fprintf (stderr, "ERROR: Invalid polling rate: '%s'\n", poll_arg);
 720                   exit (EXIT_FAILURE);
 721                 }
 722 
 723               if (0.01 > parsed_rate || MAX_POLL_RATE <= parsed_rate)
 724                 {
 725                   (void)fprintf (stderr, "ERROR: Polling rate must be between 0.01 and %.0f seconds.\n",
 726                                  MAX_POLL_RATE - 1.0F);
 727                   exit (EXIT_FAILURE);
 728                 }
 729 
 730               poll_rate = parsed_rate;
 731 #if defined(TESTING)
 732               (void)fprintf (stderr, "*** Set polling rate: %f\n", poll_rate);
 733 #endif
 734             }
 735             break;
 736 
 737           case 'v':
 738 #if defined(TESTING)
 739             (void)fprintf (stderr, "*** Display version\n");
 740 #endif
 741             (void)fprintf (stderr, "Version %d.%d.%d\n", CARDUTIL_VERSION_MAJOR, CARDUTIL_VERSION_MINOR, CARDUTIL_VERSION_PATCH);
 742             exit (EXIT_SUCCESS);
 743 
 744           case 'h':
 745           case '?':
 746 #if defined(TESTING)
 747             (void)fprintf (stderr, "*** Display help\n");
 748 #endif
 749             print_help (argv [0], DEFAULT_POLL_RATE);
 750             exit (EXIT_SUCCESS);
 751 
 752           default:
 753             (void)fprintf (stderr, "*** Internal Error: did not recognize option '%d'\n", c);
 754             abort ();
 755         }
 756     }
 757 }
 758 
 759 static void
 760 init_writer (writer_state_t * writer_state, int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 761 {
 762   writer_state -> fd              = fd;
 763   writer_state -> residue         = 0;
 764   writer_state -> residue_present = false;
 765 }
 766 
 767 static int
 768 write_word12 (writer_state_t * writer_state, word12 code)
     /* [previous][next][first][last][top][bottom][index][help] */
 769 {
 770   int rc = 0;
 771 
 772   if (! writer_state -> residue_present)
 773     {
 774       unsigned char first_nibble      = (code & 0xf00) >> 8;
 775       unsigned char second_nibble     = (code & 0x0f0) >> 4;
 776       unsigned char third_nibble      = (code & 0x00F);
 777       unsigned char to_write          = (first_nibble << 4) | second_nibble;
 778       writer_state -> residue         = third_nibble;
 779       writer_state -> residue_present = true;
 780 
 781       rc = write (writer_state -> fd, & to_write, 1);
 782     }
 783   else
 784     {
 785       unsigned char first_nibble      = writer_state -> residue;
 786       writer_state -> residue_present = false;
 787       unsigned char second_nibble     = (code & 0xf00) >> 8;
 788       unsigned int to_write           = (first_nibble << 4) | second_nibble;
 789 
 790       rc = write (writer_state -> fd, (unsigned char *) & to_write, 1);
 791       if (0 < rc)
 792         {
 793           to_write = (code & 0x0ff);
 794           rc       = write (writer_state -> fd, & to_write, 1);
 795         }
 796     }
 797 
 798   return rc;
 799 }
 800 
 801 static ssize_t
 802 getCardLine (int fd, char * buffer, size_t cap, int line_num)
     /* [previous][next][first][last][top][bottom][index][help] */
 803 {
 804   size_t n = 0;
 805   for (;;)
 806     {
 807       unsigned char ch;
 808       ssize_t rc = read (fd, & ch, 1);
 809 
 810       if (0 > rc)
 811         return rc;
 812       else if (0 == rc)
 813         return -1;
 814       else
 815         {
 816           if ('\n' == ch)
 817             {
 818               if (0 < n && '\r' == buffer [n - 1])
 819                 {
 820                   if (! crlf_warned)
 821                     {
 822                       (void)fprintf (stderr, "WARNING: <CRLF> line ending detected, converting to <LF>\n");
 823                       crlf_warned = true;
 824                     }
 825 
 826                   n--;
 827                 }
 828 
 829               buffer [n] = '\0';
 830               return (ssize_t)n;
 831             }
 832 
 833           if (n < cap - 1)
 834             buffer [n++] = ch;
 835           else
 836             {
 837               (void)fprintf (stderr, "WARNING: Line %d exceeds %d characters, input truncated\n",
 838                              line_num, CARD_COL_COUNT);
 839               buffer [cap - 1] = '\0';
 840 
 841               do
 842                 rc = read (fd, & ch, 1);
 843               while (1 == rc && '\n' != ch);
 844 
 845               return (ssize_t)n;
 846             }
 847         }
 848     }
 849 }
 850 
 851 static uint16 ascii_table [128] =
 852 {
 853   05403, 04401, 04201, 04101, 00005, 01023, 01013, 01007,
 854   02011, 04021, 02021, 04103, 04043, 04023, 04013, 04007,
 855   06403, 02401, 02201, 02101, 00043, 00023, 00201, 01011,
 856   02003, 02403, 00007, 01005, 02043, 02023, 02013, 02007,
 857   00000, 02202, 00006, 00102, 02102, 01042, 04000, 00022,
 858   04022, 02022, 02042, 04012, 01102, 02000, 04102, 01400,
 859   01000, 00400, 00200, 00100, 00040, 00020, 00010, 00004,
 860   00002, 00001, 00202, 02012, 04042, 00012, 01012, 01006,
 861   00042, 04400, 04200, 04100, 04040, 04020, 04010, 04004,
 862   04002, 04001, 02400, 02200, 02100, 02040, 02020, 02010,
 863   02004, 02002, 02001, 01200, 01100, 01040, 01020, 01010,
 864   01004, 01002, 01001, 05022, 04202, 06022, 02006, 01022,
 865   00402, 05400, 05200, 05100, 05040, 05020, 05010, 05004,
 866   05002, 05001, 06400, 06200, 06100, 06040, 06020, 06010,
 867   06004, 06002, 06001, 03200, 03100, 03040, 03020, 03010,
 868   03004, 03002, 03001, 05000, 04006, 03000, 03400, 00000
 869 };
 870 
 871 static uint16 gcos_table [128] =
 872 {
 873   05403, 04401, 04201, 04101, 00005, 01023, 01013, 01007,
 874   02011, 04021, 02021, 04103, 04043, 04023, 04013, 04007,
 875   06403, 02401, 02201, 02101, 00043, 00023, 00201, 01011,
 876   02003, 02403, 00007, 01005, 02043, 02023, 02013, 02007,
 877   00000, 01006, 01012, 00102, 02102, 01042, 04000, 02006,
 878   04022, 02022, 02042, 05000, 01102, 02000, 04102, 01400,
 879   01000, 00400, 00200, 00100, 00040, 00020, 00010, 00004,
 880   00002, 00001, 00022, 02012, 04012, 01022, 00012, 00006,
 881   00042, 04400, 04200, 04100, 04040, 04020, 04010, 04004,
 882   04002, 04001, 02400, 02200, 02100, 02040, 02020, 02010,
 883   02004, 02002, 02001, 01200, 01100, 01040, 01020, 01010,
 884   01004, 01002, 01001, 00202, 04006, 04042, 03000, 01202,
 885   00402, 05400, 05200, 05100, 05040, 05020, 05010, 05004,
 886   05002, 05001, 06400, 06200, 06100, 06040, 06020, 06010,
 887   06004, 06002, 06001, 03200, 03100, 03040, 03020, 03010,
 888   03004, 03002, 03001, 05000, 04006, 03000, 03400, 00000
 889 };
 890 
 891 static int
 892 lookup_hcode (unsigned char c, word12 * h)
     /* [previous][next][first][last][top][bottom][index][help] */
 893 {
 894   if (128 <= c)
 895     return -1;
 896 
 897   if (use_ascii_encoding)
 898     * h = ascii_table [c];
 899   else
 900     * h = gcos_table [c];
 901 
 902   return 0;
 903 }
 904 
 905 static void
 906 convert_card_image (const char * s, size_t len, card_image_t * card)
     /* [previous][next][first][last][top][bottom][index][help] */
 907 {
 908   size_t l = len < CARD_COL_COUNT ? len : CARD_COL_COUNT;
 909 
 910   for (size_t i = 0; i < l; i++)
 911     {
 912       word12 hcode = 0;
 913       if (0 != lookup_hcode (s [i], & hcode))
 914         {
 915           if (daemon_mode)
 916             (void)fprintf (stderr, "WARNING: ");
 917           else
 918             (void)fprintf (stderr, "ERROR: ");
 919 
 920           (void)fprintf (stderr, "Could not translate character 0x%x at column %" PRIuMAX "\n",
 921                          (unsigned)(unsigned char)s [i], (uintmax_t)i + 1);
 922 
 923           if (! daemon_mode)
 924             exit (EXIT_FAILURE);
 925         }
 926 
 927       card -> column [i] = hcode;
 928     }
 929 
 930   for (size_t i = l; i < CARD_COL_COUNT; i++)
 931     card -> column [i] = 0;
 932 }
 933 
 934 static int
 935 write_card (writer_state_t * writer_state, card_image_t * card)
     /* [previous][next][first][last][top][bottom][index][help] */
 936 {
 937   for (size_t i = 0; i < CARD_COL_COUNT; i++)
 938     {
 939       int rc = write_word12 (writer_state, card -> column [i]);
 940       if (0 >= rc)
 941         {
 942           (void)fprintf (stderr, "WARNING: Error writing Hollerith code at %s[%s:%d]\n",
 943                          __func__, __FILE__, __LINE__);
 944           return rc;
 945         }
 946     }
 947 
 948   return 0;
 949 }
 950 
 951 static int
 952 emit_card (char * contents, char * cardImage, size_t cardImageLen, writer_state_t * writer_state)
     /* [previous][next][first][last][top][bottom][index][help] */
 953 {
 954   total_card_count += 1;
 955   (void)fprintf (stderr, "Adding %s card\n", contents);
 956 
 957   (void)strncpy (cardImage, contents, cardImageLen);
 958   cardImage [sizeof (cardImage) - 1] = '\0';
 959 
 960   convert_card_image (cardImage, (size_t)strlen (cardImage), & card);
 961 
 962   if (write_card (writer_state, & card))
 963     {
 964       (void)fprintf (stderr, "ERROR: write_card failure at %s[%s:%d]\n",
 965                      __func__, __FILE__, __LINE__);
 966       return 1;
 967     }
 968 
 969   return 0;
 970 }
 971 
 972 static int
 973 process_stream (int ifd, int ofd)
     /* [previous][next][first][last][top][bottom][index][help] */
 974 {
 975   writer_state_t writer_state;
 976   init_writer (& writer_state, ofd);
 977   ssize_t len;
 978   bool read_one      = false;
 979   int line_num       = 0;
 980   enum state_t state = StartDeck;
 981 
 982   while (state != DoneReading)
 983     {
 984       switch (state)
 985         {
 986           case StartDeck:
 987             if (use_ascii_encoding)
 988               {
 989                 int rc = emit_card ("++EOF", cardImage, sizeof (cardImage) - 1, & writer_state);
 990                 if (rc)
 991                   return -1;
 992 
 993                 rc = emit_card ("++UID 1", cardImage, sizeof (cardImage) - 1, & writer_state);
 994                 if (rc)
 995                   return -1;
 996               }
 997 
 998             state = ReadCards;
 999             break;
1000 
1001           case ReadCards:
1002             line_num++;
1003             len = getCardLine (ifd, cardImage, sizeof (cardImage), line_num);
1004             if (0 <= len)
1005               {
1006                 if (! read_one)
1007                   read_one = true;
1008 
1009                 if (upper_all)
1010                   for (char * p = cardImage; * p; ++p)
1011                     * p = toupper (* p);
1012                 else if (upper_jcl && cardImage [0] == '$')
1013                   for (char * p = cardImage; * p; ++p)
1014                     * p = toupper (* p);
1015 
1016                 card_count       += 1;
1017                 total_card_count += 1;
1018                 (void)fprintf (stderr, "Converting card %d\n", card_count);
1019                 convert_card_image (cardImage, (size_t)len, & card);
1020 
1021                 if (write_card (& writer_state, & card))
1022                   {
1023                     (void)fprintf (stderr, "write_card failure, exiting at %s[%s:%d]\n",
1024                                    __func__, __FILE__, __LINE__);
1025                     return -1;
1026                   }
1027               }
1028             else
1029               {
1030                 if (read_one)
1031                   state = NoMoreCards;
1032                 else
1033                   {
1034                     if (! daemon_mode)
1035                       {
1036                         (void)fprintf (stderr, "ERROR: could not read from stdin\n");
1037                         exit (EXIT_FAILURE);
1038                       }
1039                     else
1040                       (void)fprintf (stderr, "WARNING: could not read file data\n");
1041                   }
1042               }
1043             break;
1044 
1045           case NoMoreCards:
1046             if (use_gcos_encoding)
1047               {
1048                 int rc = emit_card ("***EOF", cardImage, sizeof (cardImage) - 1, & writer_state);
1049                 if (rc)
1050                   return -1;
1051 
1052                 state = DoneReading;
1053               }
1054             else
1055               {
1056                 int rc = emit_card ("++EOF", cardImage, sizeof (cardImage) - 1, & writer_state);
1057                 if (rc)
1058                   return -1;
1059 
1060                 rc = emit_card ("++UID 1", cardImage, sizeof (cardImage) - 1, & writer_state);
1061                 if (rc)
1062                   return -1;
1063 
1064                 state = DoneReading;
1065               }
1066             (void)fprintf (stderr, "Emitted %d total cards\n", total_card_count);
1067             card_count = total_card_count = 0;
1068             break;
1069 
1070           case DoneReading:
1071             /*NOTREACHED*/ /* unreachable */
1072             break;
1073         }
1074     }
1075 
1076     return 0;
1077 }
1078 
1079 static mz_bool
1080 put_buf_func (const void * pBuf, int len, void * pUser)
     /* [previous][next][first][last][top][bottom][index][help] */
1081 {
1082   return (fwrite (pBuf, 1, len, (FILE *)pUser) == (size_t)len);
1083 }
1084 
1085 static void
1086 daemon_loop (const char * in_dir, const char * out_dir, const char * archive_dir, bool compress_archive)
     /* [previous][next][first][last][top][bottom][index][help] */
1087 {
1088   const char * safe_chars = "cdhkmnprswxyzCDFGJKMNPRSTXY57";
1089   int safe_chars_len = strlen (safe_chars);
1090 
1091   srand (time (NULL));
1092   while (1)
1093     {
1094       DIR * d;
1095       struct dirent * dir;
1096       d = opendir (in_dir);
1097       time_t rawtime;
1098       struct tm * timelocal;
1099       char timebuffer [128];
1100 
1101       if (d)
1102         {
1103           while (NULL != (dir = readdir (d)))
1104             {
1105               const char * ext = strrchr (dir -> d_name, '.');
1106 
1107               if (ext && 0 == strcmp (ext, ".input"))
1108                 {
1109                   (void)time (& rawtime);
1110                   timelocal = localtime (& rawtime);
1111 
1112                   if (strftime (timebuffer, sizeof (timebuffer), "%A, %B %d %Y, %H:%M:%S %Z",
1113                                 timelocal))
1114                     (void)fprintf (stderr, "\nConversion job started %s\n", timebuffer);
1115 
1116                   (void)fprintf (stderr, "Converting input file '%s'\n", dir -> d_name);
1117 
1118                   char in_path [CARDUTIL_MAXPATH];
1119                   int n_in_path = snprintf (in_path, sizeof (in_path), "%s/%s", in_dir, dir -> d_name);
1120                   if (0 > n_in_path || (size_t)n_in_path >= sizeof (in_path))
1121                     {
1122                       (void)fprintf (stderr, "FATAL: snprintf input path overflow at %s[%s:%d]\n",
1123                                      __func__, __FILE__, __LINE__);
1124                       exit (EXIT_FAILURE);
1125                     }
1126 
1127                   int in_fd = open (in_path, O_RDONLY);
1128                   if (0 > in_fd)
1129                     {
1130                       (void)fprintf (stderr, "open input failure: '%s' (Error %d)\n",
1131                                      xstrerror_l (errno), errno);
1132                       continue;
1133                     }
1134 
1135                   time_t t = time (NULL);
1136                   if ((time_t)-1 == t)
1137                     {
1138                       (void)fprintf (stderr, "failed to read system time; using fallback\n");
1139                       t = 0;
1140                     }
1141                   else if (0 > t)
1142                     {
1143                       (void)fprintf (stderr, "absurd system time returned; using fallback\n");
1144                       t = 0;
1145                     }
1146 
1147                   char out_name [256];
1148                   int n_out_name = snprintf (out_name, sizeof (out_name), "%ld%c%c%c%c", (long)t,
1149                                              safe_chars[rand () % safe_chars_len],
1150                                              safe_chars[rand () % safe_chars_len],
1151                                              safe_chars[rand () % safe_chars_len],
1152                                              safe_chars[rand () % safe_chars_len]);
1153                   if (0 > n_out_name || (size_t)n_out_name >= sizeof (out_name))
1154                     {
1155                       (void)fprintf (stderr, "FATAL: snprintf out_name overflow at %s[%s:%d]\n",
1156                                      __func__, __FILE__, __LINE__);
1157                       exit (EXIT_FAILURE);
1158                     }
1159 
1160                   char open_path [CARDUTIL_MAXPATH];
1161                   int n_open_path = snprintf (open_path, sizeof (open_path), "%s/OPEN.%s", out_dir, out_name);
1162                   if (0 > n_open_path || (size_t)n_open_path >= sizeof (open_path))
1163                     {
1164                       (void)fprintf (stderr, "FATAL: snprintf open_path overflow at %s[%s:%d]\n",
1165                                      __func__, __FILE__, __LINE__);
1166                       exit (EXIT_FAILURE);
1167                     }
1168 
1169                   int out_fd = open (open_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
1170                   if (0 > out_fd)
1171                     {
1172                       (void)fprintf (stderr, "open output failure: '%s' (Error %d)\n",
1173                                      xstrerror_l (errno), errno);
1174                       (void)close (in_fd);
1175                       continue;
1176                     }
1177 
1178                   struct stat st_buf;
1179                   if (0 > fstat (in_fd, & st_buf))
1180                     {
1181                       (void)fprintf (stderr, "fstat failure: '%s' (Error %d)\n",
1182                                      xstrerror_l (errno), errno);
1183                       (void)close (in_fd);
1184                       (void)close (out_fd);
1185                       continue;
1186                     }
1187 
1188                   if (0 == st_buf . st_size)
1189                     {
1190                       (void)fprintf (stderr, "WARNING: Input file '%s' is empty, removing\n", in_path);
1191                       (void)close (in_fd);
1192                       (void)close (out_fd);
1193                       (void)unlink (open_path);
1194                       if (0 != unlink (in_path))
1195                         (void)fprintf (stderr, "unlink failure: '%s' (Error %d)\n",
1196                                        xstrerror_l (errno), errno);
1197                       else
1198                         (void)fprintf (stderr, "Processed and removed '%s'\n", in_path);
1199                       continue;
1200                     }
1201 
1202                   crlf_warned = false;
1203 
1204                   if (0 != process_stream (in_fd, out_fd))
1205                     {
1206                       (void)fprintf (stderr, "ERROR: process_stream failed for '%s'\n", in_path);
1207                       (void)close (in_fd);
1208                       (void)close (out_fd);
1209                       (void)unlink (open_path);
1210                       continue;
1211                     }
1212 
1213                   (void)close (in_fd);
1214                   (void)close (out_fd);
1215 
1216                   char final_path [CARDUTIL_MAXPATH];
1217                   struct stat st;
1218 
1219                   for (;;)
1220                     {
1221                       int n_final_path = snprintf (final_path, sizeof (final_path), "%s/bdeck.%s",
1222                                                    out_dir, out_name);
1223                       if (0 > n_final_path || (size_t)n_final_path >= sizeof (final_path))
1224                         {
1225                           (void)fprintf (stderr, "FATAL: snprintf final_path overflow at %s[%s:%d]\n",
1226                                          __func__, __FILE__, __LINE__);
1227                           exit (EXIT_FAILURE);
1228                         }
1229                       if (0 > stat (final_path, & st))
1230                         {
1231                           if (ENOENT == errno)
1232                             break;
1233                         }
1234 
1235                       size_t out_name_len = strlen (out_name);
1236                       if (out_name_len + 1 < sizeof (out_name))
1237                         {
1238                           out_name [out_name_len]     = safe_chars [rand () % safe_chars_len];
1239                           out_name [out_name_len + 1] = '\0';
1240                         }
1241                     }
1242 
1243                   if (0 != rename (open_path, final_path))
1244                     {
1245                       (void)fprintf (stderr, "rename failure: '%s' (Error %d)\n",
1246                                      xstrerror_l (errno), errno);
1247                       (void)unlink (open_path);
1248                     }
1249                   else
1250                     (void)fprintf (stderr, "Wrote output file '%s'\n", final_path);
1251 
1252                   if (archive_dir)
1253                     {
1254                       struct stat st;
1255                       if (0 != stat (archive_dir, & st) || ! S_ISDIR (st . st_mode))
1256                         {
1257                           (void)fprintf (stderr, "ERROR: Archive directory '%s': '%s' (Error %d)\n",
1258                                          archive_dir, xstrerror_l (errno), errno);
1259                           if (0 != unlink (in_path))
1260                             (void)fprintf (stderr, "unlink failure: '%s' (Error %d)\n",
1261                                            xstrerror_l (errno), errno);
1262                           else
1263                             (void)fprintf (stderr, "Processed and removed '%s'\n", in_path);
1264                         }
1265                       else
1266                         {
1267                           char archive_path [CARDUTIL_MAXPATH];
1268 
1269                           for (;;)
1270                             {
1271                               long int archive_time = time (NULL);
1272                               char rand_chars [5];
1273 
1274                               for (int i = 0; 4 > i; i++)
1275                                 rand_chars [i] = safe_chars [rand () % safe_chars_len];
1276 
1277                               rand_chars [4] = '\0';
1278 
1279                               char * in_path_dup = strdup (in_path);
1280                               char * bname       = basename (in_path_dup);
1281                               int n_archive_path = snprintf (archive_path, sizeof (archive_path), "%s/%ld%s.%s",
1282                                                              archive_dir, archive_time, rand_chars, bname);
1283                               if (0 > n_archive_path || (size_t)n_archive_path >= sizeof (archive_path))
1284                                 {
1285                                   (void)fprintf (stderr, "FATAL: snprintf archive_path overflow at %s[%s:%d]\n",
1286                                                  __func__, __FILE__, __LINE__);
1287                                   exit (EXIT_FAILURE);
1288                                 }
1289                               FREE (in_path_dup);
1290 
1291                               if (0 > stat (archive_path, & st))
1292                                 if (ENOENT == errno)
1293                                   break;
1294                             }
1295 
1296                           if (0 != rename (in_path, archive_path))
1297                             (void)fprintf (stderr, "rename failure: %s (Error %d)\n",
1298                                            xstrerror_l (errno), errno);
1299                           else
1300                             {
1301                               (void)fprintf (stderr, "Archived '%s' to '%s'\n",
1302                                              in_path, archive_path);
1303                               if (compress_archive)
1304                                 {
1305                                   char gz_path [CARDUTIL_MAXPATH];
1306                                   size_t maxpathlen = CARDUTIL_MAXPATH - 4;
1307 
1308                                   if (strlen (archive_path) > maxpathlen)
1309                                     {
1310                                       (void)fprintf (stderr, "path too long (>%" PRIuMAX ") to append '.gz' extension!\n",
1311                                                      (uintmax_t)maxpathlen);
1312                                       continue;
1313                                     }
1314 
1315                                   int mpl_limit = (maxpathlen > (size_t)INT_MAX) ? INT_MAX : (int)maxpathlen; //-V547
1316                                   int n_gz_path = snprintf (gz_path, sizeof (gz_path), "%.*s.gz", mpl_limit, archive_path);
1317                                   if (0 > n_gz_path || (size_t)n_gz_path >= sizeof (gz_path))
1318                                     {
1319                                       (void)fprintf (stderr, "FATAL: snprintf gz_path overflow at %s[%s:%d]\n",
1320                                                      __func__, __FILE__, __LINE__);
1321                                       exit (EXIT_FAILURE);
1322                                     }
1323 
1324                                   FILE * f_in = fopen (archive_path, "rb");
1325                                   if (NULL == f_in)
1326                                     {
1327                                       (void)fprintf (stderr, "fopen failure: %s (Error %d)\n",
1328                                                      xstrerror_l (errno), errno);
1329                                       continue;
1330                                     }
1331 
1332                                   /* cppcheck-suppress-begin nullPointerOutOfResources */
1333                                   (void)fseek (f_in, 0, SEEK_END);
1334                                   long f_size = ftell (f_in);
1335                                   (void)fseek (f_in, 0, SEEK_SET);
1336                                   /* cppcheck-suppress-end nullPointerOutOfResources */
1337 
1338                                   if (0 > f_size)
1339                                     {
1340                                       (void)fprintf (stderr, "WARNING: ftell failed: %s (Error %d)\n",
1341                                                      xstrerror_l (errno), errno);
1342                                       (void)fclose (f_in);
1343                                       continue;
1344                                     }
1345 
1346                                   if (0 == f_size)
1347                                     {
1348                                       (void)fclose (f_in);
1349                                       continue;
1350                                     }
1351 
1352 #if defined(__GNUC__) && (defined(__MINGW32__) || defined(__MINGW64__) || defined(CROSS_MINGW32) || defined(CROSS_MINGW64))
1353 # pragma GCC diagnostic push
1354 # pragma GCC diagnostic ignored "-Wtype-limits"
1355 #endif
1356                                   if (SSIZE_MAX < f_size) //-V547
1357                                     {
1358                                       (void)fprintf (stderr, "WARNING: input exceeds %" PRIdMAX " bytes\n",
1359                                                      (intmax_t)SSIZE_MAX);
1360                                       (void)fclose (f_in);
1361                                       continue;
1362                                     }
1363 #if defined(__GNUC__) && (defined(__MINGW32__) || defined(__MINGW64__) || defined(CROSS_MINGW32) || defined(CROSS_MINGW64))
1364 # pragma GCC diagnostic pop
1365 #endif
1366 
1367                                   unsigned char * p_in_buf = malloc ((size_t)f_size);
1368                                   if (NULL == p_in_buf)
1369                                     {
1370                                       (void)fprintf (stderr, "FATAL: Out of memory, aborting at %s[%s:%d]\n",
1371                                                      __func__, __FILE__, __LINE__);
1372                                       (void)fclose (f_in);
1373                                       abort ();
1374                                     }
1375 
1376                                   /* cppcheck-suppress nullPointerOutOfMemory */
1377                                   if (1 != fread (p_in_buf, f_size, 1, f_in))
1378                                     {
1379                                       (void)fprintf (stderr, "WARNING: fread failed");
1380 
1381                                       if (feof (f_in))
1382                                         (void)fprintf (stderr, ": unexpected EOF\n");
1383                                       else if (ferror (f_in))
1384                                         {
1385                                           (void)fprintf (stderr, ": I/O error\n");
1386                                           clearerr (f_in);
1387                                         }
1388                                       else
1389                                         (void)fprintf (stderr, "\n");
1390 
1391                                       FREE (p_in_buf);
1392 
1393                                       (void)fclose (f_in);
1394                                       continue;
1395                                     }
1396 
1397                                   (void)fclose (f_in);
1398 
1399                                   tdefl_compressor * p_comp = tdefl_compressor_alloc ();
1400                                   if (! p_comp)
1401                                     {
1402                                       (void)fprintf (stderr, "WARNING: tdefl_compressor_alloc failure!\n");
1403                                       FREE (p_in_buf);
1404                                       continue;
1405                                     }
1406 
1407                                   FILE * f_out = fopen (gz_path, "wb");
1408                                   if (NULL == f_out)
1409                                     {
1410                                       (void)fprintf (stderr, "fopen failure: %s (Error %d)\n",
1411                                                      xstrerror_l (errno), errno);
1412                                       FREE (p_in_buf);
1413                                       tdefl_compressor_free (p_comp);
1414                                       continue;
1415                                     }
1416 
1417                                   /* cppcheck-suppress-begin nullPointerOutOfResources */
1418                                   (void)fwrite ("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff", 1, 10, f_out);
1419 
1420                                   (void)tdefl_init (p_comp, put_buf_func, f_out, tdefl_create_comp_flags_from_zip_params
1421                                                     (MZ_DEFAULT_COMPRESSION, -15, MZ_DEFAULT_STRATEGY));
1422 
1423                                   if (! tdefl_compress_buffer (p_comp, p_in_buf, f_size, TDEFL_FINISH))
1424                                     {
1425                                       tdefl_status st = tdefl_get_prev_return_status (p_comp);
1426                                       (void)fprintf (stderr, "WARNING: tdefl_compress_buffer failure: status %d\n", (int)st);
1427                                       (void)fclose (f_out);
1428                                       tdefl_compressor_free (p_comp);
1429                                       FREE (p_in_buf);
1430                                       (void)unlink (gz_path);
1431                                       continue;
1432                                     }
1433 
1434                                   mz_uint32 crc = mz_crc32 (0, p_in_buf, f_size);
1435 
1436                                   (void)fwrite (& crc, 4, 1, f_out);
1437                                   (void)fwrite (& f_size, 4, 1, f_out);
1438 
1439                                   (void)fclose (f_out);
1440                                   /* cppcheck-suppress-end nullPointerOutOfResources */
1441 
1442                                   tdefl_compressor_free (p_comp);
1443 
1444                                   (void)fprintf (stderr, "Compressed '%s' to '%s'\n",
1445                                                  archive_path, gz_path);
1446 
1447                                   (void)unlink (archive_path);
1448 
1449                                   FREE (p_in_buf);
1450                                 }
1451                             }
1452                         }
1453                     }
1454                   else
1455                     {
1456                       if (0 != unlink (in_path))
1457                         (void)fprintf (stderr, "unlink failure: %s (Error %d)\n",
1458                                        xstrerror_l (errno), errno);
1459                       else
1460                         (void)fprintf (stderr, "Processed and removed '%s'\n", in_path);
1461                     }
1462 
1463                   (void)time (& rawtime);
1464                   timelocal = localtime (& rawtime);
1465 
1466                   if (strftime (timebuffer, sizeof (timebuffer), "%A, %B %d %Y, %H:%M:%S %Z", timelocal))
1467                     (void)fprintf (stderr, "Conversion job ended %s\n", timebuffer);
1468                 }
1469             }
1470 
1471           (void)closedir (d);
1472         }
1473 
1474       int rc = monotonic_nssleep ((uint64_t)(poll_rate * 1000000000.0));
1475       if (EINTR == rc)
1476         (void)fprintf (stderr, "WARNING: monotonic_nssleep interrupted\n");
1477       else if (0 != rc)
1478         {
1479           (void)fprintf (stderr, "FATAL: monotonic_nssleep failure");
1480           if (errno)
1481             (void)fprintf (stderr, ": %s (Error %d)\n", xstrerror_l (errno), errno);
1482           else
1483             (void)fprintf (stderr, "\n");
1484           abort ();
1485         }
1486     }
1487 }
1488 
1489 int
1490 main (int argc, char * argv [])
     /* [previous][next][first][last][top][bottom][index][help] */
1491 {
1492 #if !defined(NO_LOCALE)
1493   (void)setlocale (LC_ALL, "");
1494 #endif
1495 
1496   parse_options (argc, argv);
1497 
1498   if (! use_ascii_encoding && use_gcos_encoding)
1499     {
1500       if (upper_all)
1501         {
1502 #if defined(TESTING)
1503           (void)fprintf (stderr, "*** GBCD upper-casing disabled");
1504 #endif
1505           upper_all = false;
1506         }
1507       else if (upper_jcl)
1508         {
1509 #if defined(TESTING)
1510           (void)fprintf (stderr, "*** GBCD JCL-only upper-casing enabled");
1511 #endif
1512           upper_jcl = true;
1513           upper_all = false;
1514         }
1515       else
1516         {
1517 #if defined(TESTING)
1518           (void)fprintf (stderr, "*** GBCD upper-casing enabled");
1519 #endif
1520           upper_all = true;
1521         }
1522     }
1523 
1524   if (! daemon_mode)
1525     {
1526       if (fabs (poll_rate - DEFAULT_POLL_RATE) > DBL_EPSILON)
1527         {
1528           (void)fprintf (stderr, "ERROR: --poll option requires --daemon\n");
1529           exit (EXIT_FAILURE);
1530         }
1531 
1532       if (input_dir)
1533         {
1534           (void)fprintf (stderr, "ERROR: --input option requires --daemon\n");
1535           exit (EXIT_FAILURE);
1536         }
1537 
1538       if (output_dir)
1539         {
1540           (void)fprintf (stderr, "ERROR: --output option requires --daemon\n");
1541           exit (EXIT_FAILURE);
1542         }
1543 
1544       if (archive_dir)
1545         {
1546           (void)fprintf (stderr, "ERROR: --archive option requires --daemon\n");
1547           exit (EXIT_FAILURE);
1548         }
1549 
1550       if (compress_archive)
1551         {
1552           (void)fprintf (stderr, "ERROR: --compress option requires --daemon and --archive\n");
1553           exit (EXIT_FAILURE);
1554         }
1555     }
1556 
1557   if (daemon_mode)
1558     {
1559       if ((input_dir && '-' == * input_dir) ||
1560           (output_dir && '-' == * output_dir) ||
1561           (archive_dir && '-' == * archive_dir))
1562         {
1563           (void)fprintf (stderr, "ERROR: Directory names should not begin with a dash ('-')\n");
1564           exit (EXIT_FAILURE);
1565         }
1566 
1567       if (NULL == input_dir || NULL == output_dir)
1568         {
1569           (void)fprintf (stderr, "ERROR: Daemon mode requires -i <input_dir> and -o <output_dir>\n");
1570           exit (EXIT_FAILURE);
1571         }
1572 
1573       if (compress_archive && NULL == archive_dir)
1574         {
1575           (void)fprintf (stderr, "ERROR: --compress option requires --archive <archive_dir>\n");
1576           exit (EXIT_FAILURE);
1577         }
1578 
1579       struct stat st;
1580 
1581       if (0 != stat (input_dir, & st) || ! S_ISDIR (st . st_mode))
1582         {
1583           (void)fprintf (stderr, "ERROR: Input directory not found: '%s'\n", input_dir);
1584           exit (EXIT_FAILURE);
1585         }
1586 
1587       if (0 != access (input_dir, R_OK | X_OK))
1588         {
1589           (void)fprintf (stderr, "ERROR: Input directory is not readable: '%s'\n", input_dir);
1590           exit (EXIT_FAILURE);
1591         }
1592 
1593       if (0 != stat (output_dir, & st) || ! S_ISDIR (st . st_mode))
1594         {
1595           (void)fprintf (stderr, "ERROR: Output directory not found: '%s'\n", output_dir);
1596           exit (EXIT_FAILURE);
1597         }
1598 
1599       if (0 != access (output_dir, W_OK))
1600         {
1601           (void)fprintf (stderr, "ERROR: Output directory not writable: '%s'\n", output_dir);
1602           exit (EXIT_FAILURE);
1603         }
1604 
1605       if (archive_dir)
1606         {
1607           if (0 != stat (archive_dir, & st) || ! S_ISDIR (st . st_mode))
1608             {
1609               (void)fprintf (stderr, "ERROR: Archive directory not found: '%s'\n", archive_dir);
1610               exit (EXIT_FAILURE);
1611             }
1612 
1613           if (0 != access (archive_dir, W_OK))
1614             {
1615               (void)fprintf (stderr, "ERROR: Archive directory not writable: '%s'\n", archive_dir);
1616               exit (EXIT_FAILURE);
1617             }
1618         }
1619 
1620       char r_input_dir [CARDUTIL_MAXPATH];
1621       char r_output_dir [CARDUTIL_MAXPATH];
1622       char r_archive_dir [CARDUTIL_MAXPATH];
1623 
1624 #if !defined(__MINGW32__) && !defined(__MINGW64__) && !defined(CROSS_MINGW32) && !defined(CROSS_MINGW64)
1625       if (! realpath (input_dir, r_input_dir))
1626         {
1627           (void)fprintf (stderr, "ERROR: Cannot canonicalize input directory '%s': %s (Error %d)\n",
1628                          input_dir, xstrerror_l (errno), errno);
1629           exit (EXIT_FAILURE);
1630         }
1631 
1632       if (! realpath (output_dir, r_output_dir))
1633         {
1634           (void)fprintf (stderr, "ERROR: Cannot canonicalize output directory '%s': %s (Error %d)\n",
1635                          output_dir, xstrerror_l (errno), errno);
1636           exit (EXIT_FAILURE);
1637         }
1638 
1639       if (archive_dir && ! realpath (archive_dir, r_archive_dir))
1640         {
1641           (void)fprintf (stderr, "ERROR: Cannot canonicalize archive directory '%s': %s (Error %d)\n",
1642                          archive_dir, xstrerror_l (errno), errno);
1643           exit (EXIT_FAILURE);
1644         }
1645 #else
1646       (void)strncpy (r_input_dir, input_dir, sizeof (r_input_dir));
1647       r_input_dir [sizeof (r_input_dir) - 1] = '\0';
1648 
1649       (void)strncpy (r_output_dir, output_dir, sizeof (r_output_dir));
1650       r_output_dir [sizeof (r_output_dir) - 1] = '\0';
1651 
1652       if (archive_dir)
1653         {
1654           (void)strncpy (r_archive_dir, archive_dir, sizeof (r_archive_dir));
1655           r_archive_dir [sizeof (r_archive_dir) - 1] = '\0';
1656         }
1657       else
1658         r_archive_dir[0] = '\0';
1659 #endif
1660 
1661       if (0 == strcmp (r_input_dir, ".") || 0 == strcmp (r_input_dir, ".."))
1662         {
1663           (void)fprintf (stderr, "ERROR: Input directory should not be specified as '.' or '..'\n");
1664           exit (EXIT_FAILURE);
1665         }
1666 
1667       if (0 == strcmp (r_output_dir, ".") || 0 == strcmp (r_output_dir, ".."))
1668         {
1669           (void)fprintf (stderr, "ERROR: Output directory should not be specified as '.' or '..'\n");
1670           exit (EXIT_FAILURE);
1671         }
1672 
1673       if (archive_dir && (0 == strcmp (r_archive_dir, ".") || 0 == strcmp (r_archive_dir, "..")))
1674         {
1675           (void)fprintf (stderr, "ERROR: Archive directory should not be specified as '.' or '..'\n");
1676           exit (EXIT_FAILURE);
1677         }
1678 
1679       if (0 == strcmp (r_input_dir, r_output_dir))
1680         {
1681           (void)fprintf (stderr, "ERROR: Input and output directories cannot be the same.\n");
1682           exit (EXIT_FAILURE);
1683         }
1684 
1685       if (archive_dir && 0 == strcmp (r_input_dir, r_archive_dir))
1686         {
1687           (void)fprintf (stderr, "ERROR: Input and archive directories cannot be the same.\n");
1688           exit (EXIT_FAILURE);
1689         }
1690 
1691       if (archive_dir && 0 == strcmp (r_output_dir, r_archive_dir))
1692         {
1693           (void)fprintf (stderr, "ERROR: Output and archive directories cannot be the same.\n");
1694           exit (EXIT_FAILURE);
1695         }
1696 
1697       if (0 == stat (input_dir, & st))
1698         if (0 != (st . st_mode & S_IWOTH))
1699           (void)fprintf (stderr, "NOTE: The input directory specified is world-writable!\n");
1700 
1701       bool output_world_writable  = 0 != (st . st_mode & S_IWOTH);
1702       bool archive_world_writable = false;
1703 
1704       if (archive_dir)
1705         if (0 == stat (archive_dir, & st))
1706           archive_world_writable = 0 != (st . st_mode & S_IWOTH);
1707 
1708       if (output_world_writable && archive_world_writable)
1709         (void)fprintf (stderr, "WARNING: World writable output and archive directories are considered insecure!\n");
1710       else if (output_world_writable)
1711         (void)fprintf (stderr, "WARNING: World writable output directory is considered insecure!\n");
1712       else if (archive_world_writable)
1713         (void)fprintf (stderr, "WARNING: World writable archive directory is considered insecure!\n");
1714 
1715       char * daemon_label;
1716       if (use_ascii_encoding)
1717         daemon_label = "MCC";
1718       else if (use_gcos_encoding)
1719         daemon_label = "GBCD";
1720       else
1721         {
1722           (void)fprintf (stderr, "FATAL: Unknown output format, aborting!\n");
1723           abort ();
1724         }
1725 
1726 #if defined(TESTING)
1727       (void)fprintf (stderr, "\n");
1728 #endif
1729 
1730       pid_t pid = getpid ();
1731       if (0 == pid)
1732         (void)fprintf (stderr, "****\nCard Utility (%s Daemon Mode)\n****\n",
1733                        daemon_label);
1734       else
1735         (void)fprintf (stderr, "****\nCard Utility (%s Daemon Mode) [PID %" PRIdMAX "]\n****\n",
1736                        daemon_label, (intmax_t)pid);
1737 
1738 #if defined(TESTING)
1739       (void)fprintf (stderr, "\n");
1740       (void)fprintf (stderr, "* Input directory     : %s\n", input_dir);
1741       (void)fprintf (stderr, "* Output directory    : %s\n", output_dir);
1742       (void)fprintf (stderr, "* Archive directory   : %s\n", archive_dir ? archive_dir : "disabled");
1743       (void)fprintf (stderr, "* Archive compression : %s\n", compress_archive ? "enabled" : "disabled");
1744       (void)fprintf (stderr, "* Daemon polling rate : %f\n", poll_rate);
1745 #endif
1746 
1747       daemon_loop (input_dir, output_dir, archive_dir, compress_archive);
1748     }
1749   else
1750     {
1751       if (2 > argc) /* name + 1 option */
1752         {
1753           print_help (argv [0], DEFAULT_POLL_RATE);
1754           return EXIT_FAILURE;
1755         }
1756 #if defined(TESTING)
1757       (void)fprintf (stderr, "\n");
1758 #endif
1759       (void)fprintf (stderr, "****\nCard Utility\n****\n");
1760 
1761       int ifd = STDIN_FILENO;
1762       int ofd = STDOUT_FILENO;
1763 
1764       crlf_warned = false;
1765 
1766       if (0 != process_stream (ifd, ofd))
1767         {
1768           (void)fprintf (stderr, "ERROR: process_stream failed at %s[%s:%d]\n",
1769                          __func__, __FILE__, __LINE__);
1770           exit (EXIT_FAILURE);
1771         }
1772 
1773       (void)close (ifd);
1774       (void)close (ofd);
1775     }
1776 
1777   (void)fprintf (stderr, "Done\n");
1778 
1779   return EXIT_SUCCESS;
1780 }

/* [previous][next][first][last][top][bottom][index][help] */