about summary refs log tree commit diff
path: root/lib/libpm.c
blob: d5bad7a4309c011b84e34ceb0ce3dfe833a999fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
/**************************************************************************
                                  libpm.c
***************************************************************************
  This file contains fundamental libnetpbm services.

  Some of the subroutines in this library are intended and documented
  for use by Netpbm users, but most of them are just used by other
  Netpbm library subroutines.
**************************************************************************/

#define _DEFAULT_SOURCE      /* New name for SVID & BSD source defines */
#define _BSD_SOURCE          /* Make sure strdup is in string.h */
#define _XOPEN_SOURCE 500    /* Make sure ftello, fseeko are defined */

#include "netpbm/pm_config.h"

#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <setjmp.h>
#include <time.h>
#include <limits.h>
#if HAVE_FORK
#include <sys/wait.h>
#endif
#include <sys/types.h>

#include "netpbm/pm_c_util.h"
#include "netpbm/mallocvar.h"
#include "netpbm/version.h"
#include "netpbm/nstring.h"
#include "netpbm/shhopt.h"
#include "compile.h"

#include "pm.h"

/* The following are set by pm_init(), then used by subsequent calls to other
   pm_xxx() functions.
   */
const char * pm_progname;

int pm_plain_output;
    /* Boolean: programs should produce output in plain format */

static bool pm_showmessages;  
    /* Programs should display informational messages (because the user didn't
       specify the --quiet option).
    */
static jmp_buf * pm_jmpbufP = NULL;
    /* A description of the point to which the program should hyperjump
       if a libnetpbm function encounters an error (libnetpbm functions
       don't normally return in that case).

       User sets this to something in his own extra-library context.
       Libnetpbm routines that have something that needs to be cleaned up
       preempt it.

       NULL, which is the default value, means when a libnetpbm function
       encounters an error, it causes the process to exit.
    */
static pm_usererrormsgfn * userErrorMsgFn = NULL;
    /* A function to call to issue an error message.

       NULL means use the library default: print to Standard Error
    */

static pm_usermessagefn * userMessageFn = NULL;
    /* A function to call to issue an error message.

       NULL means use the library default: print to Standard Error
    */



void
pm_setjmpbuf(jmp_buf * const jmpbufP) {
    pm_jmpbufP = jmpbufP;
}



void
pm_setjmpbufsave(jmp_buf *  const jmpbufP,
                 jmp_buf ** const oldJmpbufPP) {

    *oldJmpbufPP = pm_jmpbufP;
    pm_jmpbufP = jmpbufP;
}



void
pm_longjmp(void) {

    if (pm_jmpbufP)
        longjmp(*pm_jmpbufP, 1);
    else
        exit(1);
}



void
pm_fork(int *         const iAmParentP,
        pid_t *       const childPidP,
        const char ** const errorP) {
/*----------------------------------------------------------------------------
   Same as POSIX fork, except with a nicer interface and works
   (fails cleanly) on systems that don't have POSIX fork().
-----------------------------------------------------------------------------*/
#if HAVE_FORK
    int rc;

    rc = fork();

    if (rc < 0) {
        pm_asprintf(errorP, "Failed to fork a process.  errno=%d (%s)",
                    errno, strerror(errno));
    } else {
        *errorP = NULL;

        if (rc == 0) {
            *iAmParentP = FALSE;
        } else {
            *iAmParentP = TRUE;
            *childPidP = rc;
        }
    }
#else
    pm_asprintf(errorP, "Cannot fork a process, because this system does "
                "not have POSIX fork()");
#endif
}



void
pm_waitpid(pid_t         const pid,
           int *         const statusP,
           int           const options,
           pid_t *       const exitedPidP,
           const char ** const errorP) {

#if HAVE_FORK
    pid_t rc;
    rc = waitpid(pid, statusP, options);
    if (rc == (pid_t)-1) {
        pm_asprintf(errorP, "Failed to wait for process exit.  "
                    "waitpid() errno = %d (%s)",
                    errno, strerror(errno));
    } else {
        *exitedPidP = rc;
        *errorP = NULL;
    }
#else
    pm_error("INTERNAL ERROR: Attempt to wait for a process we created on "
             "a system on which we can't create processes");
#endif
}



void
pm_waitpidSimple(pid_t const pid) {

    int status;
    pid_t exitedPid;
    const char * error;

    pm_waitpid(pid, &status, 0, &exitedPid, &error);

    if (error) {
        pm_errormsg("%s", error);
        pm_strfree(error);
        pm_longjmp();
    } else {
        assert(exitedPid != 0);
    }
}



