Merge pull request #1 from atsampson/master
[calfbox.git] / tarfile.c
blobc32d691776bfcd62bc827b1ba99013e3f55166d4
1 /*
2 Calf Box, an open source musical instrument.
3 Copyright (C) 2010-2013 Krzysztof Foltman
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU 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.
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 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "config-api.h"
20 #include "errors.h"
21 #include "tarfile.h"
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <malloc.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <stdlib.h>
29 #include <unistd.h>
31 struct tar_record
33 char name[100];
34 char mode[8];
35 char uid[8];
36 char gid[8];
37 char size[12];
38 char mtime[12];
39 char checksum[8];
40 char typeflag;
41 char linkname[100];
42 char ustar[6];
43 char ustarver[2];
44 char uname[32];
45 char gname[32];
46 char devmajor[8];
47 char devminor[8];
48 char prefix[155];
49 char padding[12];
52 static void remove_item_if(gpointer p);
54 struct cbox_tarfile *cbox_tarfile_open(const char *pathname, GError **error)
56 gboolean debug = cbox_config_get_int("debug", "tarfile", 0);
57 gchar *canonical = realpath(pathname, NULL);
58 if (!canonical)
60 if (error)
61 g_set_error(error, G_FILE_ERROR, g_file_error_from_errno (errno), "cannot determine canonical name of '%s'", pathname);
62 return NULL;
64 int fd = open(canonical, O_RDONLY | O_LARGEFILE);
65 if (fd < 0)
67 free(canonical);
68 if (error)
69 g_set_error(error, G_FILE_ERROR, g_file_error_from_errno (errno), "cannot open '%s'", pathname);
70 return NULL;
72 GHashTable *byname = NULL, *byname_nc = NULL;
74 byname = g_hash_table_new(g_str_hash, g_str_equal);
75 byname_nc = g_hash_table_new(g_str_hash, g_str_equal);
76 if (!byname || !byname_nc)
77 goto error;
79 struct cbox_tarfile *tf = calloc(1, sizeof(struct cbox_tarfile));
80 if (!tf)
81 goto error;
82 tf->fd = fd;
83 tf->items_byname = byname;
84 tf->items_byname_nc = byname_nc;
85 tf->refs = 1;
86 tf->file_pathname = canonical;
87 while(1)
89 struct tar_record rec;
90 int nbytes = read(fd, &rec, sizeof(rec));
91 if (nbytes != sizeof(rec))
92 break;
94 int len = sizeof(rec.name);
95 while(len > 0 && (rec.name[len - 1] == ' ' || rec.name[len - 1] == '\0'))
96 len--;
98 char sizetext[13];
99 memcpy(sizetext, rec.size, 12);
100 sizetext[12] = '\0';
101 unsigned long long size = strtoll(sizetext, NULL, 8);
103 // skip block if name is empty
104 if (!len)
105 goto skipitem;
106 struct cbox_taritem *ti = calloc(1, sizeof(struct cbox_taritem));
107 if (ti)
109 int offset = 0;
110 if (len >= 2 && rec.name[0] == '.' && rec.name[1] == '/')
111 offset = 2;
112 ti->filename = g_strndup(rec.name + offset, len - offset);
113 ti->filename_nc = g_utf8_casefold(rec.name + offset, len - offset);
114 if (!ti->filename || !ti->filename_nc)
115 goto itemerror;
116 ti->offset = lseek64(fd, 0, SEEK_CUR);
117 ti->size = size;
118 ti->refs = 2;
120 // Overwrite old items by the same name and/or same case-folded name
121 remove_item_if(g_hash_table_lookup(tf->items_byname, ti->filename));
122 remove_item_if(g_hash_table_lookup(tf->items_byname_nc, ti->filename_nc));
124 g_hash_table_insert(tf->items_byname, ti->filename, ti);
125 g_hash_table_insert(tf->items_byname_nc, ti->filename_nc, ti);
126 if (debug)
127 printf("name = %s len = %d offset = %d readsize = %d\n", ti->filename, len, (int)ti->offset, (int)size);
129 goto skipitem;
131 itemerror:
132 rec.name[99] = '\0';
133 g_warning("Could not allocate memory for tar item %s", rec.name);
134 if (ti)
136 if (ti->filename_nc)
137 g_free(ti->filename_nc);
138 if (ti->filename)
139 g_free(ti->filename);
140 free(ti);
142 skipitem:
143 lseek64(fd, (size + 511) &~ 511, SEEK_CUR);
145 return tf;
147 error:
148 if (byname)
149 g_hash_table_destroy(byname);
150 if (byname_nc)
151 g_hash_table_destroy(byname_nc);
152 free(canonical);
153 g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot allocate memory for tarfile data");
154 return NULL;
157 void remove_item_if(gpointer p)
159 if (!p)
160 return;
162 struct cbox_taritem *ti = p;
163 // If all references (by name and by case-folded name) gone, remove the item
164 if (!--ti->refs)
166 g_free(ti->filename);
167 g_free(ti->filename_nc);
168 free(ti);
172 struct cbox_taritem *cbox_tarfile_get_item_by_name(struct cbox_tarfile *tarfile, const char *item_filename, gboolean ignore_case)
174 if (item_filename[0] == '.' && item_filename[1] == '/')
175 item_filename += 2;
176 if (ignore_case)
178 gchar *folded = g_utf8_casefold(item_filename, -1);
179 struct cbox_taritem *item = g_hash_table_lookup(tarfile->items_byname_nc, folded);
180 g_free(folded);
181 return item;
183 else
184 return g_hash_table_lookup(tarfile->items_byname, item_filename);
187 int cbox_tarfile_openitem(struct cbox_tarfile *tarfile, struct cbox_taritem *item)
189 int fd = open(tarfile->file_pathname, O_RDONLY | O_LARGEFILE);
190 if (fd >= 0)
191 lseek64(fd, item->offset, SEEK_SET);
192 return fd;
195 void cbox_tarfile_closeitem(struct cbox_tarfile *tarfile, struct cbox_taritem *item, int fd)
197 if (fd >= 0)
198 close(fd);
201 static void delete_foreach_func(gpointer key, gpointer value, gpointer user_data)
203 struct cbox_taritem *ti = value;
204 if (!--ti->refs)
206 g_free(ti->filename);
207 g_free(ti->filename_nc);
208 free(ti);
212 void cbox_tarfile_destroy(struct cbox_tarfile *tf)
214 g_hash_table_foreach(tf->items_byname, delete_foreach_func, NULL);
215 g_hash_table_foreach(tf->items_byname_nc, delete_foreach_func, NULL);
216 close(tf->fd);
217 g_hash_table_destroy(tf->items_byname);
218 g_hash_table_destroy(tf->items_byname_nc);
219 free(tf->file_pathname);
220 free(tf);
223 ////////////////////////////////////////////////////////////////////////////////
225 sf_count_t tarfile_get_filelen(void *user_data)
227 struct cbox_tarfile_sndstream *ss = user_data;
229 return ss->item->size;
232 sf_count_t tarfile_seek(sf_count_t offset, int whence, void *user_data)
234 struct cbox_tarfile_sndstream *ss = user_data;
235 switch(whence)
237 case SEEK_SET:
238 ss->filepos = offset;
239 break;
240 case SEEK_CUR:
241 ss->filepos += offset;
242 break;
243 case SEEK_END:
244 ss->filepos = ss->item->size;
245 break;
247 if (((int64_t)ss->filepos) < 0)
248 ss->filepos = 0;
249 if (((int64_t)ss->filepos) >= ss->item->size)
250 ss->filepos = ss->item->size;
251 return ss->filepos;
254 sf_count_t tarfile_read(void *ptr, sf_count_t count, void *user_data)
256 struct cbox_tarfile_sndstream *ss = user_data;
257 ssize_t len = pread64(ss->file->fd, ptr, count, ss->item->offset + ss->filepos);
258 if (len > 0)
259 ss->filepos += len;
260 return len;
263 sf_count_t tarfile_tell(void *user_data)
265 struct cbox_tarfile_sndstream *ss = user_data;
266 return ss->filepos;
269 struct SF_VIRTUAL_IO cbox_taritem_virtual_io = {
270 .get_filelen = tarfile_get_filelen,
271 .seek = tarfile_seek,
272 .read = tarfile_read,
273 .write = NULL,
274 .tell = tarfile_tell,
277 SNDFILE *cbox_tarfile_opensndfile(struct cbox_tarfile *tarfile, struct cbox_taritem *item, struct cbox_tarfile_sndstream *stream, SF_INFO *sfinfo)
279 stream->file = tarfile;
280 stream->item = item;
281 stream->filepos = 0;
282 return sf_open_virtual(&cbox_taritem_virtual_io, SFM_READ, sfinfo, stream);
285 ////////////////////////////////////////////////////////////////////////////////
287 struct cbox_tarpool *cbox_tarpool_new(void)
289 struct cbox_tarpool *pool = calloc(1, sizeof(struct cbox_tarpool));
290 pool->files = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
291 return pool;
294 struct cbox_tarfile *cbox_tarpool_get_tarfile(struct cbox_tarpool *pool, const char *name, GError **error)
296 gchar *c = realpath(name, NULL);
297 if (!c)
299 g_set_error(error, G_FILE_ERROR, g_file_error_from_errno (errno), "cannot find a real path for '%s': %s", name, strerror(errno));
300 return NULL;
302 struct cbox_tarfile *tf = g_hash_table_lookup(pool->files, c);
303 if (tf)
304 tf->refs++;
305 else
307 tf = cbox_tarfile_open(c, error);
308 if (!tf)
310 free(c);
311 return NULL;
313 g_hash_table_insert(pool->files, c, tf);
315 return tf;
318 void cbox_tarpool_release_tarfile(struct cbox_tarpool *pool, struct cbox_tarfile *file)
320 if (!--file->refs)
322 // XXXKF the insertion key is realpath(name) but the removal key is realpath(realpath(name))
323 // usually it shouldn't cause problems, but it should be improved.
324 if (!g_hash_table_lookup(pool->files, file->file_pathname))
325 g_warning("Removing tarfile %s not in the pool hash", file->file_pathname);
326 g_hash_table_remove(pool->files, file->file_pathname);
327 cbox_tarfile_destroy(file);
331 void cbox_tarpool_destroy(struct cbox_tarpool *pool)
333 int nelems = g_hash_table_size(pool->files);
334 if (nelems)
335 g_warning("%d unfreed elements in tar pool %p.", nelems, pool);
336 g_hash_table_destroy(pool->files);
337 free(pool);