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

Contents of /trunk/uar/uar.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 32 - (show annotations)
Wed Aug 7 18:57:45 2024 UTC (7 months, 3 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 34170 byte(s)
refactor(uar): better I/O strategies to decrease memory usage
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 <strings.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <time.h>
19 #include <unistd.h>
20
21 #if defined(__linux__)
22 # include <linux/limits.h>
23 #elif defined(__APPLE__)
24 # include <sys/syslimits.h>
25 #else
26 # include <limits.h>
27 #endif
28
29 const unsigned char UAR_MAGIC[] = { 0x99, 'U', 'A', 'R' };
30
31 struct uar_header
32 {
33 uint8_t magic[4];
34 uint16_t version;
35 uint32_t flags;
36 uint64_t nfiles;
37 uint64_t size;
38 } __attribute__ ((packed));
39
40 struct uar_file
41 {
42 enum uar_file_type type;
43 int __pad1;
44 char *name;
45 uint64_t namelen;
46 uint64_t offset;
47 union
48 {
49 uint64_t size;
50 struct
51 {
52 char *loc;
53 uint64_t loclen;
54 } linkinfo;
55 } data;
56 mode_t mode;
57 time_t mtime;
58 int __pad2;
59 };
60
61 struct uar_archive
62 {
63 struct uar_header header;
64 struct uar_file **files;
65 struct uar_file *root;
66 enum uar_error ecode;
67 bool is_stream;
68 uint8_t *buffer; /* Deprecated */
69 FILE *stream;
70 int last_errno;
71 char *err_file;
72 };
73
74 static void
75 uar_set_error (struct uar_archive *uar, enum uar_error ecode,
76 const char *err_file)
77 {
78 uar->ecode = ecode;
79 uar->last_errno = errno;
80 free (uar->err_file);
81 uar->err_file = err_file == NULL ? NULL : strdup (err_file);
82 }
83
84 const char *
85 uar_strerror (const struct uar_archive *restrict uar)
86 {
87 switch (uar->ecode)
88 {
89 case UAR_SUCCESS:
90 return "success";
91 case UAR_INVALID_MAGIC:
92 return "invalid archive magic";
93 case UAR_INVALID_FILE:
94 return "invalid file";
95 case UAR_INVALID_PATH:
96 return "invalid path string";
97 case UAR_IO_ERROR:
98 return "archive I/O error";
99 case UAR_OUT_OF_MEMORY:
100 return "out of memory";
101 case UAR_INVALID_ARGUMENT:
102 return "invalid argument";
103 case UAR_INVALID_OPERATION:
104 return "invalid operation";
105 case UAR_SYSTEM_ERROR:
106 return "system error";
107 case UAR_SYSCALL_ERROR:
108 return strerror (uar->last_errno);
109 default:
110 return "unknown error";
111 }
112 }
113
114 bool
115 uar_has_error (const struct uar_archive *restrict uar)
116 {
117 return uar->ecode != UAR_SUCCESS;
118 }
119
120 /* TODO: Use static storage for path */
121 static char *
122 path_concat (const char *p1, const char *p2, size_t len1, size_t len2)
123 {
124 char *path = malloc (len1 + len2 + 2);
125
126 if (path == NULL)
127 return NULL;
128
129 strncpy (path, p1, len1);
130 path[len1] = '/';
131 strncpy (path + len1 + 1, p2, len2);
132 path[len1 + len2 + 1] = 0;
133 return path;
134 }
135
136 struct uar_archive *
137 uar_create (void)
138 {
139 struct uar_archive *uar = malloc (sizeof (struct uar_archive));
140
141 if (uar == NULL)
142 return NULL;
143
144 uar->is_stream = false;
145 uar->buffer = NULL;
146 uar->ecode = UAR_SUCCESS;
147 uar->header.size = 0;
148 memcpy (uar->header.magic, UAR_MAGIC, 4);
149 uar->header.version = 1;
150 uar->header.flags = 0;
151 uar->header.nfiles = 0;
152 uar->files = NULL;
153 uar->stream = NULL;
154 uar->root = NULL;
155 uar->last_errno = 0;
156 uar->err_file = NULL;
157
158 return uar;
159 }
160
161 static struct uar_file *
162 uar_initialize_root (struct uar_archive *uar)
163 {
164 struct uar_file *root = uar_file_create ("/", 1, 0, 0);
165
166 if (root == NULL)
167 {
168 uar_set_error (uar, UAR_OUT_OF_MEMORY, "/");
169 return NULL;
170 }
171
172 root->type = UF_DIR;
173 root->mode = S_IFDIR | 0755;
174 root->mtime = time (NULL);
175
176 if (!uar_add_file_entry (uar, root))
177 {
178 uar_file_destroy (root);
179 return NULL;
180 }
181
182 uar->root = root;
183
184 return root;
185 }
186
187 static bool
188 uar_initialize (struct uar_archive *uar)
189 {
190 if (uar_initialize_root (uar) == NULL)
191 return false;
192
193 return true;
194 }
195
196 bool
197 uar_stream_write (struct uar_archive *uar, const char *filename)
198 {
199 if (uar == NULL || !uar->is_stream || uar->stream == NULL)
200 return false;
201
202 FILE *stream = fopen (filename, "wb");
203
204 if (fwrite (&uar->header, 1, sizeof (struct uar_header), stream)
205 != sizeof (struct uar_header))
206 {
207 uar_set_error (uar, UAR_SYSCALL_ERROR, NULL);
208 fclose (stream);
209 return false;
210 }
211
212 for (uint64_t i = 0; i < uar->header.nfiles; i++)
213 {
214 struct uar_file *file = uar->files[i];
215
216 if (fwrite (file, 1, sizeof (struct uar_file), stream)
217 != sizeof (struct uar_file))
218 {
219 uar_set_error (uar, UAR_SYSCALL_ERROR, file->name);
220 fclose (stream);
221 return false;
222 }
223
224 if (fwrite (file->name, 1, file->namelen, stream) != file->namelen)
225 {
226 uar_set_error (uar, UAR_SYSCALL_ERROR, file->name);
227 fclose (stream);
228 return false;
229 }
230 }
231
232 uar->stream = freopen (NULL, "rb", uar->stream);
233
234 if (uar->stream == NULL)
235 {
236 uar_set_error (uar, UAR_SYSCALL_ERROR, NULL);
237 fclose (stream);
238 return false;
239 }
240
241 size_t buf_size
242 = uar->header.size >= (1024 * 1024) ? 1024 * 1024 : uar->header.size;
243 uint8_t *buf = malloc (buf_size);
244
245 if (buf == NULL)
246 {
247 uar_set_error (uar, UAR_OUT_OF_MEMORY, NULL);
248 fclose (stream);
249 return false;
250 }
251
252 uint64_t size = uar->header.size;
253
254 while (size > 0 && !feof (uar->stream))
255 {
256 if (size < buf_size)
257 buf_size = size;
258
259 if (fread (buf, 1, buf_size, uar->stream) != buf_size)
260 {
261 uar_set_error (uar, UAR_SYSCALL_ERROR, NULL);
262 fclose (stream);
263 free (buf);
264 return false;
265 }
266
267 if (fwrite (buf, 1, buf_size, stream) != buf_size)
268 {
269 uar_set_error (uar, UAR_SYSCALL_ERROR, NULL);
270 fclose (stream);
271 free (buf);
272 return false;
273 }
274
275 if (size < buf_size)
276 buf_size = size;
277 else
278 size -= buf_size;
279 }
280
281 fclose (stream);
282 return true;
283 }
284
285 struct uar_archive *
286 uar_create_stream (void)
287 {
288 struct uar_archive *uar = uar_create ();
289 int cerrno;
290
291 if (uar == NULL)
292 return NULL;
293
294 uar->is_stream = true;
295 uar->stream = tmpfile ();
296
297 if (uar->stream == NULL)
298 goto uar_create_stream_error;
299
300 if (!uar_initialize (uar))
301 goto uar_create_stream_error;
302
303 goto uar_create_stream_ret;
304
305 uar_create_stream_error:
306 cerrno = errno;
307 uar_close (uar);
308 errno = cerrno;
309 uar_create_stream_ret:
310 return uar;
311 }
312
313 struct uar_file *
314 uar_stream_add_file (struct uar_archive *uar, const char *uar_filename,
315 const char *fs_filename, struct stat *stinfo)
316 {
317 assert (uar != NULL && "uar is NULL");
318 assert (uar->is_stream && "uar is not in stream mode");
319 assert (uar_filename != NULL && "uar_filename is NULL");
320 assert (fs_filename != NULL && "fs_filename is NULL");
321
322 if (uar->root == NULL)
323 {
324 if (uar_initialize_root (uar) == NULL)
325 return NULL;
326 }
327
328 struct stat custom_stinfo = { 0 };
329 enum uar_error ecode = UAR_SUCCESS;
330 void *buffer = NULL;
331 struct uar_file *file = NULL;
332
333 if (stinfo == NULL)
334 {
335 if (lstat (fs_filename, &custom_stinfo) != 0)
336 {
337 uar_set_error (uar, UAR_SYSCALL_ERROR, fs_filename);
338 perror ("uar_stream_add_file::lstat");
339 return NULL;
340 }
341 else
342 stinfo = &custom_stinfo;
343 }
344
345 FILE *stream = fopen (fs_filename, "rb");
346
347 if (stream == NULL)
348 {
349 uar_set_error (uar, UAR_SYSCALL_ERROR, fs_filename);
350 perror ("uar_stream_add_file::fopen");
351 return NULL;
352 }
353
354 fseek (stream, 0, SEEK_END);
355 long size = ftell (stream);
356
357 if (size < 0)
358 {
359 ecode = UAR_SYSCALL_ERROR;
360 goto uar_stream_add_file_end;
361 }
362
363 fseek (stream, 0, SEEK_SET);
364
365 file = uar_file_create (uar_filename, 0, size, uar->header.size);
366
367 if (file == NULL)
368 {
369 ecode = UAR_SYSCALL_ERROR;
370 perror ("uar_stream_add_file::uar_file_create");
371 goto uar_stream_add_file_end;
372 }
373
374 file->mode = stinfo->st_mode;
375 file->data.size = size;
376 file->mtime = stinfo->st_mtime;
377 uar->header.size += size;
378 uar->root->data.size += size;
379
380 if (!uar_add_file_entry (uar, file))
381 {
382 perror ("uar_stream_add_file::uar_add_file_entry");
383 uar_file_destroy (file);
384 fclose (stream);
385 return NULL;
386 }
387
388 buffer = malloc (size);
389
390 if (buffer == NULL)
391 {
392 ecode = UAR_OUT_OF_MEMORY;
393 goto uar_stream_add_file_end;
394 }
395
396 if (size != 0 && fread (buffer, 1, size, stream) != (size_t) size)
397 {
398 ecode = UAR_SYSCALL_ERROR;
399 goto uar_stream_add_file_end;
400 }
401
402 if (fwrite (buffer, 1, size, uar->stream) != (size_t) size)
403 {
404 ecode = UAR_SYSCALL_ERROR;
405 goto uar_stream_add_file_end;
406 }
407
408 uar_stream_add_file_end:
409 if (ecode != UAR_SUCCESS && file != NULL)
410 uar_file_destroy (file);
411
412 if (buffer != NULL)
413 free (buffer);
414
415 uar_set_error (uar, ecode, fs_filename);
416 fclose (stream);
417 return ecode == UAR_SUCCESS ? file : NULL;
418 }
419
420 struct uar_file *
421 uar_stream_add_dir (struct uar_archive *uar, const char *uar_dirname,
422 const char *fs_dirname, struct stat *stinfo,
423 uar_callback_t callback)
424 {
425 struct stat custom_stinfo = { 0 };
426 enum uar_error ecode = UAR_SUCCESS;
427 struct uar_file *file = NULL;
428 uint64_t size = 0;
429 DIR *dir = NULL;
430
431 if (stinfo == NULL)
432 {
433 if (lstat (fs_dirname, &custom_stinfo) != 0)
434 {
435 uar_set_error (uar, UAR_SYSCALL_ERROR, fs_dirname);
436 return NULL;
437 }
438 else
439 stinfo = &custom_stinfo;
440 }
441
442 file = uar_file_create (uar_dirname, 0, 0, uar->header.size);
443
444 if (file == NULL)
445 {
446 ecode = UAR_OUT_OF_MEMORY;
447 goto uar_stream_add_dir_error;
448 }
449
450 file->type = UF_DIR;
451 file->mode = stinfo->st_mode;
452 file->mtime = stinfo->st_mtime;
453
454 if (!uar_add_file_entry (uar, file))
455 {
456 ecode = UAR_OUT_OF_MEMORY;
457 goto uar_stream_add_dir_error;
458 }
459
460 if (!callback (uar, file, uar_dirname, fs_dirname))
461 {
462 ecode = UAR_SUCCESS;
463 goto uar_stream_add_dir_error;
464 }
465
466 dir = opendir (fs_dirname);
467
468 if (dir == NULL)
469 {
470 ecode = UAR_SYSCALL_ERROR;
471 goto uar_stream_add_dir_error;
472 }
473
474 struct dirent *entry = NULL;
475
476 while ((entry = readdir (dir)) != NULL)
477 {
478 if (strcmp (entry->d_name, ".") == 0
479 || strcmp (entry->d_name, "..") == 0)
480 continue;
481
482 size_t dname_len = strlen (entry->d_name);
483 char *fs_fullpath = path_concat (fs_dirname, entry->d_name,
484 strlen (fs_dirname), dname_len);
485 char *uar_fullpath = path_concat (uar_dirname, entry->d_name,
486 strlen (uar_dirname), dname_len);
487
488 struct uar_file *entry_file = uar_stream_add_entry (
489 uar, uar_fullpath, fs_fullpath, NULL, callback);
490
491 if (entry_file == NULL)
492 {
493 ecode = UAR_SYSCALL_ERROR;
494 goto uar_stream_add_dir_ret;
495 }
496
497 size += entry_file->data.size;
498 free (fs_fullpath);
499 free (uar_fullpath);
500 }
501
502 file->data.size = size;
503
504 uar_stream_add_dir_error:
505 uar_set_error (uar, ecode, fs_dirname);
506 uar_stream_add_dir_ret:
507 if (dir != NULL)
508 closedir (dir);
509
510 if (ecode != UAR_SUCCESS && file != NULL)
511 uar_file_destroy (file);
512
513 return ecode == UAR_SUCCESS ? file : NULL;
514 }
515
516 struct uar_file *
517 uar_stream_add_link (struct uar_archive *uar, const char *uar_name,
518 const char *fs_name, struct stat *stinfo)
519 {
520 struct stat custom_stinfo = { 0 };
521 struct uar_file *file = NULL;
522
523 if (stinfo == NULL)
524 {
525 if (lstat (fs_name, &custom_stinfo) != 0)
526 {
527 uar_set_error (uar, UAR_SYSCALL_ERROR, fs_name);
528 return NULL;
529 }
530 else
531 stinfo = &custom_stinfo;
532 }
533
534 file = uar_file_create (uar_name, 0, 0, uar->header.size);
535
536 if (file == NULL)
537 {
538 uar_set_error (uar, UAR_OUT_OF_MEMORY, fs_name);
539 return NULL;
540 }
541
542 file->type = UF_LINK;
543 file->mode = stinfo->st_mode;
544 file->mtime = stinfo->st_mtime;
545
546 char link_buf[PATH_MAX] = { 0 };
547
548 ssize_t link_len = readlink (fs_name, link_buf, PATH_MAX);
549
550 if (link_len == -1)
551 {
552 uar_set_error (uar, UAR_SYSCALL_ERROR, fs_name);
553 uar_file_destroy (file);
554 return NULL;
555 }
556
557 file->data.linkinfo.loclen = link_len;
558 file->data.linkinfo.loc = malloc (link_len + 1);
559
560 if (file->data.linkinfo.loc == NULL)
561 {
562 uar_set_error (uar, UAR_OUT_OF_MEMORY, fs_name);
563 uar_file_destroy (file);
564 return NULL;
565 }
566
567 memcpy (file->data.linkinfo.loc, link_buf, link_len);
568 file->data.linkinfo.loc[link_len] = 0;
569
570 if (!uar_add_file_entry (uar, file))
571 {
572 uar_file_destroy (file);
573 return NULL;
574 }
575
576 return file;
577 }
578
579 struct uar_file *
580 uar_stream_add_entry (struct uar_archive *uar, const char *uar_name,
581 const char *fs_name, struct stat *stinfo,
582 uar_callback_t callback)
583 {
584 assert (uar != NULL && "uar is NULL");
585 assert (uar->is_stream && "uar is not in stream mode");
586 assert (uar_name != NULL && "uar_name is NULL");
587 assert (fs_name != NULL && "fs_name is NULL");
588
589 struct stat custom_stinfo = { 0 };
590 struct uar_file *file = NULL;
591
592 if (stinfo == NULL)
593 {
594 if (lstat (fs_name, &custom_stinfo) != 0)
595 {
596 uar_set_error (uar, UAR_SYSCALL_ERROR, fs_name);
597 return NULL;
598 }
599 else
600 stinfo = &custom_stinfo;
601 }
602
603 if (S_ISREG (stinfo->st_mode))
604 {
605 file = uar_stream_add_file (uar, uar_name, fs_name, stinfo);
606
607 if (file == NULL)
608 {
609 return NULL;
610 }
611
612 if (!callback (uar, file, uar_name, fs_name))
613 {
614 uar_set_error (uar, UAR_SUCCESS, fs_name);
615 return NULL;
616 }
617 }
618 else if (S_ISDIR (stinfo->st_mode))
619 {
620 file
621 = uar_stream_add_dir (uar, uar_name, fs_name, stinfo, callback);
622 }
623 else
624 {
625 file = uar_stream_add_link (uar, uar_name, fs_name, stinfo);
626 }
627
628 return file;
629 }
630
631 struct uar_archive *
632 uar_open (const char *filename)
633 {
634 struct uar_archive *uar = NULL;
635 FILE *stream = NULL;
636 int cerrno;
637
638 errno = 0;
639 uar = uar_create ();
640
641 if (uar == NULL)
642 return NULL;
643
644 stream = fopen (filename, "rb");
645
646 if (stream == NULL)
647 {
648 uar_set_error (uar, UAR_IO_ERROR, NULL);
649 goto uar_open_ret;
650 }
651
652 fseek (stream, 0, SEEK_END);
653 size_t size = ftell (stream);
654 fseek (stream, 0, SEEK_SET);
655
656 if (fread (&uar->header, sizeof (struct uar_header), 1, stream) != 1)
657 {
658 uar_set_error (uar, UAR_IO_ERROR, NULL);
659 goto uar_open_ret;
660 }
661
662 if (memcmp (uar->header.magic, UAR_MAGIC, 4) != 0)
663 {
664 uar_set_error (uar, UAR_INVALID_MAGIC, NULL);
665 goto uar_open_ret;
666 }
667
668 uint64_t filearr_size = uar->header.nfiles * sizeof (struct uar_file);
669
670 if (filearr_size > size)
671 {
672 uar_set_error (uar, UAR_IO_ERROR, NULL);
673 goto uar_open_ret;
674 }
675
676 if (uar->header.size > size)
677 {
678 uar_set_error (uar, UAR_IO_ERROR, NULL);
679 goto uar_open_ret;
680 }
681
682 uar->files = calloc (uar->header.nfiles, sizeof (struct uar_file *));
683
684 if (uar->files == NULL)
685 {
686 uar_set_error (uar, UAR_OUT_OF_MEMORY, NULL);
687 goto uar_open_ret;
688 }
689
690 for (uint64_t i = 0; i < uar->header.nfiles; i++)
691 {
692 struct uar_file *file = malloc (sizeof (struct uar_file));
693
694 if (file == NULL)
695 {
696 uar_set_error (uar, UAR_OUT_OF_MEMORY, NULL);
697 goto uar_open_ret;
698 }
699
700 if (fread (file, sizeof (struct uar_file), 1, stream) != 1)
701 {
702 uar_set_error (uar, UAR_IO_ERROR, NULL);
703 goto uar_open_ret;
704 }
705
706 if (file->namelen > PATH_MAX)
707 {
708 uar_set_error (uar, UAR_INVALID_PATH, NULL);
709 goto uar_open_ret;
710 }
711
712 file->name = malloc (file->namelen + 1);
713
714 if (file->name == NULL)
715 {
716 uar_set_error (uar, UAR_OUT_OF_MEMORY, NULL);
717 goto uar_open_ret;
718 }
719
720 if (fread (file->name, 1, file->namelen, stream) != file->namelen)
721 {
722 uar_set_error (uar, UAR_IO_ERROR, file->name);
723 goto uar_open_ret;
724 }
725
726 file->name[file->namelen] = 0;
727 uar->files[i] = file;
728 }
729
730 uar->buffer = malloc (uar->header.size + 1);
731
732 if (uar->buffer == NULL)
733 {
734 uar_set_error (uar, UAR_OUT_OF_MEMORY, NULL);
735 goto uar_open_ret;
736 }
737
738 if (fread (uar->buffer, 1, uar->header.size, stream) != uar->header.size)
739 {
740 uar_set_error (uar, UAR_IO_ERROR, NULL);
741 goto uar_open_ret;
742 }
743
744 uar_open_ret:
745 cerrno = errno;
746 fclose (stream);
747 errno = cerrno;
748 return uar;
749 }
750
751 void
752 uar_file_destroy (struct uar_file *file)
753 {
754 if (file == NULL)
755 return;
756
757 if (file->type == UF_LINK)
758 free (file->data.linkinfo.loc);
759
760 free (file->name);
761 free (file);
762 }
763
764 void
765 uar_close (struct uar_archive *uar)
766 {
767 if (uar == NULL)
768 return;
769
770 if (uar->is_stream)
771 fclose (uar->stream);
772 else
773 free (uar->buffer);
774
775 for (uint64_t i = 0; i < uar->header.nfiles; i++)
776 {
777 struct uar_file *file = uar->files[i];
778 uar_file_destroy (file);
779 }
780
781 free (uar->err_file);
782 free (uar->files);
783 free (uar);
784 }
785
786 bool
787 uar_add_file_entry (struct uar_archive *restrict uar, struct uar_file *file)
788 {
789 if (uar == NULL || file == NULL)
790 {
791 uar_set_error (uar, UAR_INVALID_ARGUMENT, NULL);
792 return false;
793 }
794
795 uar->files = realloc (uar->files,
796 (uar->header.nfiles + 1) * sizeof (struct uar_file));
797
798 if (uar->files == NULL)
799 {
800 uar_set_error (uar, UAR_OUT_OF_MEMORY, NULL);
801 return false;
802 }
803
804 uar->files[uar->header.nfiles] = file;
805 uar->header.nfiles++;
806
807 return true;
808 }
809
810 struct uar_file *
811 uar_file_create (const char *name, uint64_t namelen, uint64_t size,
812 uint32_t offset)
813 {
814 struct uar_file *file;
815 int cerrno;
816 bool abs = false;
817
818 if (namelen == 0)
819 namelen = strlen (name);
820
821 if (namelen >= PATH_MAX)
822 {
823 errno = ENAMETOOLONG;
824 return NULL;
825 }
826
827 abs = name[0] == '/';
828 namelen += (abs ? 0 : 1);
829
830 file = malloc (sizeof (struct uar_file));
831
832 if (file == NULL)
833 return NULL;
834
835 bzero (file, sizeof (struct uar_file));
836
837 file->type = UF_FILE;
838 file->mode = 0644;
839 file->mtime = 0;
840 file->name = malloc (namelen + 1);
841
842 if (file->name == NULL)
843 {
844 cerrno = errno;
845 free (file);
846 errno = cerrno;
847 return NULL;
848 }
849
850 if (!abs)
851 file->name[0] = '/';
852
853 strncpy (file->name + (abs ? 0 : 1), name, namelen);
854 file->name[namelen] = 0;
855 file->namelen = namelen;
856 file->data.size = size;
857 file->offset = offset;
858
859 return file;
860 }
861
862 struct uar_file *
863 uar_add_file (struct uar_archive *restrict uar, const char *name,
864 const char *path, struct stat *stinfo)
865 {
866 assert (uar != NULL && "uar is NULL");
867 assert (name != NULL && "name is NULL");
868 assert (path != NULL && "path is NULL");
869 assert (!uar->is_stream && "uar in non-stream mode is not supported yet");
870
871 uint64_t namelen = strlen (name);
872 struct stat *file_stinfo = stinfo, st_stinfo = { 0 };
873
874 if (namelen >= PATH_MAX)
875 {
876 uar_set_error (uar, UAR_INVALID_PATH, path);
877 return NULL;
878 }
879
880 if (file_stinfo == NULL)
881 {
882 if (lstat (path, &st_stinfo) != 0)
883 {
884 uar_set_error (uar, UAR_IO_ERROR, path);
885 return NULL;
886 }
887
888 file_stinfo = &st_stinfo;
889 }
890
891 FILE *stream = fopen (path, "rb");
892
893 if (stream == NULL)
894 {
895 uar_set_error (uar, UAR_IO_ERROR, path);
896 return NULL;
897 }
898
899 fseek (stream, 0, SEEK_END);
900 long size = ftell (stream);
901
902 if (size < 0)
903 {
904 uar_set_error (uar, UAR_IO_ERROR, path);
905 fclose (stream);
906 return NULL;
907 }
908
909 fseek (stream, 0, SEEK_SET);
910
911 struct uar_file *file
912 = uar_file_create (name, namelen, size, uar->header.size);
913
914 if (file == NULL)
915 {
916 uar_set_error (uar, UAR_OUT_OF_MEMORY, path);
917 fclose (stream);
918 return NULL;
919 }
920
921 file->mtime = file_stinfo->st_mtime;
922 uar->header.size += size;
923
924 if (!uar_add_file_entry (uar, file))
925 {
926 uar_file_destroy (file);
927 fclose (stream);
928 return NULL;
929 }
930
931 uar->buffer = realloc (uar->buffer, uar->header.size);
932
933 if (uar->buffer == NULL)
934 {
935 uar_set_error (uar, UAR_OUT_OF_MEMORY, path);
936 fclose (stream);
937 return NULL;
938 }
939
940 if (size != 0 && fread (uar->buffer + file->offset, size, 1, stream) != 1)
941 {
942 uar_set_error (uar, UAR_IO_ERROR, path);
943 fclose (stream);
944 return NULL;
945 }
946
947 fclose (stream);
948 return file;
949 }
950
951 struct uar_file *
952 uar_add_dir (struct uar_archive *uar, const char *dname, const char *path,
953 bool (*callback) (struct uar_file *file, const char *fullname,
954 const char *fullpath))
955 {
956 assert (uar != NULL && "uar is NULL");
957 assert (dname != NULL && "dname is NULL");
958 assert (path != NULL && "path is NULL");
959 assert (!uar->is_stream && "uar in non-stream mode is not supported yet");
960
961 char *name = (char *) dname;
962 bool free_name = false;
963 int cerrno;
964 uint64_t namelen;
965
966 if (strcmp (name, ".") == 0)
967 {
968 name = strdup ("/");
969 free_name = true;
970 namelen = 1;
971 }
972 else
973 namelen = strlen (name);
974
975 if (namelen >= PATH_MAX)
976 {
977 uar_set_error (uar, UAR_INVALID_PATH, path);
978 return NULL;
979 }
980
981 DIR *dir = opendir (path);
982 struct dirent *entry = NULL;
983
984 if (dir == NULL)
985 {
986 uar_set_error (uar, UAR_INVALID_FILE, path);
987 return NULL;
988 }
989
990 struct uar_file *dir_file
991 = uar_file_create (name, namelen, 0, uar->header.size);
992 uint64_t dir_size = 0;
993
994 dir_file->type = UF_DIR;
995
996 if (callback != NULL && !callback (dir_file, name, path))
997 {
998 uar_set_error (uar, UAR_SUCCESS, NULL);
999 uar_file_destroy (dir_file);
1000 return NULL;
1001 }
1002
1003 if (!uar_add_file_entry (uar, dir_file))
1004 {
1005 uar_file_destroy (dir_file);
1006 return NULL;
1007 }
1008
1009 while ((entry = readdir (dir)) != NULL)
1010 {
1011 if (strcmp (entry->d_name, ".") == 0
1012 || strcmp (entry->d_name, "..") == 0)
1013 continue;
1014
1015 struct stat stinfo = { 0 };
1016
1017 if (256 + namelen >= PATH_MAX)
1018 {
1019 uar_set_error (uar, UAR_INVALID_PATH, path);
1020 uar_file_destroy (dir_file);
1021 closedir (dir);
1022 return NULL;
1023 }
1024
1025 uint64_t dnamelen = strlen (entry->d_name);
1026
1027 char *fullpath
1028 = path_concat (path, entry->d_name, strlen (path), dnamelen);
1029 assert (fullpath != NULL);
1030
1031 char *fullname
1032 = path_concat (name, entry->d_name, namelen, dnamelen);
1033 assert (fullname != NULL);
1034
1035 if (lstat (fullpath, &stinfo) != 0)
1036 {
1037 uar_set_error (uar, UAR_IO_ERROR, fullpath);
1038 goto uar_add_dir_error;
1039 }
1040
1041 if (S_ISREG (stinfo.st_mode))
1042 {
1043 struct uar_file *file
1044 = uar_add_file (uar, fullname, fullpath, &stinfo);
1045
1046 if (file == NULL)
1047 {
1048 goto uar_add_dir_error;
1049 }
1050
1051 if (callback != NULL
1052 && !callback (file, fullname, fullpath))
1053 {
1054 uar_set_error (uar, UAR_SUCCESS, NULL);
1055 goto uar_add_dir_error;
1056 }
1057
1058 file->mode = stinfo.st_mode;
1059 dir_size += file->data.size;
1060 }
1061 else if (S_ISDIR (stinfo.st_mode))
1062 {
1063 struct uar_file *direntry
1064 = uar_add_dir (uar, fullname, fullpath, callback);
1065
1066 if (direntry == NULL)
1067 {
1068 goto uar_add_dir_error;
1069 }
1070
1071 direntry->mode = stinfo.st_mode;
1072 dir_size += direntry->data.size;
1073 }
1074 else
1075 assert (false && "Not supported");
1076
1077 free (fullpath);
1078 free (fullname);
1079
1080 continue;
1081
1082 uar_add_dir_error:
1083 cerrno = errno;
1084 uar_file_destroy (dir_file);
1085 free (fullpath);
1086 free (fullname);
1087 errno = cerrno;
1088 goto uar_add_dir_end;
1089 }
1090
1091 dir_file->data.size = dir_size;
1092
1093 uar_add_dir_end:
1094 cerrno = errno;
1095 closedir (dir);
1096
1097 if (free_name)
1098 free (name);
1099
1100 errno = cerrno;
1101 return dir_file;
1102 }
1103
1104 void
1105 uar_file_set_mode (struct uar_file *file, mode_t mode)
1106 {
1107 file->mode = mode;
1108 }
1109
1110 static void
1111 uar_debug_print_file_contents (const struct uar_archive *uar,
1112 struct uar_file *file)
1113 {
1114 printf (" contents:\n");
1115 printf ("==================\n");
1116 fflush (stdout);
1117
1118 ssize_t size
1119 = write (STDOUT_FILENO, uar->buffer + file->offset, file->data.size);
1120
1121 if (size == -1 || ((uint64_t) size) != file->data.size)
1122 {
1123 perror ("write");
1124 return;
1125 }
1126
1127 putchar ('\n');
1128 printf ("==================\n");
1129 }
1130
1131 void
1132 uar_debug_print (const struct uar_archive *uar, bool print_file_contents)
1133 {
1134 printf ("uar_archive:\n");
1135 printf (" magic: %02x %02x %02x %02x\n", uar->header.magic[0],
1136 uar->header.magic[1], uar->header.magic[2], uar->header.magic[3]);
1137 printf (" version: %u\n", uar->header.version);
1138 printf (" flags: %u\n", uar->header.flags);
1139 printf (" nfiles: %lu\n", uar->header.nfiles);
1140 printf (" size: %lu\n", uar->header.size);
1141 printf (" stream?: %i\n", uar->is_stream);
1142
1143 for (uint64_t i = 0; i < uar->header.nfiles; i++)
1144 {
1145 struct uar_file *file = uar->files[i];
1146
1147 printf (" %s[%lu]:\n",
1148 file->type == UF_FILE ? "file"
1149 : file->type == UF_DIR ? "directory"
1150 : "link",
1151 i);
1152 printf (" name: \033[1m%s%s\033[0m\n", uar_file_get_name (file),
1153 file->type == UF_DIR
1154 ? file->name[0] == '/' && file->namelen == 1 ? "" : "/"
1155 : file->type == UF_LINK ? "@"
1156 : "");
1157 printf (" offset: %lu\n", file->offset);
1158 printf (" mode: %04o\n", file->mode);
1159
1160 if (file->type == UF_LINK)
1161 printf (" points to: %s\n", file->data.linkinfo.loc);
1162 else
1163 printf (" size: %lu\n", file->data.size);
1164
1165 if (file->type == UF_FILE && print_file_contents)
1166 uar_debug_print_file_contents (uar, file);
1167 }
1168 }
1169
1170 bool
1171 uar_write (struct uar_archive *uar, const char *filename)
1172 {
1173 FILE *stream = fopen (filename, "wb");
1174
1175 if (stream == NULL)
1176 {
1177 uar_set_error (uar, UAR_IO_ERROR, filename);
1178 return false;
1179 }
1180
1181 if (fwrite (&uar->header, sizeof (struct uar_header), 1, stream) != 1)
1182 {
1183 uar_set_error (uar, UAR_IO_ERROR, filename);
1184 fclose (stream);
1185 return false;
1186 }
1187
1188 for (uint64_t i = 0; i < uar->header.nfiles; i++)
1189 {
1190 struct uar_file *file = uar->files[i];
1191
1192 if (fwrite (file, sizeof (struct uar_file), 1, stream) != 1)
1193 {
1194 uar_set_error (uar, UAR_IO_ERROR, file->name);
1195 fclose (stream);
1196 return false;
1197 }
1198
1199 if (fwrite (file->name, 1, file->namelen, stream) != file->namelen)
1200 {
1201 uar_set_error (uar, UAR_IO_ERROR, file->name);
1202 fclose (stream);
1203 return false;
1204 }
1205 }
1206
1207 if (fwrite (uar->buffer, 1, uar->header.size, stream) != uar->header.size)
1208 {
1209 uar_set_error (uar, UAR_IO_ERROR, NULL);
1210 fclose (stream);
1211 return false;
1212 }
1213
1214 fclose (stream);
1215 return true;
1216 }
1217
1218 bool
1219 uar_extract (struct uar_archive *uar, const char *cwd,
1220 bool (*callback) (struct uar_file *file))
1221 {
1222 if (cwd != NULL && chdir (cwd) != 0)
1223 {
1224 uar_set_error (uar, UAR_SYSTEM_ERROR, NULL);
1225 return false;
1226 }
1227
1228 for (uint64_t i = 0; i < uar->header.nfiles; i++)
1229 {
1230 struct uar_file *file = uar->files[i];
1231
1232 if (callback != NULL && !callback (file))
1233 return false;
1234
1235 char *name = file->name;
1236
1237 if (name[0] == '/')
1238 name += 2;
1239
1240 switch (file->type)
1241 {
1242 case UF_FILE:
1243 {
1244 FILE *stream = fopen (name, "wb");
1245
1246 if (stream == NULL)
1247 {
1248 uar_set_error (uar, UAR_IO_ERROR, name);
1249 return false;
1250 }
1251
1252 if (fwrite (uar->buffer + file->offset, 1,
1253 file->data.size, stream)
1254 != file->data.size)
1255 {
1256 uar_set_error (uar, UAR_IO_ERROR, name);
1257 return false;
1258 }
1259
1260 fchmod (fileno (stream), file->mode & 07777);
1261 fclose (stream);
1262 }
1263 break;
1264
1265 case UF_DIR:
1266 if (file->namelen == 1 && file->name[0] == '/')
1267 continue;
1268
1269 if (mkdir (name, file->mode) != 0)
1270 {
1271 uar_set_error (uar, UAR_SYSTEM_ERROR, name);
1272 return false;
1273 }
1274
1275 break;
1276
1277 default:
1278 assert (false && "unknown file type");
1279 return false;
1280 }
1281 }
1282
1283 return true;
1284 }
1285
1286 bool
1287 uar_iterate (struct uar_archive *uar,
1288 bool (*callback) (struct uar_file *file, void *data), void *data)
1289 {
1290 for (uint64_t i = 0; i < uar->header.nfiles; i++)
1291 {
1292 struct uar_file *file = uar->files[i];
1293
1294 if (!callback (file, data))
1295 return false;
1296 }
1297
1298 return true;
1299 }
1300
1301 const char *
1302 uar_file_get_name (const struct uar_file *file)
1303 {
1304 return file->name;
1305 }
1306
1307 enum uar_file_type
1308 uar_file_get_type (const struct uar_file *file)
1309 {
1310 return file->type;
1311 }
1312
1313 mode_t
1314 uar_file_get_mode (const struct uar_file *file)
1315 {
1316 return file->mode;
1317 }
1318
1319 uint64_t
1320 uar_file_get_size (const struct uar_file *file)
1321 {
1322 if (file->type == UF_LINK)
1323 return 0;
1324
1325 return file->data.size;
1326 }
1327
1328 uint64_t
1329 uar_file_get_namelen (const struct uar_file *file)
1330 {
1331 return file->namelen;
1332 }
1333
1334 uint64_t
1335 uar_get_file_count (const struct uar_archive *restrict uar)
1336 {
1337 return uar->header.nfiles;
1338 }
1339
1340 time_t
1341 uar_file_get_mtime (const struct uar_file *file)
1342 {
1343 return file->mtime;
1344 }
1345
1346 const char *
1347 uar_get_error_file (const struct uar_archive *uar)
1348 {
1349 return uar->err_file;
1350 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26