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

Diff of /trunk/uar/main.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 30 by rakinar2, Tue Aug 6 14:24:41 2024 UTC revision 36 by rakinar2, Thu Aug 8 19:13:26 2024 UTC
# Line 24  Line 24 
24  #include <assert.h>  #include <assert.h>
25  #include <errno.h>  #include <errno.h>
26  #include <getopt.h>  #include <getopt.h>
27    #include <grp.h>
28  #include <libgen.h>  #include <libgen.h>
29    #include <limits.h>
30    #include <linux/limits.h>
31    #include <math.h>
32    #include <pwd.h>
33  #include <stdarg.h>  #include <stdarg.h>
34  #include <stdbool.h>  #include <stdbool.h>
35  #include <stdio.h>  #include <stdio.h>
36  #include <stdlib.h>  #include <stdlib.h>
37  #include <string.h>  #include <string.h>
38    #include <strings.h>
39  #include <sys/stat.h>  #include <sys/stat.h>
40    #include <time.h>
41    #include <unistd.h>
42    
43  #include "uar.h"  #include "uar.h"
44  #include "xmalloc.h"  #include "xmalloc.h"
# Line 51  Line 59 
59    
60  /* Command line options. */  /* Command line options. */
61  static struct option const long_options[] = {  static struct option const long_options[] = {
62      { "create",    no_argument,       NULL, 'c' },      { "create",         no_argument,       NULL, 'c' },
63      { "extract",   no_argument,       NULL, 'x' },      { "extract",        no_argument,       NULL, 'x' },
64      { "list",      no_argument,       NULL, 't' },      { "human-readable", no_argument,       NULL, 'm' },
65      { "verbose",   no_argument,       NULL, 'v' },      { "list",           no_argument,       NULL, 't' },
66      { "file",      required_argument, NULL, 'f' },      { "verbose",        no_argument,       NULL, 'v' },
67      { "directory", required_argument, NULL, 'C' },      { "file",           required_argument, NULL, 'f' },
68      { "help",      no_argument,       NULL, 'h' },      { "directory",      required_argument, NULL, 'C' },
69      { "version",   no_argument,       NULL, 'V' },      { "help",           no_argument,       NULL, 'h' },
70      { NULL,        0,                 NULL, 0   },      { "version",        no_argument,       NULL, 'V' },
71        { NULL,             0,                 NULL, 0   },
72  };  };
73    
74  static char const short_options[] = "cxtvf:C:hV";  static char const short_options[] = "cxtvmf:C:hV";
75    
76  /* Program name. */  /* Program name. */
77  static char *progname = NULL;  static char *progname = NULL;
# Line 70  static char *progname = NULL; Line 79  static char *progname = NULL;
79  /* Flags for the command line options. */  /* Flags for the command line options. */
80  enum uar_mode  enum uar_mode
81  {  {
82      MODE_NONE,      MODE_NONE = 0,
83      MODE_CREATE,      MODE_CREATE,
84      MODE_EXTRACT,      MODE_EXTRACT,
85      MODE_LIST      MODE_LIST
# Line 80  struct uar_params Line 89  struct uar_params
89  {  {
90      enum uar_mode mode;      enum uar_mode mode;
91      bool verbose;      bool verbose;
92        bool hr_sizes;
93      char *file;      char *file;
94      char *cwd;      char *cwd;
95      union      char **targets;
96      {      char **rtargets;
97          struct      size_t ntargets;
         {  
             char **targets;  
             size_t ntargets;  
         } create;  
     } params;  
98  };  };
99    
100  static struct uar_params params = { 0 };  static struct uar_params params = { 0 };
# Line 107  usage (void) Line 112  usage (void)
112      printf ("  -c, --create            Create a new archive\n");      printf ("  -c, --create            Create a new archive\n");
113      printf ("  -x, --extract           Extract files from an archive\n");      printf ("  -x, --extract           Extract files from an archive\n");
114      printf ("  -t, --list              List the contents of an archive\n");      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");      printf ("  -v, --verbose           Verbose mode\n");
117      printf (      printf (
118          "  -f, --file=ARCHIVE      Use archive file or directory ARCHIVE\n");          "  -f, --file=ARCHIVE      Use archive file or directory ARCHIVE\n");
# Line 175  perr (char const *format, ...) Line 181  perr (char const *format, ...)
181  static void  static void
182  cleanup ()  cleanup ()
183  {  {
184      if (params.params.create.targets != NULL)      for (size_t i = 0; i < params.ntargets; i++)
185          free (params.params.create.targets);          free (params.targets[i]);
186    
187        if (params.targets != NULL)
188            free (params.targets);
189    
190      if (params.cwd != NULL)      if (params.cwd != NULL)
191          free (params.cwd);          free (params.cwd);
# Line 190  static void Line 199  static void
199  initialize (char *argv0)  initialize (char *argv0)
200  {  {
201      atexit (&cleanup);      atexit (&cleanup);
202        progname = argv0;
     progname = strrchr (argv0, '/');  
   
     if (progname != NULL)  
         progname++;  
     else  
         progname = argv0;  
203  }  }
204    
205    /* Create archive callback. */
206  static bool  static bool
207  create_archive_callback (struct uar_file *file,  create_archive_callback (struct uar_archive *uar,
208                           const char *fullname __attribute__ ((unused)),                           struct uar_file *file __attribute__ ((unused)),
209                           const char *fullpath __attribute__ ((unused)))                           const char *uar_name __attribute__ ((unused)),
210  {                           const char *fs_name, enum uar_error_level level,
211      enum uar_file_type type = uar_get_entry_type (file);                           const char *message)
212      pinfo ("adding %s: %s\n",  {
213             type == UF_FILE  ? "file"      if (level == UAR_ELEVEL_NONE)
214             : type == UF_DIR ? "directory"          {
215                              : "link",              if (params.verbose)
216             uar_get_file_name (file));                  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;      return true;
226  }  }
227    
# Line 218  static void Line 230  static void
230  create_archive (void)  create_archive (void)
231  {  {
232      assert (params.mode == MODE_CREATE);      assert (params.mode == MODE_CREATE);
233      assert (params.params.create.ntargets > 0);      assert (params.ntargets > 0);
234      assert (params.params.create.targets != NULL);      assert (params.targets != NULL);
235    
236      pinfo ("creating archive: %s\n", params.file);      if (params.verbose)
237            pinfo ("creating archive: %s\n", params.file);
238    
239      struct uar_archive *uar = uar_create ();      struct uar_archive *uar = uar_stream_create ();
240    
241      if (uar == NULL || uar_has_error (uar))      if (uar == NULL)
242          {          {
243              pinfo ("failed to create archive: %s\n", strerror (errno));              pinfo ("failed to create archive: %s\n", strerror (errno));
244              return;              return;
245          }          }
246    
247      for (size_t i = 0; i < params.params.create.ntargets; i++)      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 };              struct stat stinfo = { 0 };
252    
253              if (stat (params.params.create.targets[i], &stinfo) != 0)              if (stat (params.targets[i], &stinfo) != 0)
254                  {                  {
255                      perr ("cannot stat '%s': %s\n",                      perr ("cannot stat '%s': %s\n", params.targets[i],
256                            params.params.create.targets[i], strerror (errno));                            strerror (errno));
257                      uar_close (uar);                      uar_close (uar);
258                      return;                      return;
259                  }                  }
260    
261              struct uar_file *file = NULL;              const char *base = basename (params.rtargets[i]);
262    
263              if (S_ISREG (stinfo.st_mode))              if (strcmp (base, ".") == 0 || strcmp (base, "..") == 0)
264                  {                  base = basename (params.targets[i]);
                     pinfo ("adding file: %s\n",  
                            params.params.create.targets[i]);  
                     file = uar_add_file (  
                         uar, basename (params.params.create.targets[i]),  
                         params.params.create.targets[i]);  
265    
266                      if (file == NULL)              struct uar_file *file
267                          {                  = uar_stream_add_entry (uar, base, params.rtargets[i], &stinfo);
                             perr ("failed to add file: %s\n", strerror (errno));  
                             uar_close (uar);  
                             return;  
                         }  
                 }  
             else if (S_ISDIR (stinfo.st_mode))  
                 {  
                     file = uar_add_dir (  
                         uar, basename (params.params.create.targets[i]),  
                         params.params.create.targets[i],  
                         &create_archive_callback);  
268    
269                      if (file == NULL)              if (file == NULL || uar_has_error (uar))
                         {  
                             perr ("failed to add directory: %s (%s)\n",  
                                   strerror (errno), uar_strerror (uar));  
                             uar_close (uar);  
                             return;  
                         }  
                 }  
             else if (S_ISLNK (stinfo.st_mode))  
                 {  
                     assert (false && "Not implemented");  
                 }  
             else  
270                  {                  {
271                      perr ("failed to add file: %s: file type not supported\n",                      const char *error_file = uar_get_error_file (uar);
272                            params.params.create.targets[i]);                      perr ("failed to add '%s': %s\n",
273                      uar_close (uar);                            error_file == NULL ? params.targets[i] : error_file,
274                      return;                            uar_strerror (uar));
275                        exit (1);
276                  }                  }
   
             assert (file != NULL);  
             uar_file_set_mode (file, stinfo.st_mode & 07777);  
277          }          }
278    
279      pinfo ("writing archive: %s\n", params.file);      if (!uar_stream_write (uar, params.file))
   
     if (!uar_write (uar, params.file))  
280          {          {
281              perr ("failed to write archive: %s\n", strerror (errno));              const char *error_file = uar_get_error_file (uar);
282              uar_close (uar);              pinfo ("failed to write archive: %s%s%s\n",
283                       error_file == NULL ? "" : error_file,
284                       error_file == NULL ? "" : ": ", uar_strerror (uar));
285              return;              return;
286          }          }
287    
# Line 310  create_archive (void) Line 295  create_archive (void)
295  static bool  static bool
296  extract_archive_callback (struct uar_file *file)  extract_archive_callback (struct uar_file *file)
297  {  {
298      pinfo ("extracting: %s\n", uar_get_file_name (file));      pinfo ("extracting: %s\n", uar_file_get_name (file));
299      return true;      return true;
300  }  }
301    
302  /* Extract an archive. */  /* Extract an archive. */
303  static void  static void
304  extract_archive ()  extract_archive (void)
305  {  {
306      assert (params.mode == MODE_EXTRACT);      assert (params.mode == MODE_EXTRACT);
307    
# Line 343  extract_archive () Line 328  extract_archive ()
328      uar_close (uar);      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  int
549  main (int argc, char **argv)  main (int argc, char **argv)
550  {  {
# Line 357  main (int argc, char **argv) Line 559  main (int argc, char **argv)
559              if (opt == -1)              if (opt == -1)
560                  break;                  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)              switch (opt)
570                  {                  {
571                  case 'c':                  case 'c':
# Line 376  main (int argc, char **argv) Line 585  main (int argc, char **argv)
585                      debug ("Verbose mode enabled\n", progname);                      debug ("Verbose mode enabled\n", progname);
586                      break;                      break;
587    
588                    case 'm':
589                        params.hr_sizes = true;
590                        break;
591    
592                  case 'f':                  case 'f':
593                      params.file = optarg;                      params.file = optarg;
594                      break;                      break;
# Line 393  main (int argc, char **argv) Line 606  main (int argc, char **argv)
606                      exit (0);                      exit (0);
607    
608                  case '?':                  case '?':
                     usage ();  
                     exit (1);  
   
609                  default:                  default:
610                        debug ("Unknown/Unhandled option: %c\n", opt);
611                        bzero (&params, sizeof (params));
612                      exit (1);                      exit (1);
613                  }                  }
614          }          }
# Line 424  main (int argc, char **argv) Line 636  main (int argc, char **argv)
636    
637      if (params.cwd != NULL)      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;              char *dir = params.cwd;
648              params.cwd = realpath (dir, NULL);              params.cwd = realpath (dir, NULL);
649    
# Line 449  main (int argc, char **argv) Line 669  main (int argc, char **argv)
669      switch (params.mode)      switch (params.mode)
670          {          {
671          case MODE_CREATE:          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++)              for (int i = optind; i < argc; i++)
679                  {                  {
680                      params.params.create.targets = xrealloc (                      char *path = realpath (argv[i], NULL);
681                          params.params.create.targets,  
682                          (params.params.create.ntargets + 1) * sizeof (char *));                      if (path == NULL)
683                      params.params.create.targets[params.params.create.ntargets]                          {
684                          = argv[i];                              perr ("failed to read '%s': %s\n", argv[i],
685                      params.params.create.ntargets++;                                    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 ();              create_archive ();
705              break;              break;
706    
707          case MODE_EXTRACT:          case MODE_EXTRACT:
708                if (params.file == NULL)
709                    {
710                        perr ("no archive file specified\n");
711                        exit (1);
712                    }
713    
714              extract_archive ();              extract_archive ();
715              break;              break;
716    
717          case MODE_LIST:          case MODE_LIST:
718              assert (false && "Not implemented yet");              if (params.file == NULL)
719                    {
720                        perr ("no archive file specified\n");
721                        exit (1);
722                    }
723    
724                list_archive ();
725              break;              break;
726    
727          default:          default:

Legend:
Removed from v.30  
changed lines
  Added in v.36

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26