Improve context switching and scheduler APIs.
[monolithium.git] / kernel / src / filesystem.c
1 /*
2  * filesystem.c
3  *
4  * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <filesystem.h>
21 #include <sync.h>
22 #include <exception.h>
23 #include <memory.h>
24 #include <heap.h>
25 #include <syscalls.h>
26
27 static DECLARE_LIST(fs_driver_list);
28 static DECLARE_LIST(volumes);
29 lock_t fs_driver_list_lock = 0;
30 resource_t volume_list_res = 0;
31
32 static inline int count_delimiters(const char *string)
33 {
34     int count = 0;
35     while ((string = strchr(string, PATH_DELIMITER_CHAR)) != NULL) count++;
36     return count;
37 }
38
39 mounted_volume_t *get_volume_from_path(const char *path)
40 {
41     mounted_volume_t *volume = NULL;
42     list_entry_t *ptr;
43     acquire_resource_shared(&volume_list_res);
44
45     for (ptr = volumes.next; ptr != &volumes; ptr = ptr->next)
46     {
47         mounted_volume_t *vol = CONTAINER_OF(ptr, mounted_volume_t, list);
48         int length = strlen(vol->mountpoint);
49
50         if (strncmp(path, vol->mountpoint, length) == 0
51             && (path[length] == '\0' || path[length] == PATH_DELIMITER_CHAR))
52         {
53             volume = vol;
54             break;
55         }
56     }
57
58     release_resource(&volume_list_res);
59     return volume;
60 }
61
62 void report_filesystem_event(const char *path, dword_t type)
63 {
64     list_entry_t *ptr;
65     mounted_volume_t *volume = get_volume_from_path(path);
66     int path_length = strlen(path);
67     acquire_lock(&volume->event_watch_list_lock);
68
69     for (ptr = volume->event_watch_list.next; ptr != &volume->event_watch_list; ptr = ptr->next)
70     {
71         event_watch_entry_t *watch = CONTAINER_OF(ptr, event_watch_entry_t, list);
72         reference(&watch->directory->header);
73         int prefix_length = strlen(watch->directory->global->path);
74
75         if (strncmp(path, watch->directory->global->path, prefix_length) == 0
76             && path[prefix_length] == PATH_DELIMITER_CHAR)
77         {
78             file_event_t *file_event = __builtin_alloca(sizeof(file_event_t) + path_length);
79             file_event->type = type;
80             strcpy(file_event->filename, path);
81
82             write_pipe(&watch->pipe, &file_event, sizeof(file_event) + path_length + 1);
83         }
84
85         dereference(&watch->directory->header);
86     }
87
88     release_lock(&volume->event_watch_list_lock);
89 }
90
91 void file_cleanup(file_t *file)
92 {
93     acquire_resource_exclusive(&file->volume->resource);
94     file->volume->driver->unload_file(file);
95     release_resource(&file->volume->resource);
96
97     file->volume->open_files--;
98 }
99
100 void file_instance_cleanup(file_instance_t *instance)
101 {
102     file_t *file = instance->global;
103
104     if (instance->watch)
105     {
106         ASSERT(file->attributes & FILE_ATTR_DIRECTORY);
107         ASSERT(instance->watch->directory == instance);
108
109         acquire_lock(&file->volume->event_watch_list_lock);
110         list_remove(&instance->watch->list);
111         release_lock(&file->volume->event_watch_list_lock);
112
113         pipe_cleanup(&instance->watch->pipe.header);
114
115         free(instance->watch);
116         instance->watch = NULL;
117     }
118
119     file->volume->driver->close_file(instance);
120
121     if (instance->mode & FILE_MODE_DELETE_ON_CLOSE)
122     {
123         if (file->volume->driver->delete_file(file->volume, file->path, FALSE) == ERR_SUCCESS)
124         {
125             file->attributes |= FILE_ATTR_DELETED;
126         }
127     }
128
129     dereference(&file->header);
130 }
131
132 void register_filesystem_driver(fs_driver_t *driver)
133 {
134     acquire_lock(&fs_driver_list_lock);
135     list_append(&fs_driver_list, &driver->list);
136     release_lock(&fs_driver_list_lock);
137 }
138
139 bool_t unregister_filesystem_driver(fs_driver_t *driver)
140 {
141     acquire_lock(&fs_driver_list_lock);
142     list_remove(&driver->list);
143     release_lock(&fs_driver_list_lock);
144
145     return TRUE;
146 }
147
148 dword_t register_mounted_volume(mounted_volume_t *volume)
149 {
150     acquire_resource_exclusive(&volume_list_res);
151
152     volume->resource = 0;
153     volume->open_files = 0;
154     list_init(&volume->event_watch_list);
155     volume->event_watch_list_lock = 0;
156
157     int delimiters = count_delimiters(volume->mountpoint);
158     list_entry_t *ptr;
159
160     for (ptr = volumes.next; ptr != &volumes; ptr = ptr->next)
161     {
162         mounted_volume_t *current = CONTAINER_OF(ptr, mounted_volume_t, list);
163         if (delimiters >= count_delimiters(current->mountpoint)) break;
164     }
165
166     list_put_before(ptr, &volume->list);
167
168     release_resource(&volume_list_res);
169     return ERR_SUCCESS;
170 }
171
172 dword_t unregister_mounted_volume(mounted_volume_t *volume)
173 {
174     if (volume->open_files > 0) return ERR_BUSY;
175     acquire_resource_exclusive(&volume_list_res);
176     acquire_resource_exclusive(&volume->resource);
177
178     list_remove(&volume->list);
179
180     release_resource(&volume->resource);
181     release_resource(&volume_list_res);
182     return ERR_SUCCESS;
183 }
184
185 dword_t normalize_path(const char *path, char *normalized_path)
186 {
187     static const char *block_dev_prefix = "BlockDevices/";
188     static const char *char_dev_prefix = "CharDevices/";
189
190     dword_t ret = ERR_SUCCESS;
191     char *path_copy;
192     char *endptr;
193
194     if (*path == '#')
195     {
196         path_copy = malloc(strlen(path + 1) + strlen(block_dev_prefix) + 1);
197         strcpy(path_copy, block_dev_prefix);
198         strcat(path_copy, path + 1);
199     }
200     else if (*path == '@')
201     {
202         path_copy = malloc(strlen(path + 1) + strlen(char_dev_prefix) + 1);
203         strcpy(path_copy, char_dev_prefix);
204         strcat(path_copy, path + 1);
205     }
206     else
207     {
208         path_copy = strdup(path);
209     }
210
211     char *token;
212     *normalized_path = '\0';
213
214     for (token = strtok_r(path_copy, PATH_DELIMITER_STRING, &endptr);
215          token != NULL;
216          token = strtok_r(NULL, PATH_DELIMITER_STRING, &endptr))
217     {
218         if (strcmp(token, ".") == 0) continue;
219         if (strcmp(token, "..") == 0)
220         {
221             char *ptr = strrchr(normalized_path, PATH_DELIMITER_CHAR);
222             if (ptr) *ptr = '\0';
223         }
224
225         if ((strlen(normalized_path) + strlen(token) + 2) > MAX_PATH)
226         {
227             ret = ERR_INVALID;
228             break;
229         }
230
231         if (normalized_path[0]) strcat(normalized_path, PATH_DELIMITER_STRING);
232         strcat(normalized_path, token);
233     }
234
235     free(path_copy);
236     return ret;
237 }
238
239 sysret_t syscall_mount(const char *device, const char *mountpoint, const char *filesystem, dword_t flags)
240 {
241     dword_t ret = ERR_NOTFOUND;
242     list_entry_t *i;
243     char *safe_device;
244     char *safe_mountpoint;
245     char *safe_filesystem;
246
247     if (get_previous_mode() == USER_MODE)
248     {
249         safe_device = copy_user_string(device);
250         safe_mountpoint = copy_user_string(mountpoint);
251         safe_filesystem = copy_user_string(filesystem);
252     }
253     else
254     {
255         safe_device = (char*)device;
256         safe_mountpoint = (char*)mountpoint;
257         safe_filesystem = (char*)filesystem;
258     }
259
260     acquire_lock(&fs_driver_list_lock);
261
262     for (i = fs_driver_list.next; i != &fs_driver_list; i = i->next)
263     {
264         fs_driver_t *fs = CONTAINER_OF(i, fs_driver_t, list);
265
266         if (filesystem == NULL || strcmp(fs->name, safe_filesystem) == 0)
267         {
268             ret = fs->mount(safe_device, safe_mountpoint, flags);
269             if (ret == ERR_SUCCESS) break;
270         }
271     }
272
273     release_lock(&fs_driver_list_lock);
274
275     if (get_previous_mode() == USER_MODE)
276     {
277         free(safe_device);
278         free(safe_mountpoint);
279         free(safe_filesystem);
280     }
281
282     return ret;
283 }
284
285 sysret_t syscall_unmount(const char *mountpoint)
286 {
287     acquire_resource_exclusive(&volume_list_res);
288     list_entry_t *ptr;
289     mounted_volume_t *vol = NULL;
290
291     for (ptr = volumes.next; ptr != &volumes; ptr = ptr->next)
292     {
293         mounted_volume_t *current = CONTAINER_OF(ptr, mounted_volume_t, list);
294
295         if (strcmp(current->mountpoint, mountpoint) == 0)
296         {
297             vol = current;
298             break;
299         }
300     }
301
302     release_resource(&volume_list_res);
303
304     if (vol->open_files) return ERR_BUSY;
305     return vol->driver->unmount(vol);
306 }
307
308 dword_t open_file_internal(const char *path, file_instance_t **file_instance, dword_t mode, dword_t attributes)
309 {
310     char normalized_path[MAX_PATH];
311     file_t *file = NULL;
312     file_instance_t *instance = NULL;
313
314     dword_t ret = normalize_path(path, normalized_path);
315     if (ret != ERR_SUCCESS) return ret;
316
317     mounted_volume_t *vol = get_volume_from_path(normalized_path);
318     if (vol == NULL) return ERR_NOTFOUND;
319
320     if (reference_by_name(normalized_path, OBJECT_FILE, (object_t**)&file))
321     {
322         if (file->attributes & FILE_ATTR_DELETED)
323         {
324             if (!(mode & FILE_MODE_CREATE))
325             {
326                 dereference(&file->header);
327                 return ERR_NOTFOUND;
328             }
329
330             mode |= FILE_MODE_TRUNCATE;
331         }
332
333         if (((file->global_mode ^ mode) & (FILE_MODE_SHARE_READ | FILE_MODE_SHARE_WRITE))
334             || (!(file->global_mode & FILE_MODE_SHARE_READ) && (mode & FILE_MODE_READ))
335             || (!(file->global_mode & FILE_MODE_SHARE_WRITE) && (mode & (FILE_MODE_WRITE | FILE_MODE_TRUNCATE))))
336         {
337             dereference(&file->header);
338             return ERR_FORBIDDEN;
339         }
340     }
341     else
342     {
343         file = (file_t*)malloc(sizeof(file_t));
344         if (file == NULL) return ERR_NOMEMORY;
345
346         init_object(&file->header, normalized_path, OBJECT_FILE);
347         file->volume = vol;
348
349         file->path = &file->header.name[strlen(vol->mountpoint)];
350         if (*file->path == PATH_DELIMITER_CHAR) file->path++;
351
352         file->global_mode = mode;
353         file->attributes = attributes;
354
355         ret = vol->driver->load_file(&file);
356         if (ret != ERR_SUCCESS)
357         {
358             free(file->header.name);
359             free(file);
360             return ret;
361         }
362
363         ret = create_object(&file->header);
364         if (ret != ERR_SUCCESS)
365         {
366             free(file->header.name);
367             free(file);
368             return ret;
369         }
370     }
371
372     instance = (file_instance_t*)malloc(sizeof(file_instance_t));
373     if (instance == NULL)
374     {
375         dereference(&file->header);
376         return ERR_NOMEMORY;
377     }
378
379     init_object(&instance->header, NULL, OBJECT_FILE_INSTANCE);
380     instance->global = file;
381     instance->mode = mode;
382
383     ret = vol->driver->open_file(&instance);
384     if (ret != ERR_SUCCESS)
385     {
386         free(instance);
387         dereference(&file->header);
388         return ERR_NOMEMORY;
389     }
390
391     ret = create_object(&instance->header);
392     if (ret == ERR_SUCCESS)
393     {
394         *file_instance = instance;
395     }
396     else
397     {
398         free(instance);
399         dereference(&file->header);
400     }
401
402     return ret;
403 }
404
405 sysret_t syscall_open_file(const char *path, handle_t *handle, dword_t mode, dword_t attributes)
406 {
407     dword_t ret = ERR_SUCCESS;
408     file_instance_t *file;
409     char *safe_path;
410     handle_t safe_handle = 0;
411
412     if (get_previous_mode() == USER_MODE)
413     {
414         if (!check_usermode(handle, sizeof(handle_t))) return ERR_BADPTR;
415         safe_path = copy_user_string(path);
416         if (safe_path == NULL)
417         {
418             ret = ERR_BADPTR;
419             goto cleanup;
420         }
421     }
422     else safe_path = (char*)path;
423
424     ret = open_file_internal(safe_path, &file, mode, attributes);
425     if (ret != ERR_SUCCESS) goto cleanup;
426
427     ret = open_object(&file->header, 0, &safe_handle);
428     if (ret != ERR_SUCCESS)
429     {
430         dereference(&file->header);
431         goto cleanup;
432     }
433
434     EH_TRY
435     {
436         *handle = safe_handle;
437     }
438     EH_CATCH
439     {
440         syscall_close_object(safe_handle);
441         ret = ERR_BADPTR;
442     }
443     EH_DONE;
444
445 cleanup:
446     if (get_previous_mode() == USER_MODE) free(safe_path);
447     return ret;
448 }
449
450 sysret_t syscall_delete_file(const char *path)
451 {
452     dword_t ret = ERR_SUCCESS;
453     char normalized_path[MAX_PATH];
454     char *safe_path;
455
456     if (get_previous_mode() == USER_MODE)
457     {
458         safe_path = copy_user_string(path);
459         if (safe_path == NULL) return ERR_NOMEMORY;
460     }
461     else
462     {
463         safe_path = (char*)path;
464     }
465
466     ret = normalize_path(safe_path, normalized_path);
467     if (ret != ERR_SUCCESS) goto cleanup;
468
469     mounted_volume_t *vol = get_volume_from_path(normalized_path);
470     if (vol == NULL) return ERR_NOTFOUND;
471
472     if (vol->flags & MOUNT_FLAG_READONLY)
473     {
474         ret = ERR_WRITEPROT;
475         goto cleanup;
476     }
477
478     acquire_resource_exclusive(&vol->resource);
479
480     file_t *file = NULL;
481     reference_by_name(normalized_path, OBJECT_FILE, (object_t**)&file);
482
483     char *relative_path = &normalized_path[strlen(vol->mountpoint)];
484     if (*relative_path == PATH_DELIMITER_CHAR) relative_path++;
485     ret = vol->driver->delete_file(vol, relative_path, file == NULL);
486
487     if (ret == ERR_SUCCESS && file != NULL)
488     {
489         file->attributes |= FILE_ATTR_DELETED;
490         dereference(&file->header);
491     }
492
493     release_resource(&vol->resource);
494
495 cleanup:
496     if (get_previous_mode() == USER_MODE) free(safe_path);
497     return ret;
498 }
499
500 sysret_t syscall_query_file(handle_t handle, file_info_type_t type, void *buffer, size_t size)
501 {
502     dword_t ret = ERR_SUCCESS;
503     void *safe_buffer;
504     file_instance_t *file = NULL;
505
506     if (get_previous_mode() == USER_MODE)
507     {
508         if (!check_usermode(buffer, size)) return ERR_BADPTR;
509
510         safe_buffer = malloc(size);
511         if (safe_buffer == NULL) return ERR_NOMEMORY;
512         memset(safe_buffer, 0, size);
513     }
514     else
515     {
516         safe_buffer = buffer;
517     }
518
519     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
520
521     acquire_resource_shared(&file->global->volume->resource);
522
523     switch (type)
524     {
525     case FILE_INFO_ATTRIBUTES:
526         if (size >= sizeof(dword_t)) *((dword_t*)safe_buffer) = file->global->attributes;
527         else ret = ERR_SMALLBUF;
528         break;
529
530     case FILE_INFO_NAME:
531         if (size >= (strlen(file->global->header.name) + 1)) strncpy(safe_buffer, file->global->header.name, size);
532         else ret = ERR_SMALLBUF;
533         break;
534
535     case FILE_INFO_TIME:
536         ret = ERR_NOSYSCALL; // TODO
537         break;
538
539     case FILE_INFO_SIZE:
540         if (size >= sizeof(qword_t)) *((qword_t*)safe_buffer) = file->global->size;
541         else ret = ERR_SMALLBUF;
542         break;
543
544     case FILE_INFO_OWNER:
545         if (size >= sizeof(dword_t)) *((dword_t*)safe_buffer) = file->global->owner_uid;
546         else ret = ERR_SMALLBUF;
547         break;
548
549     default:
550         ret = ERR_INVALID;
551     }
552
553     release_resource(&file->global->volume->resource);
554
555     if (get_previous_mode() == USER_MODE)
556     {
557         EH_TRY memcpy(buffer, safe_buffer, size);
558         EH_CATCH ret = ERR_BADPTR;
559         EH_DONE;
560     }
561
562     if (file) dereference(&file->header);
563     if (get_previous_mode() == USER_MODE) free(safe_buffer);
564     return ret;
565 }
566
567 sysret_t syscall_set_file(handle_t handle, file_info_type_t set_type, void *buffer, size_t size)
568 {
569     void *safe_buffer;
570     file_instance_t *file = NULL;
571
572     if (get_previous_mode() == USER_MODE)
573     {
574         if (!check_usermode(buffer, size)) return ERR_BADPTR;
575
576         safe_buffer = malloc(size);
577         if (safe_buffer == NULL) return ERR_NOMEMORY;
578         memcpy(safe_buffer, buffer, size);
579     }
580     else
581     {
582         safe_buffer = buffer;
583     }
584
585     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
586
587     acquire_resource_exclusive(&file->global->volume->resource);
588     dword_t ret = file->global->volume->driver->set_file(file->global, set_type, safe_buffer, size);
589     release_resource(&file->global->volume->resource);
590
591     if (file) dereference(&file->header);
592     if (get_previous_mode() == USER_MODE) free(safe_buffer);
593     return ret;
594 }
595
596 sysret_t syscall_list_directory(handle_t handle, char *filename, bool_t continue_scan)
597 {
598     file_instance_t *directory;
599     char safe_filename[MAX_PATH];
600
601     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&directory)) return ERR_INVALID;
602
603     acquire_resource_shared(&directory->global->volume->resource);
604     dword_t ret = directory->global->volume->driver->list_dir(directory, safe_filename, continue_scan);
605     release_resource(&directory->global->volume->resource);
606
607     if (ret == ERR_SUCCESS)
608     {
609         if (get_previous_mode() != USER_MODE || check_usermode(filename, strlen(safe_filename) + 1))
610         {
611             EH_TRY strcpy(filename, safe_filename);
612             EH_CATCH ret = ERR_BADPTR;
613             EH_DONE;
614         }
615         else
616         {
617             ret = ERR_BADPTR;
618         }
619     }
620
621     return ret;
622 }
623
624 sysret_t syscall_read_file(handle_t handle, void *buffer, qword_t offset, size_t size, size_t *bytes_read)
625 {
626     dword_t ret;
627     file_instance_t *file;
628     dword_t read_count = 0;
629     void *safe_buffer = NULL;
630
631     if (get_previous_mode() == USER_MODE)
632     {
633         if (!check_usermode(buffer, size)) return ERR_BADPTR;
634         if (!check_usermode(bytes_read, sizeof(dword_t))) return ERR_BADPTR;
635
636         ret = pin_memory(buffer, &safe_buffer, size, FALSE);
637         if (ret != ERR_SUCCESS) return ret;
638     }
639     else
640     {
641         safe_buffer = buffer;
642     }
643
644     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
645
646     if (!(file->mode & FILE_MODE_READ))
647     {
648         ret = ERR_FORBIDDEN;
649         goto cleanup;
650     }
651
652     acquire_resource_shared(&file->global->volume->resource);
653     ret = file->global->volume->driver->read_file(file, safe_buffer, offset, size, &read_count);
654     release_resource(&file->global->volume->resource);
655
656 cleanup:
657     EH_TRY *bytes_read = read_count;
658     EH_CATCH ret = ERR_BADPTR;
659     EH_DONE;
660
661     if (get_previous_mode() == USER_MODE) unmap_memory(safe_buffer);
662     dereference(&file->header);
663     return ret;
664 }
665
666 sysret_t syscall_write_file(handle_t handle, const void *buffer, qword_t offset, size_t size, dword_t *bytes_written)
667 {
668     dword_t ret;
669     file_instance_t *file;
670     dword_t written_count = 0;
671     void *safe_buffer = NULL;
672
673     if (get_previous_mode() == USER_MODE)
674     {
675         if (!check_usermode(buffer, size)) return ERR_BADPTR;
676         if (!check_usermode(bytes_written, sizeof(dword_t))) return ERR_BADPTR;
677
678         ret = pin_memory(buffer, &safe_buffer, size, TRUE);
679         if (ret != ERR_SUCCESS) return ret;
680     }
681     else
682     {
683         safe_buffer = (void*)buffer;
684     }
685
686     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
687
688     if (!(file->mode & FILE_MODE_WRITE))
689     {
690         ret = ERR_FORBIDDEN;
691         goto cleanup;
692     }
693
694     if (file->global->volume->flags & MOUNT_FLAG_READONLY)
695     {
696         ret = ERR_WRITEPROT;
697         goto cleanup;
698     }
699
700     acquire_resource_exclusive(&file->global->volume->resource);
701     ret = file->global->volume->driver->write_file(file, safe_buffer, offset, size, &written_count);
702     release_resource(&file->global->volume->resource);
703
704     EH_TRY *bytes_written = written_count;
705     EH_DONE;
706
707 cleanup:
708     if (get_previous_mode() == USER_MODE) unmap_memory(safe_buffer);
709     dereference(&file->header);
710     return ret;
711 }
712
713 sysret_t syscall_wait_directory_event(handle_t handle, dword_t event_mask, file_event_t *buffer, size_t size, dword_t timeout)
714 {
715     file_instance_t *directory;
716
717     if (get_previous_mode() == USER_MODE && !check_usermode(buffer, size)) return ERR_BADPTR;
718     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&directory)) return ERR_INVALID;
719
720     if (!(directory->global->attributes & FILE_ATTR_DIRECTORY))
721     {
722         dereference(&directory->header);
723         return ERR_ISNOTDIR;
724     }
725
726     if (directory->watch == NULL)
727     {
728         event_watch_entry_t *watch = (event_watch_entry_t*)malloc(sizeof(event_watch_entry_t));
729         watch->directory = directory;
730         init_pipe(&watch->pipe, PIPE_MESSAGE);
731
732         mounted_volume_t *volume = directory->global->volume;
733         acquire_lock(&volume->event_watch_list_lock);
734         list_append(&volume->event_watch_list, &watch->list);
735
736         if (!__sync_bool_compare_and_swap(&directory->watch, NULL, watch))
737         {
738             list_remove(&watch->list);
739             free(watch);
740         }
741
742         release_lock(&volume->event_watch_list_lock);
743     }
744
745     directory->watch->event_mask = event_mask;
746     dword_t ret = read_pipe(&directory->watch->pipe, buffer, &size, timeout);
747
748     dereference(&directory->header);
749     return ret;
750 }