FreeCalypso > hg > freecalypso-citrine
comparison services/ffs/fsck.c @ 0:75a11d740a02
initial import of gsm-fw from freecalypso-sw rev 1033:5ab737ac3ad7
author | Mychaela Falconia <falcon@freecalypso.org> |
---|---|
date | Thu, 09 Jun 2016 00:02:41 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:75a11d740a02 |
---|---|
1 /****************************************************************************** | |
2 * Flash File System (ffs) | |
3 * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com | |
4 * | |
5 * FFS file system integrity checking, journalling, init and exit | |
6 * | |
7 * $Id: fsck.c 1.3.1.1.1.33 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ | |
8 * | |
9 ******************************************************************************/ | |
10 | |
11 #include <string.h> | |
12 #include <assert.h> | |
13 | |
14 #include "ffs.h" | |
15 #include "core.h" | |
16 #include "drv.h" | |
17 #include "ffstrace.h" | |
18 | |
19 /****************************************************************************** | |
20 * Functions | |
21 ******************************************************************************/ | |
22 | |
23 bref_t blocks_fsck(void); | |
24 iref_t inodes_fsck(void); | |
25 | |
26 /****************************************************************************** | |
27 * Init and Exit | |
28 ******************************************************************************/ | |
29 | |
30 effs_t ffs_initialize(void) | |
31 { | |
32 bref_t b; | |
33 struct inode_s *ip; | |
34 int i; | |
35 | |
36 tlw(led_set(0)); | |
37 tlw(led_on(LED_INIT)); | |
38 ttw(str(TTrInit, "initialize {" NL)); | |
39 tw(tr(TR_BEGIN, TrFsck, "ffs_initialize() {\n")); | |
40 | |
41 // default to non-initialized ffs | |
42 fs.root = 0; | |
43 fs.debug[0] = fs.debug[1] = fs.debug[2] = fs.debug[3] = 0; | |
44 fs.testflags = 0; | |
45 | |
46 tlw(led_on(LED_DRV_INIT)); | |
47 fs.initerror = ffsdrv_init(); // read manufacturer and device ID | |
48 tlw(led_off(LED_DRV_INIT)); | |
49 if (fs.initerror < 0) { | |
50 tlw(led_off(0)); | |
51 tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); | |
52 ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); | |
53 return fs.initerror; | |
54 } | |
55 | |
56 for (i = 0; i < 2; i++) | |
57 { | |
58 tlw(led_on(LED_BLOCKS_FSCK)); | |
59 fs.initerror = EFFS_INVALID; | |
60 fs.initerror = b = blocks_fsck(); | |
61 tlw(led_off(LED_BLOCKS_FSCK)); | |
62 if (fs.initerror < 0) { | |
63 tlw(led_off(0)); | |
64 tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); | |
65 ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); | |
66 return fs.initerror; | |
67 } | |
68 | |
69 tlw(led_on(LED_INODES_FSCK)); | |
70 fs.initerror = EFFS_INVALID; | |
71 fs.initerror = inodes_fsck(); | |
72 tlw(led_off(LED_INODES_FSCK)); | |
73 if (fs.initerror < 0) { | |
74 tlw(led_off(0)); | |
75 tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); | |
76 ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); | |
77 return fs.initerror; | |
78 } | |
79 | |
80 // parse the fs options in the root inode's name | |
81 ip = inode_addr(fs.root); | |
82 fs_params_init(addr2name(offset2addr(location2offset(ip->location)))); | |
83 | |
84 if ((fs.initerror = journal_init(fs.ijournal)) == 0) | |
85 break; | |
86 } | |
87 | |
88 // Init all file_descriptors to zero | |
89 memset(fs.fd, 0, sizeof(struct file_descriptor_s) * fs.fd_max); | |
90 | |
91 // If blocks_fsck() found a block that needs cleaning, we do it, now | |
92 // that all the file system has been initialized. | |
93 if (b > 0) { | |
94 block_clean(b - 1); | |
95 block_free(b - 1); | |
96 } | |
97 | |
98 statistics_init(); | |
99 | |
100 // In target, we do this before entering the task event loop... | |
101 // Otherwise we would in some cases impose a long reboot delay if we did | |
102 // it here. If we test in target it is nessesary to call | |
103 // blocks_reclaim() anyway because we re-init ffs. | |
104 | |
105 #if (TARGET == 1) //NOTEME: can this be done in another/better way? | |
106 #if (WITH_TFFS == 1) | |
107 blocks_reclaim(); | |
108 #endif | |
109 #else | |
110 blocks_reclaim(); | |
111 #endif | |
112 tlw(led_off(LED_INIT)); | |
113 tw(tr(TR_END, TrFsck, "} %d\n", EFFS_OK)); | |
114 ttw(str(TTrInit, "} 0" NL)); | |
115 | |
116 return EFFS_OK; | |
117 } | |
118 | |
119 void fs_params_init(const char *p) | |
120 { | |
121 uint8 opt, digit; | |
122 uint32 n; | |
123 int numdatablocks; | |
124 | |
125 tw(tr(TR_BEGIN, TrFsck, "fsparams_init('%s') {\n", p)); | |
126 | |
127 // Compiled default values | |
128 fs.filename_max = FFS_FILENAME_MAX; | |
129 fs.path_depth_max = FFS_PATH_DEPTH_MAX; | |
130 fs.fd_max = FFS_FD_MAX; | |
131 fs.journal_size = FFS_JOURNAL_SIZE_IN256THS; | |
132 fs.flags = 0; | |
133 fs.testflags = 0; | |
134 | |
135 // Flag that it not has been changed by an input arg. | |
136 fs.block_files_max = 0; | |
137 | |
138 // The default lost bytes percentage of a block before it is reclaimed | |
139 // is approx. 90%. | |
140 fs.lost_threshold = (256 - 256/10); | |
141 | |
142 // If we only have two blocks, we cannot make any reclaims and thus we | |
143 // have a write-once FFS system. | |
144 fs.blocks_free_min = (dev.numblocks > 2 ? 1 : 0); | |
145 | |
146 // Don't count free and inodes blocks | |
147 numdatablocks = dev.numblocks - fs.blocks_free_min - 1; | |
148 | |
149 // Abselute max number of inodes. | |
150 fs.inodes_max = dev.blocksize / sizeof(struct inode_s); | |
151 if (fs.inodes_max > FFS_INODES_MAX) | |
152 fs.inodes_max = FFS_INODES_MAX; | |
153 | |
154 // MUST be true: objects_max <= inodes_max - block_files_max, this is do | |
155 // to the fact that we always need to have block_files_max number of | |
156 // inodes left when we run a data reclaim. | |
157 fs.objects_max = fs.inodes_max / 2; | |
158 | |
159 // Find a suitable chunk_size | |
160 if (dev.numblocks*dev.blocksize > 1024*1024) | |
161 fs.chunk_size_max = 8192; | |
162 else | |
163 fs.chunk_size_max = (2048 > (dev.blocksize / 8) | |
164 ? (dev.blocksize / 8) | |
165 : 2048); | |
166 fs.fd_buf_size = fs.chunk_size_max; | |
167 | |
168 fs.journal_size = fs.journal_size * dev.blocksize / 256; | |
169 if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) | |
170 fs.journal_size = FFS_JOURNAL_SIZE_MIN; | |
171 | |
172 // Set it just below the same amount as entries in one journal file | |
173 fs.block_files_max = (fs.journal_size / sizeof(struct journal_s) | |
174 - FFS_JOURNAL_MARGIN - 2); | |
175 | |
176 // MUST be true: block_files_max < objects_max / 2. But if we want | |
177 // to reach objects_max must block_files_max >= objects_max / number | |
178 // of datablocks, however a big block_files_max require higher | |
179 // reserved_space. | |
180 if (fs.block_files_max > fs.objects_max / 2) | |
181 fs.block_files_max = fs.objects_max / 2 - 4; | |
182 | |
183 // Are we able to reach objects_max? If not then lower the number | |
184 if (fs.objects_max > numdatablocks * fs.block_files_max) | |
185 fs.objects_max = numdatablocks * fs.block_files_max + 10; | |
186 | |
187 // Absolute minimum is RESERVED_LOW the rest is 'workspace' which is | |
188 // needed to have a reasonable performance. | |
189 fs.reserved_space = dev.blocksize / 2 + | |
190 numdatablocks * dev.blocksize / 16 + RESERVED_LOW; | |
191 | |
192 // skip to first char following second slash in name | |
193 n = 0; | |
194 while (*p) { | |
195 if (*p++ == '/') { | |
196 n++; | |
197 if (n == 2) | |
198 break; | |
199 } | |
200 } | |
201 if (n == 2) { | |
202 // while still options to process... | |
203 while (*p) { | |
204 opt = *p++; // save option letter for later | |
205 // collect option value... | |
206 n = 0; | |
207 while ((digit = *p)) { | |
208 if (digit >= '0' && digit <= '9') { | |
209 n = 10 * n + digit - '0'; | |
210 p++; | |
211 } | |
212 else | |
213 break; | |
214 } | |
215 switch (opt) { | |
216 case 'b': dev.numblocks = n; break; | |
217 case 'm': fs.blocks_free_min = n; break; | |
218 case 'i': fs.inodes_max = n; break; | |
219 case 'o': fs.objects_max = n; break; | |
220 case 'n': fs.filename_max = n; break; | |
221 case 'f': fs.block_files_max = n; break; | |
222 case 'd': fs.fd_max = n; break; | |
223 case 's': fs.fd_buf_size = n; break; | |
224 case 't': fs.lost_threshold = n; break; | |
225 case 'z': fs.flags = n; break; | |
226 case 'j': fs.journal_size = n; break; | |
227 case 'c': fs.chunk_size_max = n; break; | |
228 case 'r': fs.reserved_space = n; break; | |
229 // d = &fs.path_depth_max; // really necessary? | |
230 default: | |
231 break; | |
232 } | |
233 } | |
234 } | |
235 | |
236 // Now recompute a few parameters based on adjusted values. | |
237 | |
238 // No journal file thuse no reserved space. | |
239 if (fs.journal_size == 0) { | |
240 fs.block_files_max = fs.objects_max / 2; | |
241 fs.reserved_space = 0; | |
242 fs.block_files_reserved = 0; | |
243 } | |
244 | |
245 else { | |
246 // If journal size is less than minimum must it have been changed by an | |
247 // input arg, recalculate. | |
248 if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) | |
249 fs.journal_size = fs.journal_size * dev.blocksize / 256; | |
250 | |
251 if (fs.reserved_space < RESERVED_LOW) | |
252 fs.reserved_space = fs.reserved_space * dev.blocksize / 256; | |
253 | |
254 // Only one reserved is needed however we want a margin and set it to 2 | |
255 fs.block_files_reserved = 2; | |
256 } | |
257 | |
258 // Don't count free blocks, inode block, reserved space, block headers | |
259 // and the size of one filename. | |
260 fs.filesize_max = numdatablocks * dev.blocksize - fs.reserved_space - | |
261 numdatablocks * BHEADER_SIZE - FFS_FILENAME_MAX; | |
262 | |
263 // Furthermore don't count the overhead from each chunk (alignment) | |
264 fs.filesize_max -= ((fs.filesize_max / fs.chunk_size_max) * dev.atomsize | |
265 + dev.atomsize); | |
266 | |
267 // NOTEME: chunk_size_min is never used | |
268 fs.chunk_size_min = numdatablocks / fs.objects_max; | |
269 | |
270 tw(tr(TR_FUNC, TrFsck, "dev.numblocks = %d\n", dev.numblocks)); | |
271 tw(tr(TR_FUNC, TrFsck, "fs.blocks_free_min = %d\n", fs.blocks_free_min)); | |
272 tw(tr(TR_FUNC, TrFsck, "fs.inodes_max = %d\n", fs.inodes_max)); | |
273 tw(tr(TR_FUNC, TrFsck, "fs.objects_max = %d\n", fs.objects_max)); | |
274 tw(tr(TR_FUNC, TrFsck, "fs.block_files_max = %d\n", fs.block_files_max)); | |
275 tw(tr(TR_FUNC, TrFsck, "fs.block_files_reserved = %d\n", fs.block_files_reserved)); | |
276 tw(tr(TR_FUNC, TrFsck, "fs.chunk_size_max = %d\n", fs.chunk_size_max)); | |
277 tw(tr(TR_FUNC, TrFsck, "fs.filename_max = %d\n", fs.filename_max)); | |
278 tw(tr(TR_FUNC, TrFsck, "fs.lost_threshold = %d\n", fs.lost_threshold)); | |
279 tw(tr(TR_FUNC, TrFsck, "fs.path_depth_max = %d\n", fs.path_depth_max)); | |
280 tw(tr(TR_FUNC, TrFsck, "fs.journal_size = %d\n", fs.journal_size)); | |
281 tw(tr(TR_FUNC, TrFsck, "fs.reserved_space = %d\n", fs.reserved_space)); | |
282 tw(tr(TR_FUNC, TrFsck, "fs.fd_max = %d\n", fs.fd_max)); | |
283 tw(tr(TR_FUNC, TrFsck, "fs.fd_buf_size = 0x%02x\n", fs.fd_buf_size)); | |
284 tw(tr(TR_FUNC, TrFsck, "fs.flags = 0x%02x\n", fs.flags)); | |
285 tw(tr(TR_END, TrFsck, "}\n")); | |
286 } | |
287 | |
288 // TODO: Finish pending commits/writes. | |
289 effs_t ffs_exit(void) | |
290 { | |
291 tw(tr(TR_FUNC, TrFsck, "exit() 0\n")); | |
292 | |
293 | |
294 return EFFS_OK; | |
295 } | |
296 | |
297 #if 0 // Not used in this version | |
298 // Purely for core internal use; Read a file. | |
299 effs_t file_read_int(const char *path, void *src, int size) | |
300 { | |
301 if (fs.initerror != EFFS_OK) | |
302 return fs.initerror; | |
303 | |
304 return object_read(path, src, size, 0); | |
305 } | |
306 | |
307 // Purely for core internal use; Update a file. | |
308 effs_t file_update(const char *path, void *src, int size) | |
309 { | |
310 char *name; | |
311 iref_t i, dir; | |
312 | |
313 if (fs.initerror != EFFS_OK) | |
314 return fs.initerror; | |
315 | |
316 if ((i = object_lookup(path, &name, &dir)) < 0) | |
317 return i; | |
318 | |
319 journal_begin(i); | |
320 | |
321 if ((i = object_create(name, src, size, -dir)) < 0) | |
322 return i; | |
323 | |
324 journal_end(0); | |
325 | |
326 return EFFS_OK; | |
327 } | |
328 #endif | |
329 | |
330 /****************************************************************************** | |
331 * blocks_fsck() | |
332 ******************************************************************************/ | |
333 | |
334 blocksize_t block_used(bref_t b) | |
335 { | |
336 blocksize_t used; | |
337 uint32 *p, *q; | |
338 | |
339 tlw(led_toggle(LED_BLOCKS_FSCK)); | |
340 | |
341 // We search backwards through block to find the last used byte and | |
342 // thus the total number of used bytes. Note that this code depends | |
343 // on the fact that an erased flash location is 0xFF! | |
344 p = (uint32 *) offset2addr(dev.binfo[b].offset); | |
345 for (q = p + dev.blocksize/4 - 4; q > p; q -= 4) { | |
346 if ( ~(q[0] & q[1] & q[2] & q[3]) ) | |
347 break; | |
348 } | |
349 | |
350 if ( ~(q[0] & q[1] & q[2] & q[3]) ) | |
351 q += 4; | |
352 used = atomalign((char *) q - (char *) p); | |
353 | |
354 tw(tr(TR_FUNC, TrFsckLow, "ffs_block_used(%d) %d\n", b, used)); | |
355 | |
356 return used; | |
357 } | |
358 | |
359 | |
360 age_t age_distance(age_t x, age_t y) | |
361 { | |
362 age_t a = x - y; | |
363 | |
364 if (a > 0x8000) | |
365 a = -a; | |
366 | |
367 tw(tr(TR_FUNC, TrFsckLow, "age_distance(%d, %d) %d\n", x, y, a)); | |
368 | |
369 return a; | |
370 } | |
371 | |
372 // For each ffs block, we initialise the basic bstat array information, | |
373 // namely the number of used bytes. Also, we locate the inodes block and if | |
374 // a previous operation was interrupted by a powerfail, we clean it up. | |
375 // | |
376 // We return EFFS_OK if all is fine. If a positive integer is returned, it | |
377 // denotes a block that needs to be cleaned by block_clean() once FFS | |
378 // has been properly intialized (we actually return the block number + 1 | |
379 // because otherwise it would clash with EFFS_OK return code). If no inodes | |
380 // block is found or another error occurs, we return the error code. | |
381 bref_t blocks_fsck(void) | |
382 { | |
383 bref_t b, b_to_clean, b_inode_lost; | |
384 int age_valid; | |
385 age_t age_min, age_max, age_dist, age_dist_min, age_dist_max; | |
386 struct block_header_s *bhp; | |
387 struct block_header_old_s *obhp; | |
388 | |
389 ttw(str(TTrInitLow, "blocks_fsck {" NL)); | |
390 tw(tr(TR_BEGIN, TrFsck, "blocks_fsck() {\n")); | |
391 | |
392 // initialize ages to the illegal/unset value | |
393 age_min = age_max = age_dist = 0; | |
394 | |
395 fs.format = 0; | |
396 fs.inodes = -1; | |
397 fs.newinodes = -1; | |
398 b_inode_lost = -1; | |
399 b_to_clean = EFFS_OK; | |
400 | |
401 for (b = 0; b < dev.numblocks; b++) | |
402 { | |
403 tlw(led_toggle(LED_DRV_INIT)); | |
404 | |
405 // read block flags from flash | |
406 bhp = (struct block_header_s *) offset2addr(dev.binfo[b].offset); | |
407 obhp = (struct block_header_old_s *) bhp; | |
408 | |
409 bstat[b].used = dev.blocksize; | |
410 bstat[b].lost = bstat[b].used; | |
411 bstat[b].flags = bhp->flags; | |
412 bstat[b].objects = 0; | |
413 | |
414 age_valid = 0; | |
415 | |
416 if (bhp->magic_low != BLOCK_MAGIC_LOW || | |
417 bhp->magic_high != BLOCK_MAGIC_HIGH) { | |
418 // The block magic as bad! It *could* be because the flash | |
419 // memory map is incorrect or because another application has | |
420 // spuriously written to the flash or ... who knows what. First | |
421 // we check to see if the reason is that we are dealing with a | |
422 // (really) old ffs format version. | |
423 if (obhp->magic_low == OLD_BLOCK_MAGIC_LOW && | |
424 obhp->magic_high == OLD_FFS_FORMAT_VERSION) { | |
425 tw(tr(TR_FUNC, TrFsck, "OLD ")); | |
426 fs.format = obhp->magic_high; | |
427 // We simulate that all the blocks are data blocks, in order | |
428 // to have some well-defined state that preformat() can work | |
429 // on. Later we will return EFFS_BADFORMAT and otherwise | |
430 // leave everything as it is, *without* modifying anything! | |
431 bstat[b].flags = BF_IS_DATA; | |
432 } | |
433 else { | |
434 // Quickly test if block is in empty state. We do not make a | |
435 // full check with block_used() because that takes too | |
436 // long --- we let preformat() do that. | |
437 if (bhp->magic_low == FLASH_NULL16 && | |
438 bhp->magic_high == FLASH_NULL16 && | |
439 bhp->age == FLASH_NULL16 && | |
440 bhp->version == FLASH_NULL16 && | |
441 bhp->flags == FLASH_NULL16) | |
442 { | |
443 bstat[b].used = 0; | |
444 bstat[b].lost = 0; | |
445 bstat[b].flags = BF_IS_EMPTY; | |
446 tw(tr(TR_FUNC, TrFsck, "EMPTY ")); | |
447 } | |
448 else { | |
449 // If the block is not free, it is probably corrupted. | |
450 // Thus we reset its age and free it. | |
451 tw(tr(TR_FUNC, TrFsck, "magic = 0x%08x\n", | |
452 bhp->magic_low | (bhp->magic_high << 16))); | |
453 ffsdrv.write_halfword(&bhp->age, 0); | |
454 block_free(b); | |
455 tw(tr(TR_FUNC, TrFsck, "BAD ")); | |
456 } | |
457 } | |
458 } | |
459 else { | |
460 fs.format = bhp->version; | |
461 age_valid = 1; | |
462 | |
463 if (!is_block(b, BF_IS_FREE)) { | |
464 bstat[b].used = block_used(b); | |
465 bstat[b].lost = bstat[b].used - BHEADER_SIZE; | |
466 } | |
467 | |
468 if (is_block(b, BF_IS_FREE)) { | |
469 // The only case where we do not call block_used() is | |
470 // when the block is truly free. | |
471 bstat[b].used = 0; | |
472 bstat[b].lost = 0; | |
473 tw(tr(TR_FUNC, TrFsck, "FREE ")); | |
474 ttw(ttr(TTrInitLow, "FREE" NL)); | |
475 | |
476 } | |
477 else if (is_block(b, BF_IS_DATA)) { | |
478 tw(tr(TR_FUNC, TrFsck, "DATA ")); | |
479 ttw(ttr(TTrInitLow, "DATA" NL)); | |
480 } | |
481 else if (is_block(b, BF_IS_CLEANING)) { | |
482 // Here we schedule a block_clean(). Note that we can | |
483 // and do not execute the block cleaning now, as the info | |
484 // that block_clean() needs is not at all ready at this | |
485 // point in the initialization. So we set a flag and then | |
486 // clean the block at the end of ffs_initialize() | |
487 tw(tr(TR_FUNC, TrFsck, "CLEANING ")); | |
488 ttw(ttr(TTrInitLow, "CLEANING" NL)); | |
489 b_to_clean = b + 1; | |
490 } | |
491 else if (is_block(b, BF_IS_COPYING)) { | |
492 tw(tr(TR_FUNC, TrFsck, "COPYING ")); | |
493 ttw(ttr(TTrInitLow, "COPYING" NL)); | |
494 fs.newinodes = b; | |
495 } | |
496 else if (is_block(b, BF_IS_INODES)) { | |
497 tw(tr(TR_FUNC, TrFsck, "INODES ")); | |
498 ttw(ttr(TTrInitLow, "INODES" NL)); | |
499 fs.inodes = b; | |
500 } | |
501 else if (is_block(b, BF_IS_INODES_LOST)) { | |
502 tw(tr(TR_FUNC, TrFsck, "INODESLOST")); | |
503 ttw(ttr(TTrInitLow, "INODESLOST" NL)); | |
504 b_inode_lost = b; | |
505 } | |
506 else { | |
507 block_free(b); | |
508 tw(tr(TR_FUNC, TrFsck, "INVALID ")); | |
509 ttw(ttr(TTrInitLow, "INVALID" NL)); | |
510 } | |
511 } | |
512 | |
513 tw(tr(TR_NULL, TrFsck, " %2d: (0x%05x) %02x, used = %6d\n", | |
514 b, dev.binfo[b].offset, bstat[b].flags & 0xFF, bstat[b].used)); | |
515 | |
516 if (age_valid) { | |
517 if (age_min == 0) { | |
518 // Initialize minimum and maximum block ages | |
519 age_min = age_max = bhp->age; | |
520 tw(tr(TR_FUNC, TrFsckLow, "age_min/max = %d\n", age_min)); | |
521 } | |
522 else { | |
523 age_dist_min = age_distance(bhp->age, age_min); | |
524 age_dist_max = age_distance(bhp->age, age_max); | |
525 if (age_dist_min > age_dist || | |
526 age_dist_max > age_dist) { | |
527 if (age_dist_max > age_dist_min) { | |
528 age_dist = age_dist_max; | |
529 age_min = bhp->age; | |
530 tw(tr(TR_FUNC, TrFsckLow, "age_min = %d (dist = %d)\n", | |
531 age_min, age_dist)); | |
532 } | |
533 else { | |
534 age_dist = age_dist_min; | |
535 age_max = bhp->age; | |
536 tw(tr(TR_FUNC, TrFsckLow, "age_max = %d (dist = %d)\n", | |
537 age_max, age_dist)); | |
538 } | |
539 } | |
540 } | |
541 } | |
542 } | |
543 tlw(led_off(LED_DRV_INIT)); | |
544 tw(tr(TR_FUNC, TrFsck, "age min, max, max-min = %d, %d, %d\n", | |
545 age_min, age_max, (uint16) (age_max-age_min))); | |
546 // If age_max is untouched is is because all blocks were in the 'Empty' | |
547 // state. In this case we let the age be as it is (0xFFFF). | |
548 if (age_max == 0) | |
549 age_max = age_min = BLOCK_AGE_MAX; | |
550 | |
551 // Handle age wrap around thus ensuring fs.age_max is set correctly. We | |
552 // have to type-cast the whole computation, otherwise it will be | |
553 // incorrect. | |
554 if ((age_t) (age_max - age_min) > 0x8000) { | |
555 age_dist = age_max; | |
556 age_max = age_min; | |
557 age_min = age_dist; | |
558 } | |
559 | |
560 // save maximum age found for the case of a bad block that is going to | |
561 // be reclaimed later on by blocks_reclaim() | |
562 fs.age_max = age_max; | |
563 | |
564 tw(tr(TR_FUNC, TrFsck, "fs.format = 0x%04x\n", fs.format)); | |
565 tw(tr(TR_FUNC, TrFsck, "fs.inodes, newinodes = %d, %d\n", | |
566 fs.inodes, fs.newinodes)); | |
567 ttw(ttr(TTrInit, "fs.inodes, newinodes = %d, %d" NL, | |
568 fs.inodes, fs.newinodes)); | |
569 tw(tr(TR_FUNC, TrFsck, "age min, max = %d, %d\n", age_min, age_max)); | |
570 | |
571 // If any blocks were in the EMPTY state, now is the time to bring them | |
572 // into the FREE state. Note that we must only do this *after* | |
573 // fs.age_max has been initialized. | |
574 for (b = 0; b < dev.numblocks; b++) { | |
575 if (is_block(b, BF_IS_EMPTY)) { | |
576 if ((bstat[b].used = block_used(b)) == 0) | |
577 block_preformat(b, 0); | |
578 else | |
579 block_free(b); | |
580 } | |
581 } | |
582 | |
583 if (fs.inodes >= 0) { | |
584 // The 'old' inode block is still valid thus we keep it. | |
585 if (fs.newinodes >= 0) | |
586 // The copying of inodes to the new block was not finished thus | |
587 // we free the block | |
588 block_free(fs.newinodes); | |
589 inodes_set(fs.inodes); | |
590 } | |
591 else { | |
592 // Copying must have been finished | |
593 if (fs.newinodes >= 0 && b_inode_lost >= 0) { | |
594 // The inode reclaim did finish but currently there is no valid | |
595 // inode block thus the operation must be finished by committing | |
596 // the new block as the valid inode block. | |
597 fs.inodes = b_inode_lost; | |
598 block_commit(); | |
599 } | |
600 else { | |
601 // No old or new Inode block! | |
602 tw(tr(TR_END, TrFsck, "} %d\n", EFFS_NOFORMAT)); | |
603 ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); | |
604 return EFFS_NOFORMAT; | |
605 } | |
606 } | |
607 | |
608 if ((fs.format >> 8) != (FFS_FORMAT_VERSION >> 8)) { | |
609 tw(tr(TR_END, TrFsck, "} %d\n", EFFS_BADFORMAT)); | |
610 ttw(ttr(TTrInitLow, "} %d" NL, EFFS_BADFORMAT)); | |
611 return EFFS_BADFORMAT; | |
612 } | |
613 | |
614 // FIXME: Insert age sanity check; age distance must not be too big (> 2 | |
615 // * FFS_AGE_DISTANCE)? | |
616 | |
617 tw(tr(TR_END, TrFsck, "} %d\n", b_to_clean)); | |
618 ttw(ttr(TTrInitLow, "} %d" NL, b_to_clean)); | |
619 | |
620 return b_to_clean; | |
621 } | |
622 | |
623 // Set fs.inodes and fs.inodes_addr | |
624 void inodes_set(iref_t i) | |
625 { | |
626 fs.inodes = i; | |
627 fs.inodes_addr = (struct inode_s *) | |
628 (offset2addr(dev.binfo[fs.inodes].offset) | |
629 + dev.atomsize - sizeof(struct inode_s)); | |
630 } | |
631 | |
632 | |
633 /****************************************************************************** | |
634 * inodes_fsck() | |
635 ******************************************************************************/ | |
636 | |
637 // Now for each inode in the inodes block, update the bstat array | |
638 // information: free, used, objects. Also, locate the root inode. We could | |
639 // optimize this a little, because bstat[binodes].used gives an inidication | |
640 // of how many inodes are actually present in the system. | |
641 iref_t inodes_fsck(void) | |
642 { | |
643 iref_t i; | |
644 struct inode_s *ip; | |
645 char *addr; | |
646 bref_t block; | |
647 | |
648 ttw(str(TTrInitLow, "inodes_fsck {" NL)); | |
649 tw(tr(TR_BEGIN, TrFsck, "inodes_fsck() {\n")); | |
650 tw(tr(TR_FUNC, TrFsck, "inodes in block %d:\n", fs.inodes)); | |
651 | |
652 // the fields of the bstat entry for the inodes have the meaning: | |
653 // used = total number of used inodes (valid, erased, invalid) | |
654 // lost = total number of lost inodes (erased, invalid) | |
655 // objects = index of first free inode (used by inode_alloc()) | |
656 | |
657 fs.root = 0; // default to root inode not found | |
658 fs.ijournal = 0; // default to journal file inode not found | |
659 bstat[fs.inodes].objects = 1; | |
660 bstat[fs.inodes].used = 0; | |
661 bstat[fs.inodes].lost = 0; | |
662 fs.sequence = 0; // just for debug (fun) | |
663 | |
664 // we must set some default value for this, so we set it to max possible! | |
665 fs.inodes_max = dev.blocksize / sizeof(struct inode_s); | |
666 | |
667 ip = inode_addr(1); | |
668 tw(tr(TR_FUNC, TrFsck, " i addr cld sib seq upd flag size name\n")); | |
669 for (i = 1; i < fs.inodes_max; i++, ip++) | |
670 { | |
671 // just for debug (fun) | |
672 if (ip->sequence > fs.sequence) | |
673 fs.sequence = ip->sequence; | |
674 | |
675 // compute block index and total data space occupied | |
676 block = offset2block(location2offset(ip->location)); | |
677 | |
678 // Only scan used inodes. blocks_fsck() accounted all used space as | |
679 // also being lost space, so now we subtract from the lost space, | |
680 // the space used by valid objects | |
681 if (ip->location != FLASH_NULL32) | |
682 { | |
683 bstat[fs.inodes].used++; | |
684 | |
685 tw(tr(TR_FUNC, TrFsck, "%3d 0x%05X %3d %3d %4d %3d %s%s%s%s%s%s %6d %s\n", | |
686 i, | |
687 location2offset(ip->location), | |
688 ip->child, ip->sibling, | |
689 ip->sequence, ip->updates, | |
690 is_object(ip, OT_DIR) ? "d" : "", | |
691 is_object(ip, OT_LINK) ? "l" : "", | |
692 is_object(ip, OT_FILE) ? "f" : "", | |
693 is_object(ip, OT_SEGMENT) ? "s" : "", | |
694 is_object(ip, OT_ERASED) ? " " : "", | |
695 IS_BIT_SET(ip->flags, OF_READONLY) && !is_object(ip, OT_ERASED) ? | |
696 "r" : " ", | |
697 ip->size, | |
698 // Erased chunks do not have any name so we can not trace erased objects! | |
699 (ip->size && !is_object(ip, OT_SEGMENT) && !is_object(ip, OT_ERASED) ? | |
700 addr2name(offset2addr(location2offset(ip->location))) : "") | |
701 )); | |
702 | |
703 if (is_object_valid(ip)) { | |
704 // This inode is valid, so we account the data space as used | |
705 // and the inode as used too. | |
706 bstat[block].lost -= ip->size; | |
707 bstat[block].objects++; | |
708 // test if this is the root inode. store index if it is. | |
709 if (!is_object(ip, OT_SEGMENT)) { | |
710 addr = addr2name(offset2addr(location2offset(ip->location))); | |
711 if (*addr == '/') | |
712 fs.root = i; | |
713 else if (*addr == '.' && | |
714 ffs_strcmp(addr, FFS_JOURNAL_NAME) == 0) { | |
715 fs.ijournal = i; | |
716 } | |
717 } | |
718 } | |
719 else if (is_object(ip, OT_ERASED)) { | |
720 // this inode's data is deleted, so we account the data | |
721 // space as used and lost and the inode as lost too. | |
722 bstat[fs.inodes].lost++; | |
723 } | |
724 else { | |
725 // This is an invalid object, so we account the data space | |
726 // as used and lost and the inode as lost too. NOTEME: error | |
727 // what should we do? Perhaps we should record semi-lost | |
728 // inodes? Can we safely account for it here if this is an | |
729 // object to be recovered because another inode.copied is | |
730 // referring to this? Will used/lost etc. be updated | |
731 // correctly then? | |
732 bstat[fs.inodes].lost++; | |
733 tw(tr(TR_NULL, TrFsck, "(invalid = %d)\n", ip->flags & OT_MASK)); | |
734 } | |
735 } | |
736 } | |
737 ttw(ttr(TTrInit, "fs.root=%d, journal=%d" NL, fs.root, fs.ijournal)); | |
738 tw(tr(TR_END, TrFsck, "} used: %d, lost: %d, root: %d, journal: %d\n", | |
739 bstat[fs.inodes].used, bstat[fs.inodes].lost, fs.root, fs.ijournal)); | |
740 | |
741 fs.sequence++; | |
742 | |
743 tw(tr_bstat()); | |
744 | |
745 if (fs.root == 0) { | |
746 ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); | |
747 return EFFS_NOFORMAT; | |
748 } | |
749 | |
750 ttw(str(TTrInitLow, "} 0" NL)); | |
751 | |
752 return EFFS_OK; | |
753 } | |
754 | |
755 | |
756 /****************************************************************************** | |
757 * Preformat and format | |
758 ******************************************************************************/ | |
759 | |
760 // Prepare all blocks for fs_format(). Because ffs_is_formattable() has | |
761 // already been called prior to this function, we know that no sector erase | |
762 // is in progress! The blocks are prepared by putting them into the 'Free' | |
763 // state. | |
764 effs_t fs_preformat(void) | |
765 { | |
766 bref_t b; | |
767 | |
768 ttw(str(TTrFormat, "preformat {" NL)); | |
769 tw(tr(TR_BEGIN, TrFormat, "fs_preformat() {\n")); | |
770 | |
771 // Mark ffs as being non-formatted from now on. | |
772 fs.root = 0; | |
773 | |
774 // We must initialize bstat[fs.inodes].used and inodes_high, such that | |
775 // inodes_reclaim() isn't triggered in reclaim() on the following | |
776 // fs_format(). | |
777 inodes_set(0); | |
778 bstat[fs.inodes].used = 0; | |
779 bstat[fs.inodes].lost = 0; | |
780 bstat[fs.inodes].objects = 0; | |
781 | |
782 // While format is in progress, we make FFS inaccessible to other | |
783 // functions... | |
784 fs.initerror = EFFS_NOFORMAT; | |
785 | |
786 if (dev.manufact == 0) { | |
787 b = EFFS_NODEVICE; | |
788 } | |
789 else { | |
790 for (b = 0; b < dev.numblocks; b++) { | |
791 if (is_block(b, BF_IS_EMPTY)) { | |
792 if ((bstat[b].used = block_used(b)) == 0) | |
793 block_preformat(b, 0); | |
794 else | |
795 block_free(b); | |
796 } | |
797 else if (!is_block(b, BF_IS_FREE)) { | |
798 block_free(b); | |
799 } | |
800 } | |
801 b = EFFS_OK; | |
802 } | |
803 | |
804 tw(tr(TR_END, TrFormat, "} %d\n", b)); | |
805 ttw(ttr(TTrFormat, "} %d" NL, b)); | |
806 | |
807 return b; | |
808 } | |
809 | |
810 // Preformat a single block thus taking it from the 'Empty' state into | |
811 // 'Free' state. | |
812 void block_preformat(bref_t b, age_t age) | |
813 { | |
814 int set_age_max; | |
815 struct block_header_s *bhp = | |
816 (struct block_header_s *) offset2addr(dev.binfo[b].offset); | |
817 | |
818 tw(tr(TR_BEGIN, TrFormat, "fs_block_preformat(%d, %d)\n", b, age)); | |
819 | |
820 if (age == 0) { | |
821 age = fs.age_max; | |
822 } | |
823 else { | |
824 // We schedule an update of fs.age_max. Due to proper handling of | |
825 // age wrap-around, we can not actually set it now. | |
826 set_age_max = (age == fs.age_max); | |
827 age++; | |
828 if (age == 0) | |
829 age++; | |
830 if (set_age_max) { | |
831 fs.age_max = age; | |
832 tw(tr(TR_FUNC, TrFormat, "new fs.age_max = %d\n", fs.age_max)); | |
833 } | |
834 } | |
835 | |
836 ffsdrv.write_halfword(&bhp->age, age); | |
837 ffsdrv.write_halfword(&bhp->version, FFS_FORMAT_VERSION); | |
838 ffsdrv.write_halfword(&bhp->magic_low, BLOCK_MAGIC_LOW); | |
839 ffsdrv.write_halfword(&bhp->magic_high, BLOCK_MAGIC_HIGH); | |
840 | |
841 bstat[b].flags = BF_IS_EMPTY; | |
842 bstat[b].used = 0; | |
843 bstat[b].lost = 0; | |
844 bstat[b].objects = 0; | |
845 | |
846 block_flags_write(b, BF_FREE); | |
847 | |
848 tw(tr(TR_END, TrFormat, "")); | |
849 } | |
850 | |
851 // After preformat() has erased two blocks, this function can be called to | |
852 // initialize ffs by writing fs data and metadata. Note that ffs_begin() is | |
853 // *not* called before this function in ffs.c. Otherwise we would never | |
854 // enter this function because fs.root is zero. NOTEME: this is also a bug | |
855 // as this means we risk that this operation is started while an erase (or a | |
856 // write) is in progress! How the flash device reacts to this is currently | |
857 // unknown. | |
858 effs_t fs_format(const char *name) | |
859 { | |
860 bref_t i, b; | |
861 | |
862 ttw(str(TTrFormat, "format {" NL)); | |
863 tw(tr(TR_BEGIN, TrFormat, "fs_format('%s') {\n", name)); | |
864 | |
865 // Initialize file system parameters. It should be safe to change these | |
866 // now, as the format cannot fail at this point onwards. | |
867 fs_params_init(name); | |
868 | |
869 // Make the first block be the inodes block | |
870 if ((fs.inodes = block_alloc(1, BF_COPYING)) < 0) | |
871 return EFFS_AGAIN; | |
872 block_flags_write(fs.inodes, BF_INODES); | |
873 inodes_set(fs.inodes); | |
874 | |
875 // Make all block as data blocks except from the free_min and inode block | |
876 for (i = 0; i < dev.numblocks - fs.blocks_free_min - 1; i++) | |
877 if ((b = block_alloc(0, BF_DATA)) < 0) | |
878 return EFFS_AGAIN; | |
879 | |
880 // Restart object sequencing (debug feature only) | |
881 fs.sequence = 0; | |
882 | |
883 // Create root directory | |
884 journal_begin(0); | |
885 if ((fs.root = object_create(name, 0, 0, 0)) < 0) { | |
886 tw(tr(TR_END, TrFormat, "} %d\n", fs.root)); | |
887 return fs.root; | |
888 } | |
889 journal_commit(OT_DIR); | |
890 | |
891 if ((fs.ijournal = journal_create(0)) < 0) { | |
892 tw(tr(TR_END, TrFormat, "} %d\n", fs.ijournal)); | |
893 return fs.ijournal; | |
894 } | |
895 | |
896 fs.initerror = ffs_initialize(); | |
897 | |
898 ttw(ttr(TTrFormat, "} %d" NL, fs.initerror)); | |
899 tw(tr(TR_END, TrFormat, "} %d\n", fs.initerror)); | |
900 | |
901 return fs.initerror; | |
902 } | |
903 | |
904 // Check if we are ready to preformat (flag = 0) or format (flag = 1) | |
905 // | |
906 // For a format, we must first ensure no blocks are valid e.g. a preformat | |
907 // has already been run. Next, we must ensure we have preformatted all | |
908 // blocks e.g. all blocks are in the 'Free' state. This is actually the same | |
909 // thing but it sure helps the user because it yields a more precise error | |
910 // code when the format fails. In future we might be able to start a format | |
911 // when only two blocks have been preformatted, but this is harder because | |
912 // we have to make sure not to read from the physical sector that we are | |
913 // erasing, and this is exactly what ffs_ffs_initialize() currently does | |
914 // (when it is called at the end of format()). | |
915 // | |
916 // For a preformat, we must ensure an erase is not in progress (because we | |
917 // don't know how the device will react to a new erase when an erase is | |
918 // currently suspended). | |
919 effs_t is_formattable(int8 flag) | |
920 { | |
921 bref_t i, free, valid; | |
922 effs_t error = EFFS_OK; | |
923 | |
924 tw(tr(TR_FUNC, TrFormat, "is_formattable() ")); | |
925 | |
926 // Count the number of valid and free blocks. These numbers will later | |
927 // be checked to see if we are really ready for a (pre)format(). Note | |
928 // that we *only* read block flags from the bstat[] array. We must not | |
929 // read directly from the flash sectors because an erase might be in | |
930 // progress! | |
931 for (i = 0, free = 0, valid = 0; i < dev.numblocks; i++) { | |
932 if (is_block(i, BF_IS_DATA) || is_block(i, BF_IS_INODES)) | |
933 valid++; | |
934 if (is_block(i, BF_IS_FREE)) | |
935 free++; | |
936 } | |
937 if (flag == 0) { | |
938 // In the case of a preformat, ensure an erase is not in | |
939 // progress (because we don't know how the device will react to a new | |
940 // erase when an erase is currently suspended). | |
941 if (dev.state == DEV_ERASE || dev.state == DEV_ERASE_SUSPEND) { | |
942 tw(tr(TR_NULL, TrFormat, "(%d)\n", EFFS_AGAIN)); | |
943 return EFFS_AGAIN; | |
944 } | |
945 } | |
946 else { | |
947 if (valid > 0) | |
948 // Ensure we have preformatted prior to a format. | |
949 error = EFFS_NOPREFORMAT; | |
950 else if (free < dev.numblocks) | |
951 // Ensure all blocks are free before a format(). If not, a | |
952 // preformat() is currently in progress. | |
953 error = EFFS_AGAIN; | |
954 } | |
955 | |
956 tw(tr(TR_NULL, TrFormat, "(%d)\n", error)); | |
957 return error; | |
958 } | |
959 | |
960 | |
961 /****************************************************************************** | |
962 * Journalling | |
963 ******************************************************************************/ | |
964 | |
965 // The following matrix illustrates how the members of an inode change for | |
966 // the various (journalled) operations: | |
967 // | |
968 // | flags | size | loc | child | siblg | dir | oldi | updates | |
969 // ---------+-------+------+-----+-------+-------+-----+------+-------- | |
970 // create | new | new | new | - | - | ins | n/a | 0 | |
971 // fupdate | o | new | new | o | - | ins | del | old+1 | |
972 // relocate | o | o | new | o | - | ins | del | old+1 | |
973 // fctrl | new | o | o | o | - | ins | del | old+1 | |
974 // remove | n/a | n/a | n/a | n/a | n/a | n/a | del | n/a | |
975 // | |
976 // - = leave empty (0xFFFF) | |
977 // ins = insert/append into directory | |
978 // o = old value | |
979 // | |
980 // We don't have to store child member in the journal entry because either | |
981 // it is EMPTY (fs.journal.oldi = 0) or it is retrieved from oldip->child. | |
982 | |
983 // NOTEME: With journalling implemented, object_relocate might be able just | |
984 // to make a simple data copy! | |
985 | |
986 // block_clean() is safe (without journalling), now that only ip->size is | |
987 // set to zero. | |
988 | |
989 // Begin a new journal. Either a fresh object create (oldi == 0) or an | |
990 // update of an existing object (oldi == iref of old object) | |
991 void journal_begin(iref_t oldi) | |
992 { | |
993 tw(tr(TR_FUNC, TrJournal, "journal_begin(%d)\n", oldi)); | |
994 | |
995 fs.journal.i = 0; | |
996 fs.journal.state = JOURNAL_IS_EMPTY; | |
997 fs.journal.repli = 0; | |
998 fs.link_child = 1; //Default link child in journal_commit() | |
999 | |
1000 if (oldi == 0) { | |
1001 fs.journal.flags = 0xFF; | |
1002 fs.journal.diri = 0; | |
1003 fs.journal.oldi = 0; | |
1004 fs.journal.location = 0; | |
1005 fs.journal.size = 0; | |
1006 } | |
1007 else { | |
1008 struct inode_s *oldip = inode_addr(oldi); | |
1009 fs.journal.flags = oldip->flags; | |
1010 fs.journal.diri = oldi; | |
1011 fs.journal.oldi = oldi; | |
1012 fs.journal.location = oldip->location; | |
1013 fs.journal.size = oldip->size; | |
1014 } | |
1015 } | |
1016 | |
1017 // NOTEME: We have compressed the macro code because it will NOT compile on | |
1018 // Unix otherwise. So until we find out why, we use this as a work-around. | |
1019 #if (FFS_TEST == 1) | |
1020 #define JOURNAL_TEST(testcase, text) if (fs.testflags == testcase) { tw(tr(TR_END, TrJournal, "} (" text ")\n")); return; } | |
1021 #else | |
1022 #define JOURNAL_TEST(testcase, text) | |
1023 #endif | |
1024 | |
1025 // NOTEME: Should we empty journal file when we are anyway relocating it in | |
1026 // data_reclaim()? | |
1027 void journal_end(uint8 type) | |
1028 { | |
1029 struct inode_s *ip = inode_addr(fs.ijournal); | |
1030 struct journal_s *addr = (struct journal_s *) | |
1031 offset2addr(location2offset(ip->location) + fs.journal_pos); | |
1032 | |
1033 tw(tr(TR_BEGIN, TrJournal, "journal_end(0x%x) {\n", type)); | |
1034 tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x (%d)\n", fs.journal_pos, | |
1035 (fs.journal_pos - JOURNAL_POS_INITIAL) / sizeof(struct journal_s))); | |
1036 | |
1037 // If this is a create, set the object type | |
1038 if (type != 0 && fs.journal.oldi == 0) | |
1039 fs.journal.flags = (fs.journal.flags & OF_MASK) | type; | |
1040 | |
1041 // If there is no journal file, we can do without it, although we | |
1042 // certainly don't like it! | |
1043 if (fs.ijournal == 0) { | |
1044 journal_commit(0); | |
1045 tw(tr(TR_END, TrJournal, "} No jounal file\n")); | |
1046 return; | |
1047 } | |
1048 | |
1049 JOURNAL_TEST(JOURNAL_TEST_EMPTY, "Oops in JOURNAL_IS_EMPTY"); | |
1050 | |
1051 // Write RAM journal to journal file. | |
1052 if (fs.journal.state == (uint8) JOURNAL_IS_EMPTY) { | |
1053 fs.journal.state = JOURNAL_IS_WRITING; | |
1054 ffsdrv.write(addr, &fs.journal, sizeof(fs.journal)); | |
1055 } | |
1056 | |
1057 JOURNAL_TEST(JOURNAL_TEST_WRITING, "Oops in JOURNAL_IS_WRITING"); | |
1058 | |
1059 // Advance journal file's state | |
1060 if (fs.journal.state == (uint8) JOURNAL_IS_WRITING) { | |
1061 fs.journal.state = JOURNAL_IS_READY; | |
1062 ffsdrv_write_byte(&addr->state, fs.journal.state); | |
1063 } | |
1064 | |
1065 JOURNAL_TEST(JOURNAL_TEST_READY, "Oops in JOURNAL_IS_READY"); | |
1066 | |
1067 journal_commit(0); | |
1068 | |
1069 JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING"); | |
1070 JOURNAL_TEST(JOURNAL_TEST_COMMITTED, "Oops in JOURNAL_COMMITTED"); | |
1071 | |
1072 // Advance journal file's state | |
1073 ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); | |
1074 | |
1075 JOURNAL_TEST(JOURNAL_TEST_DONE, "Oops in JOURNAL_IS_DONE"); | |
1076 | |
1077 // Advance journal | |
1078 fs.journal_pos += sizeof(struct journal_s); | |
1079 | |
1080 // Unless we are currently relocating the journal file itself, check if | |
1081 // journal file is near full and relocate it if it is. | |
1082 if (fs.journal_pos >= fs.journal_size - FFS_JOURNAL_MARGIN * | |
1083 sizeof(struct journal_s) && fs.journal.oldi != fs.ijournal) { | |
1084 tw(tr(TR_FUNC, TrJournal, "Journal file (near) full!\n")); | |
1085 journal_create(fs.ijournal); | |
1086 } | |
1087 | |
1088 // Check if we have just committed the journal file itself | |
1089 if (fs.journal.oldi == fs.ijournal) { | |
1090 fs.journal_pos = JOURNAL_POS_INITIAL; | |
1091 fs.ijournal = fs.journal.i; | |
1092 tw(tr(TR_FUNC, TrJournal, "Journal file re-created, fs.ijournal = %d\n", | |
1093 fs.ijournal)); | |
1094 } | |
1095 tw(tr(TR_END, TrJournal, "}\n")); | |
1096 } | |
1097 | |
1098 // Write contents of fs.journal to FFS meta data (inodes). Note that we do | |
1099 // NOT traverse ip->copied as we used to do in the old | |
1100 // object_update_commit(). Also, we do not check if object has been | |
1101 // erased after traversing ip->copied. All this code has been removed | |
1102 // because we will very soon have full callback functionality and thus the | |
1103 // code is redundant. | |
1104 void journal_commit(uint8 type) | |
1105 { | |
1106 struct inode_s *ip = inode_addr(fs.journal.i); | |
1107 struct inode_s *oldip = inode_addr(fs.journal.oldi); | |
1108 struct inode_s *dp; | |
1109 bref_t b; | |
1110 | |
1111 tw(tr(TR_BEGIN, TrJournal, "journal_commit(%d) {\n", type)); | |
1112 tw(tr(TR_FUNC, TrJournal, "i = %d\n", fs.journal.i)); | |
1113 ttw(ttr(TTrObj, "jc(){" NL)); | |
1114 | |
1115 if (fs.journal.i) | |
1116 { | |
1117 // If this is a create, set the object type | |
1118 if (type != 0 && fs.journal.oldi == 0) | |
1119 fs.journal.flags = (fs.journal.flags & OF_MASK) | type; | |
1120 | |
1121 tw(tr(TR_FUNC, TrJournal, "loc = 0x%04x, size = %d\n", | |
1122 fs.journal.location, fs.journal.size)); | |
1123 ffsdrv.write((uint32 *) &ip->location, (uint32 *) &fs.journal.location, sizeof(location_t)); | |
1124 ffsdrv.write_halfword((uint16 *) &ip->size, fs.journal.size); | |
1125 | |
1126 if (fs.journal.oldi != 0 && fs.link_child != 0) | |
1127 // If this is an update, we copy the child member from old | |
1128 // inode. We must do this before we validate the new object, | |
1129 // otherwise an intermediate readdir() will detect an empty | |
1130 // directory! | |
1131 ffsdrv.write_halfword((uint16*) &ip->child, oldip->child); | |
1132 | |
1133 tw(tr(TR_FUNC, TrJournal, "seq = %d\n", fs.sequence)); | |
1134 // We must check if sequence is already written because if this | |
1135 // commit was inititiated by journal_init(), we don't know exactly | |
1136 // what was written | |
1137 if (ip->sequence == FLASH_NULL16) | |
1138 ffsdrv.write_halfword(&ip->sequence, fs.sequence++); | |
1139 if (fs.journal.oldi == 0) | |
1140 ffsdrv.write_halfword(&ip->updates, 0); | |
1141 else | |
1142 ffsdrv.write_halfword(&ip->updates, oldip->updates + 1); | |
1143 | |
1144 JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING") | |
1145 | |
1146 // Insert object into directory structure. We must do this before | |
1147 // deleting old object, otherwise an intermediate readdir() will | |
1148 // fail with EFFS_NOTFOUND. Note that when the root directory is | |
1149 // created, fs.journal.diri is zero --- thus the test! | |
1150 if (fs.journal.diri != 0) { | |
1151 tw(tr(TR_FUNC, TrJournal, "diri = %d ", fs.journal.diri)); | |
1152 if (fs.journal.diri < 0) { | |
1153 tw(tr(TR_NULL, TrJournal, "child\n")); | |
1154 dp = inode_addr(-fs.journal.diri); | |
1155 ffsdrv.write_halfword((uint16 *) &dp->child, fs.journal.i); | |
1156 } | |
1157 else { | |
1158 tw(tr(TR_NULL, TrJournal, "sibling\n")); | |
1159 dp = inode_addr(fs.journal.diri); | |
1160 ffsdrv.write_halfword((uint16 *) &dp->sibling, fs.journal.i); | |
1161 } | |
1162 } | |
1163 | |
1164 // The new object is validated before the old object is deleted. | |
1165 // This is in order to avoid an interrupting stat or read operation | |
1166 // to fail with EFFS_NOTFOUND | |
1167 tw(tr(TR_FUNC, TrJournal, "flags = 0x%02x\n", fs.journal.flags)); | |
1168 ffsdrv_write_byte(&ip->flags, fs.journal.flags); | |
1169 | |
1170 // Update bstat[] appropriately | |
1171 b = offset2block(location2offset(ip->location)); | |
1172 bstat[b].objects++; | |
1173 tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); | |
1174 } | |
1175 | |
1176 tw(tr(TR_FUNC, TrJournal, "oldi = %d\n", fs.journal.oldi)); | |
1177 if (fs.journal.oldi != 0) | |
1178 { | |
1179 // If this is an update or an erase, we erase the old object | |
1180 ffsdrv_write_byte(&oldip->flags, OT_ERASED); | |
1181 | |
1182 // Update bstat according to deletion of the old object. | |
1183 b = offset2block(location2offset(oldip->location)); | |
1184 bstat[b].objects--; | |
1185 tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); | |
1186 | |
1187 // If we moved the data (all cases, except fcontrol), update lost | |
1188 if (fs.journal.location != oldip->location) | |
1189 bstat[b].lost += oldip->size; | |
1190 | |
1191 bstat[fs.inodes].lost++; | |
1192 | |
1193 // If we renamed a file to an existing filename, remove the replaced file. | |
1194 if (fs.journal.repli > 0) | |
1195 object_remove(fs.journal.repli); // Ignore error! | |
1196 } | |
1197 | |
1198 tw(tr(TR_END, TrJournal, "}\n")); | |
1199 ttw(ttr(TTrObj, "}" NL)); | |
1200 } | |
1201 | |
1202 // Save the current journal into "old" journal. We need this because an | |
1203 // object_create() can call data_reclaim() which can call object_relocate() | |
1204 // which uses the journal system. | |
1205 int journal_push(void) | |
1206 { | |
1207 memcpy(&fs.ojournal, &fs.journal, sizeof(struct journal_s)); | |
1208 fs.journal_depth++; | |
1209 if (fs.journal_depth > 1) { | |
1210 tw(tr(TR_FUNC, TrAll, "FATAL: journal_push() to depth %d\n", | |
1211 fs.journal_depth)); | |
1212 return -1; | |
1213 } | |
1214 | |
1215 tw(tr(TR_FUNC, TrJournal, "journal_push() to depth %d\n", | |
1216 fs.journal_depth)); | |
1217 | |
1218 return EFFS_OK; | |
1219 } | |
1220 | |
1221 // Recall "old" journal into current journal | |
1222 int journal_pop(void) | |
1223 { | |
1224 tw(tr(TR_FUNC, TrJournal, "journal_pop() from depth %d\n", | |
1225 fs.journal_depth)); | |
1226 | |
1227 fs.journal_depth--; | |
1228 if (fs.journal_depth < 0) { | |
1229 tw(tr(TR_FUNC, TrAll, "FATAL: journal_pop() to depth %d\n", | |
1230 fs.journal_depth)); | |
1231 return -1; | |
1232 } | |
1233 memcpy(&fs.journal, &fs.ojournal, sizeof(struct journal_s)); | |
1234 | |
1235 return EFFS_OK; | |
1236 } | |
1237 | |
1238 // Initialize the journalling system. Create journal file if it not already | |
1239 // exist. Commit/write pending journal if such exists --- return 1 in that | |
1240 // case. Otherwise, if journal file is clean (no journals pending) and all | |
1241 // is fine, return EFFS_OK. | |
1242 effs_t journal_init(iref_t i) | |
1243 { | |
1244 int j; | |
1245 struct inode_s *ip = inode_addr(i); | |
1246 struct journal_s *addr; | |
1247 | |
1248 if (i == 0) { | |
1249 // Journal file does not exist, so create it | |
1250 if ((i = journal_create(0)) <= 0) { | |
1251 fs.ijournal = 0; | |
1252 return i; | |
1253 } | |
1254 } | |
1255 | |
1256 fs.journal_depth = 0; | |
1257 fs.journal_pos = JOURNAL_POS_INITIAL; | |
1258 | |
1259 addr = (struct journal_s *) | |
1260 offset2addr(location2offset(ip->location) + fs.journal_pos); | |
1261 | |
1262 tw(tr(TR_BEGIN, TrJournal, "journal_init(%d) {\n", i)); | |
1263 | |
1264 fs.ijournal = i; | |
1265 | |
1266 // Search for first non-completed journal entry. | |
1267 for (j = 0; /* FIXME: limit to end of journal */; j++, addr++) { | |
1268 if (addr->state != (uint8) JOURNAL_IS_DONE) | |
1269 break; | |
1270 } | |
1271 tw(tr(TR_FUNC, TrJournal, "entry %d is in state 0x%x\n", j, addr->state)); | |
1272 | |
1273 fs.journal_pos += j * sizeof(fs.journal); | |
1274 i = EFFS_OK; | |
1275 | |
1276 if (addr->state == (uint8) JOURNAL_IS_EMPTY) { | |
1277 tw(tr(TR_FUNC, TrJournal, "Last journal is in EMPTY state\n")); | |
1278 // Journal file is proper, so just record position | |
1279 } | |
1280 else if (addr->state == (uint8) JOURNAL_IS_READY) { | |
1281 // Copy the entry into fs.journal. | |
1282 tw(tr(TR_FUNC, TrJournal, "Last journal is in READY state\n")); | |
1283 memcpy(&fs.journal, addr, sizeof(fs.journal)); | |
1284 journal_end(0); | |
1285 i = 1; | |
1286 } | |
1287 else { | |
1288 // Journal entry wasn't finished, so just ignore it after updating | |
1289 // its state to JOURNAL_IS_DONE. | |
1290 tw(tr(TR_FUNC, TrJournal, "Last journal is between EMPTY and READY\n")); | |
1291 ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); | |
1292 fs.journal_pos += sizeof(fs.journal); | |
1293 } | |
1294 | |
1295 if (ip->size != fs.journal_size + atomalign(sizeof(FFS_JOURNAL_NAME) + 1)) { | |
1296 tw(tr(TR_FUNC, TrJournal, "Wrong journal size, create new\n")); | |
1297 // Journal size do not match default size, so create new. This | |
1298 // should only happen if we use an old FFS image with a newer FFS | |
1299 // version. | |
1300 if ((i = journal_create(fs.ijournal)) <= 0) { | |
1301 fs.ijournal = 0; | |
1302 return i; | |
1303 } | |
1304 } | |
1305 | |
1306 tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x\n", fs.journal_pos)); | |
1307 tw(tr(TR_END, TrJournal, "} %d\n", i)); | |
1308 | |
1309 return i; | |
1310 } | |
1311 | |
1312 // Create the journal file from scratch or relocate an existing one. It is | |
1313 // marked read-only just for clarity --- it cannot be deleted anyway! | |
1314 // fs_format() calls this function. Note that no data are written in | |
1315 // object_create() because the journal file is handled specially in that | |
1316 // function. | |
1317 iref_t journal_create(iref_t oldi) | |
1318 { | |
1319 iref_t i; | |
1320 | |
1321 tw(tr(TR_BEGIN, TrJournal, "journal_create(%d) {\n", oldi)); | |
1322 tw(tr(TR_FUNC, TrJournal, "journal file size = %d\n", fs.journal_size)); | |
1323 | |
1324 if (fs.journal_size == 0) { | |
1325 tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.journal_size = 0 (No journal file wanted)\n")); | |
1326 tw(tr(TR_END, TrJournal, "} %d\n", 0)); | |
1327 return 0; | |
1328 } | |
1329 | |
1330 // If we are working on a write-once file system, we do not need a | |
1331 // journal. | |
1332 if (fs.blocks_free_min == 0) { | |
1333 tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.blocks_free_min = 0 (write-once system)\n")); | |
1334 tw(tr(TR_END, TrJournal, "} %d\n", 0)); | |
1335 return 0; | |
1336 } | |
1337 | |
1338 journal_begin(oldi); | |
1339 | |
1340 i = object_create(FFS_JOURNAL_NAME, 0, fs.journal_size, -fs.root); | |
1341 if (i < 0) { | |
1342 tw(tr(TR_END, TrJournal, "} %d\n", i)); | |
1343 return i; | |
1344 } | |
1345 fs.journal.flags = BIT_SET(fs.journal.flags, OF_READONLY); | |
1346 | |
1347 // commit the creation or relocation | |
1348 if (oldi != 0) | |
1349 journal_end(0); | |
1350 else { | |
1351 journal_commit(OT_FILE); | |
1352 fs.journal_pos = JOURNAL_POS_INITIAL; | |
1353 } | |
1354 | |
1355 tw(tr(TR_END, TrJournal, "} %d\n", i)); | |
1356 | |
1357 return i; | |
1358 } | |
1359 | |
1360 /****************************************************************************** | |
1361 * FFS Begin and End | |
1362 ******************************************************************************/ | |
1363 | |
1364 // The following two functions should surround the code of every API | |
1365 // function in ffs.c (except preformat and format). The functions | |
1366 // ensures that the operation about to be executed can be made without | |
1367 // race-conditions or other problems. | |
1368 #if (TARGET == 0) | |
1369 int debug_suspend = 0; | |
1370 #endif | |
1371 | |
1372 | |
1373 // Check if ffs has been initialized. Suspend an erase operation. | |
1374 effs_t ffs_begin(void) | |
1375 { | |
1376 #if (TARGET == 0) | |
1377 if (debug_suspend > 0) { | |
1378 tw(tr(TR_FUNC, TrAll, "FATAL: Previous erase_suspend was not resumed\n")); | |
1379 return EFFS_CORRUPTED; | |
1380 } | |
1381 // tw(tr(TR_FUNC, TrHelper, "Set debug_suspend\n")); | |
1382 debug_suspend = 1; | |
1383 #endif | |
1384 | |
1385 if (fs.initerror != EFFS_OK) | |
1386 return fs.initerror; | |
1387 | |
1388 // Suspend an erase in progress (only applicable if we are using a | |
1389 // multi-bank device driver) | |
1390 if (dev.state == DEV_ERASE) { | |
1391 ffsdrv.erase_suspend(); | |
1392 } | |
1393 else if (dev.state == DEV_WRITE) { | |
1394 ffsdrv.write_end(); | |
1395 } | |
1396 | |
1397 return EFFS_OK; | |
1398 } | |
1399 | |
1400 // Resume an erase operation that was in progress. | |
1401 int ffs_end(int error) | |
1402 { | |
1403 #if (TARGET == 1) | |
1404 // Resume an erase in progress (only applicable if we are using a | |
1405 // multi-bank device driver) | |
1406 if (dev.state == DEV_ERASE_SUSPEND) { | |
1407 ffsdrv.erase_resume(); | |
1408 } | |
1409 #else | |
1410 debug_suspend = 0; | |
1411 #endif | |
1412 | |
1413 return error; | |
1414 } | |
1415 | |
1416 /****************************************************************************** | |
1417 * FFS Statistics functions | |
1418 ******************************************************************************/ | |
1419 | |
1420 // Not implemented: | |
1421 int statistics_file_create(void) | |
1422 { | |
1423 return 0; | |
1424 } | |
1425 | |
1426 // Not implemented: | |
1427 // Rewrite the statistics file if it exists. Otherwise return error | |
1428 // code. The function is called after each data and inodes reclaim (after | |
1429 // writing the file that provoked the reclaim). | |
1430 int statistics_write(void) | |
1431 { | |
1432 return 0; | |
1433 } | |
1434 | |
1435 // Read the statistics file if it exists. Otherwise reset all statistics to | |
1436 // zero and set the magic. This function is called from ffs_init(). | |
1437 void statistics_init(void) | |
1438 { | |
1439 memset(&stats, 0, sizeof(struct ffs_stats_s)); | |
1440 } | |
1441 | |
1442 void statistics_update_drec(int valid, int lost, int candidate) | |
1443 { | |
1444 unsigned int old; | |
1445 | |
1446 switch (candidate) { | |
1447 case MOST_LOST: stats.drec.most_lost++; break; | |
1448 case MOST_UNUSED: stats.drec.most_unused++; break; | |
1449 case YOUNGEST: stats.drec.youngest++; break; | |
1450 } | |
1451 | |
1452 // Increment Most Significant Word if overflow is detected | |
1453 old = stats.drec.valid[0]; | |
1454 stats.drec.valid[0] += valid; | |
1455 if (old > stats.drec.valid[0]) | |
1456 stats.drec.valid[1]++; | |
1457 | |
1458 old = stats.drec.lost[0]; | |
1459 stats.drec.lost[0] += lost; | |
1460 if (old > stats.drec.lost[0]) | |
1461 stats.drec.lost[1]++; | |
1462 } | |
1463 | |
1464 void statistics_update_irec(int valid, int lost) | |
1465 { | |
1466 stats.irec.num++; | |
1467 stats.irec.valid += valid; | |
1468 stats.irec.lost += lost; | |
1469 } |