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(¶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 146 vboxfs_opendir(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