void
pm_setusererrormsgfn(pm_usererrormsgfn * fn) {

    userErrorMsgFn = fn;
}



void
pm_setusermessagefn(pm_usermessagefn * fn) {

    userMessageFn = fn;
}



void
pm_usage(const char usage[]) {
    pm_error("usage:  %s %s", pm_progname, usage);
}



void PM_GNU_PRINTF_ATTR(1,2)
pm_message(const char format[], ...) {

    va_list args;

    va_start(args, format);

    if (pm_showmessages) {
        const char * msg;
        pm_vasprintf(&msg, format, args);

        if (userMessageFn)
            userMessageFn(msg);
        else
            fprintf(stderr, "%s: %s\n", pm_progname, msg);

        pm_strfree(msg);
    }
    va_end(args);
}



static void
errormsg(const char * const msg) {

    if (userErrorMsgFn)
        userErrorMsgFn(msg);
    else
        fprintf(stderr, "%s: %s\n", pm_progname, msg);
}



void PM_GNU_PRINTF_ATTR(1,2)
pm_errormsg(const char format[], ...) {

    va_list args;
    const char * msg;

    va_start(args, format);

    pm_vasprintf(&msg, format, args);
    
    errormsg(msg);

    pm_strfree(msg);

    va_end(args);
}



void PM_GNU_PRINTF_ATTR(1,2)
pm_error(const char format[], ...) {
    va_list args;
    const char * msg;

    va_start(args, format);

    pm_vasprintf(&msg, format, args);
    
    errormsg(msg);

    pm_strfree(msg);

    va_end(args);

    pm_longjmp();
}



int
pm_have_float_format(void) {
/*----------------------------------------------------------------------------
  Return 1 if %f, %e, and %g work in format strings for pm_message, etc.;
  0 otherwise.

  Where they don't "work," that means the specifier just appears itself in
  the formatted strings, e.g. "the number is g".
-----------------------------------------------------------------------------*/
    return pm_vasprintf_knows_float();
}



static void *
mallocz(size_t const size) {

    return malloc(MAX(1, size));
}



void *
pm_allocrow(unsigned int const cols,
            unsigned int const size) {

    unsigned char * itrow;

    if (cols != 0 && UINT_MAX / cols < size)
        pm_error("Arithmetic overflow multiplying %u by %u to get the "
                 "size of a row to allocate.", cols, size);

    itrow = mallocz(cols * size);
    if (itrow == NULL)
        pm_error("out of memory allocating a row");

    return itrow;
}



void
pm_freerow(void * const itrow) {
    free(itrow);
}



char **
pm_allocarray(int const cols,
              int const rows,
              int const elementSize ) {
/*----------------------------------------------------------------------------
   This is for backward compatibility.  MALLOCARRAY2 is usually better.

   A problem with pm_allocarray() is that its return type is char **
   even though 'elementSize' can be other than 1.  So users have
   traditionally type cast the result.  In the old days, that was just
   messy; modern compilers can produce the wrong code if you do that.
-----------------------------------------------------------------------------*/
    char ** retval;
    void * result;

    pm_mallocarray2(&result, rows, cols, elementSize);

    if (result == NULL)
        pm_error("Failed to allocate a raster array of %u columns x %u rows",
                 cols, rows);

    retval = result;

    return retval;
}



void
pm_freearray(char ** const rowIndex, 
             int     const rows) {

    void * const rowIndexVoid = rowIndex;

    pm_freearray2(rowIndexVoid);
}



/* Case-insensitive keyword matcher. */

int
pm_keymatch(const char *       const strarg, 
            const char * const keywordarg, 
            int          const minchars) {
    int len;
    const char * keyword;
    const char * str;

    str = strarg;
    keyword = keywordarg;

    len = strlen( str );
    if ( len < minchars )
        return 0;
    while ( --len >= 0 )
        {
        register char c1, c2;

        c1 = *str++;
        c2 = *keyword++;
        if ( c2 == '\0' )
            return 0;
        if ( ISUPPER( c1 ) )
            c1 = tolower( c1 );
        if ( ISUPPER( c2 ) )
            c2 = tolower( c2 );
        if ( c1 != c2 )
            return 0;
        }
    return 1;
}


/* Log base two hacks. */

