xref: /minix/minix/lib/libvtreefs/file.c (revision 0a6a1f1d)
1 /* VTreeFS - file.c - file and directory I/O */
2 
3 #include "inc.h"
4 #include <dirent.h>
5 
6 #define GETDENTS_BUFSIZ 4096
7 
8 static char *buf = NULL;
9 static size_t bufsize = 0;
10 
11 /*
12  * Initialize the main buffer used for I/O.  Return OK or an error code.
13  */
14 int
15 init_buf(size_t size)
16 {
17 
18 	/* A default buffer size, for at least getdents. */
19 	if (size < GETDENTS_BUFSIZ)
20 		size = GETDENTS_BUFSIZ;
21 
22 	if ((buf = malloc(size)) == NULL)
23 		return ENOMEM;
24 
25 	bufsize = size;
26 	return OK;
27 }
28 
29 /*
30  * Free up the I/O buffer.
31  */
32 void
33 cleanup_buf(void)
34 {
35 
36 	free(buf);
37 
38 	buf = NULL;
39 	bufsize = 0;
40 }
41 
42 /*
43  * Read from a file.
44  */
45 ssize_t
46 fs_read(ino_t ino_nr, struct fsdriver_data * data, size_t bytes,
47 	off_t pos, int __unused call)
48 {
49 	struct inode *node;
50 	size_t off, chunk;
51 	ssize_t r, len;
52 
53 	/* Try to get inode by its inode number. */
54 	if ((node = find_inode(ino_nr)) == NULL)
55 		return EINVAL;
56 
57 	/* Check whether the node is a regular file. */
58 	if (!S_ISREG(node->i_stat.mode))
59 		return EINVAL;
60 
61 	/* For deleted files or with no read hook, feign an empty file. */
62 	if (is_inode_deleted(node) || vtreefs_hooks->read_hook == NULL)
63 		return 0; /* EOF */
64 
65 	assert(buf != NULL);
66 	assert(bufsize > 0);
67 
68 	/*
69 	 * Call the read hook to fill the result buffer, repeatedly for as long
70 	 * as 1) the request is not yet fully completed, and 2) the read hook
71 	 * fills the entire buffer.
72 	 */
73 	for (off = 0; off < bytes; ) {
74 		/* Get the next result chunk by calling the read hook. */
75 		chunk = bytes - off;
76 		if (chunk > bufsize)
77 			chunk = bufsize;
78 
79 		len = vtreefs_hooks->read_hook(node, buf, chunk, pos,
80 		    get_inode_cbdata(node));
81 
82 		/* Copy any resulting data to user space. */
83 		if (len > 0)
84 			r = fsdriver_copyout(data, off, buf, len);
85 		else
86 			r = len; /* EOF or error */
87 
88 		/*
89 		 * If an error occurred, but we already produced some output,
90 		 * return a partial result.  Otherwise return the error.
91 		 */
92 		if (r < 0)
93 			return (off > 0) ? (ssize_t)off : r;
94 
95 		off += len;
96 		pos += len;
97 
98 		if ((size_t)len < bufsize)
99 			break;
100 	}
101 
102 	return off;
103 }
104 
105 /*
106  * Write to a file.
107  */
108 ssize_t
109 fs_write(ino_t ino_nr, struct fsdriver_data * data, size_t bytes, off_t pos,
110 	int __unused call)
111 {
112 	struct inode *node;
113 	size_t off, chunk;
114 	ssize_t r;
115 
116 	if ((node = find_inode(ino_nr)) == NULL)
117 		return EINVAL;
118 
119 	if (!S_ISREG(node->i_stat.mode))
120 		return EINVAL;
121 
122 	if (is_inode_deleted(node) || vtreefs_hooks->write_hook == NULL)
123 		return EACCES;
124 
125 	if (bytes == 0)
126 		return 0;
127 
128 	assert(buf != NULL);
129 	assert(bufsize > 0);
130 
131 	/*
132 	 * Call the write hook to process the incoming data, repeatedly for as
133 	 * long as 1) the request is not yet fully completed, and 2) the write
134 	 * hook processes at least some of the given data.
135 	 */
136 	for (off = 0; off < bytes; ) {
137 		chunk = bytes - off;
138 		if (chunk > bufsize)
139 			chunk = bufsize;
140 
141 		/* Copy the data from user space. */
142 		r = fsdriver_copyin(data, off, buf, chunk);
143 
144 		/* Call the write hook for the chunk. */
145 		if (r == OK)
146 			r = vtreefs_hooks->write_hook(node, buf, chunk, pos,
147 			    get_inode_cbdata(node));
148 
149 		/*
150 		 * If an error occurred, but we already processed some input,
151 		 * return a partial result.  Otherwise return the error.
152 		 */
153 		if (r < 0)
154 			return (off > 0) ? (ssize_t)off : r;
155 
156 		off += r;
157 		pos += r;
158 
159 		if ((size_t)r == 0)
160 			break;
161 	}
162 
163 	return off;
164 }
165 
166 /*
167  * Truncate a file.
168  */
169 int
170 fs_trunc(ino_t ino_nr, off_t start_pos, off_t end_pos)
171 {
172 	struct inode *node;
173 
174 	if ((node = find_inode(ino_nr)) == NULL)
175 		return EINVAL;
176 
177 	if (!S_ISREG(node->i_stat.mode))
178 		return EINVAL;
179 
180 	if (is_inode_deleted(node) || vtreefs_hooks->trunc_hook == NULL)
181 		return EACCES;
182 
183 	/* TODO: translate this case into all-zeroes write callbacks. */
184 	if (end_pos != 0)
185 		return EINVAL;
186 
187 	return vtreefs_hooks->trunc_hook(node, start_pos,
188 	    get_inode_cbdata(node));
189 }
190 
191 /*
192  * Retrieve directory entries.
193  */
194 ssize_t
195 fs_getdents(ino_t ino_nr, struct fsdriver_data * data, size_t bytes,
196 	off_t * posp)
197 {
198 	struct fsdriver_dentry fsdentry;
199 	struct inode *node, *child;
200 	const char *name;
201 	off_t pos;
202 	int r, skip, get_next, indexed;
203 
204 	if (*posp >= ULONG_MAX)
205 		return EIO;
206 
207 	if ((node = find_inode(ino_nr)) == NULL)
208 		return EINVAL;
209 
210 	indexed = node->i_indexed;
211 	get_next = FALSE;
212 	child = NULL;
213 
214 	/* Call the getdents hook, if any, to "refresh" the directory. */
215 	if (!is_inode_deleted(node) && vtreefs_hooks->getdents_hook != NULL) {
216 		r = vtreefs_hooks->getdents_hook(node, get_inode_cbdata(node));
217 		if (r != OK)
218 			return r;
219 	}
220 
221 	assert(buf != NULL);
222 	assert(bufsize > 0);
223 
224 	fsdriver_dentry_init(&fsdentry, data, bytes, buf, bufsize);
225 
226 	for (;;) {
227 		/* Determine which inode and name to use for this entry. */
228 		pos = (*posp)++;
229 
230 		if (pos == 0) {
231 			/* The "." entry. */
232 			child = node;
233 			name = ".";
234 		} else if (pos == 1) {
235 			/* The ".." entry. */
236 			child = get_parent_inode(node);
237 			if (child == NULL)
238 				child = node;
239 			name = "..";
240 		} else if (pos - 2 < indexed) {
241 			/* All indexed entries. */
242 			child = get_inode_by_index(node, pos - 2);
243 
244 			/*
245 			 * If there is no inode with this particular index,
246 			 * continue with the next index number.
247 			 */
248 			if (child == NULL) continue;
249 
250 			name = child->i_name;
251 		} else {
252 			/* All non-indexed entries. */
253 			/*
254 			 * If this is the first loop iteration, first get to
255 			 * the non-indexed child identified by the current
256 			 * position.
257 			 */
258 			if (get_next == FALSE) {
259 				skip = pos - indexed - 2;
260 				child = get_first_inode(node);
261 
262 				/* Skip indexed children. */
263 				while (child != NULL &&
264 				    child->i_index != NO_INDEX)
265 					child = get_next_inode(child);
266 
267 				/* Skip to the right position. */
268 				while (child != NULL && skip-- > 0)
269 					child = get_next_inode(child);
270 
271 				get_next = TRUE;
272 			} else
273 				child = get_next_inode(child);
274 
275 			/* No more children? Then stop. */
276 			if (child == NULL)
277 				break;
278 
279 			assert(!is_inode_deleted(child));
280 
281 			name = child->i_name;
282 		}
283 
284 		/* Add the directory entry to the output. */
285 		r = fsdriver_dentry_add(&fsdentry,
286 		    (ino_t)get_inode_number(child), name, strlen(name),
287 		    IFTODT(child->i_stat.mode));
288 		if (r < 0)
289 			return r;
290 		if (r == 0)
291 			break;
292 	}
293 
294 	return fsdriver_dentry_finish(&fsdentry);
295 }
296