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

Contents of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 33 - (show 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 /*
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 progname = argv0;
200 }
201
202 /* Create archive callback. */
203 static bool
204 create_archive_callback (struct uar_archive *uar,
205 struct uar_file *file __attribute__ ((unused)),
206 const char *uar_name __attribute__ ((unused)),
207 const char *fs_name, enum uar_error_level level,
208 const char *message)
209 {
210 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
222 return true;
223 }
224
225 /* Create an archive. */
226 static void
227 create_archive (void)
228 {
229 assert (params.mode == MODE_CREATE);
230 assert (params.ntargets > 0);
231 assert (params.targets != NULL);
232
233 if (params.verbose)
234 pinfo ("creating archive: %s\n", params.file);
235
236 struct uar_archive *uar = uar_create_stream ();
237
238 if (uar == NULL)
239 {
240 pinfo ("failed to create archive: %s\n", strerror (errno));
241 return;
242 }
243
244 uar_set_create_callback (uar, &create_archive_callback);
245
246 for (size_t i = 0; i < params.ntargets; i++)
247 {
248 struct stat stinfo = { 0 };
249
250 if (stat (params.targets[i], &stinfo) != 0)
251 {
252 perr ("cannot stat '%s': %s\n", params.targets[i],
253 strerror (errno));
254 uar_close (uar);
255 return;
256 }
257
258 struct uar_file *file
259 = uar_stream_add_entry (uar, basename (params.rtargets[i]),
260 params.rtargets[i], &stinfo);
261
262 if (file == NULL || uar_has_error (uar))
263 {
264 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 }
270 }
271
272 if (!uar_stream_write (uar, params.file))
273 {
274 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 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 pinfo ("extracting: %s\n", uar_file_get_name (file));
292 return true;
293 }
294
295 /* Extract an archive. */
296 static void
297 extract_archive (void)
298 {
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 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 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 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 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 case 'm':
532 params.hr_sizes = true;
533 break;
534
535 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 debug ("Unknown/Unhandled option: %c\n", opt);
554 bzero (&params, sizeof (params));
555 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 perr ("failed to read '%s': %s\n", file,
570 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 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 char *dir = params.cwd;
591 params.cwd = realpath (dir, NULL);
592
593 if (params.cwd == NULL)
594 {
595 perr ("failed to change working directory to '%s': %s\n",
596 dir, strerror (errno));
597 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 if (params.file == NULL)
616 {
617 perr ("no archive file name specified\n");
618 exit (1);
619 }
620
621 for (int i = optind; i < argc; i++)
622 {
623 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 }
638
639 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 create_archive ();
648 break;
649
650 case MODE_EXTRACT:
651 if (params.file == NULL)
652 {
653 perr ("no archive file specified\n");
654 exit (1);
655 }
656
657 extract_archive ();
658 break;
659
660 case MODE_LIST:
661 if (params.file == NULL)
662 {
663 perr ("no archive file specified\n");
664 exit (1);
665 }
666
667 list_archive ();
668 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