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

Contents of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 36 - (show annotations)
Thu Aug 8 19:13:26 2024 UTC (7 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 19999 byte(s)
feat(uar): better content listing; including owner and group info
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 <grp.h>
28 #include <libgen.h>
29 #include <limits.h>
30 #include <linux/limits.h>
31 #include <math.h>
32 #include <pwd.h>
33 #include <stdarg.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <sys/stat.h>
40 #include <time.h>
41 #include <unistd.h>
42
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 { "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 };
73
74 static char const short_options[] = "cxtvmf:C:hV";
75
76 /* Program name. */
77 static char *progname = NULL;
78
79 /* Flags for the command line options. */
80 enum uar_mode
81 {
82 MODE_NONE = 0,
83 MODE_CREATE,
84 MODE_EXTRACT,
85 MODE_LIST
86 };
87
88 struct uar_params
89 {
90 enum uar_mode mode;
91 bool verbose;
92 bool hr_sizes;
93 char *file;
94 char *cwd;
95 char **targets;
96 char **rtargets;
97 size_t ntargets;
98 };
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 printf (" -m, --human-readable Print human-readable sizes\n");
116 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 #ifndef NDEBUG
143 /* 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 #endif
157
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 for (size_t i = 0; i < params.ntargets; i++)
185 free (params.targets[i]);
186
187 if (params.targets != NULL)
188 free (params.targets);
189
190 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 progname = argv0;
203 }
204
205 /* Create archive callback. */
206 static bool
207 create_archive_callback (struct uar_archive *uar,
208 struct uar_file *file __attribute__ ((unused)),
209 const char *uar_name __attribute__ ((unused)),
210 const char *fs_name, enum uar_error_level level,
211 const char *message)
212 {
213 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
225 return true;
226 }
227
228 /* Create an archive. */
229 static void
230 create_archive (void)
231 {
232 assert (params.mode == MODE_CREATE);
233 assert (params.ntargets > 0);
234 assert (params.targets != NULL);
235
236 if (params.verbose)
237 pinfo ("creating archive: %s\n", params.file);
238
239 struct uar_archive *uar = uar_stream_create ();
240
241 if (uar == NULL)
242 {
243 pinfo ("failed to create archive: %s\n", strerror (errno));
244 return;
245 }
246
247 uar_set_create_callback (uar, &create_archive_callback);
248
249 for (size_t i = 0; i < params.ntargets; i++)
250 {
251 struct stat stinfo = { 0 };
252
253 if (stat (params.targets[i], &stinfo) != 0)
254 {
255 perr ("cannot stat '%s': %s\n", params.targets[i],
256 strerror (errno));
257 uar_close (uar);
258 return;
259 }
260
261 const char *base = basename (params.rtargets[i]);
262
263 if (strcmp (base, ".") == 0 || strcmp (base, "..") == 0)
264 base = basename (params.targets[i]);
265
266 struct uar_file *file
267 = uar_stream_add_entry (uar, base, params.rtargets[i], &stinfo);
268
269 if (file == NULL || uar_has_error (uar))
270 {
271 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 }
277 }
278
279 if (!uar_stream_write (uar, params.file))
280 {
281 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 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 extract_archive_callback (struct uar_file *file)
297 {
298 pinfo ("extracting: %s\n", uar_file_get_name (file));
299 return true;
300 }
301
302 /* Extract an archive. */
303 static void
304 extract_archive (void)
305 {
306 assert (params.mode == MODE_EXTRACT);
307
308 pinfo ("extracting archive: %s\n", params.file);
309
310 struct uar_archive *uar = uar_open (params.file);
311
312 if (uar == NULL || uar_has_error (uar))
313 {
314 pinfo ("failed to open archive: %s\n", strerror (errno));
315 return;
316 }
317
318 #ifdef UAR_PRINT_VERBOSE_IMPL_INFO
319 uar_debug_print (uar, false);
320 #endif
321
322 if (!uar_extract (uar, params.cwd, &extract_archive_callback))
323 {
324 pinfo ("failed to extract archive: %s\n", strerror (errno));
325 return;
326 }
327
328 uar_close (uar);
329 }
330
331 static const char *
332 stringify_mode (mode_t mode)
333 {
334 static char str[11];
335
336 str[0] = S_ISDIR (mode) ? 'd' : S_ISLNK (mode) ? 'l' : '-';
337
338 for (int i = 1; i < 10; i++)
339 str[i] = mode & (1 << (9 - i)) ? "rwxrwxrwx"[i - 1] : '-';
340
341 return str;
342 }
343
344 static int
345 count_dec_numlen (uint64_t num)
346 {
347 int len = 0;
348
349 do
350 {
351 num /= 10;
352 len++;
353 }
354 while (num > 0);
355
356 return len;
357 }
358
359 static char *
360 format_iec_size (uint64_t size)
361 {
362 static char buf[32] = { 0 };
363 const char suffix[] = { 0, 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
364 size_t i = 0;
365 long double computed = size;
366
367 while (computed > 1024 && i < sizeof (suffix) / sizeof (suffix[0]))
368 {
369 computed /= 1024;
370 i++;
371 }
372
373 if (computed == 0.00)
374 {
375 snprintf (buf, sizeof (buf), "0%c", suffix[i]);
376 return buf;
377 }
378
379 snprintf (buf, sizeof (buf), "%.2Lf%c", computed, suffix[i]);
380 return buf;
381 }
382
383 struct archive_file_info
384 {
385 const char *name;
386 char owner[128];
387 char group[128];
388 mode_t mode;
389 time_t mtime;
390 union
391 {
392
393 uint64_t bytes;
394 char str[64];
395 } size;
396 };
397
398 struct archive_file_table
399 {
400 struct archive_file_info *files;
401 size_t nfiles;
402 int widths[3];
403 };
404
405 #define TABLE_WIDTH_SIZE 0
406 #define TABLE_WIDTH_OWNER 1
407 #define TABLE_WIDTH_GROUP 2
408
409 static bool
410 list_archive_callback_analyze (struct uar_file *file, void *data)
411 {
412 struct archive_file_table *table = (struct archive_file_table *) data;
413 struct archive_file_info info = { 0 };
414
415 mode_t mode = uar_file_get_mode (file);
416 const char *name = uar_file_get_name (file);
417 uint64_t size = uar_file_get_size (file);
418 int size_len = 0;
419
420 info.mode = mode;
421 info.name = name;
422 info.mtime = uar_file_get_mtime (file);
423
424 uid_t uid = uar_file_get_uid (file);
425 gid_t gid = uar_file_get_gid (file);
426
427 struct passwd *pw = getpwuid (uid);
428 struct group *gr = getgrgid (gid);
429
430 if (pw == NULL)
431 {
432 if (params.verbose)
433 perr ("warning: failed to get user info (%i): %s\n", uid,
434 strerror (errno));
435
436 strncpy (info.owner, "unknown", sizeof (info.owner) - 1);
437 }
438 else
439 strncpy (info.owner, pw->pw_name, sizeof (info.owner) - 1);
440
441 if (gr == NULL)
442 {
443 if (params.verbose)
444 perr ("warning: failed to get group info (%i): %s\n", gid,
445 strerror (errno));
446
447 strncpy (info.group, "unknown", sizeof (info.group) - 1);
448 }
449 else
450 strncpy (info.group, gr->gr_name, sizeof (info.group) - 1);
451
452 if (params.hr_sizes)
453 {
454 char *str = format_iec_size (size);
455 size_len = strlen (str);
456 strncpy (info.size.str, str, 32);
457 }
458 else
459 {
460 info.size.bytes = size;
461 size_len = count_dec_numlen (size);
462 }
463
464 if (size_len > table->widths[TABLE_WIDTH_SIZE])
465 table->widths[TABLE_WIDTH_SIZE] = size_len;
466
467 int owner_len = strlen (info.owner);
468 int group_len = strlen (info.group);
469
470 if (owner_len > table->widths[TABLE_WIDTH_OWNER])
471 table->widths[TABLE_WIDTH_OWNER] = owner_len;
472
473 if (group_len > table->widths[TABLE_WIDTH_GROUP])
474 table->widths[TABLE_WIDTH_GROUP] = group_len;
475
476 table->files[table->nfiles++] = info;
477 return true;
478 }
479
480 static void
481 list_archive (void)
482 {
483 assert (params.mode == MODE_LIST);
484 struct archive_file_table *table = NULL;
485 struct uar_archive *uar = uar_stream_open (params.file);
486
487 if (uar == NULL)
488 {
489 pinfo ("failed to open archive file: %s\n", strerror (errno));
490 goto list_archive_end;
491 }
492
493 if (uar_has_error (uar))
494 {
495 pinfo ("failed to read archive: %s\n", uar_strerror (uar));
496 goto list_archive_end;
497 }
498
499 uint64_t nfiles = uar_get_file_count (uar);
500
501 table = xcalloc (1, sizeof (struct archive_file_table));
502 table->files = xcalloc (nfiles, sizeof (struct archive_file_info));
503 table->nfiles = 0;
504
505 if (!uar_iterate (uar, &list_archive_callback_analyze, (void *) table))
506 {
507 pinfo ("failed to read archive: %s\n", strerror (errno));
508 goto list_archive_end;
509 }
510
511 for (size_t i = 0; i < nfiles; i++)
512 {
513 struct archive_file_info info = table->files[i];
514 struct tm *tm = localtime (&info.mtime);
515 char mtime_str[10] = "never";
516 const char *mode_str = stringify_mode (info.mode);
517
518 if (tm == NULL)
519 {
520 if (params.verbose)
521 perr ("warning: failed to convert time: %s\n",
522 strerror (errno));
523 }
524 else
525 strftime (mtime_str, sizeof (mtime_str), "%b %d", tm);
526
527 if (params.hr_sizes)
528 fprintf (stdout, "%s %-*s %-*s %*s %s %s\n", mode_str,
529 table->widths[TABLE_WIDTH_OWNER], info.owner,
530 table->widths[TABLE_WIDTH_GROUP], info.group,
531 table->widths[TABLE_WIDTH_SIZE], info.size.str,
532 mtime_str, info.name);
533
534 else
535 fprintf (stdout, "%s %-*s %-*s %*lu %s %s\n", mode_str,
536 table->widths[TABLE_WIDTH_OWNER], info.owner,
537 table->widths[TABLE_WIDTH_GROUP], info.group,
538 table->widths[TABLE_WIDTH_SIZE], info.size.bytes,
539 mtime_str, info.name);
540 }
541
542 list_archive_end:
543 free (table->files);
544 free (table);
545 uar_close (uar);
546 }
547
548 int
549 main (int argc, char **argv)
550 {
551 initialize (argv[0]);
552 assert (progname != NULL && "progname is NULL");
553
554 while (true)
555 {
556 int opt
557 = getopt_long (argc, argv, short_options, long_options, NULL);
558
559 if (opt == -1)
560 break;
561
562 if ((opt == 'c' || opt == 'x' || opt == 't')
563 && params.mode != MODE_NONE)
564 {
565 perr ("only one mode can be specified\n");
566 exit (1);
567 }
568
569 switch (opt)
570 {
571 case 'c':
572 params.mode = MODE_CREATE;
573 break;
574
575 case 'x':
576 params.mode = MODE_EXTRACT;
577 break;
578
579 case 't':
580 params.mode = MODE_LIST;
581 break;
582
583 case 'v':
584 params.verbose = true;
585 debug ("Verbose mode enabled\n", progname);
586 break;
587
588 case 'm':
589 params.hr_sizes = true;
590 break;
591
592 case 'f':
593 params.file = optarg;
594 break;
595
596 case 'C':
597 params.cwd = optarg;
598 break;
599
600 case 'h':
601 usage ();
602 exit (0);
603
604 case 'V':
605 show_version ();
606 exit (0);
607
608 case '?':
609 default:
610 debug ("Unknown/Unhandled option: %c\n", opt);
611 bzero (&params, sizeof (params));
612 exit (1);
613 }
614 }
615
616 if (params.file != NULL)
617 {
618 char *file = params.file;
619
620 if ((params.mode == MODE_EXTRACT || params.mode == MODE_LIST))
621 {
622 params.file = realpath (file, NULL);
623
624 if (params.file == NULL)
625 {
626 perr ("failed to read '%s': %s\n", file,
627 strerror (errno));
628 exit (1);
629 }
630 }
631 else
632 {
633 params.file = strdup (file);
634 }
635 }
636
637 if (params.cwd != NULL)
638 {
639 if (params.mode == MODE_LIST)
640 {
641 params.cwd = NULL;
642 perr ("option '-C' or '--directory' does not make sense in "
643 "list mode\n");
644 exit (1);
645 }
646
647 char *dir = params.cwd;
648 params.cwd = realpath (dir, NULL);
649
650 if (params.cwd == NULL)
651 {
652 perr ("failed to change working directory to '%s': %s\n",
653 dir, strerror (errno));
654 exit (1);
655 }
656 }
657
658 if (params.verbose)
659 {
660 debug ("Summary of options:\n");
661 debug (" mode: %s\n", params.mode == MODE_CREATE ? "create"
662 : params.mode == MODE_EXTRACT ? "extract"
663 : "list");
664 debug (" verbose: %s\n", params.verbose ? "yes" : "no");
665 debug (" file: %s\n", params.file);
666 debug (" working directory: %s\n", params.cwd);
667 }
668
669 switch (params.mode)
670 {
671 case MODE_CREATE:
672 if (params.file == NULL)
673 {
674 perr ("no archive file name specified\n");
675 exit (1);
676 }
677
678 for (int i = optind; i < argc; i++)
679 {
680 char *path = realpath (argv[i], NULL);
681
682 if (path == NULL)
683 {
684 perr ("failed to read '%s': %s\n", argv[i],
685 strerror (errno));
686 exit (1);
687 }
688
689 params.targets
690 = xrealloc (params.targets,
691 (params.ntargets + 1) * sizeof (char *));
692 params.targets[params.ntargets] = path;
693 params.ntargets++;
694 }
695
696 params.rtargets = argv + optind;
697
698 if (params.ntargets == 0)
699 {
700 perr ("no files or directories specified\n");
701 exit (1);
702 }
703
704 create_archive ();
705 break;
706
707 case MODE_EXTRACT:
708 if (params.file == NULL)
709 {
710 perr ("no archive file specified\n");
711 exit (1);
712 }
713
714 extract_archive ();
715 break;
716
717 case MODE_LIST:
718 if (params.file == NULL)
719 {
720 perr ("no archive file specified\n");
721 exit (1);
722 }
723
724 list_archive ();
725 break;
726
727 default:
728 usage ();
729 exit (1);
730 }
731
732 return 0;
733 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26