1 /*-
2  * Copyright (c) 2010
3  *	Thorsten Glaser <tg@mirbsd.org>
4  *
5  * Provided that these terms and disclaimer and all copyright notices
6  * are retained or reproduced in an accompanying document, permission
7  * is granted to deal in this work without restriction, including un-
8  * limited rights to use, publicly perform, distribute, sell, modify,
9  * merge, give away, or sublicence.
10  *
11  * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
12  * the utmost extent permitted by applicable law, neither express nor
13  * implied; without malicious intent or gross negligence. In no event
14  * may a licensor, author or contributor be held liable for indirect,
15  * direct, other damage, loss, or other issues arising in any way out
16  * of dealing in the work, even if advised of the possibility of such
17  * damage or existence of a defect, except proven that it results out
18  * of said person's immediate fault when using the work as intended.
19  */
20 
21 #include <sys/param.h>
22 #include <sys/time.h>
23 #include <sys/stat.h>
24 #include <sys/slibkern.h>
25 #include <lib/libsa/stand.h>
26 #include <lib/libsa/ustar.h>
27 #include <lib/libsa/ustarfs.h>
28 
29 __RCSID("$MirOS: src/sys/lib/libsa/ustarfs.c,v 1.3 2010/01/30 14:57:44 tg Exp $");
30 
31 /* This is intentionally simple. We don’t support directories. */
32 
33 struct ustarfs_file {
34 	struct open_file *open_file;	/* our "parent" structure */
35 	struct stat sb;			/* file properties */
36 	uint32_t sector;		/* header sector */
37 #define SEC_VDIR ((uint32_t)-1)
38 	uint32_t nodeseekp;		/* current file offset */
39 };
40 
41 #undef DEBUG
42 #ifdef DEBUG
43 char assertion_size__ustar_hdr_t[sizeof(ustar_hdr_t) == 512 ? 1 : -1];
44 /* #define D(fmt, ...)	printf("D: %s[%u]:%s: " fmt "\n", __FILE__, __LINE__, \
45 			    __func__, ## __VA_ARGS__) */
46 #define D(fmt, ...)	printf("D: [%u]:%s: " fmt "\n", __LINE__, \
47 			    __func__, ## __VA_ARGS__)
48 #else
49 #define D(fmt, ...)	/* nothing */
50 #endif
51 
52 #ifdef __i386__
53 extern uint8_t bounce_buf[4096];
54 #else
55 uint8_t bounce_buf[1536];
56 #endif
57 static ustar_hdr_t *hp = (ustar_hdr_t *)bounce_buf;
58 #define ISAFILE()	(!hp->typeflag || hp->typeflag == '0' || \
59 			    hp->typeflag == '7')
60 #define ISALINK()	(hp->typeflag == '1')
61 #define UNOCT(x)	unoct((x), sizeof(x))
62 
63 static int rd(struct open_file *, uint32_t);
64 static int ustar_check(int);
65 static int ustar_cmpfn(const char *);
66 static int ustar_search(struct ustarfs_file *, const char *);
67 static void ustar_fillsb(struct ustarfs_file *);
68 static uint32_t unoct(char *, size_t);
69 static char *fn_normalise(const char *, const char *);
70 
71 
72 /* low-level helper routine to load one sector from disc */
73 static int
rd(struct open_file * f,uint32_t blk)74 rd(struct open_file *f, uint32_t blk)
75 {
76 	size_t buflen;
77 	int rv;
78 
79 	twiddle();
80 	D("block %u", blk);
81 	if ((rv = (f->f_dev->dv_strategy)(f->f_devdata, F_READ,
82 	    blk, 512, (void *)hp, &buflen)))
83 		D("error code %u", rv);
84 	else if (buflen != 512) {
85 		D("short read %u", (unsigned)buflen);
86 		rv = EIO;
87 	}
88 	return (rv);
89 }
90 
91 /* low-level routine to check a loaded sector for ustar */
92 int
ustar_check(int chk_typeflag)93 ustar_check(int chk_typeflag)
94 {
95 	ustar_hdr_t *h2 = hp + 1;
96 	uint32_t sum = 0, i = 0;
97 	int rv = 1;
98 
99 	if (memcmp((char *)hp + offsetof(ustar_hdr_t, magic),
100 	    ustar_magic_version, sizeof(ustar_magic_version)))
101 		goto out;
102 	if (chk_typeflag) {
103 		if (hp->typeflag && (hp->typeflag < '0' || hp->typeflag > '7'))
104 			goto out;
105 	}
106 	memcpy(h2, hp, 512);
107 	memset(h2->chksum, ' ', sizeof(h2->chksum));
108 	while (i < 512)
109 		sum += ((unsigned char *)h2)[i++];
110 	if (sum != UNOCT(hp->chksum))
111 		goto out;
112 	rv = 0;
113  out:
114 	D("rv = %d", rv);
115 	return (rv);
116 }
117 
118 uint32_t
unoct(char * s,size_t n)119 unoct(char *s, size_t n)
120 {
121 	uint32_t rv = 0;
122 	size_t i = 0;
123 
124 	while (i < n && s[i] && s[i] != ' ') {
125 		if (s[i] < '0' || s[i] > '7')
126 			return ((uint32_t)-1);
127 		rv = (rv << 3) | (s[i++] & 7);
128 	}
129 	return (rv);
130 }
131 
132 static char *
fn_normalise(const char * p1,const char * p2)133 fn_normalise(const char *p1, const char *p2)
134 {
135 	char *rv, *buf = (char *)(void *)(hp + 1);
136 	size_t n = 0, i = 260;
137 
138 	D("s1=%s%s%s s2='%s'", p1 ? "'" : "", p1 ? p1 : "(null)",
139 	    p1 ? "'" : "", p2);
140 
141 	/* construct filename at [buf+260;buf+1024[ and NUL-terminate */
142 	buf[260] = '/';
143 	if (p1 == NULL) {
144 		/* external filename */
145 		strlcpy(buf + 261, p2, 1024 - 261);
146 	} else {
147 		if (*p1) {
148 			/* ustar header prefix */
149 			memcpy(buf + 261, p1, 155);
150 			buf[261 + 155] = '\0';
151 		} else
152 			/* empty string */
153 			buf[261] = '\0';
154 		/* ustar header name or linkname */
155 		memcpy(buf + 600, p2, 100);
156 		buf[600 + 100] = '\0';
157 		strlcat(buf + 261, "/", 512);
158 		strlcat(buf + 261, buf + 600, 512);
159 	}
160 	/* D("s1+s2='%s'", buf + 260); */
161 
162 	/* normalise filename from buf+261 to [buf+1;buf+257] */
163 	/* buf[0] will always contain a slash from buf[260] */
164  expect_slash:
165 	while (buf[i] == '/')
166 		i++;
167 	if (!buf[i])
168 		goto got_NUL;
169 	buf[n++] = '/';
170  expect_nonslash:
171 	if (n == 257)		/* overflow check */
172 		goto got_NUL;
173 	if (buf[i] == '/')
174 		goto expect_slash;
175 	else if (buf[i]) {
176 		buf[n++] = buf[i++];
177 		goto expect_nonslash;
178 	}
179  got_NUL:
180 	buf[n++] = '\0';
181 
182 	rv = alloc(n);
183 	memcpy(rv, buf, n);
184 	D("rv = '%s'", rv);
185 	return (rv);
186 }
187 
188 static int
ustar_search(struct ustarfs_file * ff,const char * ofn)189 ustar_search(struct ustarfs_file *ff, const char *ofn)
190 {
191 	uint32_t i;
192 	int rv, lastwasNUL = 0;
193 
194 	D("@%u, ofn=%s%s%s", ff->sector, ofn ? "'" : "",
195 	    ofn ? ofn : "(null)", ofn ? "'" : "");
196 
197 	if (ff->sector == SEC_VDIR) {
198 		/* search from start */
199 		ff->sector = USTARFS_FIRSTSEC;
200 		goto loadsec;
201 	}
202 
203  skipdata:
204 	/* skip data sectors, if any */
205 	if (ISAFILE())
206 		ff->sector += (UNOCT(hp->size) + 511) / 512;
207  skiphdr:
208 	/* skip (current sector) ustar header block */
209 	ff->sector++;
210 
211  loadsec:
212 	/* load next (hopefully) ustar header block */
213 	if ((rv = rd(ff->open_file, ff->sector))) {
214 		D("I/O error %d", rv);
215 		return (rv);
216 	}
217 
218 	/* check for header (or end of such) */
219 	if (ustar_check(1)) {
220 		/* check for end of archive */
221 		for (i = 0; i < 512; ++i)
222 			if (((char *)(void *)hp)[i])
223 				break;
224 		if (i == 512) {
225 			/* sector was all NULs */
226 			if (lastwasNUL++) {
227 				D("end of archive");
228 				return (ENOENT);
229 			}
230 		} else
231 			/* sector was not all NULs */
232 			lastwasNUL = 0;
233 		goto skiphdr;
234 	}
235 	/* sector contains a valid and known ustar header */
236 	lastwasNUL = 0;
237 
238 	if (ofn && ustar_cmpfn(ofn))
239 		/* filename does not match expectations */
240 		goto skipdata;
241 
242 	/* found ustar header which, if given, matches filename */
243 	D("found");
244 	return (0);
245 }
246 
247 static int
ustar_cmpfn(const char * ofn)248 ustar_cmpfn(const char *ofn)
249 {
250 	char *hfn;
251 	int rv = 0;
252 
253 	hfn = fn_normalise(hp->prefix, hp->name);
254 	D("ofn '%s' with hfn '%s'", ofn, hfn);
255 	if (strcmp(ofn, hfn))
256 		rv = 1;
257 	free(hfn, strlen(hfn) + 1);
258 	return (rv);
259 }
260 
261 static void
ustar_fillsb(struct ustarfs_file * ff)262 ustar_fillsb(struct ustarfs_file *ff)
263 {
264 	bzero(&ff->sb, sizeof(ff->sb));
265 
266 	ff->sb.st_mode = UNOCT(hp->mode);
267 	ff->sb.st_uid = UNOCT(hp->uid);
268 	ff->sb.st_gid = UNOCT(hp->gid);
269 #if 0
270 	ff->sb.st_rdev = makedev(UNOCT(hp->devmajor), UNOCT(hp->devminor));
271 	ff->sb.st_mtime = UNOCT(hp->mtime);
272 #endif
273 	if (ISAFILE())
274 		ff->sb.st_size = UNOCT(hp->size);
275 }
276 
277 int
ustarfs_open(char * path,struct open_file * f)278 ustarfs_open(char *path, struct open_file *f)
279 {
280 	int rv = 0;
281 	struct ustarfs_file *ff;
282 
283 	D("path='%s'", path);
284 	if (path[0] == '/' && path[1] == '.' && path[2] == '/')
285 		path += 3;
286 
287 	/* check first sector for valid ustar header */
288 	if ((rv = rd(f, USTARFS_FIRSTSEC)) || ustar_check(0))
289 		return (rv ? rv : EUNLAB);
290 
291 	/* allocate fs specific data structure */
292 	ff = alloc(sizeof(struct ustarfs_file));
293 	bzero(ff, sizeof(struct ustarfs_file));
294 	f->f_fsdata = ff;
295 	ff->open_file = f;
296 
297 	/* check if we want “the directory” (e.g. for “ls”) */
298 	if (!path[0] || (path[0] == '/' && (!path[1] ||
299 	    (path[1] == '.' && !path[2])))) {
300 		/* “” or “/” or “/.” */
301 		ff->sector = SEC_VDIR;
302 		ff->nodeseekp = SEC_VDIR;
303 		D("got directory");
304 		return (0);
305 	}
306 
307 	path = fn_normalise(NULL, path);
308 	ff->sector = USTARFS_FIRSTSEC;
309 	ff->nodeseekp = 0;
310 	if (ustar_cmpfn(path)) {
311  do_search:
312 		rv = ustar_search(ff, path);
313 	}
314 	if (rv) {
315 		D("cannot find, error %d", rv);
316 		free(ff, sizeof(struct ustarfs_file));
317 	} else if (ISALINK()) {
318 		D("got link, redoing");
319 		free(path, strlen(path) + 1);
320 		path = fn_normalise("", hp->linkname);
321 		/* redo from start */
322 		ff->sector = SEC_VDIR;
323 		goto do_search;
324 	} else
325 		ustar_fillsb(ff);
326 	free(path, strlen(path) + 1);
327 	D("success");
328 	return (rv);
329 }
330 
331 int
ustarfs_close(struct open_file * f)332 ustarfs_close(struct open_file *f)
333 {
334 	struct ustarfs_file *ff;
335 
336 	if (f == NULL)
337 		return (0);
338 
339 	ff = f->f_fsdata;
340 	if (ff) {
341 		free(ff, sizeof(struct ustarfs_file));
342 	}
343 	return (0);
344 }
345 
346 int
ustarfs_read(struct open_file * f,void * buf,size_t size,size_t * resid)347 ustarfs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
348 {
349 	struct ustarfs_file *ff = f->f_fsdata;
350 	uint32_t datasec, dataofs, n;
351 	int rv = 0;
352 
353 	D("%u @ %u", (unsigned)size, ff->nodeseekp);
354 
355 	/* are we at or past EOF already? */
356 	if (ff->nodeseekp >= ff->sb.st_size)
357 		goto out;
358 
359 	datasec = ff->sector + 1 + (ff->nodeseekp / 512);
360 	dataofs = ff->nodeseekp & 511;
361 
362 	/* remaining octets in the sector */
363 	n = 512 - dataofs;
364 
365 	/* amount we actually want to read */
366 	if (n > size)
367 		n = size;
368 
369 	/* bounds check against file size */
370 	if (ff->nodeseekp + n > ff->sb.st_size)
371 		n = ff->sb.st_size - ff->nodeseekp;
372 
373 	/* n is now amount of bytes we actually CAN read from one sector */
374 
375 	/* try to read the sector we’re in */
376 	if ((rv = rd(ff->open_file, datasec)))
377 		goto out;
378 
379 	/* copy (part) of the sector */
380 	memcpy(buf, ((char *)hp) + dataofs, n);
381 	ff->nodeseekp += n;
382 	size -= n;
383 	D("success, read %u bytes", n);
384 
385  out:
386 	if (resid)
387 		*resid = size;
388 	return (rv);
389 }
390 
391 int
ustarfs_write(struct open_file * f,void * buf,size_t size,size_t * resid)392 ustarfs_write(struct open_file *f, void *buf, size_t size, size_t *resid)
393 {
394 	return (EROFS);
395 }
396 
397 off_t
ustarfs_seek(struct open_file * f,off_t offset,int where)398 ustarfs_seek(struct open_file *f, off_t offset, int where)
399 {
400 	struct ustarfs_file *ff = f->f_fsdata;
401 
402 	if (ff->sector == SEC_VDIR) {
403 		if (where == SEEK_SET && offset == 0) {
404 			ff->nodeseekp = SEC_VDIR;
405 			return (0);
406 		}
407 		return (-1);
408 	}
409 
410 	switch (where) {
411 	case SEEK_SET:
412 		ff->nodeseekp = offset;
413 		break;
414 	case SEEK_CUR:
415 		ff->nodeseekp += offset;
416 		break;
417 	case SEEK_END:
418 		ff->nodeseekp = ff->sb.st_size + offset;
419 		break;
420 	default:
421 		return (-1);
422 	}
423 	return (ff->nodeseekp);
424 }
425 
426 int
ustarfs_stat(struct open_file * f,struct stat * sb)427 ustarfs_stat(struct open_file *f, struct stat *sb)
428 {
429 	struct ustarfs_file *ff = f->f_fsdata;
430 
431 	if (ff->sector == SEC_VDIR) {
432 		/* quick and dirty */
433 		bzero(sb, sizeof(struct stat));
434 		sb->st_mode = 040555;
435 	} else
436 		memcpy(sb, &ff->sb, sizeof(struct stat));
437 
438 	return (0);
439 }
440 
441 int
ustarfs_readdir(struct open_file * f,char * name)442 ustarfs_readdir(struct open_file *f, char *name)
443 {
444 	struct ustarfs_file *ff = f->f_fsdata;
445 	int rv;
446 
447 	if (ff->sector != SEC_VDIR)
448 		return (EINVAL);
449 
450 	/* reset? */
451 	if (name == NULL) {
452 		ff->nodeseekp = SEC_VDIR;
453 		return (0);
454 	}
455 
456 	ff->sector = ff->nodeseekp;
457 	if (ff->sector != SEC_VDIR)
458 		if ((rv = rd(ff->open_file, ff->sector)))
459 			goto oops;
460 	rv = ustar_search(ff, NULL);
461  oops:
462 	ff->nodeseekp = ff->sector;
463 	ff->sector = SEC_VDIR;
464 
465 	if (!rv) {
466 		char *fn;
467 
468 		fn = fn_normalise(hp->prefix, hp->name);
469 		memcpy(name, fn, strlen(fn) + 1);
470 		free(fn, strlen(fn) + 1);
471 	} else
472 		*name = '\0';
473 
474 	return (rv);
475 }
476