int
pm_maxvaltobits(int const maxval) {
    if ( maxval <= 1 )
        return 1;
    else if ( maxval <= 3 )
        return 2;
    else if ( maxval <= 7 )
        return 3;
    else if ( maxval <= 15 )
        return 4;
    else if ( maxval <= 31 )
        return 5;
    else if ( maxval <= 63 )
        return 6;
    else if ( maxval <= 127 )
        return 7;
    else if ( maxval <= 255 )
        return 8;
    else if ( maxval <= 511 )
        return 9;
    else if ( maxval <= 1023 )
        return 10;
    else if ( maxval <= 2047 )
        return 11;
    else if ( maxval <= 4095 )
        return 12;
    else if ( maxval <= 8191 )
        return 13;
    else if ( maxval <= 16383 )
        return 14;
    else if ( maxval <= 32767 )
        return 15;
    else if ( (long) maxval <= 65535L )
        return 16;
    else
        pm_error( "maxval of %d is too large!", maxval );
        return -1;  /* Should never come here */
}

int
pm_bitstomaxval(int const bits) {
    return ( 1 << bits ) - 1;
}


unsigned int PURE_FN_ATTR
pm_lcm(unsigned int const x, 
       unsigned int const y,
       unsigned int const z,
       unsigned int const limit) {
/*----------------------------------------------------------------------------
  Compute the least common multiple of 'x', 'y', and 'z'.  If it's bigger than
  'limit', though, just return 'limit'.
-----------------------------------------------------------------------------*/
    unsigned int biggest;
    unsigned int candidate;

    if (x == 0 || y == 0 || z == 0)
        pm_error("pm_lcm(): Least common multiple of zero taken.");

    biggest = MAX(x, MAX(y,z));

    candidate = biggest;
    while (((candidate % x) != 0 ||       /* not a multiple of x */
            (candidate % y) != 0 ||       /* not a multiple of y */
            (candidate % z) != 0 ) &&     /* not a multiple of z */
           candidate <= limit)
        candidate += biggest;

    if (candidate > limit) 
        candidate = limit;

    return candidate;
}



void
pm_init(const char * const progname,
        unsigned int const flags) {
/*----------------------------------------------------------------------------
   Initialize static variables that Netpbm library routines use.

   Any user of Netpbm library routines is expected to call this at the
   beginning of this program, before any other Netpbm library routines.

   A program may call this via pm_proginit() instead, though.
-----------------------------------------------------------------------------*/
    pm_setMessage(FALSE, NULL);

    pm_progname = progname;

#ifdef O_BINARY
#ifdef HAVE_SETMODE
    /* Set the stdin and stdout mode to binary.  This means nothing on Unix,
       but matters on Windows.
       
       Note that stdin and stdout aren't necessarily image files.  In
       particular, stdout is sometimes text for human consumption,
       typically printed on the terminal.  Binary mode isn't really
       appropriate for that case.  We do this setting here without
       any knowledge of how stdin and stdout are being used because it is
       easy.  But we do make an exception for the case that we know the
       file is a terminal, to get a little closer to doing the right
       thing.  
    */
    if (!isatty(0)) setmode(0,O_BINARY);  /* Standard Input */
    if (!isatty(1)) setmode(1,O_BINARY);  /* Standard Output */
#endif /* HAVE_SETMODE */
#endif /* O_BINARY */
}



static void
showVersion(void) {
    pm_message( "Using libnetpbm from Netpbm Version: %s", NETPBM_VERSION );
#if defined(COMPILE_TIME) && defined(COMPILED_BY)
    pm_message( "Compiled %s by user \"%s\"",
                COMPILE_TIME, COMPILED_BY );
#endif
#ifdef BSD
    pm_message( "BSD defined" );
#endif /*BSD*/
#ifdef SYSV
    pm_message( "SYSV defined" );
#endif /*SYSV*/
#ifdef MSDOS
    pm_message( "MSDOS defined" );
#endif /*MSDOS*/
#ifdef AMIGA
    pm_message( "AMIGA defined" );
#endif /* AMIGA */
    {
        const char * rgbdef;
        pm_message( "RGB_ENV='%s'", RGBENV );
        rgbdef = getenv(RGBENV);
        if( rgbdef )
            pm_message( "RGBENV= '%s' (env vbl set to '%s')", 
                        RGBENV, rgbdef );
        else
            pm_message( "RGBENV= '%s' (env vbl is unset)", RGBENV);
    }
}



