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