| /* Copyright (c) 2018 Google Inc | 
 |  * Barret Rhoden <brho@cs.berkeley.edu> | 
 |  * See LICENSE for details. | 
 |  * | 
 |  * fs_file: structs and helpers for files for 9ns devices | 
 |  */ | 
 |  | 
 | #include <fs_file.h> | 
 | #include <kmalloc.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include <assert.h> | 
 | #include <error.h> | 
 | #include <umem.h> | 
 | #include <pmap.h> | 
 |  | 
 | /* Initializes a zalloced fs_file.  The caller is responsible for filling in | 
 |  * dir, except for name.  Most fields are fine with being zeroed.  Note the kref | 
 |  * == 0 too. */ | 
 | void fs_file_init(struct fs_file *f, const char *name, struct fs_file_ops *ops) | 
 | { | 
 | 	qlock_init(&f->qlock); | 
 | 	fs_file_set_basename(f, name); | 
 | 	f->ops = ops; | 
 | 	/* TODO: consider holding off on initializing the PM, since only walked | 
 | 	 * and opened entries could use it.  pm == NULL means no PM yet. | 
 | 	 * Negative entries will never be used in this manner.  Doing it now | 
 | 	 * avoids races, though it's mostly zeroing cache-hot fields. */ | 
 | 	f->pm = &f->static_pm; | 
 | 	pm_init(f->pm, (struct page_map_operations*)ops, f); | 
 | } | 
 |  | 
 | void fs_file_set_basename(struct fs_file *f, const char *name) | 
 | { | 
 | 	size_t name_len = strlen(name) + 1; | 
 |  | 
 | 	if (name_len > KNAMELEN) | 
 | 		f->dir.name = kzmalloc(name_len, MEM_WAIT); | 
 | 	else | 
 | 		f->dir.name = f->static_name; | 
 | 	memcpy(f->dir.name, name, name_len); | 
 | } | 
 |  | 
 | /* Technically, a reader could see the old string pointer and read it.  That | 
 |  * memory could be realloced and used for something else.  But thanks to the | 
 |  * seqctr, the reader will retry.  Otherwise, we might not need the seqctr, | 
 |  * since we never change_basename when a file is in a tree.  So far. | 
 |  * | 
 |  * The only reader that races with setting the name is stat.  Regular lookups | 
 |  * won't see the file, since it was removed from the HT, and readdirs won't see | 
 |  * it due to the parent's qlock. */ | 
 | void fs_file_change_basename(struct fs_file *f, const char *name) | 
 | { | 
 | 	char *old_name = NULL; | 
 | 	char *new_name = NULL; | 
 | 	size_t name_len = strlen(name) + 1; | 
 |  | 
 | 	if (name_len > KNAMELEN) | 
 | 		new_name = kzmalloc(name_len, MEM_WAIT); | 
 | 	qlock(&f->qlock); | 
 | 	if (f->dir.name != f->static_name) | 
 | 		old_name = f->dir.name; | 
 | 	if (new_name) | 
 | 		f->dir.name = new_name; | 
 | 	else | 
 | 		f->dir.name = f->static_name; | 
 | 	memcpy(f->dir.name, name, name_len); | 
 | 	/* TODO: if we store the hash of the name in the file, do so here. */ | 
 | 	qunlock(&f->qlock); | 
 | 	kfree(old_name); | 
 | } | 
 |  | 
 | /* Helper for building a dir.  Caller sets qid path and vers.  YMMV. */ | 
 | void fs_file_init_dir(struct fs_file *f, uint16_t dir_type, uint32_t dir_dev, | 
 |                       struct username *user, int perm) | 
 | { | 
 | 	struct dir *dir = &f->dir; | 
 |  | 
 | 	if (perm & DMDIR) | 
 | 		dir->qid.type |= QTDIR; | 
 | 	if (perm & DMAPPEND) | 
 | 		dir->qid.type |= QTAPPEND; | 
 | 	if (perm & DMEXCL) | 
 | 		dir->qid.type |= QTEXCL; | 
 | 	if (perm & DMSYMLINK) | 
 | 		dir->qid.type |= QTSYMLINK; | 
 | 	dir->type = dir_type; | 
 | 	dir->dev = dir_dev; | 
 | 	/* dir->mode stores all the DM bits, but note that userspace can only | 
 | 	 * affect the permissions (S_PMASK) bits. */ | 
 | 	dir->mode = perm; | 
 | 	__set_acmtime(f, FSF_ATIME | FSF_BTIME | FSF_MTIME | FSF_CTIME); | 
 | 	dir->length = 0; | 
 | 	/* TODO: this is a mess if you use anything other than eve.  If you use | 
 | 	 * a process, that memory is sitting in the proc struct, but we have | 
 | 	 * weak refs on it.  What happens when that proc exits?  Disaster. */ | 
 | 	assert(user == &eve); | 
 | 	dir->uid = user->name; | 
 | 	dir->gid = user->name; | 
 | 	dir->muid = user->name; | 
 | } | 
 |  | 
 | static char *copy_str(const char *s) | 
 | { | 
 | 	char *ret; | 
 | 	size_t sz; | 
 |  | 
 | 	if (!s) | 
 | 		return NULL; | 
 | 	sz = strlen(s) + 1; | 
 | 	ret = kmalloc(sz, MEM_WAIT); | 
 | 	memcpy(ret, s, sz); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* Deep copies the contents of dir into the fs_file's dir. */ | 
 | void fs_file_copy_from_dir(struct fs_file *f, struct dir *dir) | 
 | { | 
 | 	memcpy(&f->dir, dir, sizeof(struct dir)); | 
 | 	fs_file_set_basename(f, dir->name); | 
 | 	/* TODO: sort out usernames.  Not only are these just eve, but they are | 
 | 	 * not struct user or something and they ignore whatever the name was | 
 | 	 * from the remote end. */ | 
 | 	f->dir.uid = eve.name; | 
 | 	f->dir.gid = eve.name; | 
 | 	f->dir.muid = eve.name; | 
 | 	f->dir.ext = copy_str(dir->ext); | 
 | } | 
 |  | 
 | void cleanup_fs_file(struct fs_file *f) | 
 | { | 
 | 	if (f->dir.name != f->static_name) | 
 | 		kfree(f->dir.name); | 
 | 	/* TODO: Not sure if these will be refcounted objects in the future. | 
 | 	 * Keep this in sync with other code that manages/sets uid/gid/muid. */ | 
 | 	f->dir.uid = NULL; | 
 | 	f->dir.gid = NULL; | 
 | 	f->dir.muid = NULL; | 
 | 	if (f->dir.ext) | 
 | 		kfree(f->dir.ext); | 
 | 	f->dir.ext = NULL; | 
 | 	pm_destroy(f->pm); | 
 | 	/* Might share mappings in the future.  Catch it here. */ | 
 | 	assert(f->pm == &f->static_pm); | 
 | } | 
 |  | 
 | void __set_acmtime_to(struct fs_file *f, int which, struct timespec *t) | 
 | { | 
 | 	/* WRITE_ONCE, due to lockless peakers */ | 
 | 	if (which & FSF_ATIME) { | 
 | 		WRITE_ONCE(f->dir.atime.tv_sec, t->tv_sec); | 
 | 		WRITE_ONCE(f->dir.atime.tv_nsec, t->tv_nsec); | 
 | 	} | 
 | 	if (which & FSF_BTIME) { | 
 | 		WRITE_ONCE(f->dir.btime.tv_sec, t->tv_sec); | 
 | 		WRITE_ONCE(f->dir.btime.tv_nsec, t->tv_nsec); | 
 | 	} | 
 | 	if (which & FSF_CTIME) { | 
 | 		WRITE_ONCE(f->dir.ctime.tv_sec, t->tv_sec); | 
 | 		WRITE_ONCE(f->dir.ctime.tv_nsec, t->tv_nsec); | 
 | 	} | 
 | 	if (which & FSF_MTIME) { | 
 | 		WRITE_ONCE(f->dir.mtime.tv_sec, t->tv_sec); | 
 | 		WRITE_ONCE(f->dir.mtime.tv_nsec, t->tv_nsec); | 
 | 	} | 
 | } | 
 |  | 
 | /* Caller should hold f's qlock */ | 
 | void __set_acmtime(struct fs_file *f, int which) | 
 | { | 
 | 	struct timespec now = nsec2timespec(epoch_nsec()); | 
 |  | 
 | 	__set_acmtime_to(f, which, &now); | 
 | } | 
 |  | 
 | /* Recall that the frontend always has the most up-to-date info.  This gets | 
 |  * synced to the backend when we flush or fsync. */ | 
 | void set_acmtime_to(struct fs_file *f, int which, struct timespec *t) | 
 | { | 
 | 	ERRSTACK(1); | 
 |  | 
 | 	qlock(&f->qlock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&f->qlock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	if ((which & FSF_ATIME) && !caller_has_file_perms(f, O_READ)) | 
 | 		error(EPERM, "insufficient perms to set atime"); | 
 | 	if ((which & FSF_BTIME) && !caller_is_username(f->dir.uid)) | 
 | 		error(EPERM, "insufficient perms to set btime"); | 
 | 	if ((which & FSF_CTIME) && !caller_has_file_perms(f, O_WRITE)) | 
 | 		error(EPERM, "insufficient perms to set ctime"); | 
 | 	if ((which & FSF_MTIME) && !caller_has_file_perms(f, O_WRITE)) | 
 | 		error(EPERM, "insufficient perms to set mtime"); | 
 | 	__set_acmtime_to(f, which, t); | 
 | 	qunlock(&f->qlock); | 
 | 	poperror(); | 
 | } | 
 |  | 
 | void set_acmtime_noperm(struct fs_file *f, int which) | 
 | { | 
 | 	struct timespec now = nsec2timespec(epoch_nsec()); | 
 |  | 
 | 	/* <3 atime.  We'll go with an hour resolution, like NTFS. */ | 
 | 	if (which == FSF_ATIME) { | 
 | 		if (now.tv_sec < ACCESS_ONCE(f->dir.atime.tv_sec) + 3600) | 
 | 			return; | 
 | 	} | 
 | 	qlock(&f->qlock); | 
 | 	__set_acmtime_to(f, which, &now); | 
 | 	qunlock(&f->qlock); | 
 | } | 
 |  | 
 | size_t fs_file_stat(struct fs_file *f, uint8_t *m_buf, size_t m_buf_sz) | 
 | { | 
 | 	size_t ret; | 
 |  | 
 | 	qlock(&f->qlock); | 
 | 	ret = convD2M(&f->dir, m_buf, m_buf_sz); | 
 | 	qunlock(&f->qlock); | 
 | 	if (ret <= BIT16SZ) | 
 | 		error(EINVAL, "buffer too small for stat"); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* Helper: update file metadata after a write */ | 
 | static void write_metadata(struct fs_file *f, off64_t offset, | 
 |                            bool always_update_len) | 
 | { | 
 | 	qlock(&f->qlock); | 
 | 	f->flags |= FSF_DIRTY; | 
 | 	if (always_update_len || (offset > f->dir.length)) | 
 | 		WRITE_ONCE(f->dir.length, offset); | 
 | 	__set_acmtime(f, FSF_MTIME | FSF_CTIME); | 
 | 	qunlock(&f->qlock); | 
 | } | 
 |  | 
 | /* Punches a hole from begin to end.  Pages completely in the hole will be | 
 |  * removed.  Otherwise, the edges will be zeroed. | 
 |  * | 
 |  * Concurrent truncates with reads and writes can lead to weird data. | 
 |  * truncate()/punch_hole will attempt to remove data in page-sized chunks. | 
 |  * Concurrent users (with a PM refcnt, under the current code) will prevent | 
 |  * removal.  punch_hole will memset those areas to 0, similar to a concurrent | 
 |  * write. | 
 |  * | 
 |  * read() will return data that is up to the file's length.  write() and | 
 |  * punch_hole() will add or remove data and set the length.  When adding data | 
 |  * (write), we add it first, then set the len.  When removing data, we set the | 
 |  * len, then remove it.  If you mix those ops, the len could be set above an | 
 |  * area where the data is still being mucked with.  read/write/mmap all grab | 
 |  * references to the PM's page slot, locking the page in the page cache for a | 
 |  * little.  Truncate often won't remove those pages, but it will try to zero | 
 |  * them.  reads and mmaps will check the length on their own, while it is being | 
 |  * changed by other ops. | 
 |  * | 
 |  * A few examples: | 
 |  * - Trunc to 0 during write to N.  A reader might get zeros instead of the data | 
 |  *   written (trunc was dropping/zeroing the pages after write wrote them). | 
 |  * - Trunc to 0, trunc back to N, with concurrent reads/mmaps of the area in | 
 |  *   between: a reader might see the old data or tears in the data. | 
 |  * - mmaps of pages in a region that gets hole-punched and faults at the same | 
 |  *   time might not get a SIGBUS / ESPIPE.  That length check is best effort. | 
 |  * - After we remove hole pages from the page cache, but before we tell the | 
 |  *   backend, a read/write/mmap-fault to a page in the hole could fetch the old | 
 |  *   data from the backend before the FS op removes the data from the backend, | 
 |  *   and we'd end up with some old data.  The root issue here is that the | 
 |  *   frontend is a cache, and it has the most recent version of the data.  In | 
 |  *   the case of hole punching, we want there to be an absence of data. | 
 |  *   Technically, we could have zeroed pages, but we don't want the overhead of | 
 |  *   that.  So we drop the pages - that situation looks the same as not having | 
 |  *   the data in the cache/frontend. | 
 |  * | 
 |  * To prevent these things, we could qlock the entire file during all ops, or | 
 |  * even just for trunc, write, and loading pages into the PM for read.  That was | 
 |  * my first version of this.  But you can imagine backends that don't require | 
 |  * this sort of serialization (e.g. ramfs, future #mnts, etc), and it | 
 |  * complicates some mmap / pagemap code.  If you want the qlock to protect the | 
 |  * invariant (one of which is that the file's contents below len are always | 
 |  * valid; another is that hole punches don't keep old data), then we can add | 
 |  * some sort of file locking. | 
 |  */ | 
 | static void fs_file_punch_hole(struct fs_file *f, off64_t begin, off64_t end) | 
 | { | 
 | 	size_t first_pg_idx, last_pg_idx, nr_pages, zero_amt; | 
 | 	struct page *page; | 
 | 	int error; | 
 |  | 
 | 	/* Caller should check for this */ | 
 | 	assert((long)begin >= 0); | 
 | 	assert((long)end >= 0); | 
 | 	if (end <= begin) | 
 | 		return; | 
 | 	/* We're punching for the range [begin, end), but inclusive for the | 
 | 	 * pages: [first_pg_idx, last_pg_idx]. */ | 
 | 	first_pg_idx = LA2PPN(begin); | 
 | 	last_pg_idx = LA2PPN(ROUNDUP(end, PGSIZE)) - 1; | 
 | 	nr_pages = last_pg_idx - first_pg_idx + 1; | 
 | 	if (PGOFF(begin)) { | 
 | 		error = pm_load_page(f->pm, first_pg_idx, &page); | 
 | 		if (error) | 
 | 			error(-error, "punch_hole pm_load_page failed"); | 
 | 		zero_amt = MIN(PGSIZE - PGOFF(begin), end - begin); | 
 | 		memset(page2kva(page) + PGOFF(begin), 0, zero_amt); | 
 | 		atomic_or(&page->pg_flags, PG_DIRTY); | 
 | 		pm_put_page(page); | 
 | 		first_pg_idx++; | 
 | 		nr_pages--; | 
 | 		if (!nr_pages) | 
 | 			return; | 
 | 	} | 
 | 	if (PGOFF(end)) { | 
 | 		/* if this unaligned end is beyond the EOF, we might pull in a | 
 | 		 * page of zeros, then zero the first part of it. */ | 
 | 		error = pm_load_page(f->pm, last_pg_idx, &page); | 
 | 		if (error) | 
 | 			error(-error, "punch_hole pm_load_page failed"); | 
 | 		memset(page2kva(page), 0, PGOFF(end)); | 
 | 		atomic_or(&page->pg_flags, PG_DIRTY); | 
 | 		pm_put_page(page); | 
 | 		last_pg_idx--; | 
 | 		nr_pages--; | 
 | 		if (!nr_pages) | 
 | 			return; | 
 | 	} | 
 | 	pm_remove_or_zero_pages(f->pm, first_pg_idx, nr_pages); | 
 | 	/* After we removed the pages from the PM, but before we tell the | 
 | 	 * backend, someone could load a backend page.  Note that we only tell | 
 | 	 * the backend about the intermediate pages - we already dealt with the | 
 | 	 * edge pages above, and the PM has the latest, dirty version of them. | 
 | 	 * */ | 
 | 	f->ops->punch_hole(f, first_pg_idx << PGSHIFT, | 
 | 	                   (first_pg_idx + nr_pages) << PGSHIFT); | 
 | } | 
 |  | 
 | void fs_file_truncate(struct fs_file *f, off64_t to) | 
 | { | 
 | 	off64_t old_len = fs_file_get_length(f); | 
 |  | 
 | 	fs_file_perm_check(f, O_WRITE); | 
 | 	if ((to > old_len) && !f->ops->can_grow_to(f, to)) | 
 | 		error(EINVAL, "can't grow file to %lu bytes", to); | 
 | 	write_metadata(f, to, true); | 
 | 	if (to < old_len) { | 
 | 		/* Round up the old_len to avoid making an unnecessary partial | 
 | 		 * page of zeros at the end of the file. */ | 
 | 		fs_file_punch_hole(f, to, ROUNDUP(old_len, PGSIZE)); | 
 | 	} | 
 | } | 
 |  | 
 | /* Standard read.  We sync with write, in that once the length is set, we'll | 
 |  * attempt to read those bytes. */ | 
 | size_t fs_file_read(struct fs_file *f, uint8_t *buf, size_t count, | 
 |                     off64_t offset) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct page *page; | 
 | 	size_t copy_amt, pg_off, pg_idx, total_remaining; | 
 | 	volatile size_t so_far = 0;		/* volatile for waserror */ | 
 | 	const uint8_t *buf_end = buf + count; | 
 | 	int error; | 
 |  | 
 | 	/* These errors should have been caught by higher level code */ | 
 | 	if ((uintptr_t)buf + count < (uintptr_t)buf) | 
 | 		panic("Bad buf %p + count %p", buf, count); | 
 | 	if (offset + count < offset) | 
 | 		panic("Bad offset %p + count %p", offset, count); | 
 | 	if (waserror()) { | 
 | 		if (so_far) { | 
 | 			poperror(); | 
 | 			return so_far; | 
 | 		} | 
 | 		nexterror(); | 
 | 	} | 
 | 	while (buf < buf_end) { | 
 | 		/* Check early, so we don't load pages beyond length needlessly. | 
 | 		 * The PM/FSF op might just create zeroed pages when asked. */ | 
 | 		if (offset + so_far >= fs_file_get_length(f)) | 
 | 			break; | 
 | 		pg_off = PGOFF(offset + so_far); | 
 | 		pg_idx = LA2PPN(offset + so_far); | 
 | 		error = pm_load_page(f->pm, pg_idx, &page); | 
 | 		if (error) | 
 | 			error(-error, "read pm_load_page failed"); | 
 | 		copy_amt = MIN(PGSIZE - pg_off, buf_end - buf); | 
 | 		/* Lockless peak.  Check the len so we don't read beyond EOF. | 
 | 		 * We have a page, but we don't necessarily have access to all | 
 | 		 * of it. */ | 
 | 		total_remaining = fs_file_get_length(f) - (offset + so_far); | 
 | 		if (copy_amt > total_remaining) { | 
 | 			copy_amt = total_remaining; | 
 | 			buf_end = buf + copy_amt; | 
 | 		} | 
 | 		memcpy_to_safe(buf, page2kva(page) + pg_off, copy_amt); | 
 | 		buf += copy_amt; | 
 | 		so_far += copy_amt; | 
 | 		pm_put_page(page); | 
 | 	} | 
 | 	if (so_far) | 
 | 		set_acmtime_noperm(f, FSF_ATIME); | 
 | 	poperror(); | 
 | 	return so_far; | 
 | } | 
 |  | 
 | size_t fs_file_write(struct fs_file *f, const uint8_t *buf, size_t count, | 
 |                      off64_t offset) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct page *page; | 
 | 	size_t copy_amt, pg_off, pg_idx; | 
 | 	volatile size_t so_far = 0;		/* volatile for waserror */ | 
 | 	const uint8_t *buf_end = buf + count; | 
 | 	int error; | 
 |  | 
 | 	/* These errors should have been caught by higher level code */ | 
 | 	if ((uintptr_t)buf + count < (uintptr_t)buf) | 
 | 		panic("Bad buf %p + count %p", buf, count); | 
 | 	if (offset + count < offset) | 
 | 		panic("Bad offset %p + count %p", offset, count); | 
 | 	if (waserror()) { | 
 | 		if (so_far) { | 
 | 			write_metadata(f, offset + so_far, false); | 
 | 			poperror(); | 
 | 			return so_far; | 
 | 		} | 
 | 		nexterror(); | 
 | 	}; | 
 | 	if (offset + count > fs_file_get_length(f)) { | 
 | 		if (!f->ops->can_grow_to(f, offset + count)) | 
 | 			error(EINVAL, "can't write file to %lu bytes", offset + | 
 | 			      count); | 
 | 	} | 
 | 	while (buf < buf_end) { | 
 | 		pg_off = PGOFF(offset + so_far); | 
 | 		pg_idx = LA2PPN(offset + so_far); | 
 | 		error = pm_load_page(f->pm, pg_idx, &page); | 
 | 		if (error) | 
 | 			error(-error, "write pm_load_page failed"); | 
 | 		copy_amt = MIN(PGSIZE - pg_off, buf_end - buf); | 
 | 		/* TODO: If you ask the kernel to write from a user address that | 
 | 		 * will page fault, the memcpy will fail and we'll move on to | 
 | 		 * the next region.  To avoid leaving a chunk of uninitialized | 
 | 		 * memory, we'll zero it out in the page cache.  Otherwise the | 
 | 		 * user could come back and read old kernel data. | 
 | 		 * | 
 | 		 * The real fix will be to have the kernel throw an error if it | 
 | 		 * was a segfault or block if it wasn't.  Note that when the | 
 | 		 * kernel attempts to access the user's page, it does so with a | 
 | 		 * handle_page_fault_nofile, which won't attempt to handle | 
 | 		 * file-backed VMRs *even if* the file is in the page cache. | 
 | 		 * Yikes! */ | 
 | 		if (memcpy_from_safe(page2kva(page) + pg_off, buf, copy_amt)) | 
 | 			memset(page2kva(page) + pg_off, 0, copy_amt); | 
 | 		buf += copy_amt; | 
 | 		so_far += copy_amt; | 
 | 		atomic_or(&page->pg_flags, PG_DIRTY); | 
 | 		pm_put_page(page); | 
 | 	} | 
 | 	assert(buf == buf_end); | 
 | 	assert(count == so_far); | 
 | 	/* We set the len *after* writing for our lockless reads.  If we set len | 
 | 	 * before, then read() could start as soon as we loaded the page (all | 
 | 	 * zeros), but before we wrote the actual data.  They'd get zeros | 
 | 	 * instead of what we added. */ | 
 | 	write_metadata(f, offset + so_far, false); | 
 | 	poperror(); | 
 | 	return so_far; | 
 | } | 
 |  | 
 | static void wstat_mode(struct fs_file *f, int new_mode) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	int mode; | 
 |  | 
 | 	qlock(&f->qlock); | 
 | 	if (waserror()) { | 
 | 		qunlock(&f->qlock); | 
 | 		nexterror(); | 
 | 	} | 
 | 	if (!caller_is_username(f->dir.uid)) | 
 | 		error(EPERM, "wrong user for wstat, need %s", f->dir.uid); | 
 | 	/* Only allowing changes in permissions, not random stuff like whether | 
 | 	 * it is a directory or symlink. */ | 
 | 	static_assert(!(DMMODE_BITS & S_PMASK)); | 
 | 	mode = (f->dir.mode & ~S_PMASK) | (new_mode & S_PMASK); | 
 | 	WRITE_ONCE(f->dir.mode, mode); | 
 | 	__set_acmtime(f, FSF_CTIME); | 
 | 	qunlock(&f->qlock); | 
 | 	poperror(); | 
 | } | 
 |  | 
 | size_t fs_file_wstat(struct fs_file *f, uint8_t *m_buf, size_t m_buf_sz) | 
 | { | 
 | 	struct dir *m_dir; | 
 | 	size_t m_sz; | 
 |  | 
 | 	/* common trick in wstats.  we want the dir and any strings in the M. | 
 | 	 * the strings are smaller than the entire M (which is strings plus the | 
 | 	 * real dir M).  the strings will be placed right after the dir | 
 | 	 * (dir[1]). */ | 
 | 	m_dir = kzmalloc(sizeof(struct dir) + m_buf_sz, MEM_WAIT); | 
 | 	m_sz = convM2D(m_buf, m_buf_sz, &m_dir[0], (char*)&m_dir[1]); | 
 | 	if (!m_sz) { | 
 | 		kfree(m_dir); | 
 | 		error(ENODATA, "couldn't convM2D"); | 
 | 	} | 
 | 	/* We'll probably have similar issues for all of the strings.  At that | 
 | 	 * point, we might not even bother reading the strings in. */ | 
 | 	if (!emptystr(m_dir->name)) | 
 | 		error(EINVAL, "do not rename with wstat"); | 
 | 	if (m_dir->mode != -1) | 
 | 		wstat_mode(f, m_dir->mode); | 
 | 	if (m_dir->length != -1) | 
 | 		fs_file_truncate(f, m_dir->length); | 
 | 	if ((int64_t)m_dir->atime.tv_sec != -1) | 
 | 		set_acmtime_to(f, FSF_ATIME, &m_dir->atime); | 
 | 	if ((int64_t)m_dir->btime.tv_sec != -1) | 
 | 		set_acmtime_to(f, FSF_BTIME, &m_dir->btime); | 
 | 	if ((int64_t)m_dir->ctime.tv_sec != -1) | 
 | 		set_acmtime_to(f, FSF_CTIME, &m_dir->ctime); | 
 | 	if ((int64_t)m_dir->mtime.tv_sec != -1) | 
 | 		set_acmtime_to(f, FSF_MTIME, &m_dir->mtime); | 
 | 	/* TODO: handle uid/gid/muid changes */ | 
 | 	kfree(m_dir); | 
 | 	return m_sz; | 
 | } |