static void
showNetpbmHelp(const char progname[]) {
/*----------------------------------------------------------------------------
  Tell the user where to get help for this program, assuming it is a Netpbm
  program (a program that comes with the Netpbm package, as opposed to a 
  program that just uses the Netpbm libraries).

  Tell him to go to the URL listed in the Netpbm configuration file.
  The Netpbm configuration file is the file named by the NETPBM_CONF
  environment variable, or /etc/netpbm if there is no such environment
  variable.

  If the configuration file doesn't exist or can't be read, or doesn't
  contain a DOCURL value, tell him to go to a hardcoded source for
  documentation.
-----------------------------------------------------------------------------*/
    const char * netpbmConfigFileName;
    FILE * netpbmConfigFile;
    char * docurl;

    if (getenv("NETPBM_CONF"))
        netpbmConfigFileName = getenv("NETPBM_CONF");
    else 
        netpbmConfigFileName = "/etc/netpbm";
    
    netpbmConfigFile = fopen(netpbmConfigFileName, "r");
    if (netpbmConfigFile == NULL) {
        pm_message("Unable to open Netpbm configuration file '%s'.  "
                   "Errno = %d (%s).  "
                   "Use the NETPBM_CONF environment variable "
                   "to control the identity of the Netpbm configuration file.",
                   netpbmConfigFileName,errno, strerror(errno));
        docurl = NULL;
    } else {
        docurl = NULL;  /* default */
        while (!feof(netpbmConfigFile) && !ferror(netpbmConfigFile)) {
            char line[80+1];
            fgets(line, sizeof(line), netpbmConfigFile);
            if (line[0] != '#') {
                sscanf(line, "docurl=%s", docurl);
            }
        }
        if (docurl == NULL)
            pm_message("No 'docurl=' line in Netpbm configuration file '%s'.",
                       netpbmConfigFileName);

        fclose(netpbmConfigFile);
    }
    if (docurl == NULL)
        pm_message("We have no reliable indication of where the Netpbm "
                   "documentation is, but try "
                   "http://netpbm.sourceforge.net or email "
                   "Bryan Henderson (bryanh@giraffe-data.com) for help.");
    else
        pm_message("This program is part of the Netpbm package.  Find "
                   "documentation for it at %s/%s\n", docurl, progname);
}



static void
parseCommonOptions(int *         const argcP,
                   const char ** const argv,
                   bool *        const showMessagesP,
                   bool *        const showVersionP,
                   bool *        const showHelpP,
                   bool *        const plainOutputP) {

    unsigned int inCursor;
    unsigned int outCursor;

    *showMessagesP = true;   /* initial assumption */
    *showVersionP  = false;  /* initial assumption */
    *showHelpP     = false;  /* initial assumption */
    *plainOutputP  = false;  /* initial assumption */

    for (inCursor = 1, outCursor = 1; inCursor < *argcP; ++inCursor) {
            if (strcaseeq(argv[inCursor], "-quiet") ||
                strcaseeq(argv[inCursor], "--quiet")) 
                *showMessagesP = false;
            else if (strcaseeq(argv[inCursor], "-version") ||
                     strcaseeq(argv[inCursor], "--version")) 
                *showVersionP = true;
            else if (strcaseeq(argv[inCursor], "-help") ||
                     strcaseeq(argv[inCursor], "--help") ||
                     strcaseeq(argv[inCursor], "-?")) 
                *showHelpP = true;
            else if (strcaseeq(argv[inCursor], "-plain") ||
                     strcaseeq(argv[inCursor], "--plain"))
                *plainOutputP = true;
            else
                argv[outCursor++] = argv[inCursor];
        }
    *argcP = outCursor;
}



void
pm_proginit(int *         const argcP,
            const char ** const argv) {
/*----------------------------------------------------------------------------
   Do various initialization things that all programs in the Netpbm package,
   and programs that emulate such programs, should do.

   This includes processing global options.  We scan argv[], which has *argcP
   elements, for common options and execute the functions for the ones we
   find.  We remove the common options from argv[], updating *argcP
   accordingly.

   This includes calling pm_init() to initialize the Netpbm libraries.
-----------------------------------------------------------------------------*/
    const char * const progname = pm_arg0toprogname(argv[0]);
        /* points to static memory in this library */
    bool showMessages;
    bool plain;
    bool justShowVersion;
        /* We're supposed to just show the version information, then exit the
           program.
        */
    bool justShowHelp;
        /* We're supposed to just tell user where to get help, then exit the
           program.
        */

    pm_init(progname, 0);

    parseCommonOptions(argcP, argv,
                       &showMessages, &justShowVersion, &justShowHelp,
                       &plain);

    pm_plain_output = plain ? 1 : 0;

    pm_setMessage(showMessages ? 1 : 0, NULL);

    if (justShowVersion) {
        showVersion();
        exit(0);
    } else if (justShowHelp) {
        pm_error("Use 'man %s' for help.", progname);
        /* If we can figure out a way to distinguish Netpbm programs from 
           other programs using the Netpbm libraries, we can do better here.
        */
        if (0)
            showNetpbmHelp(progname);
        exit(0);
    }
}



