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
init_buf(size_t size)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
cleanup_buf(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
fs_read(ino_t ino_nr,struct fsdriver_data * data,size_t bytes,off_t pos,int __unused call)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
fs_write(ino_t ino_nr,struct fsdriver_data * data,size_t bytes,off_t pos,int __unused call)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
fs_trunc(ino_t ino_nr,off_t start_pos,off_t end_pos)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
fs_getdents(ino_t ino_nr,struct fsdriver_data * data,size_t bytes,off_t * posp)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