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

Annotation of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 32 - (hide annotations)
Wed Aug 7 18:57:45 2024 UTC (7 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 17790 byte(s)
refactor(uar): better I/O strategies to decrease memory usage
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    
200     progname = strrchr (argv0, '/');
201    
202     if (progname != NULL)
203     progname++;
204     else
205     progname = argv0;
206     }
207    
208 rakinar2 32 static bool __attribute__ ((unused))
209     create_archive_callback (struct uar_archive *uar __attribute__ ((unused)),
210     struct uar_file *file __attribute__ ((unused)),
211 rakinar2 30 const char *fullname __attribute__ ((unused)),
212 rakinar2 32 const char *fullpath)
213 rakinar2 30 {
214 rakinar2 32 if (!params.verbose)
215     return true;
216    
217     fprintf (stdout, "%s\n", fullpath);
218 rakinar2 30 return true;
219     }
220    
221 rakinar2 23 /* Create an archive. */
222     static void
223     create_archive (void)
224     {
225     assert (params.mode == MODE_CREATE);
226 rakinar2 32 assert (params.ntargets > 0);
227     assert (params.targets != NULL);
228 rakinar2 23
229 rakinar2 32 if (params.verbose)
230     pinfo ("creating archive: %s\n", params.file);
231 rakinar2 23
232 rakinar2 32 struct uar_archive *uar = uar_create_stream ();
233 rakinar2 23
234 rakinar2 32 if (uar == NULL)
235 rakinar2 23 {
236     pinfo ("failed to create archive: %s\n", strerror (errno));
237     return;
238     }
239    
240 rakinar2 32 for (size_t i = 0; i < params.ntargets; i++)
241 rakinar2 23 {
242     struct stat stinfo = { 0 };
243    
244 rakinar2 32 if (stat (params.targets[i], &stinfo) != 0)
245 rakinar2 23 {
246 rakinar2 32 perr ("cannot stat '%s': %s\n", params.targets[i],
247     strerror (errno));
248 rakinar2 23 uar_close (uar);
249     return;
250     }
251    
252 rakinar2 32 struct uar_file *file = uar_stream_add_entry (
253     uar, basename (params.rtargets[i]), params.rtargets[i], &stinfo,
254     &create_archive_callback);
255 rakinar2 23
256 rakinar2 32 if (file == NULL || uar_has_error (uar))
257 rakinar2 23 {
258 rakinar2 32 const char *error_file = uar_get_error_file (uar);
259     perr ("failed to add '%s': %s\n",
260     error_file == NULL ? params.targets[i] : error_file,
261     uar_strerror (uar));
262     exit (1);
263 rakinar2 23 }
264     }
265    
266 rakinar2 32 if (!uar_stream_write (uar, params.file))
267 rakinar2 23 {
268 rakinar2 32 const char *error_file = uar_get_error_file (uar);
269     pinfo ("failed to write archive: %s%s%s\n",
270     error_file == NULL ? "" : error_file,
271     error_file == NULL ? "" : ": ", uar_strerror (uar));
272 rakinar2 23 return;
273     }
274    
275     #ifdef UAR_PRINT_VERBOSE_IMPL_INFO
276     uar_debug_print (uar, false);
277     #endif
278     uar_close (uar);
279     }
280    
281     /* Archive extraction callback. */
282     static bool
283     extract_archive_callback (struct uar_file *file)
284     {
285 rakinar2 32 pinfo ("extracting: %s\n", uar_file_get_name (file));
286 rakinar2 23 return true;
287     }
288    
289     /* Extract an archive. */
290     static void
291 rakinar2 32 extract_archive (void)
292 rakinar2 23 {
293     assert (params.mode == MODE_EXTRACT);
294    
295     pinfo ("extracting archive: %s\n", params.file);
296    
297     struct uar_archive *uar = uar_open (params.file);
298    
299     if (uar == NULL || uar_has_error (uar))
300     {
301     pinfo ("failed to open archive: %s\n", strerror (errno));
302     return;
303     }
304    
305     #ifdef UAR_PRINT_VERBOSE_IMPL_INFO
306     uar_debug_print (uar, false);
307     #endif
308    
309     if (!uar_extract (uar, params.cwd, &extract_archive_callback))
310     {
311     pinfo ("failed to extract archive: %s\n", strerror (errno));
312     return;
313     }
314    
315     uar_close (uar);
316     }
317    
318 rakinar2 32 static const char *
319     stringify_mode (mode_t mode)
320     {
321     static char str[11];
322    
323     str[0] = S_ISDIR (mode) ? 'd' : S_ISLNK (mode) ? 'l' : '-';
324    
325     for (int i = 1; i < 10; i++)
326     str[i] = mode & (1 << (9 - i)) ? "rwxrwxrwx"[i - 1] : '-';
327    
328     return str;
329     }
330    
331     static int
332     count_dec_numlen (uint64_t num)
333     {
334     int len = 0;
335    
336     do
337     {
338     num /= 10;
339     len++;
340     }
341     while (num > 0);
342    
343     return len;
344     }
345    
346     static char *
347     format_iec_size (uint64_t size)
348     {
349     static char buf[32] = { 0 };
350     const char suffix[] = { ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
351     size_t i = 0;
352     long double computed = size;
353    
354     while (computed > 1024 && i < sizeof (suffix) / sizeof (suffix[0]))
355     {
356     computed /= 1024;
357     i++;
358     }
359    
360     snprintf (buf, sizeof (buf), "%.02Lf%c", computed, suffix[i]);
361     return buf;
362     }
363    
364     struct archive_file_info
365     {
366     mode_t mode;
367     const char *name;
368     time_t mtime;
369     union
370     {
371    
372     uint64_t bytes;
373     char str[64];
374     } size;
375     };
376    
377     struct archive_file_table
378     {
379     struct archive_file_info *files;
380     size_t nfiles;
381     int widths[1];
382     };
383    
384     #define TABLE_WIDTH_SIZE 1
385    
386     static bool
387     list_archive_callback_analyze (struct uar_file *file, void *data)
388     {
389     struct archive_file_table *table = (struct archive_file_table *) data;
390     struct archive_file_info info = { 0 };
391    
392     mode_t mode = uar_file_get_mode (file);
393     const char *name = uar_file_get_name (file);
394     uint64_t size = uar_file_get_size (file);
395     int size_len = 0;
396    
397     info.mode = mode;
398     info.name = name;
399     info.mtime = uar_file_get_mtime (file);
400    
401     if (params.hr_sizes)
402     {
403     char *str = format_iec_size (size);
404     size_len = strlen (str);
405     strncpy (info.size.str, str, 32);
406     }
407     else
408     {
409     info.size.bytes = size;
410     size_len = count_dec_numlen (size);
411     }
412    
413     if (size_len > table->widths[TABLE_WIDTH_SIZE])
414     table->widths[TABLE_WIDTH_SIZE] = size_len;
415    
416     table->files[table->nfiles++] = info;
417     return true;
418     }
419    
420     static void
421     list_archive (void)
422     {
423     assert (params.mode == MODE_LIST);
424     struct archive_file_table *table = NULL;
425     struct uar_archive *uar = uar_open (params.file);
426    
427     if (uar == NULL || uar_has_error (uar))
428     {
429     pinfo ("failed to open archive: %s\n", strerror (errno));
430     goto list_archive_end;
431     }
432    
433     uint64_t nfiles = uar_get_file_count (uar);
434    
435     table = xcalloc (1, sizeof (struct archive_file_table));
436     table->files = xcalloc (nfiles, sizeof (struct archive_file_info));
437     table->nfiles = 0;
438    
439     if (!uar_iterate (uar, &list_archive_callback_analyze, (void *) table))
440     {
441     pinfo ("failed to read archive: %s\n", strerror (errno));
442     goto list_archive_end;
443     }
444    
445     for (size_t i = 0; i < nfiles; i++)
446     {
447     struct archive_file_info info = table->files[i];
448     struct tm *tm = localtime (&info.mtime);
449     char mtime_str[10] = "none";
450     const char *mode_str = stringify_mode (info.mode);
451    
452     if (tm == NULL)
453     {
454     fprintf (stderr,
455     "%s: warning: failed to convert time: %s\n",
456     progname, strerror (errno));
457     }
458     else
459     {
460     strftime (mtime_str, sizeof (mtime_str), "%b %d", tm);
461     }
462    
463     if (params.hr_sizes)
464     {
465    
466     fprintf (stdout, "%s %*s %s %s\n", mode_str,
467     table->widths[TABLE_WIDTH_SIZE], info.size.str,
468     mtime_str, info.name);
469     }
470     else
471     {
472    
473     fprintf (stdout, "%s %*lu %s %s\n", mode_str,
474     table->widths[TABLE_WIDTH_SIZE], info.size.bytes,
475     mtime_str, info.name);
476     }
477     }
478    
479     list_archive_end:
480     free (table->files);
481     free (table);
482     uar_close (uar);
483     }
484    
485 rakinar2 23 int
486     main (int argc, char **argv)
487     {
488     initialize (argv[0]);
489     assert (progname != NULL && "progname is NULL");
490    
491     while (true)
492     {
493     int opt
494     = getopt_long (argc, argv, short_options, long_options, NULL);
495    
496     if (opt == -1)
497     break;
498    
499 rakinar2 32 if ((opt == 'c' || opt == 'x' || opt == 't')
500     && params.mode != MODE_NONE)
501     {
502     perr ("only one mode can be specified\n");
503     exit (1);
504     }
505    
506 rakinar2 23 switch (opt)
507     {
508     case 'c':
509     params.mode = MODE_CREATE;
510     break;
511    
512     case 'x':
513     params.mode = MODE_EXTRACT;
514     break;
515    
516     case 't':
517     params.mode = MODE_LIST;
518     break;
519    
520     case 'v':
521     params.verbose = true;
522     debug ("Verbose mode enabled\n", progname);
523     break;
524    
525 rakinar2 32 case 'm':
526     params.hr_sizes = true;
527     break;
528    
529 rakinar2 23 case 'f':
530     params.file = optarg;
531     break;
532    
533     case 'C':
534     params.cwd = optarg;
535     break;
536    
537     case 'h':
538     usage ();
539     exit (0);
540    
541     case 'V':
542     show_version ();
543     exit (0);
544    
545     case '?':
546     default:
547 rakinar2 32 debug ("Unknown/Unhandled option: %c\n", opt);
548     bzero (&params, sizeof (params));
549 rakinar2 23 exit (1);
550     }
551     }
552    
553     if (params.file != NULL)
554     {
555     char *file = params.file;
556    
557     if ((params.mode == MODE_EXTRACT || params.mode == MODE_LIST))
558     {
559     params.file = realpath (file, NULL);
560    
561     if (params.file == NULL)
562     {
563 rakinar2 25 perr ("failed to read '%s': %s\n", file,
564 rakinar2 23 strerror (errno));
565     exit (1);
566     }
567     }
568     else
569     {
570     params.file = strdup (file);
571     }
572     }
573    
574     if (params.cwd != NULL)
575     {
576 rakinar2 32 if (params.mode == MODE_LIST)
577     {
578     params.cwd = NULL;
579     perr ("option '-C' or '--directory' does not make sense in "
580     "list mode\n");
581     exit (1);
582     }
583    
584 rakinar2 23 char *dir = params.cwd;
585     params.cwd = realpath (dir, NULL);
586    
587     if (params.cwd == NULL)
588     {
589 rakinar2 25 perr ("failed to change working directory to '%s': %s\n",
590     dir, strerror (errno));
591 rakinar2 23 exit (1);
592     }
593     }
594    
595     if (params.verbose)
596     {
597     debug ("Summary of options:\n");
598     debug (" mode: %s\n", params.mode == MODE_CREATE ? "create"
599     : params.mode == MODE_EXTRACT ? "extract"
600     : "list");
601     debug (" verbose: %s\n", params.verbose ? "yes" : "no");
602     debug (" file: %s\n", params.file);
603     debug (" working directory: %s\n", params.cwd);
604     }
605    
606     switch (params.mode)
607     {
608     case MODE_CREATE:
609 rakinar2 32 if (params.file == NULL)
610     {
611     perr ("no archive file name specified\n");
612     exit (1);
613     }
614    
615 rakinar2 23 for (int i = optind; i < argc; i++)
616     {
617 rakinar2 32 char *path = realpath (argv[i], NULL);
618    
619     if (path == NULL)
620     {
621     perr ("failed to read '%s': %s\n", argv[i],
622     strerror (errno));
623     exit (1);
624     }
625    
626     params.targets
627     = xrealloc (params.targets,
628     (params.ntargets + 1) * sizeof (char *));
629     params.targets[params.ntargets] = path;
630     params.ntargets++;
631 rakinar2 23 }
632    
633 rakinar2 32 params.rtargets = argv + optind;
634    
635     if (params.ntargets == 0)
636     {
637     perr ("no files or directories specified\n");
638     exit (1);
639     }
640    
641 rakinar2 23 create_archive ();
642     break;
643    
644     case MODE_EXTRACT:
645 rakinar2 32 if (params.file == NULL)
646     {
647     perr ("no archive file specified\n");
648     exit (1);
649     }
650    
651 rakinar2 23 extract_archive ();
652     break;
653    
654     case MODE_LIST:
655 rakinar2 32 if (params.file == NULL)
656     {
657     perr ("no archive file specified\n");
658     exit (1);
659     }
660    
661     list_archive ();
662 rakinar2 23 break;
663    
664     default:
665     usage ();
666     exit (1);
667     }
668    
669     return 0;
670     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26