xref: /minix/minix/lib/libvboxfs/dir.c (revision e3b78ef1)
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
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
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(&param[0], vboxfs_root);
96 		vbox_set_u64(&param[1], handle);
97 		vbox_set_u32(&param[2], 0);		/* flags */
98 		vbox_set_u32(&param[3], sizeof(block->data));
99 		vbox_set_ptr(&param[4], NULL, 0, VBOX_DIR_OUT);
100 		vbox_set_ptr(&param[5], block->data, sizeof(block->data),
101 		    VBOX_DIR_IN);
102 		vbox_set_u32(&param[6], 0);		/* resume point */
103 		vbox_set_u32(&param[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(&param[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(&param[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
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
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
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