#include "tar.h" #include #include #include #include #include #include #include #include #define BUF_SIZE 64 static void *tar_open(const char *path, int len, void *base, size_t base_len); static char tar_type(void *meta); static void tar_dirbuild(struct dirbuild *db, const char *meta, void *base, size_t base_len); static void tar_read(struct ufs_request *res, void *base, size_t base_len); static int tar_size(void *sector); static int oct_parse(char *str, size_t len); static const char *root_fakemeta = ""; /* see comment in tar_open */ void tar_driver(void *base) { static char buf[BUF_SIZE]; struct ufs_request res; void *ptr; while (!c0_fs_wait(buf, BUF_SIZE, &res)) { switch (res.op) { case VFSOP_OPEN: ptr = tar_open(buf, res.len, base, ~0); c0_fs_respond(ptr, ptr ? 0 : -1, 0); break; case VFSOP_READ: tar_read(&res, base, ~0); break; case VFSOP_GETSIZE: if (tar_type(res.id) != '5') { c0_fs_respond(NULL, tar_size(res.id), 0); } else { struct dirbuild db; dir_start(&db, res.offset, NULL, 0); tar_dirbuild(&db, res.id, base, ~0); c0_fs_respond(NULL, dir_finish(&db), 0); } break; default: c0_fs_respond(NULL, -1, 0); // unsupported break; } } exit(0); } static char tar_type(void *meta) { if (meta == root_fakemeta) return '5'; return *(char*)(meta + 156); } #include static void *tar_open(const char *path, int len, void *base, size_t base_len) { void *res; if (len <= 0) return NULL; if (*path == '/') { path += 1; // skip the leading slash len -= 1; } /* TAR archives don't (seem to) contain an entry for the root dir, so i'm * returning a fake one. this isn't a full entry because i'm currently too * lazy to create a full one - thus, it has to be special cased in tar_read */ if (len == 0) { return (void*)root_fakemeta; } res = tar_find(path, len, base, base_len); if (res && tar_type(res) == '1') { /* hard link */ _klogf("hard link to %s", res+157); res = tar_find(res + 157, strnlen(res + 157, 100), base, base_len); } return res; } static void tar_dirbuild(struct dirbuild *db, const char *meta, void *base, size_t base_len) { size_t meta_len = strlen(meta); for (size_t off = 0; off < base_len;) { if (0 != memcmp(base + off + 257, "ustar", 5)) break; // not a metadata sector /* check if prefix matches */ if (0 == memcmp(base + off, meta, meta_len) && *(char*)(base + off + meta_len) != '\0') { char *suffix = base + off + meta_len; /* check if the path contains any non-trailing slashes */ char *slash = strchr(suffix, '/'); if (!slash || slash[1] == '\0') { if (dir_append(db, suffix)) break; } } int size = tar_size(base + off); off += 512; // skip this metadata sector off += (size + 511) & ~511; // skip the data sectors } } static void tar_read(struct ufs_request *res, void *base, size_t base_len) { void *meta = (void*)res->id; static char buf[BUF_SIZE]; // TODO reuse a single buffer for both tar_driver and tar_read switch (tar_type(meta)) { case '\0': case '0': /* normal files */ fs_normslice(&res->offset, &res->len, tar_size(meta), false); c0_fs_respond(meta + 512 + res->offset, res->len, 0); break; case '5': /* directory */ struct dirbuild db; dir_start(&db, res->offset, buf, sizeof buf); tar_dirbuild(&db, meta, base, base_len); c0_fs_respond(buf, dir_finish(&db), 0); break; default: c0_fs_respond(NULL, -1, 0); break; } } static int tar_size(void *sector) { return oct_parse(sector + 124, 11); } void *tar_find(const char *path, size_t path_len, void *base, size_t base_len) { int size; if (path_len > 100) return NULL; // illegal path for (size_t off = 0; off < base_len;) { if (0 != memcmp(base + off + 257, "ustar", 5)) break; // not a metadata sector if (0 == memcmp(base + off, path, path_len) && *(char*)(base + off + path_len) == '\0') return base + off; // file found, quit size = tar_size(base + off); off += 512; // skip this metadata sector off += (size + 511) & ~511; // skip the data sectors } return NULL; } static int oct_parse(char *str, size_t len) { int res = 0; for (size_t i = 0; i < len; i++) { res *= 8; res += str[i] - '0'; // no format checking } return res; }