diff options
Diffstat (limited to 'firmware/common/fileobj_mgr.c')
-rw-r--r-- | firmware/common/fileobj_mgr.c | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c new file mode 100644 index 0000000000..8e7831d36c --- /dev/null +++ b/firmware/common/fileobj_mgr.c | |||
@@ -0,0 +1,396 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 by Michael Sevakis | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #include "config.h" | ||
22 | #include "system.h" | ||
23 | #include "debug.h" | ||
24 | #include "file.h" | ||
25 | #include "dir.h" | ||
26 | #include "disk_cache.h" | ||
27 | #include "fileobj_mgr.h" | ||
28 | #include "dircache_redirect.h" | ||
29 | |||
30 | /** | ||
31 | * Manages file and directory streams on all volumes | ||
32 | * | ||
33 | * Intended for internal use by disk, file and directory code | ||
34 | */ | ||
35 | |||
36 | |||
37 | /* there will always be enough of these for all user handles, thus these | ||
38 | functions don't return failure codes */ | ||
39 | #define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS) | ||
40 | |||
41 | /* describes the file as an image on the storage medium */ | ||
42 | static struct fileobj_binding | ||
43 | { | ||
44 | struct file_base_binding bind; /* base info list item (first!) */ | ||
45 | uint16_t flags; /* F(D)(O)_* bits of this file/dir */ | ||
46 | uint16_t writers; /* number of writer streams */ | ||
47 | struct filestr_cache cache; /* write mode shared cache */ | ||
48 | file_size_t size; /* size of this file */ | ||
49 | struct ll_head list; /* open streams for this file/dir */ | ||
50 | } fobindings[MAX_FILEOBJS]; | ||
51 | static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR; | ||
52 | static struct ll_head free_bindings; | ||
53 | static struct ll_head busy_bindings[NUM_VOLUMES]; | ||
54 | |||
55 | #define BUSY_BINDINGS(volume) \ | ||
56 | (&busy_bindings[IF_MV_VOL(volume)]) | ||
57 | |||
58 | #define BASEBINDING_LIST(bindp) \ | ||
59 | (BUSY_BINDINGS(BASEBINDING_VOL(bindp))) | ||
60 | |||
61 | #define FREE_BINDINGS() \ | ||
62 | (&free_bindings) | ||
63 | |||
64 | #define BINDING_FIRST(type, volume...) \ | ||
65 | ((struct fileobj_binding *)type##_BINDINGS(volume)->head) | ||
66 | |||
67 | #define BINDING_NEXT(fobp) \ | ||
68 | ((struct fileobj_binding *)(fobp)->bind.node.next) | ||
69 | |||
70 | #define FOR_EACH_BINDING(volume, fobp) \ | ||
71 | for (struct fileobj_binding *fobp = BINDING_FIRST(BUSY, volume); \ | ||
72 | fobp; fobp = BINDING_NEXT(fobp)) | ||
73 | |||
74 | #define STREAM_FIRST(fobp) \ | ||
75 | ((struct filestr_base *)(fobp)->list.head) | ||
76 | |||
77 | #define STREAM_NEXT(s) \ | ||
78 | ((struct filestr_base *)(s)->node.next) | ||
79 | |||
80 | #define STREAM_THIS(s) \ | ||
81 | (s) | ||
82 | |||
83 | #define FOR_EACH_STREAM(what, start, s) \ | ||
84 | for (struct filestr_base *s = STREAM_##what(start); \ | ||
85 | s; s = STREAM_NEXT(s)) | ||
86 | |||
87 | |||
88 | /* syncs information for the stream's old and new parent directory if any are | ||
89 | currently opened */ | ||
90 | static void fileobj_sync_parent(const struct file_base_info *infop[], | ||
91 | int count) | ||
92 | { | ||
93 | FOR_EACH_BINDING(infop[0]->volume, fobp) | ||
94 | { | ||
95 | if ((fobp->flags & (FO_DIRECTORY|FO_REMOVED)) != FO_DIRECTORY) | ||
96 | continue; /* not directory or removed can't be parent of anything */ | ||
97 | |||
98 | struct filestr_base *parentstrp = STREAM_FIRST(fobp); | ||
99 | struct fat_file *parentfilep = &parentstrp->infop->fatfile; | ||
100 | |||
101 | for (int i = 0; i < count; i++) | ||
102 | { | ||
103 | if (!fat_dir_is_parent(parentfilep, &infop[i]->fatfile)) | ||
104 | continue; | ||
105 | |||
106 | /* discard scan/read caches' parent dir info */ | ||
107 | FOR_EACH_STREAM(THIS, parentstrp, s) | ||
108 | filestr_discard_cache(s); | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | |||
113 | /* see if this file has open streams and return that fileobj_binding if so, | ||
114 | else grab a new one from the free list; returns true if this stream is | ||
115 | the only open one */ | ||
116 | static bool binding_assign(const struct file_base_info *srcinfop, | ||
117 | struct fileobj_binding **fobpp) | ||
118 | { | ||
119 | FOR_EACH_BINDING(srcinfop->fatfile.volume, fobp) | ||
120 | { | ||
121 | if (fobp->flags & FO_REMOVED) | ||
122 | continue; | ||
123 | |||
124 | if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile)) | ||
125 | { | ||
126 | /* already has open streams */ | ||
127 | *fobpp = fobp; | ||
128 | return false; | ||
129 | } | ||
130 | } | ||
131 | |||
132 | /* not found - allocate anew */ | ||
133 | *fobpp = BINDING_FIRST(FREE); | ||
134 | ll_remove_first(FREE_BINDINGS()); | ||
135 | ll_init(&(*fobpp)->list); | ||
136 | return true; | ||
137 | } | ||
138 | |||
139 | /* mark descriptor as unused and return to the free list */ | ||
140 | static void binding_add_to_free_list(struct fileobj_binding *fobp) | ||
141 | { | ||
142 | fobp->flags = 0; | ||
143 | ll_insert_last(FREE_BINDINGS(), &fobp->bind.node); | ||
144 | } | ||
145 | |||
146 | /** File and directory internal interface **/ | ||
147 | |||
148 | void file_binding_insert_last(struct file_base_binding *bindp) | ||
149 | { | ||
150 | ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node); | ||
151 | } | ||
152 | |||
153 | void file_binding_remove(struct file_base_binding *bindp) | ||
154 | { | ||
155 | ll_remove(BASEBINDING_LIST(bindp), &bindp->node); | ||
156 | } | ||
157 | |||
158 | #ifdef HAVE_DIRCACHE | ||
159 | void file_binding_insert_first(struct file_base_binding *bindp) | ||
160 | { | ||
161 | ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node); | ||
162 | } | ||
163 | |||
164 | void file_binding_remove_next(struct file_base_binding *prevp, | ||
165 | struct file_base_binding *bindp) | ||
166 | { | ||
167 | ll_remove_next(BASEBINDING_LIST(bindp), &prevp->node); | ||
168 | (void)bindp; | ||
169 | } | ||
170 | #endif /* HAVE_DIRCACHE */ | ||
171 | |||
172 | /* opens the file object for a new stream and sets up the caches; | ||
173 | * the stream must already be opened at the FS driver level and *stream | ||
174 | * initialized. | ||
175 | * | ||
176 | * NOTE: switches stream->infop to the one kept in common for all streams of | ||
177 | * the same file, making a copy for only the first stream | ||
178 | */ | ||
179 | void fileobj_fileop_open(struct filestr_base *stream, | ||
180 | const struct file_base_info *srcinfop, | ||
181 | unsigned int callflags) | ||
182 | { | ||
183 | struct fileobj_binding *fobp; | ||
184 | bool first = binding_assign(srcinfop, &fobp); | ||
185 | |||
186 | /* add stream to this file's list */ | ||
187 | ll_insert_last(&fobp->list, &stream->node); | ||
188 | |||
189 | /* initiate the new stream into the enclave */ | ||
190 | stream->flags = FDO_BUSY | (callflags & (FD_WRITE|FD_WRONLY|FD_APPEND)); | ||
191 | stream->infop = &fobp->bind.info; | ||
192 | stream->fatstr.fatfilep = &fobp->bind.info.fatfile; | ||
193 | stream->bindp = &fobp->bind; | ||
194 | stream->mtx = &stream_mutexes[fobp - fobindings]; | ||
195 | |||
196 | if (first) | ||
197 | { | ||
198 | /* first stream for file */ | ||
199 | fobp->bind.info = *srcinfop; | ||
200 | fobp->flags = FDO_BUSY | FO_SINGLE | | ||
201 | (callflags & (FO_DIRECTORY|FO_TRUNC)); | ||
202 | fobp->writers = 0; | ||
203 | fobp->size = 0; | ||
204 | |||
205 | if (callflags & FD_WRITE) | ||
206 | { | ||
207 | /* first one is a writer */ | ||
208 | fobp->writers = 1; | ||
209 | file_cache_init(&fobp->cache); | ||
210 | filestr_assign_cache(stream, &fobp->cache); | ||
211 | } | ||
212 | |||
213 | fileobj_bind_file(&fobp->bind); | ||
214 | } | ||
215 | else | ||
216 | { | ||
217 | /* additional stream for file */ | ||
218 | fobp->flags &= ~FO_SINGLE; | ||
219 | fobp->flags |= callflags & FO_TRUNC; | ||
220 | |||
221 | /* once a file/directory, always a file/directory; such a change | ||
222 | is a bug */ | ||
223 | if ((callflags ^ fobp->flags) & FO_DIRECTORY) | ||
224 | { | ||
225 | DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", | ||
226 | __func__, stream, callflags); | ||
227 | } | ||
228 | |||
229 | if (fobp->writers) | ||
230 | { | ||
231 | /* already writers present */ | ||
232 | fobp->writers++; | ||
233 | filestr_assign_cache(stream, &fobp->cache); | ||
234 | } | ||
235 | else if (callflags & FD_WRITE) | ||
236 | { | ||
237 | /* first writer */ | ||
238 | fobp->writers = 1; | ||
239 | file_cache_init(&fobp->cache); | ||
240 | FOR_EACH_STREAM(FIRST, fobp, s) | ||
241 | filestr_assign_cache(s, &fobp->cache); | ||
242 | } | ||
243 | /* else another reader */ | ||
244 | } | ||
245 | } | ||
246 | |||
247 | /* close the stream and free associated resources */ | ||
248 | void fileobj_fileop_close(struct filestr_base *stream) | ||
249 | { | ||
250 | switch (stream->flags) | ||
251 | { | ||
252 | case 0: /* not added to manager */ | ||
253 | case FV_NONEXIST: /* forced-closed by unmounting */ | ||
254 | filestr_base_destroy(stream); | ||
255 | return; | ||
256 | } | ||
257 | |||
258 | struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; | ||
259 | unsigned int foflags = fobp->flags; | ||
260 | |||
261 | ll_remove(&fobp->list, &stream->node); | ||
262 | |||
263 | if ((foflags & FO_SINGLE) || fobp->writers == 0) | ||
264 | { | ||
265 | if (foflags & FO_SINGLE) | ||
266 | { | ||
267 | /* last stream for file; close everything */ | ||
268 | fileobj_unbind_file(&fobp->bind); | ||
269 | |||
270 | if (fobp->writers) | ||
271 | file_cache_free(&fobp->cache); | ||
272 | |||
273 | binding_add_to_free_list(fobp); | ||
274 | } | ||
275 | } | ||
276 | else if ((stream->flags & FD_WRITE) && --fobp->writers == 0) | ||
277 | { | ||
278 | /* only readers remain; switch back to stream-local caching */ | ||
279 | FOR_EACH_STREAM(FIRST, fobp, s) | ||
280 | filestr_copy_cache(s, &fobp->cache); | ||
281 | |||
282 | file_cache_free(&fobp->cache); | ||
283 | } | ||
284 | |||
285 | if (!(foflags & FO_SINGLE) && fobp->list.head == fobp->list.tail) | ||
286 | fobp->flags |= FO_SINGLE; /* only one open stream remaining */ | ||
287 | |||
288 | filestr_base_destroy(stream); | ||
289 | } | ||
290 | |||
291 | /* informs manager that file has been created */ | ||
292 | void fileobj_fileop_create(struct filestr_base *stream, | ||
293 | const struct file_base_info *srcinfop, | ||
294 | unsigned int callflags) | ||
295 | { | ||
296 | fileobj_fileop_open(stream, srcinfop, callflags); | ||
297 | fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); | ||
298 | } | ||
299 | |||
300 | /* informs manager that file has been removed */ | ||
301 | void fileobj_fileop_remove(struct filestr_base *stream, | ||
302 | const struct file_base_info *oldinfop) | ||
303 | { | ||
304 | ((struct fileobj_binding *)stream->bindp)->flags |= FO_REMOVED; | ||
305 | fileobj_sync_parent((const struct file_base_info *[]){ oldinfop }, 1); | ||
306 | } | ||
307 | |||
308 | /* informs manager that file has been renamed */ | ||
309 | void fileobj_fileop_rename(struct filestr_base *stream, | ||
310 | const struct file_base_info *oldinfop) | ||
311 | { | ||
312 | /* if there is old info then this was a move and the old parent has to be | ||
313 | informed */ | ||
314 | int count = oldinfop ? 2 : 1; | ||
315 | fileobj_sync_parent(&(const struct file_base_info *[]) | ||
316 | { oldinfop, stream->infop }[2 - count], | ||
317 | count); | ||
318 | } | ||
319 | |||
320 | /* informs manager than directory entries have been updated */ | ||
321 | void fileobj_fileop_sync(struct filestr_base *stream) | ||
322 | { | ||
323 | fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); | ||
324 | } | ||
325 | |||
326 | /* inform manager that file has been truncated */ | ||
327 | void fileobj_fileop_truncate(struct filestr_base *stream) | ||
328 | { | ||
329 | /* let caller update internal info */ | ||
330 | FOR_EACH_STREAM(FIRST, (struct fileobj_binding *)stream->bindp, s) | ||
331 | ftruncate_internal_callback(stream, s); | ||
332 | } | ||
333 | |||
334 | /* query for the pointer to the size storage for the file object */ | ||
335 | file_size_t * fileobj_get_sizep(const struct filestr_base *stream) | ||
336 | { | ||
337 | if (!stream->bindp) | ||
338 | return NULL; | ||
339 | |||
340 | return &((struct fileobj_binding *)stream->bindp)->size; | ||
341 | } | ||
342 | |||
343 | /* query manager bitflags for the file object */ | ||
344 | unsigned int fileobj_get_flags(const struct filestr_base *stream) | ||
345 | { | ||
346 | if (!stream->bindp) | ||
347 | return 0; | ||
348 | |||
349 | return ((struct fileobj_binding *)stream->bindp)->flags; | ||
350 | } | ||
351 | |||
352 | /* change manager bitflags for the file object */ | ||
353 | void fileobj_change_flags(struct filestr_base *stream, | ||
354 | unsigned int flags, unsigned int mask) | ||
355 | { | ||
356 | struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; | ||
357 | if (fobp) | ||
358 | fobp->flags = (fobp->flags & ~mask) | (flags & mask); | ||
359 | } | ||
360 | |||
361 | /* mark all open streams on a device as "nonexistant" */ | ||
362 | void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)) | ||
363 | { | ||
364 | /* right now, there is nothing else to be freed when marking a descriptor | ||
365 | as "nonexistant" but a callback could be added if that changes */ | ||
366 | FOR_EACH_VOLUME(volume, v) | ||
367 | { | ||
368 | struct fileobj_binding *fobp; | ||
369 | while ((fobp = BINDING_FIRST(BUSY, v))) | ||
370 | { | ||
371 | struct filestr_base *s; | ||
372 | while ((s = STREAM_FIRST(fobp))) | ||
373 | { | ||
374 | /* keep it "busy" to avoid races; any valid file/directory | ||
375 | descriptor returned by an open call should always be | ||
376 | closed by whomever opened it (of course!) */ | ||
377 | fileop_onclose_internal(s); | ||
378 | s->flags = FV_NONEXIST; | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | |||
384 | /* one-time init at startup */ | ||
385 | void fileobj_mgr_init(void) | ||
386 | { | ||
387 | for (unsigned int i = 0; i < NUM_VOLUMES; i++) | ||
388 | ll_init(BUSY_BINDINGS(i)); | ||
389 | |||
390 | ll_init(FREE_BINDINGS()); | ||
391 | for (unsigned int i = 0; i < MAX_FILEOBJS; i++) | ||
392 | { | ||
393 | mutex_init(&stream_mutexes[i]); | ||
394 | binding_add_to_free_list(&fobindings[i]); | ||
395 | } | ||
396 | } | ||