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

Contents of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 32 - (show 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 /*
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 #include <limits.h>
29 #include <math.h>
30 #include <stdarg.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <strings.h>
36 #include <sys/stat.h>
37 #include <time.h>
38 #include <unistd.h>
39
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 { "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 };
70
71 static char const short_options[] = "cxtvmf:C:hV";
72
73 /* Program name. */
74 static char *progname = NULL;
75
76 /* Flags for the command line options. */
77 enum uar_mode
78 {
79 MODE_NONE = 0,
80 MODE_CREATE,
81 MODE_EXTRACT,
82 MODE_LIST
83 };
84
85 struct uar_params
86 {
87 enum uar_mode mode;
88 bool verbose;
89 bool hr_sizes;
90 char *file;
91 char *cwd;
92 char **targets;
93 char **rtargets;
94 size_t ntargets;
95 };
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 printf (" -m, --human-readable Print human-readable sizes\n");
113 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 #ifndef NDEBUG
140 /* 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 #endif
154
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 for (size_t i = 0; i < params.ntargets; i++)
182 free (params.targets[i]);
183
184 if (params.targets != NULL)
185 free (params.targets);
186
187 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 static bool __attribute__ ((unused))
209 create_archive_callback (struct uar_archive *uar __attribute__ ((unused)),
210 struct uar_file *file __attribute__ ((unused)),
211 const char *fullname __attribute__ ((unused)),
212 const char *fullpath)
213 {
214 if (!params.verbose)
215 return true;
216
217 fprintf (stdout, "%s\n", fullpath);
218 return true;
219 }
220
221 /* Create an archive. */
222 static void
223 create_archive (void)
224 {
225 assert (params.mode == MODE_CREATE);
226 assert (params.ntargets > 0);
227 assert (params.targets != NULL);
228
229 if (params.verbose)
230 pinfo ("creating archive: %s\n", params.file);
231
232 struct uar_archive *uar = uar_create_stream ();
233
234 if (uar == NULL)
235 {
236 pinfo ("failed to create archive: %s\n", strerror (errno));
237 return;
238 }
239
240 for (size_t i = 0; i < params.ntargets; i++)
241 {
242 struct stat stinfo = { 0 };
243
244 if (stat (params.targets[i], &stinfo) != 0)
245 {
246 perr ("cannot stat '%s': %s\n", params.targets[i],
247 strerror (errno));
248 uar_close (uar);
249 return;
250 }
251
252 struct uar_file *file = uar_stream_add_entry (
253 uar, basename (params.rtargets[i]), params.rtargets[i], &stinfo,
254 &create_archive_callback);
255
256 if (file == NULL || uar_has_error (uar))
257 {
258 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 }
264 }
265
266 if (!uar_stream_write (uar, params.file))
267 {
268 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 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 pinfo ("extracting: %s\n", uar_file_get_name (file));
286 return true;
287 }
288
289 /* Extract an archive. */
290 static void
291 extract_archive (void)
292 {
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 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 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 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 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 case 'm':
526 params.hr_sizes = true;
527 break;
528
529 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 debug ("Unknown/Unhandled option: %c\n", opt);
548 bzero (&params, sizeof (params));
549 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 perr ("failed to read '%s': %s\n", file,
564 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 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 char *dir = params.cwd;
585 params.cwd = realpath (dir, NULL);
586
587 if (params.cwd == NULL)
588 {
589 perr ("failed to change working directory to '%s': %s\n",
590 dir, strerror (errno));
591 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 if (params.file == NULL)
610 {
611 perr ("no archive file name specified\n");
612 exit (1);
613 }
614
615 for (int i = optind; i < argc; i++)
616 {
617 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 }
632
633 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 create_archive ();
642 break;
643
644 case MODE_EXTRACT:
645 if (params.file == NULL)
646 {
647 perr ("no archive file specified\n");
648 exit (1);
649 }
650
651 extract_archive ();
652 break;
653
654 case MODE_LIST:
655 if (params.file == NULL)
656 {
657 perr ("no archive file specified\n");
658 exit (1);
659 }
660
661 list_archive ();
662 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