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

Contents of /trunk/uar/uar.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 23 - (show 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 #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