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