 f912036bae
			
		
	
	
		f912036bae
		
	
	
	
	
		
			
			Bad logic introduced as part of the fsdriver changes could cause getdents to terminate early in these libraries. Issue reported by r0ller. Change-Id: If450d5ea85e830584878d8a4ec0f00519355a353
		
			
				
	
	
		
			296 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* VTreeFS - file.c - file and directory I/O */
 | |
| 
 | |
| #include "inc.h"
 | |
| #include <dirent.h>
 | |
| 
 | |
| #define GETDENTS_BUFSIZ 4096
 | |
| 
 | |
| static char *buf = NULL;
 | |
| static size_t bufsize = 0;
 | |
| 
 | |
| /*
 | |
|  * Initialize the main buffer used for I/O.  Return OK or an error code.
 | |
|  */
 | |
| int
 | |
| init_buf(size_t size)
 | |
| {
 | |
| 
 | |
| 	/* A default buffer size, for at least getdents. */
 | |
| 	if (size < GETDENTS_BUFSIZ)
 | |
| 		size = GETDENTS_BUFSIZ;
 | |
| 
 | |
| 	if ((buf = malloc(size)) == NULL)
 | |
| 		return ENOMEM;
 | |
| 
 | |
| 	bufsize = size;
 | |
| 	return OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Free up the I/O buffer.
 | |
|  */
 | |
| void
 | |
| cleanup_buf(void)
 | |
| {
 | |
| 
 | |
| 	free(buf);
 | |
| 
 | |
| 	buf = NULL;
 | |
| 	bufsize = 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Read from a file.
 | |
|  */
 | |
| ssize_t
 | |
| fs_read(ino_t ino_nr, struct fsdriver_data * data, size_t bytes,
 | |
| 	off_t pos, int __unused call)
 | |
| {
 | |
| 	struct inode *node;
 | |
| 	size_t off, chunk;
 | |
| 	ssize_t r, len;
 | |
| 
 | |
| 	/* Try to get inode by its inode number. */
 | |
| 	if ((node = find_inode(ino_nr)) == NULL)
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	/* Check whether the node is a regular file. */
 | |
| 	if (!S_ISREG(node->i_stat.mode))
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	/* For deleted files or with no read hook, feign an empty file. */
 | |
| 	if (is_inode_deleted(node) || vtreefs_hooks->read_hook == NULL)
 | |
| 		return 0; /* EOF */
 | |
| 
 | |
| 	assert(buf != NULL);
 | |
| 	assert(bufsize > 0);
 | |
| 
 | |
| 	/*
 | |
| 	 * Call the read hook to fill the result buffer, repeatedly for as long
 | |
| 	 * as 1) the request is not yet fully completed, and 2) the read hook
 | |
| 	 * fills the entire buffer.
 | |
| 	 */
 | |
| 	for (off = 0; off < bytes; ) {
 | |
| 		/* Get the next result chunk by calling the read hook. */
 | |
| 		chunk = bytes - off;
 | |
| 		if (chunk > bufsize)
 | |
| 			chunk = bufsize;
 | |
| 
 | |
| 		len = vtreefs_hooks->read_hook(node, buf, chunk, pos,
 | |
| 		    get_inode_cbdata(node));
 | |
| 
 | |
| 		/* Copy any resulting data to user space. */
 | |
| 		if (len > 0)
 | |
| 			r = fsdriver_copyout(data, off, buf, len);
 | |
| 		else
 | |
| 			r = len; /* EOF or error */
 | |
| 
 | |
| 		/*
 | |
| 		 * If an error occurred, but we already produced some output,
 | |
| 		 * return a partial result.  Otherwise return the error.
 | |
| 		 */
 | |
| 		if (r < 0)
 | |
| 			return (off > 0) ? off : r;
 | |
| 
 | |
| 		off += len;
 | |
| 		pos += len;
 | |
| 
 | |
| 		if ((size_t)len < bufsize)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	return off;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Write to a file.
 | |
|  */
 | |
| ssize_t
 | |
| fs_write(ino_t ino_nr, struct fsdriver_data * data, size_t bytes, off_t pos,
 | |
| 	int __unused call)
 | |
| {
 | |
| 	struct inode *node;
 | |
| 	size_t off, chunk;
 | |
| 	ssize_t r;
 | |
| 
 | |
| 	if ((node = find_inode(ino_nr)) == NULL)
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	if (!S_ISREG(node->i_stat.mode))
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	if (is_inode_deleted(node) || vtreefs_hooks->write_hook == NULL)
 | |
| 		return EACCES;
 | |
| 
 | |
| 	if (bytes == 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	assert(buf != NULL);
 | |
| 	assert(bufsize > 0);
 | |
| 
 | |
| 	/*
 | |
| 	 * Call the write hook to process the incoming data, repeatedly for as
 | |
| 	 * long as 1) the request is not yet fully completed, and 2) the write
 | |
| 	 * hook processes at least some of the given data.
 | |
| 	 */
 | |
| 	for (off = 0; off < bytes; ) {
 | |
| 		chunk = bytes - off;
 | |
| 		if (chunk > bufsize)
 | |
| 			chunk = bufsize;
 | |
| 
 | |
| 		/* Copy the data from user space. */
 | |
| 		r = fsdriver_copyin(data, off, buf, chunk);
 | |
| 
 | |
| 		/* Call the write hook for the chunk. */
 | |
| 		if (r == OK)
 | |
| 			r = vtreefs_hooks->write_hook(node, buf, chunk, pos,
 | |
| 			    get_inode_cbdata(node));
 | |
| 
 | |
| 		/*
 | |
| 		 * If an error occurred, but we already processed some input,
 | |
| 		 * return a partial result.  Otherwise return the error.
 | |
| 		 */
 | |
| 		if (r < 0)
 | |
| 			return (off > 0) ? off : r;
 | |
| 
 | |
| 		off += r;
 | |
| 		pos += r;
 | |
| 
 | |
| 		if ((size_t)r == 0)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	return off;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Truncate a file.
 | |
|  */
 | |
| int
 | |
| fs_trunc(ino_t ino_nr, off_t start_pos, off_t end_pos)
 | |
| {
 | |
| 	struct inode *node;
 | |
| 
 | |
| 	if ((node = find_inode(ino_nr)) == NULL)
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	if (!S_ISREG(node->i_stat.mode))
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	if (is_inode_deleted(node) || vtreefs_hooks->trunc_hook == NULL)
 | |
| 		return EACCES;
 | |
| 
 | |
| 	/* TODO: translate this case into all-zeroes write callbacks. */
 | |
| 	if (end_pos != 0)
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	return vtreefs_hooks->trunc_hook(node, start_pos,
 | |
| 	    get_inode_cbdata(node));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Retrieve directory entries.
 | |
|  */
 | |
| ssize_t
 | |
| fs_getdents(ino_t ino_nr, struct fsdriver_data * data, size_t bytes,
 | |
| 	off_t * posp)
 | |
| {
 | |
| 	struct fsdriver_dentry fsdentry;
 | |
| 	struct inode *node, *child;
 | |
| 	const char *name;
 | |
| 	off_t pos;
 | |
| 	int r, skip, get_next, indexed;
 | |
| 
 | |
| 	if (*posp >= ULONG_MAX)
 | |
| 		return EIO;
 | |
| 
 | |
| 	if ((node = find_inode(ino_nr)) == NULL)
 | |
| 		return EINVAL;
 | |
| 
 | |
| 	indexed = node->i_indexed;
 | |
| 	get_next = FALSE;
 | |
| 	child = NULL;
 | |
| 
 | |
| 	/* Call the getdents hook, if any, to "refresh" the directory. */
 | |
| 	if (!is_inode_deleted(node) && vtreefs_hooks->getdents_hook != NULL) {
 | |
| 		r = vtreefs_hooks->getdents_hook(node, get_inode_cbdata(node));
 | |
| 		if (r != OK)
 | |
| 			return r;
 | |
| 	}
 | |
| 
 | |
| 	assert(buf != NULL);
 | |
| 	assert(bufsize > 0);
 | |
| 
 | |
| 	fsdriver_dentry_init(&fsdentry, data, bytes, buf, bufsize);
 | |
| 
 | |
| 	for (;;) {
 | |
| 		/* Determine which inode and name to use for this entry. */
 | |
| 		pos = (*posp)++;
 | |
| 
 | |
| 		if (pos == 0) {
 | |
| 			/* The "." entry. */
 | |
| 			child = node;
 | |
| 			name = ".";
 | |
| 		} else if (pos == 1) {
 | |
| 			/* The ".." entry. */
 | |
| 			child = get_parent_inode(node);
 | |
| 			if (child == NULL)
 | |
| 				child = node;
 | |
| 			name = "..";
 | |
| 		} else if (pos - 2 < indexed) {
 | |
| 			/* All indexed entries. */
 | |
| 			child = get_inode_by_index(node, pos - 2);
 | |
| 
 | |
| 			/*
 | |
| 			 * If there is no inode with this particular index,
 | |
| 			 * continue with the next index number.
 | |
| 			 */
 | |
| 			if (child == NULL) continue;
 | |
| 
 | |
| 			name = child->i_name;
 | |
| 		} else {
 | |
| 			/* All non-indexed entries. */
 | |
| 			/*
 | |
| 			 * If this is the first loop iteration, first get to
 | |
| 			 * the non-indexed child identified by the current
 | |
| 			 * position.
 | |
| 			 */
 | |
| 			if (get_next == FALSE) {
 | |
| 				skip = pos - indexed - 2;
 | |
| 				child = get_first_inode(node);
 | |
| 
 | |
| 				/* Skip indexed children. */
 | |
| 				while (child != NULL &&
 | |
| 				    child->i_index != NO_INDEX)
 | |
| 					child = get_next_inode(child);
 | |
| 
 | |
| 				/* Skip to the right position. */
 | |
| 				while (child != NULL && skip-- > 0)
 | |
| 					child = get_next_inode(child);
 | |
| 
 | |
| 				get_next = TRUE;
 | |
| 			} else
 | |
| 				child = get_next_inode(child);
 | |
| 
 | |
| 			/* No more children? Then stop. */
 | |
| 			if (child == NULL)
 | |
| 				break;
 | |
| 
 | |
| 			assert(!is_inode_deleted(child));
 | |
| 
 | |
| 			name = child->i_name;
 | |
| 		}
 | |
| 
 | |
| 		/* Add the directory entry to the output. */
 | |
| 		r = fsdriver_dentry_add(&fsdentry,
 | |
| 		    (ino_t)get_inode_number(child), name, strlen(name),
 | |
| 		    IFTODT(child->i_stat.mode));
 | |
| 		if (r < 0)
 | |
| 			return r;
 | |
| 		if (r == 0)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	return fsdriver_dentry_finish(&fsdentry);
 | |
| }
 |