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

Annotation of /trunk/uar/uar.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 23 - (hide annotations)
Mon Aug 5 17:15:48 2024 UTC (7 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 18323 byte(s)
feat: add programs implementing the universal archive format
1 rakinar2 23 #define _XOPEN_SOURCE 500
2    
3     #include "uar.h"
4     #include "malloc.h"
5    
6     #include <assert.h>
7     #include <dirent.h>
8     #include <errno.h>
9     #include <fcntl.h>
10     #include <stdbool.h>
11     #include <stdint.h>
12     #include <stdio.h>
13     #include <stdlib.h>
14     #include <string.h>
15     #include <sys/stat.h>
16     #include <sys/types.h>
17     #include <unistd.h>
18    
19     #if defined(__linux__)
20     # include <linux/limits.h>
21     #elif defined(__APPLE__)
22     # include <sys/syslimits.h>
23     #else
24     # include <limits.h>
25     #endif
26    
27     const unsigned char UAR_MAGIC[] = { 0x99, 'U', 'A', 'R' };
28    
29     struct uar_header
30     {
31     uint8_t magic[4];
32     uint16_t version;
33     uint32_t flags;
34     uint64_t nfiles;
35     uint64_t size;
36     } __attribute__ ((packed));
37    
38     enum uar_file_type
39     {
40     UF_FILE,
41     UF_DIR,
42     UF_LINK,
43     };
44    
45     struct uar_file
46     {
47     enum uar_file_type type;
48     char *name;
49     uint64_t namelen;
50     uint32_t offset;
51     union
52     {
53     uint64_t size;
54     struct
55     {
56     char *loc;
57     uint64_t loclen;
58     } linkinfo;
59     } data;
60     mode_t mode;
61     };
62    
63     struct uar_archive
64     {
65     struct uar_header header;
66     struct uar_file **files;
67     enum uar_error ecode;
68     bool is_stream;
69     uint8_t *buffer;
70     };
71    
72     static void *
73     uar_set_error (struct uar_archive *uar, enum uar_error ecode)
74     {
75     uar->ecode = ecode;
76     return NULL;
77     }
78    
79     const char *
80     uar_strerror (const struct uar_archive *restrict uar)
81     {
82     switch (uar->ecode)
83     {
84     case UAR_SUCCESS:
85     return "success";
86     case UAR_INVALID_MAGIC:
87     return "invalid archive magic";
88     case UAR_INVALID_FILE:
89     return "invalid file";
90     case UAR_INVALID_PATH:
91     return "invalid path string";
92     case UAR_IO_ERROR:
93     return "archive I/O error";
94     case UAR_OUT_OF_MEMORY:
95     return "out of memory";
96     case UAR_INVALID_ARGUMENT:
97     return "invalid argument";
98     case UAR_INVALID_OPERATION:
99     return "invalid operation";
100     default:
101     return "unknown error";
102     }
103     }
104    
105     bool
106     uar_has_error (const struct uar_archive *restrict uar)
107     {
108     return uar->ecode != UAR_SUCCESS;
109     }
110    
111     struct uar_archive *
112     uar_open (const char *filename)
113     {
114     struct uar_archive *uar = NULL;
115     FILE *stream = NULL;
116     int cerrno;
117    
118     errno = 0;
119     uar = malloc (sizeof (struct uar_archive));
120    
121     if (uar == NULL)
122     return NULL;
123    
124     stream = fopen (filename, "rb");
125    
126     if (stream == NULL)
127     {
128     uar_set_error (uar, UAR_IO_ERROR);
129     goto uar_open_ret;
130     }
131    
132     fseek (stream, 0, SEEK_END);
133     size_t size = ftell (stream);
134     fseek (stream, 0, SEEK_SET);
135    
136     if (fread (&uar->header, sizeof (struct uar_header), 1, stream) != 1)
137     {
138     uar_set_error (uar, UAR_IO_ERROR);
139     goto uar_open_ret;
140     }
141    
142     if (memcmp (uar->header.magic, UAR_MAGIC, 4) != 0)
143     {
144     uar_set_error (uar, UAR_INVALID_MAGIC);
145     goto uar_open_ret;
146     }
147    
148     uint64_t filearr_size = uar->header.nfiles * sizeof (struct uar_file);
149    
150     if (filearr_size > size)
151     {
152     uar_set_error (uar, UAR_IO_ERROR);
153     goto uar_open_ret;
154     }
155    
156     if (uar->header.size > size)
157     {
158     uar_set_error (uar, UAR_IO_ERROR);
159     goto uar_open_ret;
160     }
161    
162     uar->files = calloc (uar->header.nfiles, sizeof (struct uar_file *));
163    
164     if (uar->files == NULL)
165     {
166     uar_set_error (uar, UAR_OUT_OF_MEMORY);
167     goto uar_open_ret;
168     }
169    
170     for (uint64_t i = 0; i < uar->header.nfiles; i++)
171     {
172     struct uar_file *file = malloc (sizeof (struct uar_file));
173    
174     if (file == NULL)
175     {
176     uar_set_error (uar, UAR_OUT_OF_MEMORY);
177     goto uar_open_ret;
178     }
179    
180     if (fread (file, sizeof (struct uar_file), 1, stream) != 1)
181     {
182     uar_set_error (uar, UAR_IO_ERROR);
183     goto uar_open_ret;
184     }
185    
186     if (file->namelen > PATH_MAX)
187     {
188     uar_set_error (uar, UAR_INVALID_PATH);
189     goto uar_open_ret;
190     }
191    
192     file->name = malloc (file->namelen);
193    
194     if (file->name == NULL)
195     {
196     uar_set_error (uar, UAR_OUT_OF_MEMORY);
197     goto uar_open_ret;
198     }
199    
200     if (fread (file->name, 1, file->namelen, stream) != file->namelen)
201     {
202     uar_set_error (uar, UAR_IO_ERROR);
203     goto uar_open_ret;
204     }
205    
206     uar->files[i] = file;
207     }
208    
209     uar->buffer = malloc (uar->header.size + 1);
210    
211     if (uar->buffer == NULL)
212     {
213     uar_set_error (uar, UAR_OUT_OF_MEMORY);
214     goto uar_open_ret;
215     }
216    
217     if (fread (uar->buffer, 1, uar->header.size, stream) != uar->header.size)
218     {
219     uar_set_error (uar, UAR_IO_ERROR);
220     goto uar_open_ret;
221     }
222    
223     uar_open_ret:
224     cerrno = errno;
225     fclose (stream);
226     errno = cerrno;
227     return uar;
228     }
229    
230     void
231     uar_close (struct uar_archive *uar)
232     {
233     if (uar == NULL)
234     return;
235    
236     free (uar->buffer);
237     free (uar->files);
238     free (uar);
239     }
240    
241     struct uar_archive *
242     uar_create ()
243     {
244     struct uar_archive *uar = malloc (sizeof (struct uar_archive));
245    
246     if (uar == NULL)
247     return NULL;
248    
249     uar->is_stream = false;
250     uar->buffer = NULL;
251     uar->header.size = sizeof (struct uar_header);
252     memcpy (uar->header.magic, UAR_MAGIC, 4);
253     uar->header.version = 1;
254     uar->header.flags = 0;
255     uar->header.nfiles = 0;
256    
257     return uar;
258     }
259    
260     bool
261     uar_add_file_entry (struct uar_archive *restrict uar, struct uar_file *file)
262     {
263     if (uar == NULL || file == NULL)
264     {
265     uar_set_error (uar, UAR_INVALID_ARGUMENT);
266     return false;
267     }
268    
269     uar->files = realloc (uar->files,
270     (uar->header.nfiles + 1) * sizeof (struct uar_file));
271    
272     if (uar->files == NULL)
273     {
274     uar_set_error (uar, UAR_OUT_OF_MEMORY);
275     return false;
276     }
277    
278     uar->files[uar->header.nfiles] = file;
279     uar->header.nfiles++;
280     return true;
281     }
282    
283     struct uar_file *
284     uar_file_create (const char *name, uint64_t namelen, uint64_t size,
285     uint32_t offset)
286     {
287     struct uar_file *file;
288     assert (namelen < PATH_MAX);
289    
290     file = malloc (sizeof (struct uar_file));
291    
292     if (file == NULL)
293     return NULL;
294    
295     file->type = UF_FILE;
296     file->mode = 0644;
297     file->name = malloc (namelen + 1);
298    
299     if (file->name == NULL)
300     {
301     free (file);
302     return NULL;
303     }
304    
305     file->name[namelen] = 0;
306     file->namelen = namelen;
307     file->data.size = size;
308     file->offset = offset;
309    
310     strncpy (file->name, name, namelen);
311     return file;
312     }
313    
314     void
315     uar_file_destroy (struct uar_file *file)
316     {
317     if (file == NULL)
318     return;
319    
320     free (file->name);
321     free (file);
322     }
323    
324     struct uar_file *
325     uar_add_file (struct uar_archive *restrict uar, const char *name,
326     const char *path)
327     {
328     assert (uar != NULL && "uar is NULL");
329     assert (name != NULL && "name is NULL");
330     assert (path != NULL && "path is NULL");
331     assert (!uar->is_stream && "uar in non-stream mode is not supported yet");
332    
333     uint64_t namelen = strlen (name);
334    
335     if (namelen >= PATH_MAX)
336     {
337     uar_set_error (uar, UAR_INVALID_PATH);
338     return NULL;
339     }
340    
341     FILE *stream = fopen (path, "rb");
342    
343     if (stream == NULL)
344     {
345     uar_set_error (uar, UAR_IO_ERROR);
346     return NULL;
347     }
348    
349     fseek (stream, 0, SEEK_END);
350     uint64_t size = ftell (stream);
351     fseek (stream, 0, SEEK_SET);
352    
353     struct uar_file *file
354     = uar_file_create (name, namelen, size, uar->header.size);
355    
356     if (file == NULL)
357     {
358     uar_set_error (uar, UAR_OUT_OF_MEMORY);
359     fclose (stream);
360     return NULL;
361     }
362    
363     uar->header.size += size;
364    
365     if (!uar_add_file_entry (uar, file))
366     {
367     uar_file_destroy (file);
368     fclose (stream);
369     return NULL;
370     }
371    
372     uar->buffer = realloc (uar->buffer, uar->header.size);
373    
374     if (uar->buffer == NULL)
375     {
376     uar_set_error (uar, UAR_OUT_OF_MEMORY);
377     fclose (stream);
378     return NULL;
379     }
380    
381     if (fread (uar->buffer + file->offset, size, 1, stream) != 1)
382     {
383     uar_set_error (uar, UAR_IO_ERROR);
384     fclose (stream);
385     return NULL;
386     }
387    
388     fclose (stream);
389     return file;
390     }
391    
392     static char *
393     path_concat (const char *p1, const char *p2, size_t len1, size_t len2)
394     {
395     char *path = malloc (len1 + len2 + 2);
396    
397     if (path == NULL)
398     return NULL;
399    
400     strncpy (path, p1, len1);
401     path[len1] = '/';
402     strncpy (path + len1 + 1, p2, len2);
403     path[len1 + len2 + 1] = 0;
404     return path;
405     }
406    
407     struct uar_file *
408     uar_add_dir (struct uar_archive *uar, const char *name, const char *path)
409     {
410     assert (uar != NULL && "uar is NULL");
411     assert (name != NULL && "name is NULL");
412     assert (path != NULL && "path is NULL");
413     assert (!uar->is_stream && "uar in non-stream mode is not supported yet");
414    
415     uint64_t namelen = strlen (name);
416    
417     if (namelen >= PATH_MAX)
418     {
419     uar_set_error (uar, UAR_INVALID_PATH);
420     return NULL;
421     }
422    
423     DIR *dir = opendir (path);
424     struct dirent *entry = NULL;
425    
426     if (dir == NULL)
427     {
428     uar_set_error (uar, UAR_INVALID_FILE);
429     return NULL;
430     }
431    
432     struct uar_file *dir_file
433     = uar_file_create (name, namelen, 0, uar->header.size);
434     uint64_t dir_size = 0;
435    
436     if (!uar_add_file_entry (uar, dir_file))
437     {
438     uar_file_destroy (dir_file);
439     return NULL;
440     }
441    
442     while ((entry = readdir (dir)) != NULL)
443     {
444     if (strcmp (entry->d_name, ".") == 0
445     || strcmp (entry->d_name, "..") == 0)
446     continue;
447    
448     struct stat stinfo = { 0 };
449    
450     if (256 + namelen >= PATH_MAX)
451     {
452     uar_set_error (uar, UAR_INVALID_PATH);
453     uar_file_destroy (dir_file);
454     closedir (dir);
455     return NULL;
456     }
457    
458     uint64_t dnamelen = strlen (entry->d_name);
459    
460     char *fullpath
461     = path_concat (path, entry->d_name, strlen (path), dnamelen);
462     assert (fullpath != NULL);
463    
464     char *fullname
465     = path_concat (name, entry->d_name, namelen, dnamelen);
466     assert (fullname != NULL);
467    
468     if (stat (fullpath, &stinfo) != 0)
469     {
470     int current_errno = errno;
471     uar_set_error (uar, UAR_IO_ERROR);
472     uar_file_destroy (dir_file);
473     closedir (dir);
474     free (fullpath);
475     free (fullname);
476     errno = current_errno;
477     return NULL;
478     }
479    
480     if (S_ISREG (stinfo.st_mode))
481     {
482     struct uar_file *file
483     = uar_add_file (uar, fullname, fullpath);
484    
485     if (file == NULL)
486     {
487     int current_errno = errno;
488     uar_file_destroy (dir_file);
489     closedir (dir);
490     free (fullpath);
491     free (fullname);
492     errno = current_errno;
493     return NULL;
494     }
495    
496     file->mode = stinfo.st_mode & 07777;
497     dir_size += file->data.size;
498     }
499     else if (S_ISDIR (stinfo.st_mode))
500     {
501     struct uar_file *direntry
502     = uar_add_dir (uar, fullname, fullpath);
503    
504     if (direntry == NULL)
505     {
506     int current_errno = errno;
507     uar_file_destroy (dir_file);
508     closedir (dir);
509     free (fullpath);
510     free (fullname);
511     errno = current_errno;
512     return NULL;
513     }
514    
515     direntry->mode = stinfo.st_mode & 07777;
516     dir_size += direntry->data.size;
517     }
518     else
519     assert (false && "Not supported");
520    
521     free (fullpath);
522     }
523    
524     closedir (dir);
525    
526     dir_file->type = UF_DIR;
527     dir_file->data.size = dir_size;
528    
529     return dir_file;
530     }
531    
532     void
533     uar_file_set_mode (struct uar_file *file, mode_t mode)
534     {
535     file->mode = mode;
536     }
537    
538     static void
539     uar_debug_print_file (const struct uar_archive *uar, struct uar_file *file,
540     bool print_contents)
541     {
542     printf (" size: %lu\n", file->data.size);
543    
544     if (print_contents)
545     {
546     printf (" contents:\n");
547     printf ("==================\n");
548     fflush (stdout);
549    
550     ssize_t size = write (STDOUT_FILENO, uar->buffer + file->offset,
551     file->data.size);
552    
553     if (size == -1 || ((uint64_t) size) != file->data.size)
554     {
555     perror ("write");
556     return;
557     }
558    
559     putchar ('\n');
560     printf ("==================\n");
561     }
562     }
563    
564     void
565     uar_debug_print (const struct uar_archive *uar, bool print_file_contents)
566     {
567     printf ("uar_archive:\n");
568     printf (" magic: %02x %02x %02x %02x\n", uar->header.magic[0],
569     uar->header.magic[1], uar->header.magic[2], uar->header.magic[3]);
570     printf (" version: %u\n", uar->header.version);
571     printf (" flags: %u\n", uar->header.flags);
572     printf (" nfiles: %lu\n", uar->header.nfiles);
573     printf (" size: %lu\n", uar->header.size);
574     printf (" stream?: %i\n", uar->is_stream);
575    
576     for (uint64_t i = 0; i < uar->header.nfiles; i++)
577     {
578     struct uar_file *file = uar->files[i];
579    
580     printf (" %s[%lu]:\n",
581     file->type == UF_FILE ? "file"
582     : file->type == UF_DIR ? "directory"
583     : "link",
584     i);
585     printf (" name: \033[1m%s%s\033[0m\n", file->name,
586     file->type == UF_DIR ? "/"
587     : file->type == UF_LINK ? "@"
588     : "");
589     printf (" offset: %u\n", file->offset);
590     printf (" mode: %04o\n", file->mode);
591    
592     switch (file->type)
593     {
594     case UF_FILE:
595     uar_debug_print_file (uar, file, print_file_contents);
596     break;
597    
598     case UF_DIR:
599     printf (" size: %lu\n", file->data.size);
600     break;
601    
602     default:
603     printf (" info: unknown file type\n");
604     break;
605     }
606     }
607     }
608    
609     bool
610     uar_write (struct uar_archive *uar, const char *filename)
611     {
612     FILE *stream = fopen (filename, "wb");
613    
614     if (stream == NULL)
615     {
616     uar_set_error (uar, UAR_IO_ERROR);
617     return false;
618     }
619    
620     if (fwrite (&uar->header, sizeof (struct uar_header), 1, stream) != 1)
621     {
622     uar_set_error (uar, UAR_IO_ERROR);
623     fclose (stream);
624     return false;
625     }
626    
627     for (uint64_t i = 0; i < uar->header.nfiles; i++)
628     {
629     struct uar_file *file = uar->files[i];
630    
631     if (fwrite (file, sizeof (struct uar_file), 1, stream) != 1)
632     {
633     uar_set_error (uar, UAR_IO_ERROR);
634     fclose (stream);
635     return false;
636     }
637    
638     if (fwrite (file->name, 1, file->namelen, stream) != file->namelen)
639     {
640     uar_set_error (uar, UAR_IO_ERROR);
641     fclose (stream);
642     return false;
643     }
644     }
645    
646     if (fwrite (uar->buffer, uar->header.size, 1, stream) != 1)
647     {
648     uar_set_error (uar, UAR_IO_ERROR);
649     fclose (stream);
650     return false;
651     }
652    
653     fclose (stream);
654     return true;
655     }
656    
657     const char *
658     uar_get_file_name (const struct uar_file *file)
659     {
660     return file->name;
661     }
662    
663     bool
664     uar_extract (struct uar_archive *uar, const char *cwd,
665     bool (*callback) (struct uar_file *file))
666     {
667     if (cwd != NULL && chdir (cwd) != 0)
668     {
669     uar_set_error (uar, UAR_SYSTEM_ERROR);
670     return false;
671     }
672    
673     for (uint64_t i = 0; i < uar->header.nfiles; i++)
674     {
675     struct uar_file *file = uar->files[i];
676    
677     if (callback != NULL && !callback (file))
678     return false;
679    
680     switch (file->type)
681     {
682     case UF_FILE:
683     {
684     FILE *stream = fopen (file->name, "wb");
685    
686     if (stream == NULL)
687     {
688     uar_set_error (uar, UAR_IO_ERROR);
689     return false;
690     }
691    
692     if (fwrite (uar->buffer + file->offset, 1,
693     file->data.size, stream)
694     != file->data.size)
695     {
696     uar_set_error (uar, UAR_IO_ERROR);
697     return false;
698     }
699    
700     fchmod (fileno (stream), file->mode);
701     fclose (stream);
702     }
703     break;
704    
705     case UF_DIR:
706     if (mkdir (file->name, file->mode) != 0)
707     {
708     uar_set_error (uar, UAR_SYSTEM_ERROR);
709     return false;
710     }
711    
712     break;
713    
714     default:
715     assert (false && "unknown file type");
716     return false;
717     }
718     }
719    
720     return true;
721     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26