summaryrefslogtreecommitdiff
path: root/src/bootstrap/tar.c
blob: 61b5b036021fad1847ad98f43e9c907dc141f918 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include "tar.h"
#include <camellia/compat.h>
#include <camellia/flags.h>
#include <camellia/fs/dir.h>
#include <camellia/fsutil.h>
#include <camellia/syscalls.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#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;

		case VFSOP_GETXATTR:
			if (strcmp("virt.index", buf) == 0) {
				char res[] = "virt.mode\0virt.owner\0virt.group";
				c0_fs_respond(res, sizeof(res), 0);
			} else if (strcmp("virt.mode", buf) == 0) {
				c0_fs_respond(res.id + 100, 7, 0);
			} else if (strcmp("virt.owner", buf) == 0) {
				ssize_t ret = snprintf(buf, BUF_SIZE, "%d", oct_parse(res.id + 108, 7));
				c0_fs_respond(buf, ret, 0);
			} else if (strcmp("virt.group", buf) == 0) {
				ssize_t ret = snprintf(buf, BUF_SIZE, "%d", oct_parse(res.id + 116, 7));
				c0_fs_respond(buf, ret, 0);
			} else {
				c0_fs_respond(NULL, -ENOENT, 0);
			}
			break;

		default:
			c0_fs_respond(NULL, -ENOSYS, 0); // unsupported
			break;
		}
	}
	exit(0);
}

static char tar_type(void *meta) {
	if (meta == root_fakemeta) return '5';
	return *(char*)(meta + 156);
}

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 */
		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;
}