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

Annotation of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 33 - (hide annotations)
Thu Aug 8 16:57:39 2024 UTC (7 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 18150 byte(s)
fix(uar): better error messages during archive creation

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26