/[osn-commons]/trunk/uar/main.c
ViewVC logotype

Annotation of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 40 - (hide annotations)
Sat Aug 10 14:44:20 2024 UTC (7 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 20993 byte(s)
feat(uar): finalize extraction/creation/listing of archives
1 rakinar2 23 /*
2     * main.c -- entry point and argument parsing for the UAR program
3     *
4     * This program is part of the UAR (Universal Archive) utility suite.
5     * Copyright (C) 2024 OSN, Inc.
6     * Author: Ar Rakin <[email protected]>
7     *
8     * This program is free software: you can redistribute it and/or modify
9     * it under the terms of the GNU General Public License as published by
10     * the Free Software Foundation, either version 3 of the License, or
11     * (at your option) any later version.
12     *
13     * This program is distributed in the hope that it will be useful,
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     * GNU General Public License for more details.
17     *
18     * You should have received a copy of the GNU General Public License
19     * along with this program. If not, see <http://www.gnu.org/licenses/>.
20     */
21    
22     #define _XOPEN_SOURCE 500
23    
24     #include <assert.h>
25     #include <errno.h>
26     #include <getopt.h>
27 rakinar2 36 #include <grp.h>
28 rakinar2 23 #include <libgen.h>
29 rakinar2 32 #include <limits.h>
30 rakinar2 36 #include <linux/limits.h>
31 rakinar2 32 #include <math.h>
32 rakinar2 36 #include <pwd.h>
33 rakinar2 23 #include <stdarg.h>
34     #include <stdbool.h>
35     #include <stdio.h>
36     #include <stdlib.h>
37     #include <string.h>
38 rakinar2 32 #include <strings.h>
39 rakinar2 23 #include <sys/stat.h>
40 rakinar2 32 #include <time.h>
41     #include <unistd.h>
42 rakinar2 23
43     #include "uar.h"
44     #include "xmalloc.h"
45    
46     #ifdef HAVE_CONFIG_H
47     # include "config.h"
48     #else
49     # define PACKAGE_NAME "uar"
50     # define PACKAGE_VERSION "1.0"
51     # define PACKAGE_BUGREPORT "[email protected]"
52     #endif
53    
54     #ifndef NDEBUG
55     # define debug(...) pdebug (__FILE__, __LINE__, __VA_ARGS__)
56     #else
57     # define debug(...)
58     #endif
59    
60     /* Command line options. */
61     static struct option const long_options[] = {
62 rakinar2 32 { "create", no_argument, NULL, 'c' },
63     { "extract", no_argument, NULL, 'x' },
64     { "human-readable", no_argument, NULL, 'm' },
65     { "list", no_argument, NULL, 't' },
66     { "verbose", no_argument, NULL, 'v' },
67     { "file", required_argument, NULL, 'f' },
68     { "directory", required_argument, NULL, 'C' },
69     { "help", no_argument, NULL, 'h' },
70     { "version", no_argument, NULL, 'V' },
71     { NULL, 0, NULL, 0 },
72 rakinar2 23 };
73    
74 rakinar2 32 static char const short_options[] = "cxtvmf:C:hV";
75 rakinar2 23
76     /* Program name. */
77     static char *progname = NULL;
78    
79     /* Flags for the command line options. */
80     enum uar_mode
81     {
82 rakinar2 32 MODE_NONE = 0,
83 rakinar2 23 MODE_CREATE,
84     MODE_EXTRACT,
85     MODE_LIST
86     };
87    
88     struct uar_params
89     {
90     enum uar_mode mode;
91     bool verbose;
92 rakinar2 32 bool hr_sizes;
93 rakinar2 23 char *file;
94     char *cwd;
95 rakinar2 32 char **targets;
96     char **rtargets;
97     size_t ntargets;
98 rakinar2 23 };
99    
100     static struct uar_params params = { 0 };
101    
102     /* Print usage information. */
103     static void
104     usage (void)
105     {
106     printf ("Usage:\n");
107     printf (" uar [OPTION]... [FILE]...\n");
108     printf ("\n");
109     printf ("Universal Archive utility.\n");
110     printf ("\n");
111     printf ("Options:\n");
112     printf (" -c, --create Create a new archive\n");
113     printf (" -x, --extract Extract files from an archive\n");
114     printf (" -t, --list List the contents of an archive\n");
115 rakinar2 32 printf (" -m, --human-readable Print human-readable sizes\n");
116 rakinar2 23 printf (" -v, --verbose Verbose mode\n");
117     printf (
118     " -f, --file=ARCHIVE Use archive file or directory ARCHIVE\n");
119     printf (" -C, --directory=DIR Change to directory DIR\n");
120     printf (" -h, --help Display this help and exit\n");
121     printf (" -V, --version Output version information and exit\n");
122     printf ("\n");
123     printf ("Report bugs to: <" PACKAGE_BUGREPORT ">\n");
124     }
125    
126     /* Print version information. */
127     static void
128     show_version (void)
129     {
130     printf ("OSN %s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
131     printf ("\n");
132     printf ("Copyright (C) 2024 OSN, Inc.\n");
133     printf ("License GPLv3+: GNU GPL version 3 or later "
134     "<http://gnu.org/licenses/gpl.html>\n");
135     printf (
136     "This is free software: you are free to change and redistribute it.\n");
137     printf ("There is NO WARRANTY, to the extent permitted by law.\n");
138     printf ("\n");
139     printf ("Written by Ar Rakin <[email protected]>\n");
140     }
141    
142 rakinar2 30 #ifndef NDEBUG
143 rakinar2 23 /* Print a debug message. */
144     static void
145     pdebug (char const *file, int line, char const *format, ...)
146     {
147     if (!params.verbose)
148     return;
149    
150     va_list args;
151     va_start (args, format);
152     fprintf (stderr, "%s(verbose): %s:%i: ", progname, file, line);
153     vfprintf (stderr, format, args);
154     va_end (args);
155     }
156 rakinar2 30 #endif
157 rakinar2 23
158     /* Print a message. */
159     static void
160     pinfo (char const *format, ...)
161     {
162     va_list args;
163     va_start (args, format);
164     fprintf (stdout, "%s: ", progname);
165     vfprintf (stdout, format, args);
166     va_end (args);
167     }
168    
169     /* Print an error message. */
170     static void
171     perr (char const *format, ...)
172     {
173     va_list args;
174     va_start (args, format);
175     fprintf (stderr, "%s: ", progname);
176     vfprintf (stderr, format, args);
177     va_end (args);
178     }
179    
180     /* Cleanup memory. */
181     static void
182     cleanup ()
183     {
184 rakinar2 32 for (size_t i = 0; i < params.ntargets; i++)
185     free (params.targets[i]);
186 rakinar2 23
187 rakinar2 32 if (params.targets != NULL)
188     free (params.targets);
189    
190 rakinar2 23 if (params.cwd != NULL)
191     free (params.cwd);
192    
193     if (params.file != NULL)
194     free (params.file);
195     }
196    
197     /* Initialize the program. */
198     static void
199     initialize (char *argv0)
200     {
201     atexit (&cleanup);
202 rakinar2 33 progname = argv0;
203 rakinar2 23 }
204    
205 rakinar2 33 /* Create archive callback. */
206     static bool
207     create_archive_callback (struct uar_archive *uar,
208 rakinar2 32 struct uar_file *file __attribute__ ((unused)),
209 rakinar2 33 const char *uar_name __attribute__ ((unused)),
210     const char *fs_name, enum uar_error_level level,
211     const char *message)
212 rakinar2 30 {
213 rakinar2 33 if (level == UAR_ELEVEL_NONE)
214     {
215     if (params.verbose)
216     fprintf (stdout, "%s\n", fs_name);
217     }
218     else if (level == UAR_ELEVEL_WARNING)
219     perr ("warning: %s: %s\n", fs_name,
220     message != NULL ? message : uar_strerror (uar));
221     else if (level == UAR_ELEVEL_ERROR)
222     perr ("error: %s: %s\n", fs_name,
223     message != NULL ? message : uar_strerror (uar));
224 rakinar2 32
225 rakinar2 30 return true;
226     }
227    
228 rakinar2 23 /* Create an archive. */
229     static void
230     create_archive (void)
231     {
232     assert (params.mode == MODE_CREATE);
233 rakinar2 32 assert (params.ntargets > 0);
234     assert (params.targets != NULL);
235 rakinar2 23
236 rakinar2 32 if (params.verbose)
237     pinfo ("creating archive: %s\n", params.file);
238 rakinar2 23
239 rakinar2 36 struct uar_archive *uar = uar_stream_create ();
240 rakinar2 23
241 rakinar2 32 if (uar == NULL)
242 rakinar2 23 {
243     pinfo ("failed to create archive: %s\n", strerror (errno));
244     return;
245     }
246    
247 rakinar2 33 uar_set_create_callback (uar, &create_archive_callback);
248    
249 rakinar2 32 for (size_t i = 0; i < params.ntargets; i++)
250 rakinar2 23 {
251     struct stat stinfo = { 0 };
252    
253 rakinar2 32 if (stat (params.targets[i], &stinfo) != 0)
254 rakinar2 23 {
255 rakinar2 32 perr ("cannot stat '%s': %s\n", params.targets[i],
256     strerror (errno));
257 rakinar2 23 uar_close (uar);
258     return;
259     }
260    
261 rakinar2 36 const char *base = basename (params.rtargets[i]);
262    
263     if (strcmp (base, ".") == 0 || strcmp (base, "..") == 0)
264     base = basename (params.targets[i]);
265    
266 rakinar2 33 struct uar_file *file
267 rakinar2 36 = uar_stream_add_entry (uar, base, params.rtargets[i], &stinfo);
268 rakinar2 23
269 rakinar2 32 if (file == NULL || uar_has_error (uar))
270 rakinar2 23 {
271 rakinar2 32 const char *error_file = uar_get_error_file (uar);
272     perr ("failed to add '%s': %s\n",
273     error_file == NULL ? params.targets[i] : error_file,
274     uar_strerror (uar));
275     exit (1);
276 rakinar2 23 }
277     }
278    
279 rakinar2 32 if (!uar_stream_write (uar, params.file))
280 rakinar2 23 {
281 rakinar2 32 const char *error_file = uar_get_error_file (uar);
282     pinfo ("failed to write archive: %s%s%s\n",
283     error_file == NULL ? "" : error_file,
284     error_file == NULL ? "" : ": ", uar_strerror (uar));
285 rakinar2 23 return;
286     }
287    
288     #ifdef UAR_PRINT_VERBOSE_IMPL_INFO
289     uar_debug_print (uar, false);
290     #endif
291     uar_close (uar);
292     }
293    
294     /* Archive extraction callback. */
295     static bool
296 rakinar2 40 extract_archive_callback (struct uar_archive *uar,
297     struct uar_file *file __attribute__ ((unused)),
298     const char *uar_name __attribute__ ((unused)),
299     const char *fs_name, enum uar_error_level level,
300     const char *message)
301 rakinar2 23 {
302 rakinar2 40 if (level == UAR_ELEVEL_NONE)
303     {
304     if (params.verbose)
305     fprintf (stdout, "%s\n", fs_name);
306     }
307     else if (level == UAR_ELEVEL_WARNING)
308     perr ("warning: %s: %s\n", fs_name,
309     message != NULL ? message : uar_strerror (uar));
310     else if (level == UAR_ELEVEL_ERROR)
311     perr ("error: %s: %s\n", fs_name,
312     message != NULL ? message : uar_strerror (uar));
313    
314 rakinar2 23 return true;
315     }
316    
317     /* Extract an archive. */
318     static void
319 rakinar2 32 extract_archive (void)
320 rakinar2 23 {
321     assert (params.mode == MODE_EXTRACT);
322    
323     pinfo ("extracting archive: %s\n", params.file);
324    
325 rakinar2 40 struct uar_archive *uar = uar_stream_open (params.file);
326 rakinar2 23
327     if (uar == NULL || uar_has_error (uar))
328     {
329     pinfo ("failed to open archive: %s\n", strerror (errno));
330     return;
331     }
332    
333 rakinar2 40 uar_set_extract_callback (uar, &extract_archive_callback);
334 rakinar2 23
335 rakinar2 40 if (params.cwd != NULL)
336 rakinar2 23 {
337 rakinar2 40 if (chdir (params.cwd) != 0)
338     {
339     pinfo ("failed to change working directory: %s\n",
340     strerror (errno));
341     return;
342     }
343     }
344    
345     char *cwd = getcwd (NULL, 0);
346    
347     if (!uar_stream_extract (uar, cwd))
348     {
349 rakinar2 23 pinfo ("failed to extract archive: %s\n", strerror (errno));
350 rakinar2 40 free (cwd);
351 rakinar2 23 return;
352     }
353    
354 rakinar2 40 free (cwd);
355 rakinar2 23 uar_close (uar);
356     }
357    
358 rakinar2 32 static const char *
359     stringify_mode (mode_t mode)
360     {
361     static char str[11];
362    
363     str[0] = S_ISDIR (mode) ? 'd' : S_ISLNK (mode) ? 'l' : '-';
364    
365     for (int i = 1; i < 10; i++)
366     str[i] = mode & (1 << (9 - i)) ? "rwxrwxrwx"[i - 1] : '-';
367    
368     return str;
369     }
370    
371     static int
372     count_dec_numlen (uint64_t num)
373     {
374     int len = 0;
375    
376     do
377     {
378     num /= 10;
379     len++;
380     }
381     while (num > 0);
382    
383     return len;
384     }
385    
386     static char *
387     format_iec_size (uint64_t size)
388     {
389     static char buf[32] = { 0 };
390 rakinar2 36 const char suffix[] = { 0, 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
391 rakinar2 32 size_t i = 0;
392     long double computed = size;
393    
394     while (computed > 1024 && i < sizeof (suffix) / sizeof (suffix[0]))
395     {
396     computed /= 1024;
397     i++;
398     }
399    
400 rakinar2 36 if (computed == 0.00)
401     {
402     snprintf (buf, sizeof (buf), "0%c", suffix[i]);
403     return buf;
404     }
405    
406     snprintf (buf, sizeof (buf), "%.2Lf%c", computed, suffix[i]);
407 rakinar2 32 return buf;
408     }
409    
410     struct archive_file_info
411     {
412 rakinar2 36 const char *name;
413     char owner[128];
414     char group[128];
415 rakinar2 32 mode_t mode;
416     time_t mtime;
417     union
418     {
419    
420     uint64_t bytes;
421     char str[64];
422     } size;
423     };
424    
425     struct archive_file_table
426     {
427     struct archive_file_info *files;
428     size_t nfiles;
429 rakinar2 36 int widths[3];
430 rakinar2 32 };
431    
432 rakinar2 36 #define TABLE_WIDTH_SIZE 0
433     #define TABLE_WIDTH_OWNER 1
434     #define TABLE_WIDTH_GROUP 2
435 rakinar2 32
436     static bool
437     list_archive_callback_analyze (struct uar_file *file, void *data)
438     {
439     struct archive_file_table *table = (struct archive_file_table *) data;
440     struct archive_file_info info = { 0 };
441    
442     mode_t mode = uar_file_get_mode (file);
443     const char *name = uar_file_get_name (file);
444     uint64_t size = uar_file_get_size (file);
445     int size_len = 0;
446    
447     info.mode = mode;
448     info.name = name;
449     info.mtime = uar_file_get_mtime (file);
450    
451 rakinar2 36 uid_t uid = uar_file_get_uid (file);
452     gid_t gid = uar_file_get_gid (file);
453    
454     struct passwd *pw = getpwuid (uid);
455     struct group *gr = getgrgid (gid);
456    
457     if (pw == NULL)
458     {
459     if (params.verbose)
460     perr ("warning: failed to get user info (%i): %s\n", uid,
461     strerror (errno));
462    
463     strncpy (info.owner, "unknown", sizeof (info.owner) - 1);
464     }
465     else
466     strncpy (info.owner, pw->pw_name, sizeof (info.owner) - 1);
467    
468     if (gr == NULL)
469     {
470     if (params.verbose)
471     perr ("warning: failed to get group info (%i): %s\n", gid,
472     strerror (errno));
473    
474     strncpy (info.group, "unknown", sizeof (info.group) - 1);
475     }
476     else
477     strncpy (info.group, gr->gr_name, sizeof (info.group) - 1);
478    
479 rakinar2 32 if (params.hr_sizes)
480     {
481     char *str = format_iec_size (size);
482     size_len = strlen (str);
483     strncpy (info.size.str, str, 32);
484     }
485     else
486     {
487     info.size.bytes = size;
488     size_len = count_dec_numlen (size);
489     }
490    
491     if (size_len > table->widths[TABLE_WIDTH_SIZE])
492     table->widths[TABLE_WIDTH_SIZE] = size_len;
493    
494 rakinar2 36 int owner_len = strlen (info.owner);
495     int group_len = strlen (info.group);
496    
497     if (owner_len > table->widths[TABLE_WIDTH_OWNER])
498     table->widths[TABLE_WIDTH_OWNER] = owner_len;
499    
500     if (group_len > table->widths[TABLE_WIDTH_GROUP])
501     table->widths[TABLE_WIDTH_GROUP] = group_len;
502    
503 rakinar2 32 table->files[table->nfiles++] = info;
504     return true;
505     }
506    
507     static void
508     list_archive (void)
509     {
510     assert (params.mode == MODE_LIST);
511     struct archive_file_table *table = NULL;
512 rakinar2 36 struct uar_archive *uar = uar_stream_open (params.file);
513 rakinar2 32
514 rakinar2 36 if (uar == NULL)
515 rakinar2 32 {
516 rakinar2 36 pinfo ("failed to open archive file: %s\n", strerror (errno));
517 rakinar2 32 goto list_archive_end;
518     }
519    
520 rakinar2 36 if (uar_has_error (uar))
521     {
522     pinfo ("failed to read archive: %s\n", uar_strerror (uar));
523     goto list_archive_end;
524     }
525    
526 rakinar2 32 uint64_t nfiles = uar_get_file_count (uar);
527    
528     table = xcalloc (1, sizeof (struct archive_file_table));
529     table->files = xcalloc (nfiles, sizeof (struct archive_file_info));
530     table->nfiles = 0;
531    
532 rakinar2 40 if (!uar_stream_iterate (uar, &list_archive_callback_analyze,
533     (void *) table))
534 rakinar2 32 {
535     pinfo ("failed to read archive: %s\n", strerror (errno));
536     goto list_archive_end;
537     }
538    
539     for (size_t i = 0; i < nfiles; i++)
540     {
541     struct archive_file_info info = table->files[i];
542     struct tm *tm = localtime (&info.mtime);
543 rakinar2 36 char mtime_str[10] = "never";
544 rakinar2 32 const char *mode_str = stringify_mode (info.mode);
545    
546     if (tm == NULL)
547     {
548 rakinar2 36 if (params.verbose)
549     perr ("warning: failed to convert time: %s\n",
550     strerror (errno));
551 rakinar2 32 }
552     else
553 rakinar2 36 strftime (mtime_str, sizeof (mtime_str), "%b %d", tm);
554 rakinar2 32
555     if (params.hr_sizes)
556 rakinar2 36 fprintf (stdout, "%s %-*s %-*s %*s %s %s\n", mode_str,
557     table->widths[TABLE_WIDTH_OWNER], info.owner,
558     table->widths[TABLE_WIDTH_GROUP], info.group,
559     table->widths[TABLE_WIDTH_SIZE], info.size.str,
560     mtime_str, info.name);
561 rakinar2 32
562     else
563 rakinar2 36 fprintf (stdout, "%s %-*s %-*s %*lu %s %s\n", mode_str,
564     table->widths[TABLE_WIDTH_OWNER], info.owner,
565     table->widths[TABLE_WIDTH_GROUP], info.group,
566     table->widths[TABLE_WIDTH_SIZE], info.size.bytes,
567     mtime_str, info.name);
568 rakinar2 32 }
569    
570     list_archive_end:
571     free (table->files);
572     free (table);
573     uar_close (uar);
574     }
575    
576 rakinar2 23 int
577     main (int argc, char **argv)
578     {
579     initialize (argv[0]);
580     assert (progname != NULL && "progname is NULL");
581    
582     while (true)
583     {
584     int opt
585     = getopt_long (argc, argv, short_options, long_options, NULL);
586    
587     if (opt == -1)
588     break;
589    
590 rakinar2 32 if ((opt == 'c' || opt == 'x' || opt == 't')
591     && params.mode != MODE_NONE)
592     {
593     perr ("only one mode can be specified\n");
594     exit (1);
595     }
596    
597 rakinar2 23 switch (opt)
598     {
599     case 'c':
600     params.mode = MODE_CREATE;
601     break;
602    
603     case 'x':
604     params.mode = MODE_EXTRACT;
605     break;
606    
607     case 't':
608     params.mode = MODE_LIST;
609     break;
610    
611     case 'v':
612     params.verbose = true;
613     debug ("Verbose mode enabled\n", progname);
614     break;
615    
616 rakinar2 32 case 'm':
617     params.hr_sizes = true;
618     break;
619    
620 rakinar2 23 case 'f':
621     params.file = optarg;
622     break;
623    
624     case 'C':
625     params.cwd = optarg;
626     break;
627    
628     case 'h':
629     usage ();
630     exit (0);
631    
632     case 'V':
633     show_version ();
634     exit (0);
635    
636     case '?':
637     default:
638 rakinar2 32 debug ("Unknown/Unhandled option: %c\n", opt);
639     bzero (&params, sizeof (params));
640 rakinar2 23 exit (1);
641     }
642     }
643    
644     if (params.file != NULL)
645     {
646     char *file = params.file;
647    
648     if ((params.mode == MODE_EXTRACT || params.mode == MODE_LIST))
649     {
650     params.file = realpath (file, NULL);
651    
652     if (params.file == NULL)
653     {
654 rakinar2 25 perr ("failed to read '%s': %s\n", file,
655 rakinar2 23 strerror (errno));
656     exit (1);
657     }
658     }
659     else
660     {
661     params.file = strdup (file);
662     }
663     }
664    
665     if (params.cwd != NULL)
666     {
667 rakinar2 32 if (params.mode == MODE_LIST)
668     {
669     params.cwd = NULL;
670     perr ("option '-C' or '--directory' does not make sense in "
671     "list mode\n");
672     exit (1);
673     }
674    
675 rakinar2 23 char *dir = params.cwd;
676     params.cwd = realpath (dir, NULL);
677    
678     if (params.cwd == NULL)
679     {
680 rakinar2 25 perr ("failed to change working directory to '%s': %s\n",
681     dir, strerror (errno));
682 rakinar2 23 exit (1);
683     }
684     }
685    
686     if (params.verbose)
687     {
688     debug ("Summary of options:\n");
689     debug (" mode: %s\n", params.mode == MODE_CREATE ? "create"
690     : params.mode == MODE_EXTRACT ? "extract"
691     : "list");
692     debug (" verbose: %s\n", params.verbose ? "yes" : "no");
693     debug (" file: %s\n", params.file);
694     debug (" working directory: %s\n", params.cwd);
695     }
696    
697     switch (params.mode)
698     {
699     case MODE_CREATE:
700 rakinar2 32 if (params.file == NULL)
701     {
702     perr ("no archive file name specified\n");
703     exit (1);
704     }
705    
706 rakinar2 23 for (int i = optind; i < argc; i++)
707     {
708 rakinar2 32 char *path = realpath (argv[i], NULL);
709    
710     if (path == NULL)
711     {
712     perr ("failed to read '%s': %s\n", argv[i],
713     strerror (errno));
714     exit (1);
715     }
716    
717     params.targets
718     = xrealloc (params.targets,
719     (params.ntargets + 1) * sizeof (char *));
720     params.targets[params.ntargets] = path;
721     params.ntargets++;
722 rakinar2 23 }
723    
724 rakinar2 32 params.rtargets = argv + optind;
725    
726     if (params.ntargets == 0)
727     {
728     perr ("no files or directories specified\n");
729     exit (1);
730     }
731    
732 rakinar2 23 create_archive ();
733     break;
734    
735     case MODE_EXTRACT:
736 rakinar2 32 if (params.file == NULL)
737     {
738     perr ("no archive file specified\n");
739     exit (1);
740     }
741    
742 rakinar2 23 extract_archive ();
743     break;
744    
745     case MODE_LIST:
746 rakinar2 32 if (params.file == NULL)
747     {
748     perr ("no archive file specified\n");
749     exit (1);
750     }
751    
752     list_archive ();
753 rakinar2 23 break;
754    
755     default:
756     usage ();
757     exit (1);
758     }
759    
760     return 0;
761     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26