void
pm_setMessage(int   const newState,
              int * const oldStateP) {
    
    if (oldStateP)
        *oldStateP = pm_showmessages;

    pm_showmessages = !!newState;
}



int
pm_getMessage(void) {

    return pm_showmessages;
}



static void
extractAfterLastSlash(const char * const fullPath,
                      char *       const retval,
                      size_t       const retvalSize) {
    
    char * slashPos;

    /* Chop any directories off the left end */
    slashPos = strrchr(fullPath, '/');

    if (slashPos == NULL) {
        strncpy(retval, fullPath, retvalSize);
        retval[retvalSize-1] = '\0';
    } else {
        strncpy(retval, slashPos +1, retvalSize);
        retval[retvalSize-1] = '\0';
    }

    /* Chop any .exe off the right end */
    if (strlen(retval) >= 4 && strcmp(retval+strlen(retval)-4, ".exe") == 0)
        retval[strlen(retval)-4] = 0;
}



char *
pm_arg0toprogname(const char arg0[]) {
/*----------------------------------------------------------------------------
   Given a value for argv[0] (a command name or file name passed to a 
   program in the standard C calling sequence), return the name of the
   Netpbm program to which it refers.

   In the most ordinary case, this is simply the argument itself.

   But if the argument contains a slash, it is the part of the argument 
   after the last slash, and if there is a .exe on it (as there is for
   DJGPP), that is removed.

   The return value is in static storage within.  It is NUL-terminated,
   but truncated at 64 characters.
-----------------------------------------------------------------------------*/
#define MAX_RETVAL_SIZE 64
#if MSVCRT
    /* Note that there exists _splitpath_s, which takes a size argument,
       but it is only in "secure" extensions of MSVCRT that come only with
       MSVC; but _splitpath() comes with Windows.  MinGW has a header file
       for it.
    */
    static char retval[_MAX_FNAME];
    _splitpath(arg0, 0, 0,  retval, 0);
    if (MAX_RETVAL_SIZE < _MAX_FNAME)
        retval[MAX_RETVAL_SIZE] = '\0';
#else
    static char retval[MAX_RETVAL_SIZE+1];
    extractAfterLastSlash(arg0, retval, sizeof(retval));
#endif

    return retval;
}



unsigned int
pm_randseed(void) {

    return time(NULL) ^ getpid();

}



unsigned int
pm_parse_width(const char * const arg) {
/*----------------------------------------------------------------------------
   Return the image width represented by the decimal ASCIIZ string
   'arg'.  Fail if it doesn't validly represent a width or represents
   a width that can't be conveniently used in computation.
-----------------------------------------------------------------------------*/
    unsigned int width;
    const char * error;

    pm_interpret_uint(arg, &width, &error);

    if (error) {
        pm_error("'%s' is invalid as an image width.  %s", arg, error);
        pm_strfree(error);
    } else {
        if (width > INT_MAX-10)
            pm_error("Width %u is too large for computations.", width);
        if (width == 0)
            pm_error("Width argument must be a positive number.  You "
                     "specified 0.");
    }
    return width;
}



unsigned int
pm_parse_height(const char * const arg) {
/*----------------------------------------------------------------------------
  Same as pm_parse_width(), but for height.
-----------------------------------------------------------------------------*/
    unsigned int height;
    const char * error;

    pm_interpret_uint(arg, &height, &error);

    if (error) {
        pm_error("'%s' is invalid as an image height.  %s", arg, error);
        pm_strfree(error);
    } else {
        if (height > INT_MAX-10)
            pm_error("Height %u is too large for computations.", height);
        if (height == 0)
            pm_error("Height argument must be a positive number.  You "
                     "specified 0.");
    }
    return height;
}