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

Contents of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 57 - (show annotations)
Sat Sep 7 15:02:56 2024 UTC (6 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 20994 byte(s)
chore: update files

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_archive *uar,
297 struct uar_file *file __attribute__ ((unused)),
298 const char *uar_name __attribute__ ((unused)),
299 const char *fs_name, enum uar_error_level level,
300 const char *message)
301 {
302 if (level == UAR_ELEVEL_NONE)
303 {
304 if (params.verbose)
305 fprintf (stdout, "%s\n", fs_name);
306 }
307 else if (level == UAR_ELEVEL_WARNING)
308 perr ("warning: %s: %s\n", fs_name,
309 message != NULL ? message : uar_strerror (uar));
310 else if (level == UAR_ELEVEL_ERROR)
311 perr ("error: %s: %s\n", fs_name,
312 message != NULL ? message : uar_strerror (uar));
313
314 return true;
315 }
316
317 /* Extract an archive. */
318 static void
319 extract_archive (void)
320 {
321 assert (params.mode == MODE_EXTRACT);
322
323 pinfo ("extracting archive: %s\n", params.file);
324
325 struct uar_archive *uar = uar_stream_open (params.file);
326
327 if (uar == NULL || uar_has_error (uar))
328 {
329 pinfo ("failed to open archive: %s\n", strerror (errno));
330 return;
331 }
332
333 uar_set_extract_callback (uar, &extract_archive_callback);
334
335 if (params.cwd != NULL)
336 {
337 if (chdir (params.cwd) != 0)
338 {
339 pinfo ("failed to change working directory: %s\n",
340 strerror (errno));
341 return;
342 }
343 }
344
345 char *cwd = getcwd (NULL, 0);
346
347 if (!uar_stream_extract (uar, cwd))
348 {
349 pinfo ("failed to extract archive: %s\n", strerror (errno));
350 free (cwd);
351 return;
352 }
353
354 free (cwd);
355 uar_close (uar);
356 }
357
358 static const char *
359 stringify_mode (mode_t mode)
360 {
361 static char str[11];
362
363 str[0] = S_ISDIR (mode) ? 'd' : S_ISLNK (mode) ? 'l' : '-';
364
365 for (int i = 1; i < 10; i++)
366 str[i] = mode & (1 << (9 - i)) ? "rwxrwxrwx"[i - 1] : '-';
367
368 return str;
369 }
370
371 static int
372 count_dec_numlen (uint64_t num)
373 {
374 int len = 0;
375
376 do
377 {
378 num /= 10;
379 len++;
380 }
381 while (num > 0);
382
383 return len;
384 }
385
386 static char *
387 format_iec_size (uint64_t size)
388 {
389 static char buf[32] = { 0 };
390 const char suffix[] = { 0, 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
391 size_t i = 0;
392 long double computed = size;
393
394 while (computed > 1024 && i < sizeof (suffix) / sizeof (suffix[0]))
395 {
396 computed /= 1024;
397 i++;
398 }
399
400 if (computed == 0.00)
401 {
402 snprintf (buf, sizeof (buf), "0%c", suffix[i]);
403 return buf;
404 }
405
406 snprintf (buf, sizeof (buf), "%.2Lf%c", computed, suffix[i]);
407 return buf;
408 }
409
410 struct archive_file_info
411 {
412 const char *name;
413 char owner[128];
414 char group[128];
415 mode_t mode;
416 time_t mtime;
417 union
418 {
419
420 uint64_t bytes;
421 char str[64];
422 } size;
423 };
424
425 struct archive_file_table
426 {
427 struct archive_file_info *files;
428 size_t nfiles;
429 int widths[3];
430 };
431
432 #define TABLE_WIDTH_SIZE 0
433 #define TABLE_WIDTH_OWNER 1
434 #define TABLE_WIDTH_GROUP 2
435
436 static bool
437 list_archive_callback_analyze (struct uar_file *file, void *data)
438 {
439 struct archive_file_table *table = (struct archive_file_table *) data;
440 struct archive_file_info info = { 0 };
441
442 mode_t mode = uar_file_get_mode (file);
443 const char *name = uar_file_get_name (file);
444 uint64_t size = uar_file_get_size (file);
445 int size_len = 0;
446
447 info.mode = mode;
448 info.name = name;
449 info.mtime = uar_file_get_mtime (file);
450
451 uid_t uid = uar_file_get_uid (file);
452 gid_t gid = uar_file_get_gid (file);
453
454 struct passwd *pw = getpwuid (uid);
455 struct group *gr = getgrgid (gid);
456
457 if (pw == NULL)
458 {
459 if (params.verbose)
460 perr ("warning: failed to get user info (%i): %s\n", uid,
461 strerror (errno));
462
463 strncpy (info.owner, "unknown", sizeof (info.owner) - 1);
464 }
465 else
466 strncpy (info.owner, pw->pw_name, sizeof (info.owner) - 1);
467
468 if (gr == NULL)
469 {
470 if (params.verbose)
471 perr ("warning: failed to get group info (%i): %s\n", gid,
472 strerror (errno));
473
474 strncpy (info.group, "unknown", sizeof (info.group) - 1);
475 }
476 else
477 strncpy (info.group, gr->gr_name, sizeof (info.group) - 1);
478
479 if (params.hr_sizes)
480 {
481 char *str = format_iec_size (size);
482 size_len = strlen (str);
483 strncpy (info.size.str, str, 32);
484 }
485 else
486 {
487 info.size.bytes = size;
488 size_len = count_dec_numlen (size);
489 }
490
491 if (size_len > table->widths[TABLE_WIDTH_SIZE])
492 table->widths[TABLE_WIDTH_SIZE] = size_len;
493
494 int owner_len = strlen (info.owner);
495 int group_len = strlen (info.group);
496
497 if (owner_len > table->widths[TABLE_WIDTH_OWNER])
498 table->widths[TABLE_WIDTH_OWNER] = owner_len;
499
500 if (group_len > table->widths[TABLE_WIDTH_GROUP])
501 table->widths[TABLE_WIDTH_GROUP] = group_len;
502
503 table->files[table->nfiles++] = info;
504 return true;
505 }
506
507 static void
508 list_archive (void)
509 {
510 assert (params.mode == MODE_LIST);
511 struct archive_file_table *table = NULL;
512 struct uar_archive *uar = uar_stream_open (params.file);
513
514 if (uar == NULL)
515 {
516 pinfo ("failed to open archive file: %s\n", strerror (errno));
517 goto list_archive_end;
518 }
519
520 if (uar_has_error (uar))
521 {
522 pinfo ("failed to read archive: %s\n", uar_strerror (uar));
523 goto list_archive_end;
524 }
525
526 uint64_t nfiles = uar_get_file_count (uar);
527
528 table = xcalloc (1, sizeof (struct archive_file_table));
529 table->files = xcalloc (nfiles, sizeof (struct archive_file_info));
530 table->nfiles = 0;
531
532 if (!uar_stream_iterate (uar, &list_archive_callback_analyze,
533 (void *) table))
534 {
535 pinfo ("failed to read archive: %s\n", strerror (errno));
536 goto list_archive_end;
537 }
538
539 for (size_t i = 0; i < nfiles; i++)
540 {
541 struct archive_file_info info = table->files[i];
542 struct tm *tm = localtime (&info.mtime);
543 char mtime_str[10] = "never";
544 const char *mode_str = stringify_mode (info.mode);
545
546 if (tm == NULL)
547 {
548 if (params.verbose)
549 perr ("warning: failed to convert time: %s\n",
550 strerror (errno));
551 }
552 else
553 strftime (mtime_str, sizeof (mtime_str), "%b %d", tm);
554
555 if (params.hr_sizes)
556 fprintf (stdout, "%s %-*s %-*s %*s %s %s\n", mode_str,
557 table->widths[TABLE_WIDTH_OWNER], info.owner,
558 table->widths[TABLE_WIDTH_GROUP], info.group,
559 table->widths[TABLE_WIDTH_SIZE], info.size.str,
560 mtime_str, info.name);
561
562 else
563 fprintf (stdout, "%s %-*s %-*s %*lu %s %s\n", mode_str,
564 table->widths[TABLE_WIDTH_OWNER], info.owner,
565 table->widths[TABLE_WIDTH_GROUP], info.group,
566 table->widths[TABLE_WIDTH_SIZE], info.size.bytes,
567 mtime_str, info.name);
568 }
569
570 list_archive_end:
571 free (table->files);
572 free (table);
573 uar_close (uar);
574 }
575
576 int
577 main (int argc, char **argv)
578 {
579 initialize (argv[0]);
580 assert (progname != NULL && "progname is NULL");
581
582 while (true)
583 {
584 int opt
585 = getopt_long (argc, argv, short_options, long_options, NULL);
586
587 if (opt == -1)
588 break;
589
590 if ((opt == 'c' || opt == 'x' || opt == 't')
591 && params.mode != MODE_NONE)
592 {
593 perr ("only one mode can be specified\n");
594 exit (1);
595 }
596
597 switch (opt)
598 {
599 case 'c':
600 params.mode = MODE_CREATE;
601 break;
602
603 case 'x':
604 params.mode = MODE_EXTRACT;
605 break;
606
607 case 't':
608 params.mode = MODE_LIST;
609 break;
610
611 case 'v':
612 params.verbose = true;
613 debug ("Verbose mode enabled\n", progname);
614 break;
615
616 case 'm':
617 params.hr_sizes = true;
618 break;
619
620 case 'f':
621 params.file = optarg;
622 break;
623
624 case 'C':
625 params.cwd = optarg;
626 break;
627
628 case 'h':
629 usage ();
630 exit (0);
631
632 case 'V':
633 show_version ();
634 exit (0);
635
636 case '?':
637 default:
638 debug ("Unknown/Unhandled option: %c\n", opt);
639 bzero (&params, sizeof (params));
640 exit (1);
641 }
642 }
643
644 if (params.file != NULL)
645 {
646 char *file = params.file;
647
648 if ((params.mode == MODE_EXTRACT || params.mode == MODE_LIST))
649 {
650 params.file = realpath (file, NULL);
651
652 if (params.file == NULL)
653 {
654 perr ("failed to read '%s': %s\n", file,
655 strerror (errno));
656 exit (1);
657 }
658 }
659 else
660 {
661 params.file = strdup (file);
662 }
663 }
664
665 if (params.cwd != NULL)
666 {
667 if (params.mode == MODE_LIST)
668 {
669 params.cwd = NULL;
670 perr ("option '-C' or '--directory' does not make sense in "
671 "list mode\n");
672 exit (1);
673 }
674
675 char *dir = params.cwd;
676 params.cwd = realpath (dir, NULL);
677
678 if (params.cwd == NULL)
679 {
680 perr ("failed to change working directory to '%s': %s\n",
681 dir, strerror (errno));
682 exit (1);
683 }
684 }
685
686 if (params.verbose)
687 {
688 debug ("Summary of options:\n");
689 debug (" mode: %s\n", params.mode == MODE_CREATE ? "create"
690 : params.mode == MODE_EXTRACT ? "extract"
691 : "list");
692 debug (" verbose: %s\n", params.verbose ? "yes" : "no");
693 debug (" file: %s\n", params.file);
694 debug (" working directory: %s\n", params.cwd);
695 }
696
697 switch (params.mode)
698 {
699 case MODE_CREATE:
700 if (params.file == NULL)
701 {
702 perr ("no archive file name specified\n");
703 exit (1);
704 }
705
706 for (int i = optind; i < argc; i++)
707 {
708 char *path = realpath (argv[i], NULL);
709
710 if (path == NULL)
711 {
712 perr ("failed to read '%s': %s\n", argv[i],
713 strerror (errno));
714 exit (1);
715 }
716
717 params.targets
718 = xrealloc (params.targets,
719 (params.ntargets + 1) * sizeof (char *));
720 params.targets[params.ntargets] = path;
721 params.ntargets++;
722 }
723
724 params.rtargets = argv + optind;
725
726 if (params.ntargets == 0)
727 {
728 perr ("no files or directories specified\n");
729 exit (1);
730 }
731
732 create_archive ();
733 break;
734
735 case MODE_EXTRACT:
736 if (params.file == NULL)
737 {
738 perr ("no archive file specified\n");
739 exit (1);
740 }
741
742 extract_archive ();
743 break;
744
745 case MODE_LIST:
746 if (params.file == NULL)
747 {
748 perr ("no archive file specified\n");
749 exit (1);
750 }
751
752 list_archive ();
753 break;
754
755 default:
756 usage ();
757 exit (1);
758 }
759
760 return 0;
761 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26