]> arthur.barton.de Git - netatalk.git/blob - etc/psf/psf.c
7498b93de74bdd1104f5c7ea999142191b2f9974
[netatalk.git] / etc / psf / psf.c
1 /*
2  * $Id: psf.c,v 1.11 2009-10-14 01:38:28 didg Exp $
3  *
4  * Copyright (c) 1990,1995 Regents of The University of Michigan.
5  * All Rights Reserved. See COPYRIGHT.
6  *
7  * PostScript Filter, psf.
8  *
9  * Handles both PostScript files and text files.  Files with the
10  * '%!' PostScript header are sent directly to the printer,
11  * unmodified.  Text files are first converted to PostScript,
12  * then sent.  Printers may be directly attached or on an AppleTalk
13  * network.  Other media are possible.  Currently, psf invokes
14  * pap to send files to AppleTalk-ed printers.  Replace the pap*
15  * variables to use another program for communication.  psf only
16  * converts plain-text.  If called as "tf" or "df", psf will invoke
17  * a troff or dvi to PostScript converter.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif /* HAVE_CONFIG_H */
23
24 #define FUCKED
25
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif /* HAVE_UNISTD_H */
29 #include <sys/time.h>
30
31 /* POSIX.1 sys/wait.h check */
32 #include <sys/types.h>
33 #ifdef HAVE_SYS_WAIT_H
34 #include <sys/wait.h>
35 #endif /* HAVE_SYS_WAIT_H */
36 #ifndef WEXITSTATUS
37 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
38 #endif /* ! WEXITSTATUS */
39 #ifndef WIFEXITED
40 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
41 #endif /* ! WIFEXITED */
42
43 #include <sys/file.h>
44 #include <syslog.h>
45 #include <atalk/paths.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <signal.h>
51 #include <errno.h>
52
53 /* Forward Declarations */
54 int pexecv(char *path, char *argv[]);
55 int copyio();
56 int textps();
57
58 static char             psapath[] = _PATH_PSA;
59 static char             *psaargv[] = { "psa", 0, 0, 0, 0 };
60
61 /*
62  * If we're not doing accounting, we just call pap as below.
63  * If we are doing accounting, we call pap twice.  The first time,
64  * we call it with "-E" in arg 2, pagecount.ps in arg 3, and "-" in
65  * arg 4.  The second time, we call it with "-c" in arg 2, pagecount.ps
66  * in arg 3, and 0 in arg 4.
67  */
68 static char             pappath[] = _PATH_PAP;
69 static char             *papargv[] = { "pap", "-sstatus", 0, 0, 0, 0, 0, 0 };
70
71 static char             revpath[] = _PATH_PSORDER;
72 static char             *revargv[] = { "psorder", "-d", 0 };
73
74 static char             *filtargv[] = { 0, 0, 0 };
75
76 static char             inbuf[ 1024 * 8 ];
77 static int              inlen;
78
79 static FILE             *acctfile = NULL;
80 static int              literal;
81 static int              width = 80, length = 66, indent = 0;
82 static char             *prog, *name, *host;
83
84 static struct papersize {
85     int         width;
86     int         length;
87    float        win;
88     float       lin;
89 } papersizes[] = {
90     { 80, 66, 8.5, 11.0 },                      /* US Letter */
91     { 80, 70, 8.27, 11.69 },                    /* A4 */
92 };
93
94 int main( int ac, char **av)
95 {
96     int                 c, rc, children = 0;
97 #ifdef FUCKED
98     int                 psafileno = 0, multiconn = 0, waitidle = 0, waitidle2 = 0;
99 #endif /* FUCKED */
100     int                 status;
101     extern char         *optarg;
102     extern int          optind, opterr;
103
104     opterr = 0;
105     if (( prog = rindex( av[ 0 ], '/' )) == NULL ) {
106         prog = av[ 0 ];
107     } else {
108         prog++;
109     }
110 #ifdef ultrix
111     openlog( prog, LOG_PID );
112 #else /* ultrix */
113     openlog( prog, LOG_PID, LOG_LPR );
114 #endif /* ultrix */
115
116     while (( c = getopt( ac, av, "P:C:D:F:L:J:x:y:n:h:w:l:i:c" )) != EOF ) {
117         switch ( c ) {
118         case 'n' :
119             name = optarg;
120             break;
121
122         case 'h' :
123             host = optarg;
124             break;
125
126         case 'w' :
127             width = atoi( optarg );
128 #ifdef ZEROWIDTH
129             /*
130              * Some version of lpd pass 0 for the page width.
131              */
132             if ( width == 0 ) {
133                 width = 80;
134             }
135 #endif /* ZEROWIDTH */
136             break;
137
138         case 'l' :
139             length = atoi( optarg );
140             break;
141
142         case 'i' :
143             indent = atoi( optarg );
144             break;
145
146         case 'c' :      /* Print control chars */
147             literal++;
148             break;
149
150         case 'F' :
151         case 'L' :
152         case 'J' :
153         case 'P' :
154         case 'x' :
155         case 'y' :
156             break;
157         
158 #ifdef notdef
159         default :
160             syslog( LOG_ERR, "bad option: %c", c );
161             exit( 2 );
162 #endif /* notdef */
163         }
164     }
165     if ( ac - optind > 1 ) {
166         syslog( LOG_ERR, "Too many arguments" );
167         exit( 2 );
168     }
169 #ifdef FUCKED
170     if ( index( prog, 'w' )) {
171         waitidle++;
172     }
173     if ( index( prog, 'W' )) {
174         waitidle2++;
175     }
176     if ( index( prog, 'm' )) {
177         multiconn++;
178     }
179 #endif /* FUCKED */
180
181     syslog( LOG_INFO, "starting for %s", name ? name : "?" );
182
183 restart:
184     if (( inlen = read( 0, inbuf, sizeof( inbuf ))) < 0 ) {
185         syslog( LOG_ERR, "read: %s", strerror(errno) );
186         exit( 1 );
187     }
188     if ( inlen == 0 ) { /* nothing to be done */
189         syslog( LOG_INFO, "done" );
190         exit( 0 );
191     }
192
193     /*
194      * If we've been given an accounting file, start the accounting
195      * process.
196      */
197     if ( optind < ac ) {
198         /* build arguments */
199         psaargv[ 1 ] = av[ optind ];
200         psaargv[ 2 ] = name;
201         psaargv[ 3 ] = host;
202         if (( c = pexecv( psapath, psaargv )) < 0 ) {
203             syslog( LOG_ERR, "%s: %s", psapath, strerror(errno) );
204             exit( 2 );
205         }
206         children++;
207         syslog( LOG_INFO, "accounting with psa[%d]", c );
208     }
209
210     /*
211      * Check prog's name to decide what programs to execute.
212      */
213     if ( strstr( prog, "pap" ) != NULL ) {
214         if ( optind < ac ) {    /* accounting */
215 #ifdef FUCKED
216             if ( multiconn ) {
217                 psafileno = getdtablesize();
218                 psafileno--;
219                 dup2( 1, psafileno );
220
221                 if ( waitidle2 ) {
222                     papargv[ 2 ] = "-W";
223                     papargv[ 3 ] = "-c";
224                     papargv[ 4 ] = "-E";
225                     papargv[ 5 ] = _PATH_PAGECOUNT;
226                     papargv[ 6 ] = "-";
227                     papargv[ 7 ] = 0;
228                 } else if ( waitidle ) {
229                     papargv[ 2 ] = "-w";
230                     papargv[ 3 ] = "-c";
231                     papargv[ 4 ] = "-E";
232                     papargv[ 5 ] = _PATH_PAGECOUNT;
233                     papargv[ 6 ] = "-";
234                     papargv[ 7 ] = 0;
235                 } else {
236                     papargv[ 2 ] = "-c";
237                     papargv[ 3 ] = "-E";
238                     papargv[ 4 ] = _PATH_PAGECOUNT;
239                     papargv[ 5 ] = "-";
240                     papargv[ 6 ] = 0;
241                 }
242             } else {
243                 /*
244                  * This is how it should be done.
245                  */
246                 papargv[ 2 ] = "-c";
247                 papargv[ 3 ] = _PATH_PAGECOUNT;
248                 papargv[ 4 ] = "-";
249                 papargv[ 5 ] = _PATH_PAGECOUNT;
250                 papargv[ 6 ] = 0;
251             }
252 #endif /* FUCKED */
253         } else {
254             papargv[ 2 ] = "-c";
255             papargv[ 3 ] = "-E";
256             papargv[ 4 ] = 0;
257         }
258
259         if (( c = pexecv( pappath, papargv )) < 0 ) {
260             syslog( LOG_ERR, "%s: %s", pappath, strerror(errno) );
261             exit( 2 );
262         }
263         children++;
264         syslog( LOG_INFO, "sending to pap[%d]", c );
265     }
266
267     /*
268      * Might be a good idea to have both a "forw" and a "rev", so that
269      * reversed documents can be reordered for the printing device.
270      */
271     if ( strstr( prog, "rev" ) != NULL ) {
272         if (( c = pexecv( revpath, revargv )) < 0 ) {
273             syslog( LOG_ERR, "%s: %s", revpath, strerror(errno) );
274             exit( 2 );
275         }
276         syslog( LOG_INFO, "sending to rev[%d]", c );
277         children++;
278     }
279
280     /*
281      * Invoke an external (script) filter to produce PostScript from
282      * non-text input.
283      */
284     if ( *prog != 'i' && *prog != 'o' && *( prog + 1 ) == 'f' ) {
285         filtargv[ 0 ] = filtargv[ 1 ] = prog;
286         if (( c = pexecv( _PATH_PSFILTER, filtargv )) < 0 ) {
287             syslog( LOG_ERR, "%s: %s", _PATH_PSFILTER, strerror(errno) );
288             exit( 2 );
289         }
290         syslog( LOG_INFO, "external filter[%d]", c );
291         children++;
292         rc = copyio();          /* external filter */
293     } else {
294         if ( inlen >= 2 && inbuf[ 0 ] == '%' && inbuf[ 1 ] == '!' ) {
295             syslog( LOG_INFO, "PostScript" );
296             rc = copyio();      /* PostScript */
297         } else if ( inlen >= 2 && inbuf[ 0 ] == '\033' && inbuf[ 1 ] == '%' ) {
298             syslog( LOG_INFO, "PostScript w/PJL" );
299             rc = copyio();      /* PostScript */
300         } else {
301             syslog( LOG_INFO, "straight text" );
302             rc = textps();      /* straight text */
303         }
304     }
305
306 #ifdef FUCKED
307     if ( strstr( prog, "pap" ) != NULL && optind < ac && multiconn ) {
308         dup2( psafileno, 1 );
309         close( psafileno );
310         papargv[ 2 ] = "-c";
311         if ( waitidle2 ) {
312             papargv[ 3 ] = "-W";
313             papargv[ 4 ] = _PATH_PAGECOUNT;
314             papargv[ 5 ] = 0;
315         } else if ( waitidle ) {
316             papargv[ 3 ] = "-w";
317             papargv[ 4 ] = _PATH_PAGECOUNT;
318             papargv[ 5 ] = 0;
319         } else {
320             papargv[ 3 ] = _PATH_PAGECOUNT;
321             papargv[ 4 ] = 0;
322         }
323
324         if (( c = pexecv( pappath, papargv )) < 0 ) {
325             syslog( LOG_ERR, "%s: %s", pappath, strerror(errno) );
326             exit( 2 );
327         }
328         children++;
329         syslog( LOG_INFO, "pagecount with pap[%d]", c );
330     }
331 #endif /* FUCKED */
332
333     if ( children ) {
334         close( 1 );
335     }
336     while ( children ) {
337         if (( c = wait3( &status, 0, 0 )) < 0 ) {
338             syslog( LOG_ERR, "wait3: %s", strerror(errno) );
339             exit( 1 );
340         }
341         if ( WIFEXITED( status )) {
342 #ifndef WEXITSTATUS
343 #define WEXITSTATUS(x)  ((x).w_status)
344 #endif /* WEXITSTATUS */
345             if ( WEXITSTATUS( status ) != 0 ) {
346                 syslog( LOG_ERR, "%d died with %d", c, WEXITSTATUS( status ));
347                 exit( WEXITSTATUS( status ));
348             } else {
349                 syslog( LOG_INFO, "%d done", c );
350                 children--;
351             }
352         } else {
353             syslog( LOG_ERR, "%d died badly", c );
354             exit( 1 );
355         }
356     }
357
358     if ( rc == 3 ) {
359         syslog( LOG_INFO, "pausing" );
360         kill( getpid(), SIGSTOP );
361         syslog( LOG_INFO, "restarting" );
362         goto restart;
363     }
364
365     syslog( LOG_INFO, "done" );
366     exit( rc );
367 }
368
369 int copyio(void)
370 {
371     /* implement the FSM needed to do the suspend. Note that
372      * the last characters will be \031\001 so don't worry
373      * Fun things: 1. \031\001 should not be written to output device
374      *  2. The \031 can be last char of one read, \001 first of next
375      *      - we need to write \031 if not followed by \001
376      */
377     struct timeval      tv;
378     fd_set              fdset;
379     int                 ctl = 0;
380
381 notdone:
382     do {
383         /*
384          * First, \031 and \001 *must* be the last things in the buffer
385          * (\001 can be the first thing in the next buffer).  There's no
386          * need to scan any of the intervening bytes.  Second, if there's
387          * more input, the escape sequence was bogus, and we should keep
388          * reading.
389          */
390         if ( inlen == 1 ) {
391             if ( ctl == 1 ) {
392                 if ( inbuf[ 0 ] == '\001' ) {
393                     ctl = 2;
394                     break;
395                 }
396                 if ( write( 1, "\031", 1 ) != 1 ) {
397                     syslog( LOG_ERR, "write: %s", strerror(errno) );
398                     return( 1 );
399                 }
400                 ctl = 0;
401             }
402
403             if ( inbuf[ 0 ] == '\031' ) {
404                 ctl = 1;
405             }
406
407         } else {
408             if ( ctl == 1 ) {
409                 if ( write( 1, "\031", 1 ) != 1 ) {
410                     syslog( LOG_ERR, "write: %s", strerror(errno) );
411                     return( 1 );
412                 }
413             }
414             ctl = 0;
415             if ( inbuf[ inlen - 2 ] == '\031' &&
416                     inbuf[ inlen - 1 ] == '\001' ) {
417                 ctl = 2;
418             } else if ( inbuf[ inlen - 1 ] == '\031' ) {
419                 ctl = 1;
420             }
421         }
422
423         inlen -= ctl;
424         if (( inlen > 0 ) && ( write( 1, inbuf, inlen ) != inlen )) {
425             syslog( LOG_ERR, "write: %s", strerror(errno) );
426             return( 1 );
427         }
428         if ( ctl == 2 ) {
429             break;
430         }
431     } while (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 );
432
433     if ( ctl == 2 ) {
434         tv.tv_sec = 2;
435         tv.tv_usec = 0;
436         FD_ZERO( &fdset );
437         FD_SET( 0, &fdset );
438         if ( select( 1, &fdset, NULL, NULL, &tv ) != 0 ) {
439             if (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 ) {
440                 goto notdone;
441             }
442         }
443     }
444
445     if ( inlen < 0 ) {
446         syslog( LOG_ERR, "read: %s", strerror(errno) );
447         return( 1 );
448     }
449
450     if ( ctl == 1 ) {
451         if ( write( 1, "\031", 1 ) != 1 ) {
452             syslog( LOG_ERR, "write: %s", strerror(errno) );
453             return( 1 );
454         }
455     } else if ( ctl == 2 ) {
456         return( 3 );
457     }
458     return( 0 );
459 }
460
461 static char             *font = "Courier";
462 static int              point = 11;
463
464 static char             pspro[] = "\
465 /GSV save def                                           % global VM\n\
466 /SP {\n\
467         /SV save def                                    % save vmstate\n\
468         dup /H exch def                                 % save font height\n\
469         exch findfont exch scalefont setfont            % select font\n\
470         ( ) stringwidth pop /W exch def                 % save font width\n\
471         0.5 sub 72 mul /CY exch def                     % save start Y\n\
472         pop 0.5 add 72 mul /CX exch def                 % save start X\n\
473         CX CY moveto                                    % make current point\n\
474 } bind def\n\
475 /S /show load def\n\
476 /NL { CX CY H sub dup /CY exch def moveto } bind def\n\
477 /CR { CX CY moveto } bind def\n\
478 /B { W neg 0 rmoveto}bind def\n\
479 /T { W mul 0 rmoveto}bind def\n\
480 /EP { SV restore showpage } bind def\n\
481 %%EndProlog\n";
482
483 int textps(void)
484 {
485     struct papersize    papersize;
486     int                 state = 0, line = 0, col = 0, npages = 0, rc;
487     unsigned int        i;
488     char                *p, *end;
489
490 #define elements(x)     (sizeof(x)/sizeof((x)[0]))
491     for ( i = 0; i < elements( papersizes ); i++ ) {
492         if ( width == papersizes[ 0 ].width &&
493                 length == papersizes[ 0 ].length ) {
494             papersize = papersizes[ i ];
495             break;
496         }
497     }
498     if ( i >= elements( papersizes )) {
499         papersize = papersizes[ 0 ];            /* default */
500     }
501
502 #define ST_AVAIL                (1<<0)
503 #define ST_CONTROL              (1<<1)
504 #define ST_PAGE                 (1<<2)
505     /*
506      * convert text lines to postscript.
507      * A grungy little state machine. If I was more creative, I could
508      * probably think of a better way of doing this...
509      */
510     do {
511         p = inbuf;
512         end = inbuf + inlen;
513         while ( p < end ) {
514             if (( state & ST_PAGE ) == 0 && *p != '\031' && *p != '\001' ) {
515                 if ( npages == 0 ) {
516                     printf( "%%!PS-Adobe-2.0\n%%%%Pages: (atend)\n" );
517                     printf( "%%%%DocumentFonts: %s\n", font );
518                     fflush( stdout );
519
520                     /* output postscript prologue: */
521                     if ( write( 1, pspro, sizeof( pspro ) - 1 ) !=
522                             sizeof( pspro ) - 1 ) {
523                         syslog( LOG_ERR, "write prologue: %s", strerror(errno) );
524                         return( 1 );
525                     }
526                     if ( name && host ) {
527                         printf( "statusdict /jobname (%s@%s) put\n", name,
528                                 host );
529                     }
530                 }
531
532                 printf( "%%%%Page: ? %d\n", ++npages );
533                 printf( "%d %f %f /%s %d SP\n", indent,
534                         papersize.win, papersize.lin, font, point );
535                 state |= ST_PAGE;
536             }
537             if ( state & ST_CONTROL && *p != '\001' ) {
538                 /* It is a very bad thing to toss a job because it contains
539                  * unprintable characters.  Instead, we will convert them to
540                  * question marks.  This is adapted from a solution described
541                  * by Werner Eugster <eugster@giub.unibe.ch> on his ApplePrint
542                  * webpage (http://www.giub.unibe.ch/~eugster/appleprint.html).
543                  *
544                  * Note that this is rather ugly code.  The same change is
545                  * applied identically at two different locations in this file.
546                  * It would be better someday to combine the two.
547                 */
548                 if ( !literal ) {
549                         fprintf( stderr,
550                                 "unprintable character (0x%x) converted to ?!\n",
551                                 (unsigned char)*p );
552                         putchar( '?' ); /* Replace unprintable char with a question mark. */
553                 } else {
554                         printf( "\\%o", (unsigned char)031 );
555                 }
556                 state &= ~ST_CONTROL;
557                 col++;
558             }
559
560             switch ( *p ) {
561             case '\n' :         /* end of line */
562                 if ( state & ST_AVAIL ) {
563                     printf( ")S\n" );
564                     state &= ~ST_AVAIL;
565                 }
566                 printf( "NL\n" );
567                 line++;
568                 col = 0;
569                 if ( line >= length ) {
570                     printf( "EP\n" );
571                     state &= ~ST_PAGE;
572                     line = 0;
573                 }
574                 break;
575
576             case '\r' :         /* carriage return (for overtyping) */
577                 if ( state & ST_AVAIL ) {
578                     printf( ")S CR\n" );
579                     state &= ~ST_AVAIL;
580                 }
581                 col = 0;
582                 break;
583
584             case '\f' :         /* form feed */
585                 if ( state & ST_AVAIL ) {
586                     printf( ")S\n" );
587                     state &= ~ST_AVAIL;
588                 }
589                 printf( "EP\n" );
590                 state &= ~ST_PAGE;
591                 line = 0;
592                 col = 0;
593                 break;
594
595             case '\b' :         /* backspace */
596                 /* show line, back up one character */
597                 if ( state & ST_AVAIL ) {
598                     printf( ")S\n" );
599                     state &= ~ST_AVAIL;
600                 }
601                 printf( "B\n" );
602                 col--;
603                 break;
604
605             case '\t' :         /* tab */
606                 if ( state & ST_AVAIL ) {
607                     printf( ")S\n" );
608                     state &= ~ST_AVAIL;
609                 }
610                 printf( "%d T\n", 8 - ( col % 8 ));
611                 col += 8 - ( col % 8 );
612                 break;
613
614             /*
615              * beginning of lpr control sequence
616              */
617             case '\031' :
618                 state |= ST_CONTROL;
619                 break;
620
621             case '\001' :       /* lpr control sequence */
622                 if ( state & ST_CONTROL ) {
623                     rc = 3;
624                     goto out;
625                 }
626                 /* FALLTHROUGH */
627
628             case '\\' :
629             case ')' :
630             case '(' :
631                 if (( state & ST_AVAIL ) == 0 ) {
632                     printf( "(" );
633                     state |= ST_AVAIL;
634                 }
635                 putchar( '\\' );
636                 /* FALLTHROUGH */
637
638             default :
639                 if (( state & ST_AVAIL ) == 0 ) {
640                     printf( "(" );
641                     state |= ST_AVAIL;
642                 }
643                 if ( !isascii( *p ) || !isprint( *p )) {
644                     if ( !literal ) {
645                         fprintf( stderr,
646                             "unprintable character (0x%x) converted to ?!\n",
647                             (unsigned char)*p );
648                         putchar( '?' ); /* Replace unprintable char with a question mark. */
649                     } else {
650                         printf( "\\%o", (unsigned char)*p );
651                     }
652                 } else {
653                     putchar( *p );
654                 }
655                 col++;
656                 break;
657             }
658         p++;
659         }
660     } while (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 );
661     if ( inlen < 0 ) {
662         syslog( LOG_ERR, "read: %s", strerror(errno) );
663         return( 1 );
664     }
665     rc = 0;
666
667 out:
668     if ( state & ST_AVAIL ) {
669         printf( ")S\n" );
670         state &= ~ST_AVAIL;
671     }
672
673     if ( state & ST_PAGE ) {
674         printf( "EP\n" );
675         state &= ~ST_PAGE;
676     }
677
678     if ( npages > 0 ) {
679         printf( "%%%%Trailer\nGSV restore\n%%%%Pages: %d\n%%%%EOF\n", npages );
680         fflush( stdout );
681     }
682
683     return( rc );
684 }
685
686 /*
687  * Interface to pipe and exec, for starting children in pipelines.
688  *
689  * Manipulates file descriptors 0, 1, and 2, such that the new child
690  * is reading from the parent's output.
691  */
692 int pexecv( char *path, char *argv[])
693 {
694     int         fd[ 2 ], c;
695
696     if ( pipe( fd ) < 0 ) {
697         return( -1 );
698     }
699
700     switch ( c = fork()) {
701     case -1 :
702         return( -1 );
703         /* NOTREACHED */
704
705     case 0 :
706         if ( close( fd[ 1 ] ) < 0 ) {
707             return( -1 );
708         }
709         if ( dup2( fd[ 0 ], 0 ) < 0 ) {
710             return( -1 );
711         }
712         if ( close( fd[ 0 ] ) < 0 ) {
713             return( -1 );
714         }
715         execv( path, argv );
716         return( -1 );
717         /* NOTREACHED */
718
719     default :
720         if ( close( fd[ 0 ] ) < 0 ) {
721             return( -1 );
722         }
723         if ( dup2( fd[ 1 ], 1 ) < 0 ) {
724             return( -1 );
725         }
726         if ( close( fd[ 1 ] ) < 0 ) {
727             return( -1 );
728         }
729         return( c );
730     }
731 }