1 /* Part of libvboxfs - (c) 2012, D.C. van Moolenbroek */
2
3 #include "inc.h"
4
5 /*
6 * Directories will generally be accessed sequentially, but there is no
7 * guarantee that this is actually the case. In particular, multiple user
8 * processes may iterate over the same directory concurrently, and since
9 * process information is lost in the VFS/FS protocol, the result is a
10 * nonsequential pattern on the same single directory handle. Therefore, we
11 * must support random access. Since the VirtualBox shared folders interface
12 * does not allow for random access (the resume point cannot be used for this),
13 * we choose to read in the contents of the directory upon open, and cache it
14 * until the directory is closed again. The risk is that the cached contents
15 * will go stale.
16 *
17 * The directory will always be closed once one reader finishes going through
18 * the entire directory, so the problem is rather limited anyway. Ideally, the
19 * directory contents would be refreshed upon any invalidating local action as
20 * well as after a certain time span (since the file system can be changed from
21 * the host as well). This is currently not implemented, because the odds of
22 * things going wrong are pretty small, and the effects are not devastating.
23 *
24 * The calling functions may also request the same directory entry twice in a
25 * row, because the entry does not fit in the user buffer the first time. The
26 * routines here optimize for repeat-sequential access, while supporting fully
27 * random access as well.
28 */
29
30 #define VBOXFS_DIRDATA_SIZE 8192 /* data allocation granularity */
31
32 typedef struct vboxfs_dirblock_s {
33 STAILQ_ENTRY(vboxfs_dirblock_s) next;
34 unsigned int count;
35 char data[VBOXFS_DIRDATA_SIZE];
36 } vboxfs_dirblock_t;
37
38 typedef struct {
39 STAILQ_HEAD(blocks, vboxfs_dirblock_s) blocks;
40 unsigned int index;
41 vboxfs_dirblock_t *block;
42 unsigned int bindex;
43 unsigned int bpos;
44 } vboxfs_dirdata_t;
45
46 /*
47 * Free the memory allocated for the given directory contents storage.
48 */
49 static void
free_dir(vboxfs_dirdata_t * dirdata)50 free_dir(vboxfs_dirdata_t *dirdata)
51 {
52 vboxfs_dirblock_t *block;
53
54 while (!STAILQ_EMPTY(&dirdata->blocks)) {
55 block = STAILQ_FIRST(&dirdata->blocks);
56
57 STAILQ_REMOVE_HEAD(&dirdata->blocks, next);
58
59 free(block);
60 }
61
62 free(dirdata);
63 }
64
65 /*
66 * Read all the contents of the given directory, allocating memory as needed to
67 * store the data.
68 */
69 static int
read_dir(vboxfs_handle_t handle,sffs_dir_t * dirp)70 read_dir(vboxfs_handle_t handle, sffs_dir_t *dirp)
71 {
72 vboxfs_dirdata_t *dirdata;
73 vboxfs_dirblock_t *block;
74 vbox_param_t param[8];
75 unsigned int count;
76 int r;
77
78 dirdata = (vboxfs_dirdata_t *) malloc(sizeof(vboxfs_dirdata_t));
79 if (dirdata == NULL)
80 return ENOMEM;
81
82 memset(dirdata, 0, sizeof(*dirdata));
83 STAILQ_INIT(&dirdata->blocks);
84
85 r = OK;
86
87 do {
88 block =
89 (vboxfs_dirblock_t *) malloc(sizeof(vboxfs_dirblock_t));
90 if (block == NULL) {
91 r = ENOMEM;
92 break;
93 }
94
95 vbox_set_u32(¶m[0], vboxfs_root);
96 vbox_set_u64(¶m[1], handle);
97 vbox_set_u32(¶m[2], 0); /* flags */
98 vbox_set_u32(¶m[3], sizeof(block->data));
99 vbox_set_ptr(¶m[4], NULL, 0, VBOX_DIR_OUT);
100 vbox_set_ptr(¶m[5], block->data, sizeof(block->data),
101 VBOX_DIR_IN);
102 vbox_set_u32(¶m[6], 0); /* resume point */
103 vbox_set_u32(¶m[7], 0); /* number of files */
104
105 /* If the call fails, stop. */
106 if ((r = vbox_call(vboxfs_conn, VBOXFS_CALL_LIST, param, 8,
107 NULL)) != OK) {
108 free(block);
109 break;
110 }
111
112 /* If the number of returned files is zero, stop. */
113 if ((count = vbox_get_u32(¶m[7])) == 0) {
114 free(block);
115 break;
116 }
117
118 /*
119 * Add the block to the list. We could realloc() the block to
120 * free unused tail space, but this is not likely to gain us
121 * much in general.
122 */
123 block->count = count;
124 STAILQ_INSERT_TAIL(&dirdata->blocks, block, next);
125
126 /* Continue until a zero resume point is returned. */
127 } while (vbox_get_u32(¶m[6]) != 0);
128
129 if (r != OK) {
130 free_dir(dirdata);
131
132 return r;
133 }
134
135 dirdata->block = STAILQ_FIRST(&dirdata->blocks);
136
137 *dirp = (sffs_dir_t) dirdata;
138
139 return OK;
140 }
141
142 /*
143 * Open a directory.
144 */
145 int
vboxfs_opendir(const char * path,sffs_dir_t * handle)146 vboxfs_opendir(const char *path, sffs_dir_t *handle)
147 {
148 vboxfs_handle_t h;
149 int r;
150
151 /* Open the directory. */
152 if ((r = vboxfs_open_file(path, O_RDONLY, S_IFDIR, &h, NULL)) != OK)
153 return r;
154
155 /*
156 * Upon success, read in the full contents of the directory right away.
157 * If it succeeds, this will also set the caller's directory handle.
158 */
159 r = read_dir(h, handle);
160
161 /* We do not need the directory to be open anymore now. */
162 vboxfs_close_file(h);
163
164 return r;
165 }
166
167 /*
168 * Read one entry from a directory. On success, copy the name into buf, and
169 * return the requested attributes. If the name (including terminating null)
170 * exceeds size, return ENAMETOOLONG. Do not return dot and dot-dot entries.
171 * Return ENOENT if the index exceeds the number of files.
172 */
173 int
vboxfs_readdir(sffs_dir_t handle,unsigned int index,char * buf,size_t size,struct sffs_attr * attr)174 vboxfs_readdir(sffs_dir_t handle, unsigned int index, char *buf, size_t size,
175 struct sffs_attr *attr)
176 {
177 vboxfs_dirdata_t *dirdata;
178 vboxfs_dirinfo_t *dirinfo;
179 int r;
180
181 dirdata = (vboxfs_dirdata_t *) handle;
182
183 /*
184 * If the saved index exceeds the requested index, start from the
185 * beginning.
186 */
187 if (dirdata->index > index) {
188 dirdata->index = 0;
189 dirdata->bindex = 0;
190 dirdata->bpos = 0;
191 dirdata->block = STAILQ_FIRST(&dirdata->blocks);
192 }
193
194 /* Loop until we find the requested entry or we run out of entries. */
195 while (dirdata->block != NULL) {
196 dirinfo =
197 (vboxfs_dirinfo_t *) &dirdata->block->data[dirdata->bpos];
198
199 /* Consider all entries that are not dot or dot-dot. */
200 if (dirinfo->name.len > 2 || dirinfo->name.data[0] != '.' ||
201 (dirinfo->name.len == 2 && dirinfo->name.data[1] != '.')) {
202
203 if (dirdata->index == index)
204 break;
205
206 dirdata->index++;
207 }
208
209 /* Advance to the next entry. */
210 dirdata->bpos += offsetof(vboxfs_dirinfo_t, name) +
211 offsetof(vboxfs_path_t, data) + dirinfo->name.size;
212 if (++dirdata->bindex >= dirdata->block->count) {
213 dirdata->block = STAILQ_NEXT(dirdata->block, next);
214 dirdata->bindex = 0;
215 dirdata->bpos = 0;
216 }
217 }
218
219 /* Not enough files to satisfy the request? */
220 if (dirdata->block == NULL)
221 return ENOENT;
222
223 /* Return the information for the file we found. */
224 if ((r = vboxfs_get_path(&dirinfo->name, buf, size)) != OK)
225 return r;
226
227 vboxfs_get_attr(attr, &dirinfo->info);
228
229 return OK;
230 }
231
232 /*
233 * Close a directory.
234 */
235 int
vboxfs_closedir(sffs_dir_t handle)236 vboxfs_closedir(sffs_dir_t handle)
237 {
238 vboxfs_dirdata_t *dirdata;
239
240 dirdata = (vboxfs_dirdata_t *) handle;
241
242 free_dir(dirdata);
243
244 return OK;
245 }
246