added assfile
[raydungeon] / libs / assfile / tar.c
1 /*
2 assfile - library for accessing assets with an fopen/fread-like interface
3 Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #include "tar.h"
23
24 #define MAX_NAME_LEN    100
25 #define MAX_PREFIX_LEN  131
26
27 struct header {
28         char name[MAX_NAME_LEN];
29         char mode[8];
30         char uid[8], gid[8];
31         char size[12];
32         char mtime[12];
33         char chksum[8];
34         char typeflag;
35         char linkname[100];
36         char magic[6];
37         char version[2];
38         char uname[32], gname[32];
39         char devmajor[8], devminor[8];
40         char prefix[MAX_PREFIX_LEN];
41         char atime[12], ctime[12];
42 };
43
44 struct node {
45         struct tar_entry file;
46         struct node *next;
47 };
48
49
50 int load_tar(struct tar *tar, const char *fname)
51 {
52         int i, res = -1;
53         struct node *node, *head = 0, *tail = 0;
54         char buf[512];
55         struct header *hdr;
56         unsigned long offset = 0, size, blksize;
57         char *path, *endp;
58
59         if(!(tar->fp = fopen(fname, "rb"))) {
60                 fprintf(stderr, "load_tar: failed to open %s: %s\n", fname, strerror(errno));
61                 return -1;
62         }
63         tar->num_files = 0;
64         tar->files = 0;
65
66         while(fread(buf, 1, sizeof buf, tar->fp) == sizeof buf) {
67                 hdr = (struct header*)buf;
68
69                 offset += 512;
70                 size = strtol(hdr->size, &endp, 8);
71                 if(endp == hdr->size || size <= 0) {
72                         continue;       /* skip invalid */
73                 }
74                 blksize = ((size - 1) | 0x1ff) + 1;     /* round to next 512-block */
75
76                 /* verify filesize reasonable */
77                 fseek(tar->fp, size - 1, SEEK_CUR);
78                 if(fgetc(tar->fp) == -1) {
79                         break;  /* invalid and reached EOF */
80                 }
81                 fseek(tar->fp, blksize - size, SEEK_CUR);
82
83                 if(memcmp(hdr->magic, "ustar", 5) == 0) {
84                         int nlen = strnlen(hdr->name, MAX_NAME_LEN);
85                         int plen = strnlen(hdr->prefix, MAX_PREFIX_LEN);
86                         if(!(path = malloc(nlen + plen + 1))) {
87                                 perror("failed to allocate file path string");
88                                 goto end;
89                         }
90                         memcpy(path, hdr->prefix, plen);
91                         memcpy(path + plen, hdr->name, nlen);
92                         path[nlen + plen] = 0;
93                 } else {
94                         int nlen = strnlen(hdr->name, MAX_NAME_LEN);
95                         if(!(path = malloc(nlen + 1))) {
96                                 perror("failed to allocate file path string");
97                                 goto end;
98                         }
99                         memcpy(path, hdr->name, nlen);
100                         path[nlen] = 0;
101                 }
102
103                 if(!(node = malloc(sizeof *node))) {
104                         perror("failed to allocate file list node");
105                         goto end;
106                 }
107                 node->file.path = path;
108                 node->file.offset = offset;
109                 node->file.size = size;
110                 node->next = 0;
111                 if(head) {
112                         tail->next = node;
113                         tail = node;
114                 } else {
115                         head = tail = node;
116                 }
117                 tar->num_files++;
118
119                 /*printf(" %s - size %ld @%ld\n", node->file.path, node->file.size, node->file.offset);*/
120
121                 offset += blksize;
122         }
123
124         if(!(tar->files = malloc(tar->num_files * sizeof *tar->files))) {
125                 perror("failed to allocate file list");
126                 goto end;
127         }
128
129         node = head;
130         for(i=0; i<tar->num_files; i++) {
131                 tar->files[i] = node->file;
132                 node = node->next;
133         }
134
135         if(tar->num_files > 0) {
136                 res = 0;
137         }
138
139 end:
140         while(head) {
141                 node = head;
142                 head = head->next;
143                 if(node) {
144                         if(res == -1) {
145                                 free(node->file.path);
146                         }
147                         free(node);
148                 }
149         }
150         return res;
151 }
152
153
154 void close_tar(struct tar *tar)
155 {
156         if(!tar) return;
157
158         if(tar->fp) {
159                 fclose(tar->fp);
160                 tar->fp = 0;
161         }
162         if(tar->files && tar->num_files > 0) {
163                 int i;
164                 for(i=0; i<tar->num_files; i++) {
165                         free(tar->files[i].path);
166                 }
167                 free(tar->files);
168                 tar->files = 0;
169                 tar->num_files = 0;
170         }
171 }