c17edb1695a45907299a473b9b06544c6544d2f9
[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             event_queue_entry_t *entry = (event_queue_entry_t*)malloc(sizeof(event_queue_entry_t) + path_length + 1);
79             entry->event.type = type;
80             strcpy(entry->event.filename, path);
81
82             acquire_lock(&watch->lock);
83             list_append(&watch->event_queue, &entry->list);
84             release_lock(&watch->lock);
85
86             if (release_semaphore(&watch->event_semaphore, 1) == ERR_INVALID)
87             {
88                 acquire_lock(&watch->lock);
89                 list_remove(&entry->list);
90                 release_lock(&watch->lock);
91                 free(entry);
92             }
93         }
94
95         dereference(&watch->directory->header);
96     }
97
98     release_lock(&volume->event_watch_list_lock);
99 }
100
101 void file_cleanup(file_t *file)
102 {
103     acquire_resource_exclusive(&file->volume->resource);
104     file->volume->driver->unload_file(file);
105     release_resource(&file->volume->resource);
106
107     file->volume->open_files--;
108 }
109
110 void file_instance_cleanup(file_instance_t *instance)
111 {
112     file_t *file = instance->global;
113
114     if (instance->watch)
115     {
116         ASSERT(file->attributes & FILE_ATTR_DIRECTORY);
117         ASSERT(instance->watch->directory == instance);
118
119         acquire_lock(&file->volume->event_watch_list_lock);
120         list_remove(&instance->watch->list);
121         release_lock(&file->volume->event_watch_list_lock);
122
123         ASSERT(!instance->watch->lock);
124
125         while (instance->watch->event_queue.next != &instance->watch->event_queue)
126         {
127             event_queue_entry_t *entry = CONTAINER_OF(instance->watch->event_queue.next, event_queue_entry_t, list);
128             list_remove(&entry->list);
129             free(entry);
130         }
131
132         free(instance->watch);
133         instance->watch = NULL;
134     }
135
136     file->volume->driver->close_file(instance);
137
138     if (instance->mode & FILE_MODE_DELETE_ON_CLOSE)
139     {
140         if (file->volume->driver->delete_file(file->volume, file->path, FALSE) == ERR_SUCCESS)
141         {
142             file->attributes |= FILE_ATTR_DELETED;
143         }
144     }
145
146     dereference(&file->header);
147 }
148
149 void register_filesystem_driver(fs_driver_t *driver)
150 {
151     acquire_lock(&fs_driver_list_lock);
152     list_append(&fs_driver_list, &driver->list);
153     release_lock(&fs_driver_list_lock);
154 }
155
156 bool_t unregister_filesystem_driver(fs_driver_t *driver)
157 {
158     acquire_lock(&fs_driver_list_lock);
159     list_remove(&driver->list);
160     release_lock(&fs_driver_list_lock);
161
162     return TRUE;
163 }
164
165 dword_t register_mounted_volume(mounted_volume_t *volume)
166 {
167     acquire_resource_exclusive(&volume_list_res);
168
169     volume->resource = 0;
170     volume->open_files = 0;
171     list_init(&volume->event_watch_list);
172     volume->event_watch_list_lock = 0;
173
174     int delimiters = count_delimiters(volume->mountpoint);
175     list_entry_t *ptr;
176
177     for (ptr = volumes.next; ptr != &volumes; ptr = ptr->next)
178     {
179         mounted_volume_t *current = CONTAINER_OF(ptr, mounted_volume_t, list);
180         if (delimiters >= count_delimiters(current->mountpoint)) break;
181     }
182
183     list_put_before(ptr, &volume->list);
184
185     release_resource(&volume_list_res);
186     return ERR_SUCCESS;
187 }
188
189 dword_t unregister_mounted_volume(mounted_volume_t *volume)
190 {
191     if (volume->open_files > 0) return ERR_BUSY;
192     acquire_resource_exclusive(&volume_list_res);
193     acquire_resource_exclusive(&volume->resource);
194
195     list_remove(&volume->list);
196
197     release_resource(&volume->resource);
198     release_resource(&volume_list_res);
199     return ERR_SUCCESS;
200 }
201
202 dword_t normalize_path(const char *path, char *normalized_path)
203 {
204     static const char *block_dev_prefix = "BlockDevices/";
205     static const char *char_dev_prefix = "CharDevices/";
206
207     dword_t ret = ERR_SUCCESS;
208     char *path_copy;
209     char *endptr;
210
211     if (*path == '#')
212     {
213         path_copy = malloc(strlen(path + 1) + strlen(block_dev_prefix) + 1);
214         strcpy(path_copy, block_dev_prefix);
215         strcat(path_copy, path + 1);
216     }
217     else if (*path == '@')
218     {
219         path_copy = malloc(strlen(path + 1) + strlen(char_dev_prefix) + 1);
220         strcpy(path_copy, char_dev_prefix);
221         strcat(path_copy, path + 1);
222     }
223     else
224     {
225         path_copy = strdup(path);
226     }
227
228     char *token;
229     *normalized_path = '\0';
230
231     for (token = strtok(path_copy, PATH_DELIMITER_STRING, &endptr);
232          token != NULL;
233          token = strtok(NULL, PATH_DELIMITER_STRING, &endptr))
234     {
235         if (strcmp(token, ".") == 0) continue;
236         if (strcmp(token, "..") == 0)
237         {
238             char *ptr = strrchr(normalized_path, PATH_DELIMITER_CHAR);
239             if (ptr) *ptr = '\0';
240         }
241
242         if ((strlen(normalized_path) + strlen(token) + 2) > MAX_PATH)
243         {
244             ret = ERR_INVALID;
245             break;
246         }
247
248         if (normalized_path[0]) strcat(normalized_path, PATH_DELIMITER_STRING);
249         strcat(normalized_path, token);
250     }
251
252     free(path_copy);
253     return ret;
254 }
255
256 dword_t mount(const char *device, const char *mountpoint, const char *filesystem, dword_t flags)
257 {
258     dword_t ret = ERR_NOTFOUND;
259     list_entry_t *i;
260     char *safe_device;
261     char *safe_mountpoint;
262     char *safe_filesystem;
263
264     if (get_previous_mode() == USER_MODE)
265     {
266         safe_device = copy_user_string(device);
267         safe_mountpoint = copy_user_string(mountpoint);
268         safe_filesystem = copy_user_string(filesystem);
269     }
270     else
271     {
272         safe_device = (char*)device;
273         safe_mountpoint = (char*)mountpoint;
274         safe_filesystem = (char*)filesystem;
275     }
276
277     acquire_lock(&fs_driver_list_lock);
278
279     for (i = fs_driver_list.next; i != &fs_driver_list; i = i->next)
280     {
281         fs_driver_t *fs = CONTAINER_OF(i, fs_driver_t, list);
282
283         if (filesystem == NULL || strcmp(fs->name, safe_filesystem) == 0)
284         {
285             ret = fs->mount(safe_device, safe_mountpoint, flags);
286             if (ret == ERR_SUCCESS) break;
287         }
288     }
289
290     release_lock(&fs_driver_list_lock);
291
292     if (get_previous_mode() == USER_MODE)
293     {
294         free(safe_device);
295         free(safe_mountpoint);
296         free(safe_filesystem);
297     }
298
299     return ret;
300 }
301
302 dword_t unmount(const char *mountpoint)
303 {
304     acquire_resource_exclusive(&volume_list_res);
305     list_entry_t *ptr;
306     mounted_volume_t *vol = NULL;
307
308     for (ptr = volumes.next; ptr != &volumes; ptr = ptr->next)
309     {
310         mounted_volume_t *current = CONTAINER_OF(ptr, mounted_volume_t, list);
311
312         if (strcmp(current->mountpoint, mountpoint) == 0)
313         {
314             vol = current;
315             break;
316         }
317     }
318
319     release_resource(&volume_list_res);
320
321     if (vol->open_files) return ERR_BUSY;
322     return vol->driver->unmount(vol);
323 }
324
325 dword_t open_file_internal(const char *path, file_instance_t **file_instance, dword_t mode, dword_t attributes)
326 {
327     char normalized_path[MAX_PATH];
328     file_t *file = NULL;
329     file_instance_t *instance = NULL;
330
331     dword_t ret = normalize_path(path, normalized_path);
332     if (ret != ERR_SUCCESS) return ret;
333
334     mounted_volume_t *vol = get_volume_from_path(normalized_path);
335     if (vol == NULL) return ERR_NOTFOUND;
336
337     if (reference_by_name(normalized_path, OBJECT_FILE, (object_t**)&file))
338     {
339         if (file->attributes & FILE_ATTR_DELETED)
340         {
341             if (!(mode & FILE_MODE_CREATE))
342             {
343                 dereference(&file->header);
344                 return ERR_NOTFOUND;
345             }
346
347             mode |= FILE_MODE_TRUNCATE;
348         }
349
350         if (((file->global_mode ^ mode) & (FILE_MODE_SHARE_READ | FILE_MODE_SHARE_WRITE))
351             || (!(file->global_mode & FILE_MODE_SHARE_READ) && (mode & FILE_MODE_READ))
352             || (!(file->global_mode & FILE_MODE_SHARE_WRITE) && (mode & (FILE_MODE_WRITE | FILE_MODE_TRUNCATE))))
353         {
354             dereference(&file->header);
355             return ERR_FORBIDDEN;
356         }
357     }
358     else
359     {
360         file = (file_t*)malloc(sizeof(file_t));
361         if (file == NULL) return ERR_NOMEMORY;
362
363         file->header.name = strdup(normalized_path);
364         file->header.type = OBJECT_FILE;
365         file->volume = vol;
366
367         file->path = &file->header.name[strlen(vol->mountpoint)];
368         if (*file->path == PATH_DELIMITER_CHAR) file->path++;
369
370         file->global_mode = mode;
371         file->attributes = attributes;
372
373         ret = vol->driver->load_file(&file);
374         if (ret != ERR_SUCCESS)
375         {
376             free(file->header.name);
377             free(file);
378             return ret;
379         }
380
381         ret = create_object(&file->header);
382         if (ret != ERR_SUCCESS)
383         {
384             free(file->header.name);
385             free(file);
386             return ret;
387         }
388     }
389
390     instance = (file_instance_t*)malloc(sizeof(file_instance_t));
391     if (instance == NULL)
392     {
393         dereference(&file->header);
394         return ERR_NOMEMORY;
395     }
396
397     instance->header.name = NULL;
398     instance->header.type = OBJECT_FILE_INSTANCE;
399     instance->global = file;
400     instance->mode = mode;
401
402     ret = vol->driver->open_file(&instance);
403     if (ret != ERR_SUCCESS)
404     {
405         free(instance);
406         dereference(&file->header);
407         return ERR_NOMEMORY;
408     }
409
410     ret = create_object(&instance->header);
411     if (ret == ERR_SUCCESS)
412     {
413         *file_instance = instance;
414     }
415     else
416     {
417         free(instance);
418         dereference(&file->header);
419     }
420
421     return ret;
422 }
423
424 dword_t open_file(const char *path, handle_t *handle, dword_t mode, dword_t attributes)
425 {
426     dword_t ret = ERR_SUCCESS;
427     file_instance_t *file;
428     char *safe_path;
429     handle_t safe_handle = 0;
430
431     if (get_previous_mode() == USER_MODE)
432     {
433         if (!check_usermode(handle, sizeof(handle_t))) return ERR_BADPTR;
434         safe_path = copy_user_string(path);
435         if (safe_path == NULL)
436         {
437             ret = ERR_BADPTR;
438             goto cleanup;
439         }
440     }
441     else safe_path = (char*)path;
442
443     ret = open_file_internal(safe_path, &file, mode, attributes);
444     if (ret != ERR_SUCCESS) goto cleanup;
445
446     ret = open_object(&file->header, 0, &safe_handle);
447     if (ret != ERR_SUCCESS)
448     {
449         dereference(&file->header);
450         goto cleanup;
451     }
452
453     EH_TRY
454     {
455         *handle = safe_handle;
456     }
457     EH_CATCH
458     {
459         close_object(safe_handle);
460         ret = ERR_BADPTR;
461     }
462     EH_DONE;
463
464 cleanup:
465     if (get_previous_mode() == USER_MODE) free(safe_path);
466     return ret;
467 }
468
469 dword_t delete_file(const char *path)
470 {
471     dword_t ret = ERR_SUCCESS;
472     char normalized_path[MAX_PATH];
473     char *safe_path;
474
475     if (get_previous_mode() == USER_MODE)
476     {
477         safe_path = copy_user_string(path);
478         if (safe_path == NULL) return ERR_NOMEMORY;
479     }
480     else
481     {
482         safe_path = (char*)path;
483     }
484
485     ret = normalize_path(safe_path, normalized_path);
486     if (ret != ERR_SUCCESS) goto cleanup;
487
488     mounted_volume_t *vol = get_volume_from_path(normalized_path);
489     if (vol == NULL) return ERR_NOTFOUND;
490
491     acquire_resource_exclusive(&vol->resource);
492
493     file_t *file = NULL;
494     reference_by_name(normalized_path, OBJECT_FILE, (object_t**)&file);
495
496     char *relative_path = &normalized_path[strlen(vol->mountpoint)];
497     if (*relative_path == PATH_DELIMITER_CHAR) relative_path++;
498     ret = vol->driver->delete_file(vol, relative_path, file == NULL);
499
500     if (ret == ERR_SUCCESS && file != NULL)
501     {
502         file->attributes |= FILE_ATTR_DELETED;
503         dereference(&file->header);
504     }
505
506     release_resource(&vol->resource);
507
508 cleanup:
509     if (get_previous_mode() == USER_MODE) free(safe_path);
510     return ret;
511 }
512
513 dword_t query_file(handle_t handle, file_info_type_t type, void *buffer, size_t size)
514 {
515     dword_t ret = ERR_SUCCESS;
516     void *safe_buffer;
517     file_instance_t *file = NULL;
518
519     if (get_previous_mode() == USER_MODE)
520     {
521         if (!check_usermode(buffer, size)) return ERR_BADPTR;
522
523         safe_buffer = malloc(size);
524         if (safe_buffer == NULL) return ERR_NOMEMORY;
525         memset(safe_buffer, 0, size);
526     }
527     else
528     {
529         safe_buffer = buffer;
530     }
531
532     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
533
534     acquire_resource_shared(&file->global->volume->resource);
535
536     switch (type)
537     {
538     case FILE_INFO_ATTRIBUTES:
539         if (size >= sizeof(dword_t)) *((dword_t*)safe_buffer) = file->global->attributes;
540         else ret = ERR_SMALLBUF;
541         break;
542
543     case FILE_INFO_NAME:
544         if (size >= (strlen(file->global->header.name) + 1)) strncpy(safe_buffer, file->global->header.name, size);
545         else ret = ERR_SMALLBUF;
546         break;
547
548     case FILE_INFO_TIME:
549         ret = ERR_NOSYSCALL; // TODO
550         break;
551
552     case FILE_INFO_SIZE:
553         if (size >= sizeof(qword_t)) *((qword_t*)safe_buffer) = file->global->size;
554         else ret = ERR_SMALLBUF;
555         break;
556
557     case FILE_INFO_OWNER:
558         if (size >= sizeof(dword_t)) *((dword_t*)safe_buffer) = file->global->owner_uid;
559         else ret = ERR_SMALLBUF;
560         break;
561
562     default:
563         ret = ERR_INVALID;
564     }
565
566     release_resource(&file->global->volume->resource);
567
568     if (get_previous_mode() == USER_MODE)
569     {
570         EH_TRY memcpy(buffer, safe_buffer, size);
571         EH_CATCH ret = ERR_BADPTR;
572         EH_DONE;
573     }
574
575     if (file) dereference(&file->header);
576     if (get_previous_mode() == USER_MODE) free(safe_buffer);
577     return ret;
578 }
579
580 dword_t set_file(handle_t handle, file_info_type_t set_type, void *buffer, size_t size)
581 {
582     void *safe_buffer;
583     file_instance_t *file = NULL;
584
585     if (get_previous_mode() == USER_MODE)
586     {
587         if (!check_usermode(buffer, size)) return ERR_BADPTR;
588
589         safe_buffer = malloc(size);
590         if (safe_buffer == NULL) return ERR_NOMEMORY;
591         memcpy(safe_buffer, buffer, size);
592     }
593     else
594     {
595         safe_buffer = buffer;
596     }
597
598     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
599
600     acquire_resource_exclusive(&file->global->volume->resource);
601     dword_t ret = file->global->volume->driver->set_file(file->global, set_type, safe_buffer, size);
602     release_resource(&file->global->volume->resource);
603
604     if (file) dereference(&file->header);
605     if (get_previous_mode() == USER_MODE) free(safe_buffer);
606     return ret;
607 }
608
609 dword_t list_directory(handle_t handle, char *filename, bool_t continue_scan)
610 {
611     file_instance_t *directory;
612     char safe_filename[MAX_PATH];
613
614     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&directory)) return ERR_INVALID;
615
616     acquire_resource_shared(&directory->global->volume->resource);
617     dword_t ret = directory->global->volume->driver->list_dir(directory, safe_filename, continue_scan);
618     release_resource(&directory->global->volume->resource);
619
620     if (get_previous_mode() != USER_MODE || check_usermode(filename, strlen(safe_filename) + 1))
621     {
622         EH_TRY strcpy(filename, safe_filename);
623         EH_CATCH ret = ERR_BADPTR;
624         EH_DONE;
625     }
626     else
627     {
628         ret = ERR_BADPTR;
629     }
630
631     return ret;
632 }
633
634 dword_t read_file(handle_t handle, void *buffer, qword_t offset, size_t size, size_t *bytes_read)
635 {
636     dword_t ret;
637     file_instance_t *file;
638     dword_t read_count = 0;
639     void *safe_buffer = NULL;
640
641     if (get_previous_mode() == USER_MODE)
642     {
643         if (!check_usermode(buffer, size)) return ERR_BADPTR;
644         if (!check_usermode(bytes_read, sizeof(dword_t))) return ERR_BADPTR;
645
646         ret = pin_memory(buffer, &safe_buffer, size, FALSE);
647         if (ret != ERR_SUCCESS) return ret;
648     }
649     else
650     {
651         safe_buffer = buffer;
652     }
653
654     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
655
656     if (!(file->mode & FILE_MODE_READ))
657     {
658         ret = ERR_FORBIDDEN;
659         goto cleanup;
660     }
661
662     acquire_resource_shared(&file->global->volume->resource);
663     ret = file->global->volume->driver->read_file(file, safe_buffer, offset, size, &read_count);
664     release_resource(&file->global->volume->resource);
665
666 cleanup:
667     EH_TRY *bytes_read = read_count;
668     EH_CATCH ret = ERR_BADPTR;
669     EH_DONE;
670
671     if (get_previous_mode() == USER_MODE) unmap_memory(safe_buffer);
672     dereference(&file->header);
673     return ret;
674 }
675
676 dword_t write_file(handle_t handle, const void *buffer, qword_t offset, size_t size, dword_t *bytes_written)
677 {
678     dword_t ret;
679     file_instance_t *file;
680     dword_t written_count = 0;
681     void *safe_buffer = NULL;
682
683     if (get_previous_mode() == USER_MODE)
684     {
685         if (!check_usermode(buffer, size)) return ERR_BADPTR;
686         if (!check_usermode(bytes_written, sizeof(dword_t))) return ERR_BADPTR;
687
688         ret = pin_memory(buffer, &safe_buffer, size, TRUE);
689         if (ret != ERR_SUCCESS) return ret;
690     }
691     else
692     {
693         safe_buffer = (void*)buffer;
694     }
695
696     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&file)) return ERR_INVALID;
697
698     if (!(file->mode & FILE_MODE_WRITE))
699     {
700         ret = ERR_FORBIDDEN;
701         goto cleanup;
702     }
703
704     if (!(file->global->volume->flags & MOUNT_FLAG_READONLY))
705     {
706         ret = ERR_WRITEPROT;
707         goto cleanup;
708     }
709
710     acquire_resource_exclusive(&file->global->volume->resource);
711     ret = file->global->volume->driver->write_file(file, safe_buffer, offset, size, &written_count);
712     release_resource(&file->global->volume->resource);
713
714     EH_TRY *bytes_written = written_count;
715     EH_DONE;
716
717 cleanup:
718     if (get_previous_mode() == USER_MODE) unmap_memory(safe_buffer);
719     dereference(&file->header);
720     return ret;
721 }
722
723 dword_t wait_directory_event(handle_t handle, dword_t event_mask, file_event_t *buffer, size_t size, dword_t timeout)
724 {
725     file_instance_t *directory;
726
727     if (get_previous_mode() == USER_MODE && !check_usermode(buffer, size)) return ERR_BADPTR;
728     if (!reference_by_handle(handle, OBJECT_FILE_INSTANCE, (object_t**)&directory)) return ERR_INVALID;
729
730     if (!(directory->global->attributes & FILE_ATTR_DIRECTORY))
731     {
732         dereference(&directory->header);
733         return ERR_ISNOTDIR;
734     }
735
736     if (directory->watch == NULL)
737     {
738         event_watch_entry_t *watch = (event_watch_entry_t*)malloc(sizeof(event_watch_entry_t));
739         watch->directory = directory;
740         init_semaphore(&watch->event_semaphore, 0, EVENT_QUEUE_CAPACITY);
741         list_init(&watch->event_queue);
742
743         mounted_volume_t *volume = directory->global->volume;
744         acquire_lock(&volume->event_watch_list_lock);
745         list_append(&volume->event_watch_list, &watch->list);
746
747         if (!__sync_bool_compare_and_swap(&directory->watch, NULL, watch))
748         {
749             list_remove(&watch->list);
750             free(watch);
751         }
752
753         release_lock(&volume->event_watch_list_lock);
754     }
755
756     directory->watch->event_mask = event_mask;
757
758     dword_t ret = wait_semaphore(&directory->watch->event_semaphore, 1, timeout);
759     if (ret != ERR_SUCCESS)
760     {
761         dereference(&directory->header);
762         return ret;
763     }
764
765     acquire_lock(&directory->watch->lock);
766
767     event_queue_entry_t *entry = CONTAINER_OF(directory->watch->event_queue.next, event_queue_entry_t, list);
768     dword_t event_size = sizeof(entry->event) + strlen(entry->event.filename) + 1;
769
770     if (size >= event_size)
771     {
772         EH_TRY memcpy(buffer, &entry->event, event_size);
773         EH_CATCH ret = ERR_BADPTR;
774         EH_DONE;
775
776         if (ret == ERR_SUCCESS)
777         {
778             list_remove(&entry->list);
779             free(entry);
780         }
781     }
782     else
783     {
784         ret = ERR_SMALLBUF;
785     }
786
787     if (ret != ERR_SUCCESS) release_semaphore(&directory->watch->event_semaphore, 1);
788     release_lock(&directory->watch->lock);
789     dereference(&directory->header);
790     return ret;
791 }