1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
#pragma once
#include <stdbool.h>
#include <malloc.h>
#include <hashtbl.h>
#include <mutex.h>
#include <fs.h> // common header
// How to use :
// - When using a filesystem : never call the operations in fs_*_ops_t directly, use
// the functions defined bellow in section "public functions";
// Users of the VFS should not manipulate directly fs_node_t items, only fs_t and fs_handle_t
// - When programming a filesystem : don't worry about allocating the fs_handle_t and fs_t,
// it is done automatically
// - The three types defined below (filesystem, fs node, file handle) are reference-counters to
// some data managed by the underlying filesystem. The following types are aliases to void*,
// but are used to disambiguate the different types of void* : fs_handle_ptr, fs_node_ptr, fs_ptr
// Conventions :
// - ioctl returns negative values on error. null or positives are valid return values from underlying
// driver
// About thread safety :
// - The VFS implements a locking mechanism on FS nodes so that only one operation is executed
// at the same time on a given node (including: open, stat, walk, delete, move, create, ioctl)
// - The VFS does not implement locks on handles : several simultaneous read/writes are possible, and
// it is the driver's responsiblity to prevent that if necessary
// - The VFS does not lock nodes that have a handle open to them : it is the FS code's responsibility
// to refuse some commands if neccessary on a node that is open.
// - The VFS does not implement any locking mechanism on filesystems themselves (e.g. the add_source
// implementation must have its own locking system)
typedef void* fs_handle_ptr;
typedef void* fs_node_ptr;
typedef void* fs_ptr;
// usefull forward declarations
struct fs;
struct fs_node;
struct fs_handle;
typedef struct user_region user_region_t;
// -------------------------------------------
// Structure defining a handle to an open file
typedef struct {
// Standard read-write architecture
size_t (*read)(fs_handle_ptr f, size_t offset, size_t len, char* buf);
size_t (*write)(fs_handle_ptr f, size_t offset, size_t len, const char* buf);
bool (*readdir)(fs_handle_ptr f, dirent_t *d);
int (*ioctl)(fs_handle_ptr f, int command, void* data);
void (*close)(fs_handle_ptr f);
// Page cache architecture, must be implemented by all files opened with FM_MMAP
uint32_t (*get_page)(fs_handle_ptr f, size_t offset);
void (*commit_page)(fs_handle_ptr f, size_t offset, uint64_t time); // notifies change
} fs_handle_ops_t;
typedef struct fs_handle {
// These field are filled by the VFS's generic open() code
struct fs *fs;
struct fs_node *node;
int refs;
// These fields are filled by the FS's specific open() code
fs_handle_ops_t *ops;
fs_handle_ptr data;
int mode;
} fs_handle_t;
// -------------------------------------------
// Structure defining a filesystem node
// In the future this is where FS-level cache may be implemented : calls to dispose() are not
// executed immediately when refcount falls to zero but only when we need to free memory
// Remarks :
// - fs_node_t not to be used in public interface
// - nodes keep a reference to their parent
// - a FS node is either in memory (after one call to walk() on its parent), or not in memory
// it can be in memory only once : if it is in memory, walk() cannot be called on the parent again
// - delete() is expected to unlink the node from its parent (make it inaccessible) and delete
// the corresponding data. It is guaranteed that delete() is never called on a node that
// is currently in use. (different from posix semantics !)
// - move() may be called on a node that is currently in use
// - the root node of a filesystem is created when the filesystem is created
// - dispose() is not called on the root node when a filesystem is shutdown
// - delete() is not expected to delete recursively : it should fail on a non-empty directory
typedef struct {
bool (*open)(fs_node_ptr n, int mode, fs_handle_t *s); // open current node
bool (*stat)(fs_node_ptr n, stat_t *st);
bool (*walk)(fs_node_ptr n, const char* file, struct fs_node *node_d);
bool (*delete)(fs_node_ptr n, const char* file);
bool (*move)(fs_node_ptr dir, const char* old_name, struct fs_node *new_parent, const char *new_name);
bool (*create)(fs_node_ptr n, const char* name, int type); // create sub-node in directory
void (*dispose)(fs_node_ptr n);
} fs_node_ops_t;
typedef struct fs_node {
// These fields are filled by the VFS's generic walk() code
int refs;
mutex_t lock;
char* name; // name in parent
struct fs *fs;
hashtbl_t *children; // not all children, only those in memory
struct fs_node *parent;
// These fields are filled by the FS's specific walk() code
fs_node_ops_t *ops;
fs_node_ptr data;
} fs_node_t;
// -------------------------------------------
// Structure defining a filesystem
typedef struct {
bool (*add_source)(fs_ptr fs, fs_handle_t* source, const char* opts);
void (*shutdown)(fs_ptr fs);
} fs_ops_t;
typedef struct fs {
// Filled by VFS's make_fs()
int refs;
// Filled by FS's specific make()
fs_ops_t *ops;
fs_ptr data;
// Filled by both according to what is specified for fs_node_t
fs_node_t *root;
} fs_t;
// -------------------------------------------
// Structure defining a filesystem driver
typedef struct {
bool (*make)(fs_handle_t *source, const char* opts, fs_t *d);
} fs_driver_ops_t;
// -------------------------------------------
// All functions that return a fs_node_t*, fs_t* or fs_handle_t* return an object
// that will have to be dereferenced when not used anymore.
// (on fs_handle_t*, dereferencing is like closing, only that the actual closing happens
// when refcount falls to zero)
// Internals
void ref_fs_node(fs_node_t *n);
void unref_fs_node(fs_node_t *n);
fs_node_t* fs_walk_one(fs_node_t* from, const char *file);
fs_node_t* fs_walk_path(fs_node_t* from, const char *p);
fs_node_t* fs_walk_path_except_last(fs_node_t* from, const char *p, char* last_file_buf);
// Public functions
void register_fs_driver(const char* name, fs_driver_ops_t *ops);
fs_t* make_fs(const char* driver, fs_handle_t *source, const char* opts);
bool fs_add_source(fs_t *fs, fs_handle_t *source, const char* opts);
void ref_fs(fs_t *fs);
void unref_fs(fs_t *fs);
bool fs_create(fs_t *fs, const char* file, int type);
bool fs_delete(fs_t *fs, const char* file);
bool fs_move(fs_t *fs, const char* from, const char* to);
bool fs_stat(fs_t *fs, const char* file, stat_t *st);
fs_handle_t* fs_open(fs_t *fs, const char* file, int mode);
void ref_file(fs_handle_t *file);
void unref_file(fs_handle_t *file);
int file_get_mode(fs_handle_t *f);
bool file_stat(fs_handle_t *f, stat_t *st);
size_t file_read(fs_handle_t *f, size_t offset, size_t len, char* buf);
size_t file_write(fs_handle_t *f, size_t offset, size_t len, const char* buf);
int file_ioctl(fs_handle_t *f, int command, void* data);
bool file_readdir(fs_handle_t *f, dirent_t *d);
uint32_t file_get_page(fs_handle_t *f, size_t offset);
void file_commit_page(fs_handle_t *f, size_t offset, uint64_t time);
/* vim: set ts=4 sw=4 tw=0 noet :*/
|