Contiki-NG
Loading...
Searching...
No Matches
cfs-coffee.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2008, 2009, Swedish Institute of Computer Science
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the Institute nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * This file is part of the Contiki operating system.
30 *
31 */
32
33/**
34 * \file
35 * Coffee: A file system for a variety of storage types in
36 * memory-constrained devices.
37 *
38 * For further information, see "Enabling Large-Scale Storage in
39 * Sensor Networks with the Coffee File System" in the proceedings
40 * of ACM/IEEE IPSN 2009.
41 *
42 * \author
43 * Nicolas Tsiftes <nvt@sics.se>
44 */
45
46#include <limits.h>
47#include <string.h>
48
49#define DEBUG 0
50#if DEBUG
51#include <stdio.h>
52#define PRINTF(...) printf(__VA_ARGS__)
53#else
54#define PRINTF(...)
55#endif
56
57#include "contiki.h"
58#include "cfs/cfs.h"
59#include "cfs-coffee-arch.h"
60#include "cfs/cfs-coffee.h"
61
62/* Micro logs enable modifications on storage types that do not support
63 in-place updates. This applies primarily to flash memories. */
64#ifndef COFFEE_MICRO_LOGS
65#define COFFEE_MICRO_LOGS 1
66#endif
67
68/* If the files are expected to be appended to only, this parameter
69 can be set to save some code space. */
70#ifndef COFFEE_APPEND_ONLY
71#define COFFEE_APPEND_ONLY 0
72#endif
73
74#if COFFEE_MICRO_LOGS && COFFEE_APPEND_ONLY
75#error "Cannot have COFFEE_APPEND_ONLY set when COFFEE_MICRO_LOGS is set."
76#endif
77
78/*
79 * Prevent sectors from being erased directly after file removal.
80 * This will level the wear across sectors better, but may lead
81 * to longer garbage collection procedures.
82 */
83#ifndef COFFEE_EXTENDED_WEAR_LEVELLING
84#define COFFEE_EXTENDED_WEAR_LEVELLING 1
85#endif
86
87#if COFFEE_START & (COFFEE_SECTOR_SIZE - 1)
88#error COFFEE_START must point to the first byte in a sector.
89#endif
90
91/* File descriptor flags. */
92#define COFFEE_FD_FREE 0x0
93#define COFFEE_FD_READ 0x1
94#define COFFEE_FD_WRITE 0x2
95#define COFFEE_FD_APPEND 0x4
96
97/* File object flags. */
98#define COFFEE_FILE_MODIFIED 0x1
99
100/* Internal Coffee markers. */
101#define INVALID_PAGE ((coffee_page_t)-1)
102#define UNKNOWN_OFFSET ((cfs_offset_t)-1)
103
104/* File removal actions. They can have the same values because
105 they are passed as separate parameters. */
106#define REMOVE_LOG 1
107#define CLOSE_FDS 1
108#define ALLOW_GC 1
109
110/* "Greedy" garbage collection erases as many sectors as possible. */
111#define GC_GREEDY 0
112/* "Reluctant" garbage collection stops after erasing one sector. */
113#define GC_RELUCTANT 1
114
115/* File descriptor macros. */
116#define FD_VALID(fd) ((fd) >= 0 && (fd) < COFFEE_FD_SET_SIZE && \
117 coffee_fd_set[(fd)].flags != COFFEE_FD_FREE)
118#define FD_READABLE(fd) (coffee_fd_set[(fd)].flags & CFS_READ)
119#define FD_WRITABLE(fd) (coffee_fd_set[(fd)].flags & CFS_WRITE)
120#define FD_APPENDABLE(fd) (coffee_fd_set[(fd)].flags & CFS_APPEND)
121
122/* File object macros. */
123#define FILE_MODIFIED(file) ((file)->flags & COFFEE_FILE_MODIFIED)
124#define FILE_FREE(file) ((file)->max_pages == 0)
125#define FILE_UNREFERENCED(file) ((file)->references == 0)
126
127/* File header flags. */
128#define HDR_FLAG_VALID 0x01 /* Completely written header. */
129#define HDR_FLAG_ALLOCATED 0x02 /* Allocated file. */
130#define HDR_FLAG_OBSOLETE 0x04 /* File marked for GC. */
131#define HDR_FLAG_MODIFIED 0x08 /* Modified file, log exists. */
132#define HDR_FLAG_LOG 0x10 /* Log file. */
133#define HDR_FLAG_ISOLATED 0x20 /* Isolated page. */
134
135/* File header macros. */
136#define CHECK_FLAG(hdr, flag) ((hdr).flags & (flag))
137#define HDR_VALID(hdr) CHECK_FLAG(hdr, HDR_FLAG_VALID)
138#define HDR_ALLOCATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ALLOCATED)
139#define HDR_FREE(hdr) !HDR_ALLOCATED(hdr)
140#define HDR_LOG(hdr) CHECK_FLAG(hdr, HDR_FLAG_LOG)
141#define HDR_MODIFIED(hdr) CHECK_FLAG(hdr, HDR_FLAG_MODIFIED)
142#define HDR_ISOLATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ISOLATED)
143#define HDR_OBSOLETE(hdr) CHECK_FLAG(hdr, HDR_FLAG_OBSOLETE)
144#define HDR_ACTIVE(hdr) (HDR_ALLOCATED(hdr) && \
145 !HDR_OBSOLETE(hdr) && \
146 !HDR_ISOLATED(hdr))
147
148/* Shortcuts derived from the hardware-dependent configuration of Coffee. */
149#define COFFEE_SECTOR_COUNT \
150 (coffee_page_t)(COFFEE_SIZE / COFFEE_SECTOR_SIZE)
151#define COFFEE_PAGE_COUNT \
152 ((coffee_page_t)(COFFEE_SIZE / COFFEE_PAGE_SIZE))
153#define COFFEE_PAGES_PER_SECTOR \
154 ((coffee_page_t)(COFFEE_SECTOR_SIZE / COFFEE_PAGE_SIZE))
155
156/* This structure is used for garbage collection statistics. */
157struct sector_status {
158 coffee_page_t active;
159 coffee_page_t obsolete;
160 coffee_page_t free;
161};
162
163/* The structure of cached file objects. */
164struct file {
165 cfs_offset_t end;
166 coffee_page_t page;
167 coffee_page_t max_pages;
168 int16_t record_count;
169 uint8_t references;
170 uint8_t flags;
171};
172
173/* The file descriptor structure. */
174struct file_desc {
175 cfs_offset_t offset;
176 struct file *file;
177 uint8_t flags;
178 uint8_t io_flags;
179};
180
181/* The file header structure mimics the representation of file headers
182 in the physical storage medium. */
183struct file_header {
184 coffee_page_t log_page;
185 uint16_t log_records;
186 uint16_t log_record_size;
187 coffee_page_t max_pages;
188 uint8_t deprecated_eof_hint;
189 uint8_t flags;
190 char name[COFFEE_NAME_LENGTH];
191};
192
193/* This is needed because of a buggy compiler. */
194struct log_param {
195 cfs_offset_t offset;
196 char *buf;
197 uint16_t size;
198};
199
200/*
201 * Variables that keep track of opened files and internal
202 * optimization information for Coffee.
203 */
204static struct file coffee_files[COFFEE_MAX_OPEN_FILES];
205static struct file_desc coffee_fd_set[COFFEE_FD_SET_SIZE];
206static coffee_page_t next_free;
207static char gc_wait;
208
209/*---------------------------------------------------------------------------*/
210static void
211write_header(struct file_header *hdr, coffee_page_t page)
212{
213 hdr->flags |= HDR_FLAG_VALID;
214 COFFEE_WRITE(hdr, sizeof(*hdr), page * COFFEE_PAGE_SIZE);
215}
216/*---------------------------------------------------------------------------*/
217static void
218read_header(struct file_header *hdr, coffee_page_t page)
219{
220 COFFEE_READ(hdr, sizeof(*hdr), page * COFFEE_PAGE_SIZE);
221 hdr->name[COFFEE_NAME_LENGTH - 1] = '\0';
222
223 if(DEBUG && HDR_ACTIVE(*hdr) && !HDR_VALID(*hdr)) {
224 PRINTF("Coffee: Invalid header at page %u!\n", (unsigned)page);
225 }
226}
227/*---------------------------------------------------------------------------*/
228static int
229header_is_valid(const struct file_header *hdr, coffee_page_t page)
230{
231 /* Check 1: max_pages must be non-zero and reasonable */
232 if(hdr->max_pages == 0) {
233 /* Likely uninitialized/erased flash (0x00 or 0xFF) */
234 PRINTF("Coffee: Invalid header at page %u: max_pages is zero\n",
235 (unsigned)page);
236 return 0;
237 }
238
239 if(hdr->max_pages > COFFEE_PAGE_COUNT) {
240 /* Corrupted value larger than entire filesystem */
241 PRINTF("Coffee: Invalid header at page %u: max_pages %u exceeds total pages %u\n",
242 (unsigned)page, (unsigned)hdr->max_pages, (unsigned)COFFEE_PAGE_COUNT);
243 return 0;
244 }
245
246 /* Check 2: File must not extend beyond storage bounds */
247 if(page > COFFEE_PAGE_COUNT - hdr->max_pages) {
248 PRINTF("Coffee: Invalid header at page %u: file extends beyond storage\n",
249 (unsigned)page);
250 return 0;
251 }
252
253 /* Check 3: Log page must be valid if file is modified */
254 if(HDR_MODIFIED(*hdr)) {
255 if(hdr->log_page >= COFFEE_PAGE_COUNT) {
256 PRINTF("Coffee: Invalid header at page %u: log_page %u out of bounds\n",
257 (unsigned)page, (unsigned)hdr->log_page);
258 return 0;
259 }
260 }
261
262 /* Check 4: Log record size must fit in a page */
263 if(hdr->log_record_size > COFFEE_PAGE_SIZE) {
264 PRINTF("Coffee: Invalid header at page %u: log_record_size %u exceeds page size\n",
265 (unsigned)page, (unsigned)hdr->log_record_size);
266 return 0;
267 }
268
269 /* Check 5: Flag consistency for active files */
270 if(HDR_ACTIVE(*hdr) && !HDR_VALID(*hdr)) {
271 PRINTF("Coffee: Invalid header at page %u: active but not marked valid\n",
272 (unsigned)page);
273 return 0;
274 }
275
276 return 1;
277}
278/*---------------------------------------------------------------------------*/
279static cfs_offset_t
280absolute_offset(coffee_page_t page, cfs_offset_t offset)
281{
282 return page * COFFEE_PAGE_SIZE + sizeof(struct file_header) + offset;
283}
284/*---------------------------------------------------------------------------*/
285static coffee_page_t
286get_sector_status(coffee_page_t sector, struct sector_status *stats)
287{
288 static coffee_page_t skip_pages;
289 static char last_pages_are_active;
290 struct file_header hdr;
291 coffee_page_t active, obsolete, free;
292 coffee_page_t sector_start, sector_end;
293 coffee_page_t page;
294
295 memset(stats, 0, sizeof(*stats));
296 active = obsolete = free = 0;
297
298 /*
299 * get_sector_status() is an iterative function using local static
300 * state. It therefore requires that the caller starts iterating from
301 * sector 0 in order to reset the internal state.
302 */
303 if(sector == 0) {
304 skip_pages = 0;
305 last_pages_are_active = 0;
306 }
307
308 sector_start = sector * COFFEE_PAGES_PER_SECTOR;
309 sector_end = sector_start + COFFEE_PAGES_PER_SECTOR;
310
311 /*
312 * Account for pages belonging to a file starting in a previous
313 * segment that extends into this segment. If the whole segment is
314 * covered, we do not need to continue counting pages in this iteration.
315 */
316 if(last_pages_are_active) {
317 if(skip_pages >= COFFEE_PAGES_PER_SECTOR) {
318 stats->active = COFFEE_PAGES_PER_SECTOR;
319 skip_pages -= COFFEE_PAGES_PER_SECTOR;
320 return 0;
321 }
322 active = skip_pages;
323 } else {
324 if(skip_pages >= COFFEE_PAGES_PER_SECTOR) {
325 stats->obsolete = COFFEE_PAGES_PER_SECTOR;
326 skip_pages -= COFFEE_PAGES_PER_SECTOR;
327 return skip_pages >= COFFEE_PAGES_PER_SECTOR ? 0 : skip_pages;
328 }
329 obsolete = skip_pages;
330 }
331
332 /* Determine the amount of pages of each type that have not been
333 accounted for yet in the current sector. */
334 for(page = sector_start + skip_pages; page < sector_end;) {
335 read_header(&hdr, page);
336
337 /*
338 * If the header is invalid (corrupted storage), treat the remaining
339 * sector as free/corrupted space. This allows garbage collection
340 * to clean up the sector and prevents attempts to parse corrupted
341 * data as valid file headers.
342 */
343 if(!header_is_valid(&hdr, page)) {
344 PRINTF("Coffee: Invalid header at page %u in sector status scan\n",
345 (unsigned)page);
346 free = sector_end - page;
347 last_pages_are_active = 0;
348 break;
349 }
350
351 last_pages_are_active = 0;
352 if(HDR_ACTIVE(hdr)) {
353 last_pages_are_active = 1;
354 page += hdr.max_pages;
355 active += hdr.max_pages;
356 } else if(HDR_ISOLATED(hdr)) {
357 page++;
358 obsolete++;
359 } else if(HDR_OBSOLETE(hdr)) {
360 page += hdr.max_pages;
361 obsolete += hdr.max_pages;
362 } else {
363 free = sector_end - page;
364 break;
365 }
366 }
367
368 /*
369 * Determine the amount of pages in the following sectors that
370 * should be remembered for the next iteration. This is necessary
371 * because no file page except the first contains information
372 * about what type of page it is. A side effect of remembering this
373 * amount is that there is no need to read in the headers of each
374 * of these pages from the storage.
375 */
376 skip_pages = active + obsolete + free - COFFEE_PAGES_PER_SECTOR;
377 if(skip_pages > 0) {
378 if(last_pages_are_active) {
379 active = COFFEE_PAGES_PER_SECTOR - obsolete;
380 } else {
381 obsolete = COFFEE_PAGES_PER_SECTOR - active;
382 }
383 }
384
385 stats->active = active;
386 stats->obsolete = obsolete;
387 stats->free = free;
388
389 /*
390 * To avoid unnecessary page isolation, we notify the caller that
391 * "skip_pages" pages should be isolated only if the current file extent
392 * ends in the next sector. If the file extent ends in a more distant
393 * sector, however, the garbage collection can free the next sector
394 * immediately without requiring page isolation.
395 */
396 return (last_pages_are_active || (skip_pages >= COFFEE_PAGES_PER_SECTOR)) ?
397 0 : skip_pages;
398}
399/*---------------------------------------------------------------------------*/
400static void
401isolate_pages(coffee_page_t start, coffee_page_t skip_pages)
402{
403 struct file_header hdr;
404 coffee_page_t page;
405
406 /* Split an obsolete file starting in the previous sector and mark
407 the following pages as isolated. */
408 memset(&hdr, 0, sizeof(hdr));
409 hdr.flags = HDR_FLAG_ALLOCATED | HDR_FLAG_ISOLATED;
410
411 /* Isolation starts from the next sector. */
412 for(page = 0; page < skip_pages; page++) {
413 write_header(&hdr, start + page);
414 }
415 PRINTF("Coffee: Isolated %u pages starting in sector %d\n",
416 (unsigned)skip_pages, (int)start / COFFEE_PAGES_PER_SECTOR);
417}
418/*---------------------------------------------------------------------------*/
419static void
420collect_garbage(int mode)
421{
422 coffee_page_t sector;
423 struct sector_status stats;
424 coffee_page_t first_page, isolation_count;
425
426 PRINTF("Coffee: Running the garbage collector in %s mode\n",
427 mode == GC_RELUCTANT ? "reluctant" : "greedy");
428 /*
429 * The garbage collector erases as many sectors as possible. A sector is
430 * erasable if there are only free or obsolete pages in it.
431 */
432 for(sector = 0; sector < COFFEE_SECTOR_COUNT; sector++) {
433 isolation_count = get_sector_status(sector, &stats);
434 PRINTF("Coffee: Sector %u has %u active, %u obsolete, and %u free pages.\n",
435 (unsigned)sector, (unsigned)stats.active,
436 (unsigned)stats.obsolete, (unsigned)stats.free);
437
438 if(stats.active > 0) {
439 continue;
440 }
441
442 if((mode == GC_RELUCTANT && stats.free == 0) ||
443 (mode == GC_GREEDY && stats.obsolete > 0)) {
444 first_page = sector * COFFEE_PAGES_PER_SECTOR;
445 if(first_page < next_free) {
446 next_free = first_page;
447 }
448
449 if(isolation_count > 0) {
450 isolate_pages(first_page + COFFEE_PAGES_PER_SECTOR, isolation_count);
451 }
452
453 COFFEE_ERASE(sector);
454 PRINTF("Coffee: Erased sector %d!\n", sector);
455
456 if(mode == GC_RELUCTANT && isolation_count > 0) {
457 break;
458 }
459 }
460 }
461}
462/*---------------------------------------------------------------------------*/
463static coffee_page_t
464next_file(coffee_page_t page, struct file_header *hdr)
465{
466 coffee_page_t next;
467
468 /*
469 * The quick-skip algorithm for finding file extents is the most
470 * essential part of Coffee. The file allocation rules enable this
471 * algorithm to quickly jump over free areas and allocated extents
472 * after reading single headers and determining their status.
473 *
474 * The worst-case performance occurs when we encounter multiple long
475 * sequences of isolated pages, but such sequences are uncommon and
476 * always shorter than a sector.
477 */
478 if(HDR_FREE(*hdr)) {
479 next = (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
480 } else if(HDR_ISOLATED(*hdr)) {
481 next = page + 1;
482 } else {
483 if(hdr->max_pages > COFFEE_PAGE_COUNT - page) {
484 PRINTF("Coffee: next_file overflow at page %u with max_pages %u\n",
485 (unsigned)page, (unsigned)hdr->max_pages);
486 return COFFEE_PAGE_COUNT;
487 }
488 next = page + hdr->max_pages;
489 }
490
491 if(next > COFFEE_PAGE_COUNT) {
492 return COFFEE_PAGE_COUNT;
493 }
494
495 return next;
496}
497/*---------------------------------------------------------------------------*/
498static struct file *
499load_file(coffee_page_t start, struct file_header *hdr)
500{
501 int i, unreferenced, free;
502 struct file *file;
503
504 /*
505 * We prefer to overwrite a free slot since unreferenced ones
506 * contain usable data. Free slots are designated by the page
507 * value INVALID_PAGE.
508 */
509 for(i = 0, unreferenced = free = -1; i < COFFEE_MAX_OPEN_FILES; i++) {
510 if(FILE_FREE(&coffee_files[i])) {
511 free = i;
512 break;
513 } else if(FILE_UNREFERENCED(&coffee_files[i])) {
514 unreferenced = i;
515 }
516 }
517
518 if(free == -1) {
519 if(unreferenced != -1) {
520 i = unreferenced;
521 } else {
522 return NULL;
523 }
524 }
525
526 file = &coffee_files[i];
527 file->page = start;
528 file->end = UNKNOWN_OFFSET;
529 file->max_pages = hdr->max_pages;
530 file->flags = HDR_MODIFIED(*hdr) ? COFFEE_FILE_MODIFIED : 0;
531 /* We don't know the amount of records yet. */
532 file->record_count = -1;
533
534 return file;
535}
536/*---------------------------------------------------------------------------*/
537static struct file *
538find_file(const char *name)
539{
540 int i;
541 struct file_header hdr;
542 coffee_page_t page;
543
544 /* First check if the file metadata is cached. */
545 for(i = 0; i < COFFEE_MAX_OPEN_FILES; i++) {
546 if(FILE_FREE(&coffee_files[i])) {
547 continue;
548 }
549
550 read_header(&hdr, coffee_files[i].page);
551 if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) {
552 return &coffee_files[i];
553 }
554 }
555
556 /* Scan the flash memory sequentially otherwise. */
557 for(page = 0; page < COFFEE_PAGE_COUNT; page = next_file(page, &hdr)) {
558 read_header(&hdr, page);
559
560 /*
561 * Skip invalid headers during filesystem traversal. A corrupted
562 * file should not prevent finding other valid files. Jump to the
563 * next sector to avoid getting stuck on a corrupted region.
564 */
565 if(!header_is_valid(&hdr, page)) {
566 PRINTF("Coffee: Skipping invalid header at page %u during file search\n",
567 (unsigned)page);
568 page = (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
569 if(page >= COFFEE_PAGE_COUNT) {
570 break;
571 }
572 continue;
573 }
574
575 if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) {
576 return load_file(page, &hdr);
577 }
578 }
579
580 return NULL;
581}
582/*---------------------------------------------------------------------------*/
583static cfs_offset_t
584file_end(coffee_page_t start)
585{
586 struct file_header hdr;
587 unsigned char buf[COFFEE_PAGE_SIZE];
588 coffee_page_t page;
589 int i;
590
591 read_header(&hdr, start);
592 if(!header_is_valid(&hdr, start)) {
593 PRINTF("Coffee: Cannot determine file end for invalid header at page %u\n",
594 (unsigned)start);
595 return 0;
596 }
597
598 /*
599 * Move from the end of the range towards the beginning and look for
600 * a byte that has been modified.
601 *
602 * An important implication of this is that if the last written bytes
603 * are zeroes, then these are skipped from the calculation.
604 */
605 for(page = hdr.max_pages - 1; page < hdr.max_pages; page--) {
606 COFFEE_READ(buf, sizeof(buf), (start + page) * COFFEE_PAGE_SIZE);
607 for(i = COFFEE_PAGE_SIZE - 1; i >= 0; i--) {
608 if(buf[i] != 0) {
609 if(page == 0 && i < sizeof(hdr)) {
610 return 0;
611 }
612 return 1 + i + (page * COFFEE_PAGE_SIZE) - sizeof(hdr);
613 }
614 }
615 }
616
617 /* All bytes are writable. */
618 return 0;
619}
620/*---------------------------------------------------------------------------*/
621static coffee_page_t
622find_contiguous_pages(coffee_page_t amount)
623{
624 coffee_page_t page, start, next_page;
625 struct file_header hdr;
626
627 start = INVALID_PAGE;
628 for(page = next_free; page < COFFEE_PAGE_COUNT;) {
629 read_header(&hdr, page);
630 if(HDR_FREE(hdr)) {
631 /* Free header - no validation needed */
632 if(start == INVALID_PAGE) {
633 start = page;
634 if(start + amount >= COFFEE_PAGE_COUNT) {
635 /* We can stop immediately if the remaining pages are not enough. */
636 break;
637 }
638 }
639
640 /* All remaining pages in this sector are free --
641 jump to the next sector. */
642 next_page = next_file(page, &hdr);
643
644 /* Ensure forward progress to prevent infinite loops */
645 if(next_page <= page) {
646 PRINTF("Coffee: next_file() did not advance at page %u\n", (unsigned)page);
647 break;
648 }
649 page = next_page;
650
651 if(start + amount <= page) {
652 if(start == next_free) {
653 next_free = start + amount;
654 }
655 return start;
656 }
657 } else {
658 /*
659 * Allocated header - validate before using with next_file(). Invalid
660 * allocated headers mean we don't know the file size.
661 */
662 if(!header_is_valid(&hdr, page)) {
663 PRINTF("Coffee: Skipping invalid allocated header at page %u\n",
664 (unsigned)page);
665 /*
666 * Skip to next sector - we don't know how many pages this corrupt
667 * file occupies. Conservatively skip entire sector to avoid
668 * allocating over potentially valid data within the corrupted file.
669 */
670 start = INVALID_PAGE;
671 page = (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
672 continue;
673 }
674
675 start = INVALID_PAGE;
676 next_page = next_file(page, &hdr);
677
678 /* Ensure forward progress to prevent infinite loops */
679 if(next_page <= page) {
680 PRINTF("Coffee: next_file() did not advance at page %u\n",
681 (unsigned)page);
682 break;
683 }
684 page = next_page;
685 }
686 }
687 return INVALID_PAGE;
688}
689/*---------------------------------------------------------------------------*/
690static int
691remove_by_page(coffee_page_t page, int remove_log,
692 int close_fds, int gc_allowed)
693{
694 struct file_header hdr;
695 int i;
696
697 if(page >= COFFEE_PAGE_COUNT) {
698 PRINTF("Coffee: Cannot remove page %u: out of bounds\n", (unsigned)page);
699 return -1;
700 }
701
702 read_header(&hdr, page);
703 if(!header_is_valid(&hdr, page)) {
704 PRINTF("Coffee: Cannot remove invalid header at page %u\n", (unsigned)page);
705 return -1;
706 }
707
708 if(!HDR_ACTIVE(hdr)) {
709 return -1;
710 }
711
712 if(remove_log && HDR_MODIFIED(hdr)) {
713 if(hdr.log_page == page) {
714 PRINTF("Coffee: Circular log reference at page %u\n", (unsigned)page);
715 return -1;
716 }
717 if(remove_by_page(hdr.log_page, !REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC) < 0) {
718 return -1;
719 }
720 }
721
722 hdr.flags |= HDR_FLAG_OBSOLETE;
723 write_header(&hdr, page);
724
725 gc_wait = 0;
726
727 /* Close all file descriptors that reference the removed file. */
728 if(close_fds) {
729 for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
730 if(coffee_fd_set[i].file != NULL && coffee_fd_set[i].file->page == page) {
731 coffee_fd_set[i].flags = COFFEE_FD_FREE;
732 }
733 }
734 }
735
736 for(i = 0; i < COFFEE_MAX_OPEN_FILES; i++) {
737 if(coffee_files[i].page == page) {
738 coffee_files[i].page = INVALID_PAGE;
739 coffee_files[i].references = 0;
740 coffee_files[i].max_pages = 0;
741 }
742 }
743
744 if(!COFFEE_EXTENDED_WEAR_LEVELLING && gc_allowed) {
745 collect_garbage(GC_RELUCTANT);
746 }
747
748 return 0;
749}
750/*---------------------------------------------------------------------------*/
751static coffee_page_t
752page_count(cfs_offset_t size)
753{
754 return (size + sizeof(struct file_header) + COFFEE_PAGE_SIZE - 1) /
756}
757/*---------------------------------------------------------------------------*/
758static struct file *
759reserve(const char *name, coffee_page_t pages,
760 int allow_duplicates, unsigned flags)
761{
762 struct file_header hdr;
763 coffee_page_t page;
764 struct file *file;
765
766 if(!allow_duplicates && find_file(name) != NULL) {
767 return NULL;
768 }
769
770 page = find_contiguous_pages(pages);
771 if(page == INVALID_PAGE) {
772 if(gc_wait) {
773 return NULL;
774 }
775 collect_garbage(GC_GREEDY);
776 page = find_contiguous_pages(pages);
777 if(page == INVALID_PAGE) {
778 gc_wait = 1;
779 return NULL;
780 }
781 }
782
783 memset(&hdr, 0, sizeof(hdr));
784 strncpy(hdr.name, name, sizeof(hdr.name) - 1);
785 hdr.max_pages = pages;
786 hdr.flags = HDR_FLAG_ALLOCATED | flags;
787 write_header(&hdr, page);
788
789 PRINTF("Coffee: Reserved %u pages starting from %u for file %s\n",
790 (unsigned)pages, (unsigned)page, name);
791
792 file = load_file(page, &hdr);
793 if(file != NULL) {
794 file->end = 0;
795 }
796
797 return file;
798}
799/*---------------------------------------------------------------------------*/
800#if COFFEE_MICRO_LOGS
801static void
802adjust_log_config(struct file_header *hdr,
803 uint16_t *log_record_size, uint16_t *log_records)
804{
805 *log_record_size = hdr->log_record_size == 0 ?
806 COFFEE_PAGE_SIZE : hdr->log_record_size;
807 *log_records = hdr->log_records == 0 ?
808 COFFEE_LOG_SIZE / *log_record_size : hdr->log_records;
809}
810#endif /* COFFEE_MICRO_LOGS */
811/*---------------------------------------------------------------------------*/
812#if COFFEE_MICRO_LOGS
813static uint16_t
814modify_log_buffer(uint16_t log_record_size,
815 cfs_offset_t *offset, uint16_t *size)
816{
817 uint16_t region;
818
819 region = *offset / log_record_size;
820 *offset %= log_record_size;
821
822 if(*size > log_record_size - *offset) {
823 *size = log_record_size - *offset;
824 }
825
826 return region;
827}
828#endif /* COFFEE_MICRO_LOGS */
829/*---------------------------------------------------------------------------*/
830#if COFFEE_MICRO_LOGS
831static int
832get_record_index(coffee_page_t log_page, uint16_t search_records,
833 uint16_t region)
834{
835 cfs_offset_t base;
836 uint16_t processed;
837 uint16_t batch_size;
838 int16_t match_index, i;
839
840 base = absolute_offset(log_page, sizeof(uint16_t) * search_records);
841 batch_size = search_records > COFFEE_LOG_TABLE_LIMIT ?
842 COFFEE_LOG_TABLE_LIMIT : search_records;
843 processed = 0;
844 match_index = -1;
845
846 {
847 uint16_t indices[batch_size];
848
849 while(processed < search_records && match_index < 0) {
850 if(batch_size + processed > search_records) {
851 batch_size = search_records - processed;
852 }
853
854 base -= batch_size * sizeof(indices[0]);
855 COFFEE_READ(&indices, sizeof(indices[0]) * batch_size, base);
856
857 for(i = batch_size - 1; i >= 0; i--) {
858 if(indices[i] - 1 == region) {
859 match_index = search_records - processed - (batch_size - i);
860 break;
861 }
862 }
863
864 processed += batch_size;
865 }
866 }
867
868 return match_index;
869}
870#endif /* COFFEE_MICRO_LOGS */
871/*---------------------------------------------------------------------------*/
872#if COFFEE_MICRO_LOGS
873static int
874read_log_page(struct file_header *hdr, int16_t record_count,
875 struct log_param *lp)
876{
877 uint16_t region;
878 int16_t match_index;
879 uint16_t log_record_size;
880 uint16_t log_records;
881 cfs_offset_t base;
882 uint16_t search_records;
883
884 adjust_log_config(hdr, &log_record_size, &log_records);
885 region = modify_log_buffer(log_record_size, &lp->offset, &lp->size);
886
887 search_records = record_count < 0 ? log_records : record_count;
888 match_index = get_record_index(hdr->log_page, search_records, region);
889 if(match_index < 0) {
890 return -1;
891 }
892
893 base = absolute_offset(hdr->log_page, log_records * sizeof(region));
894 base += (cfs_offset_t)match_index * log_record_size;
895 base += lp->offset;
896 COFFEE_READ(lp->buf, lp->size, base);
897
898 return lp->size;
899}
900#endif /* COFFEE_MICRO_LOGS */
901/*---------------------------------------------------------------------------*/
902#if COFFEE_MICRO_LOGS
903static coffee_page_t
904create_log(struct file *file, struct file_header *hdr)
905{
906 uint16_t log_record_size, log_records;
907 cfs_offset_t size;
908 struct file *log_file;
909
910 adjust_log_config(hdr, &log_record_size, &log_records);
911
912 /* Log index size + log data size. */
913 size = log_records * (sizeof(uint16_t) + log_record_size);
914
915 log_file = reserve(hdr->name, page_count(size), 1, HDR_FLAG_LOG);
916 if(log_file == NULL) {
917 return INVALID_PAGE;
918 }
919
920 hdr->flags |= HDR_FLAG_MODIFIED;
921 hdr->log_page = log_file->page;
922 write_header(hdr, file->page);
923
924 file->flags |= COFFEE_FILE_MODIFIED;
925 return log_file->page;
926}
927#endif /* COFFEE_MICRO_LOGS */
928/*---------------------------------------------------------------------------*/
929static int
930merge_log(coffee_page_t file_page, int extend)
931{
932 struct file_header hdr, hdr2;
933 int fd, n;
934 cfs_offset_t offset;
935 coffee_page_t max_pages;
936 struct file *new_file;
937 int i;
938 uint16_t buf_size;
939
940 read_header(&hdr, file_page);
941 if(!header_is_valid(&hdr, file_page)) {
942 PRINTF("Coffee: Cannot merge invalid header at page %u\n",
943 (unsigned)file_page);
944 return -1;
945 }
946
947 buf_size = (hdr.log_record_size == 0) ? COFFEE_PAGE_SIZE : hdr.log_record_size;
948 if(buf_size > COFFEE_PAGE_SIZE) {
949 PRINTF("Coffee: Invalid buffer size %u at page %u\n",
950 (unsigned)buf_size, (unsigned)file_page);
951 return -1;
952 }
953
954 fd = cfs_open(hdr.name, CFS_READ);
955 if(fd < 0) {
956 return -1;
957 }
958
959 /*
960 * The reservation function adds extra space for the header, which has
961 * already been accounted for in the previous reservation.
962 */
963 max_pages = hdr.max_pages << extend;
964 new_file = reserve(hdr.name, max_pages, 1, 0);
965 if(new_file == NULL) {
966 cfs_close(fd);
967 return -1;
968 }
969
970 offset = 0;
971 do {
972 char buf[buf_size];
973 n = cfs_read(fd, buf, sizeof(buf));
974 if(n < 0) {
975 remove_by_page(new_file->page, !REMOVE_LOG, !CLOSE_FDS, ALLOW_GC);
976 cfs_close(fd);
977 return -1;
978 } else if(n > 0) {
979 COFFEE_WRITE(buf, n, absolute_offset(new_file->page, offset));
980 offset += n;
981 }
982 } while(n != 0);
983
984 for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
985 if(coffee_fd_set[i].flags != COFFEE_FD_FREE &&
986 coffee_fd_set[i].file->page == file_page) {
987 coffee_fd_set[i].file = new_file;
988 new_file->references++;
989 }
990 }
991
992 if(remove_by_page(file_page, REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC) < 0) {
993 remove_by_page(new_file->page, !REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC);
994 cfs_close(fd);
995 return -1;
996 }
997
998 /* Copy the log configuration. */
999 read_header(&hdr2, new_file->page);
1000
1001 /*
1002 * Validate the newly created file's header. While reserve() just created
1003 * this file and it should be valid, verify to ensure storage operations
1004 * completed correctly.
1005 */
1006 if(!header_is_valid(&hdr2, new_file->page)) {
1007 PRINTF("Coffee: New file header invalid at page %u after merge\n",
1008 (unsigned)new_file->page);
1009 remove_by_page(new_file->page, !REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC);
1010 cfs_close(fd);
1011 return -1;
1012 }
1013
1014 hdr2.log_record_size = hdr.log_record_size;
1015 hdr2.log_records = hdr.log_records;
1016 write_header(&hdr2, new_file->page);
1017
1018 new_file->flags &= ~COFFEE_FILE_MODIFIED;
1019 new_file->end = offset;
1020
1021 cfs_close(fd);
1022
1023 return 0;
1024}
1025/*---------------------------------------------------------------------------*/
1026#if COFFEE_MICRO_LOGS
1027static int
1028find_next_record(struct file *file, coffee_page_t log_page,
1029 int log_records)
1030{
1031 int log_record, preferred_batch_size;
1032
1033 if(file->record_count >= 0) {
1034 return file->record_count;
1035 }
1036
1037 preferred_batch_size = log_records > COFFEE_LOG_TABLE_LIMIT ?
1038 COFFEE_LOG_TABLE_LIMIT : log_records;
1039 {
1040 /* The next log record is unknown at this point; search for it. */
1041 uint16_t indices[preferred_batch_size];
1042 uint16_t processed;
1043 uint16_t batch_size;
1044
1045 log_record = log_records;
1046 for(processed = 0; processed < log_records; processed += batch_size) {
1047 batch_size = log_records - processed >= preferred_batch_size ?
1048 preferred_batch_size : log_records - processed;
1049
1050 COFFEE_READ(&indices, batch_size * sizeof(indices[0]),
1051 absolute_offset(log_page, processed * sizeof(indices[0])));
1052 for(log_record = 0; log_record < batch_size; log_record++) {
1053 if(indices[log_record] == 0) {
1054 log_record += processed;
1055 break;
1056 }
1057 }
1058 }
1059 }
1060
1061 return log_record;
1062}
1063#endif /* COFFEE_MICRO_LOGS */
1064/*---------------------------------------------------------------------------*/
1065#if COFFEE_MICRO_LOGS
1066static int
1067write_log_page(struct file *file, struct log_param *lp)
1068{
1069 struct file_header hdr;
1070 uint16_t region;
1071 coffee_page_t log_page;
1072 int16_t log_record;
1073 uint16_t log_record_size;
1074 uint16_t log_records;
1075 cfs_offset_t offset;
1076 struct log_param lp_out;
1077
1078 read_header(&hdr, file->page);
1079
1080 adjust_log_config(&hdr, &log_record_size, &log_records);
1081 region = modify_log_buffer(log_record_size, &lp->offset, &lp->size);
1082
1083 log_page = 0;
1084 if(HDR_MODIFIED(hdr)) {
1085 /* A log structure has already been created. */
1086 log_page = hdr.log_page;
1087 log_record = find_next_record(file, log_page, log_records);
1088 if(log_record >= log_records) {
1089 /* The log is full; merge the log. */
1090 PRINTF("Coffee: Merging the file %s with its log\n", hdr.name);
1091 return merge_log(file->page, 0);
1092 }
1093 } else {
1094 /* Create a log structure. */
1095 log_page = create_log(file, &hdr);
1096 if(log_page == INVALID_PAGE) {
1097 return -1;
1098 }
1099 PRINTF("Coffee: Created a log structure for file %s at page %u\n",
1100 hdr.name, (unsigned)log_page);
1101 hdr.log_page = log_page;
1102 log_record = 0;
1103 }
1104
1105 {
1106 char copy_buf[log_record_size];
1107
1108 lp_out.offset = offset = region * log_record_size;
1109 lp_out.buf = copy_buf;
1110 lp_out.size = log_record_size;
1111
1112 if((lp->offset > 0 || lp->size != log_record_size) &&
1113 read_log_page(&hdr, log_record, &lp_out) < 0) {
1114 COFFEE_READ(copy_buf, sizeof(copy_buf),
1115 absolute_offset(file->page, offset));
1116 }
1117
1118 memcpy(&copy_buf[lp->offset], lp->buf, lp->size);
1119
1120 /*
1121 * Write the region number in the region index table.
1122 * The region number is incremented to avoid values of zero.
1123 */
1124 offset = absolute_offset(log_page, 0);
1125 ++region;
1126 COFFEE_WRITE(&region, sizeof(region),
1127 offset + log_record * sizeof(region));
1128
1129 offset += log_records * sizeof(region);
1130 COFFEE_WRITE(copy_buf, sizeof(copy_buf),
1131 offset + log_record * log_record_size);
1132 file->record_count = log_record + 1;
1133 }
1134
1135 return lp->size;
1136}
1137#endif /* COFFEE_MICRO_LOGS */
1138/*---------------------------------------------------------------------------*/
1139static int
1140get_available_fd(void)
1141{
1142 int i;
1143
1144 for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
1145 if(coffee_fd_set[i].flags == COFFEE_FD_FREE) {
1146 return i;
1147 }
1148 }
1149 return -1;
1150}
1151/*---------------------------------------------------------------------------*/
1152int
1153cfs_open(const char *name, int flags)
1154{
1155 int fd;
1156 struct file_desc *fdp;
1157
1158 fd = get_available_fd();
1159 if(fd < 0) {
1160 PRINTF("Coffee: Failed to allocate a new file descriptor!\n");
1161 return -1;
1162 }
1163
1164 fdp = &coffee_fd_set[fd];
1165 fdp->flags = 0;
1166 fdp->io_flags = 0;
1167
1168 fdp->file = find_file(name);
1169 if(fdp->file == NULL) {
1170 if((flags & (CFS_READ | CFS_WRITE)) == CFS_READ) {
1171 return -1;
1172 }
1173 fdp->file = reserve(name, page_count(COFFEE_DYN_SIZE), 1, 0);
1174 if(fdp->file == NULL) {
1175 return -1;
1176 }
1177 fdp->file->end = 0;
1178 } else if(fdp->file->end == UNKNOWN_OFFSET) {
1179 fdp->file->end = file_end(fdp->file->page);
1180 }
1181
1182 fdp->flags |= flags;
1183 fdp->offset = flags & CFS_APPEND ? fdp->file->end : 0;
1184 fdp->file->references++;
1185
1186 return fd;
1187}
1188/*---------------------------------------------------------------------------*/
1189void
1191{
1192 if(FD_VALID(fd)) {
1193 coffee_fd_set[fd].flags = COFFEE_FD_FREE;
1194 coffee_fd_set[fd].file->references--;
1195 coffee_fd_set[fd].file = NULL;
1196 }
1197}
1198/*---------------------------------------------------------------------------*/
1200cfs_seek(int fd, cfs_offset_t offset, int whence)
1201{
1202 struct file_desc *fdp;
1203 cfs_offset_t new_offset;
1204
1205 if(!FD_VALID(fd)) {
1206 return -1;
1207 }
1208 fdp = &coffee_fd_set[fd];
1209
1210 if(whence == CFS_SEEK_SET) {
1211 new_offset = offset;
1212 } else if(whence == CFS_SEEK_END) {
1213 new_offset = fdp->file->end + offset;
1214 } else if(whence == CFS_SEEK_CUR) {
1215 new_offset = fdp->offset + offset;
1216 } else {
1217 return (cfs_offset_t)-1;
1218 }
1219
1220 if(new_offset < 0 || new_offset > fdp->file->max_pages * COFFEE_PAGE_SIZE) {
1221 return -1;
1222 }
1223
1224 if(fdp->file->end < new_offset) {
1225 if(FD_WRITABLE(fd)) {
1226 fdp->file->end = new_offset;
1227 } else {
1228 /* Disallow seeking past the end of the file for read only FDs */
1229 return (cfs_offset_t)-1;
1230 }
1231 }
1232
1233 return fdp->offset = new_offset;
1234}
1235/*---------------------------------------------------------------------------*/
1236int
1237cfs_remove(const char *name)
1238{
1239 struct file *file;
1240
1241 /*
1242 * Coffee removes files by marking them as obsolete. The space
1243 * is not guaranteed to be reclaimed immediately, but must be
1244 * sweeped by the garbage collector. The garbage collector is
1245 * called once a file reservation request cannot be granted.
1246 */
1247 file = find_file(name);
1248 if(file == NULL) {
1249 return -1;
1250 }
1251
1252 return remove_by_page(file->page, REMOVE_LOG, CLOSE_FDS, ALLOW_GC);
1253}
1254/*---------------------------------------------------------------------------*/
1255int
1256cfs_read(int fd, void *buf, unsigned size)
1257{
1258 struct file_desc *fdp;
1259 struct file *file;
1260#if COFFEE_MICRO_LOGS
1261 struct file_header hdr;
1262 struct log_param lp;
1263 unsigned bytes_left;
1264 int r;
1265#endif
1266
1267 if(!(FD_VALID(fd) && FD_READABLE(fd))) {
1268 return -1;
1269 }
1270
1271 fdp = &coffee_fd_set[fd];
1272 file = fdp->file;
1273
1274 if(fdp->io_flags & CFS_COFFEE_IO_ENSURE_READ_LENGTH) {
1275 while(fdp->offset + size > file->end) {
1276 ((char *)buf)[--size] = '\0';
1277 }
1278 } else if(fdp->offset + size > file->end) {
1279 size = file->end - fdp->offset;
1280 }
1281
1282 /* If the file is not modified, read directly from the file extent. */
1283 if(!FILE_MODIFIED(file)) {
1284 COFFEE_READ(buf, size, absolute_offset(file->page, fdp->offset));
1285 fdp->offset += size;
1286 return size;
1287 }
1288
1289#if COFFEE_MICRO_LOGS
1290 read_header(&hdr, file->page);
1291
1292 /*
1293 * Copy the contents of the most recent log record. If there is
1294 * no log record for the file area to read from, we simply read
1295 * from the original file extent.
1296 */
1297 for(bytes_left = size; bytes_left > 0; bytes_left -= r) {
1298 lp.offset = fdp->offset;
1299 lp.buf = buf;
1300 lp.size = bytes_left;
1301 r = read_log_page(&hdr, file->record_count, &lp);
1302
1303 /* Read from the original file if we cannot find the data in the log. */
1304 if(r < 0) {
1305 COFFEE_READ(buf, lp.size, absolute_offset(file->page, fdp->offset));
1306 r = lp.size;
1307 }
1308 fdp->offset += r;
1309 buf = (char *)buf + r;
1310 }
1311#endif /* COFFEE_MICRO_LOGS */
1312
1313 return size;
1314}
1315/*---------------------------------------------------------------------------*/
1316int
1317cfs_write(int fd, const void *buf, unsigned size)
1318{
1319 struct file_desc *fdp;
1320 struct file *file;
1321#if COFFEE_MICRO_LOGS
1322 int i;
1323 struct log_param lp;
1324 cfs_offset_t bytes_left;
1325 int8_t need_dummy_write;
1326 const char dummy[1] = { 0xff };
1327#endif
1328
1329 if(!(FD_VALID(fd) && FD_WRITABLE(fd))) {
1330 return -1;
1331 }
1332
1333 fdp = &coffee_fd_set[fd];
1334 file = fdp->file;
1335
1336 /* Attempt to extend the file if we try to write past the end. */
1337 if(!(fdp->io_flags & CFS_COFFEE_IO_FIRM_SIZE)) {
1338 while(size + fdp->offset + sizeof(struct file_header) >
1339 (file->max_pages * COFFEE_PAGE_SIZE)) {
1340 if(merge_log(file->page, 1) < 0) {
1341 return -1;
1342 }
1343 file = fdp->file;
1344 PRINTF("Extended the file at page %u\n", (unsigned)file->page);
1345 }
1346 }
1347
1348#if COFFEE_MICRO_LOGS
1349 if(!(fdp->io_flags & CFS_COFFEE_IO_FLASH_AWARE) &&
1350 (FILE_MODIFIED(file) || fdp->offset < file->end)) {
1351 need_dummy_write = 0;
1352 for(bytes_left = size; bytes_left > 0;) {
1353 lp.offset = fdp->offset;
1354 lp.buf = (void *)buf;
1355 lp.size = bytes_left;
1356 i = write_log_page(file, &lp);
1357 if(i < 0) {
1358 /* Return -1 if we wrote nothing because the log write failed. */
1359 if(size == bytes_left) {
1360 return -1;
1361 }
1362 break;
1363 } else if(i == 0) {
1364 /* The file was merged with the log. */
1365 file = fdp->file;
1366 } else {
1367 /* A log record was written. */
1368 bytes_left -= i;
1369 fdp->offset += i;
1370 buf = (char *)buf + i;
1371
1372 /* Update the file end for a potential log merge that might
1373 occur while writing log records. */
1374 if(fdp->offset > file->end) {
1375 file->end = fdp->offset;
1376 need_dummy_write = 1;
1377 }
1378 }
1379 }
1380
1381 if(need_dummy_write) {
1382 /*
1383 * A log record has been written at an offset beyond the original
1384 * extent's end. Consequently, we need to write a dummy value at the
1385 * corresponding end offset in the original extent to ensure that
1386 * the correct file size is calculated when opening the file again.
1387 */
1388 COFFEE_WRITE(dummy, 1, absolute_offset(file->page, fdp->offset - 1));
1389 }
1390 } else {
1391#endif /* COFFEE_MICRO_LOGS */
1392 if(COFFEE_APPEND_ONLY && fdp->offset < file->end) {
1393 return -1;
1394 }
1395
1396 COFFEE_WRITE(buf, size, absolute_offset(file->page, fdp->offset));
1397 fdp->offset += size;
1398#if COFFEE_MICRO_LOGS
1399 }
1400#endif /* COFFEE_MICRO_LOGS */
1401
1402 if(fdp->offset > file->end) {
1403 file->end = fdp->offset;
1404 }
1405
1406 return size;
1407}
1408/*---------------------------------------------------------------------------*/
1409int
1410cfs_opendir(struct cfs_dir *dir, const char *name)
1411{
1412 /*
1413 * Coffee is only guaranteed to support the directory names "/" and ".",
1414 * but it does not enforce this currently.
1415 */
1416 memset(dir->state, 0, sizeof(coffee_page_t));
1417 return 0;
1418}
1419/*---------------------------------------------------------------------------*/
1420int
1421cfs_readdir(struct cfs_dir *dir, struct cfs_dirent *record)
1422{
1423 struct file_header hdr;
1424 coffee_page_t page;
1425 coffee_page_t next_page;
1426
1427 memcpy(&page, dir->state, sizeof(coffee_page_t));
1428
1429 while(page < COFFEE_PAGE_COUNT) {
1430 read_header(&hdr, page);
1431 if(!header_is_valid(&hdr, page)) {
1432 PRINTF("Coffee: Skipping invalid header at page %u in readdir\n",
1433 (unsigned)page);
1434 /* Skip to next sector to avoid getting stuck on corrupted region */
1435 page = (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
1436 continue;
1437 }
1438
1439 if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr)) {
1440 memcpy(record->name,
1441 hdr.name,
1442 MIN(sizeof(record->name), sizeof(hdr.name)));
1443 record->name[MIN(sizeof(record->name), sizeof(hdr.name)) - 1] = '\0';
1444 record->size = file_end(page);
1445
1446 next_page = next_file(page, &hdr);
1447 memcpy(dir->state, &next_page, sizeof(coffee_page_t));
1448 return 0;
1449 }
1450 page = next_file(page, &hdr);
1451 }
1452
1453 return -1;
1454}
1455/*---------------------------------------------------------------------------*/
1456void
1457cfs_closedir(struct cfs_dir *dir)
1458{
1459 return;
1460}
1461/*---------------------------------------------------------------------------*/
1462int
1463cfs_coffee_reserve(const char *name, cfs_offset_t size)
1464{
1465 return reserve(name, page_count(size), 0, 0) == NULL ? -1 : 0;
1466}
1467/*---------------------------------------------------------------------------*/
1468int
1469cfs_coffee_configure_log(const char *filename, unsigned log_size,
1470 unsigned log_record_size)
1471{
1472 struct file *file;
1473 struct file_header hdr;
1474
1475 if(log_record_size == 0 || log_record_size > COFFEE_PAGE_SIZE ||
1476 log_size < log_record_size) {
1477 return -1;
1478 }
1479
1480 file = find_file(filename);
1481 if(file == NULL) {
1482 return -1;
1483 }
1484
1485 read_header(&hdr, file->page);
1486 if(!header_is_valid(&hdr, file->page)) {
1487 PRINTF("Coffee: Cannot configure log for invalid header at page %u\n",
1488 (unsigned)file->page);
1489 return -1;
1490 }
1491
1492 if(HDR_MODIFIED(hdr)) {
1493 /* Too late to customize the log. */
1494 return -1;
1495 }
1496
1497 hdr.log_records = log_size / log_record_size;
1498 hdr.log_record_size = log_record_size;
1499 write_header(&hdr, file->page);
1500
1501 return 0;
1502}
1503/*---------------------------------------------------------------------------*/
1504int
1506{
1507 if(!FD_VALID(fd)) {
1508 return -1;
1509 }
1510
1511 coffee_fd_set[fd].io_flags |= flags;
1512
1513 return 0;
1514}
1515/*---------------------------------------------------------------------------*/
1516int
1518{
1519 coffee_page_t i;
1520
1521 PRINTF("Coffee: Formatting %u sectors", (unsigned)COFFEE_SECTOR_COUNT);
1522
1523 for(i = 0; i < COFFEE_SECTOR_COUNT; i++) {
1524 COFFEE_ERASE(i);
1525 PRINTF(".");
1526 }
1527
1528 /* Formatting invalidates the file information. */
1529 memset(&coffee_files, 0, sizeof(coffee_files));
1530 memset(&coffee_fd_set, 0, sizeof(coffee_fd_set));
1531 next_free = 0;
1532 gc_wait = 1;
1533
1534 PRINTF(" done!\n");
1535
1536 return 0;
1537}
1538/*---------------------------------------------------------------------------*/
static volatile at86rf215_flags_t flags
The radio driver uses the following flags to keep track of the current state of the radio and IRQ eve...
Definition at86rf215.c:144
Header for the Coffee file system.
CFS header file.
int16_t coffee_page_t
Page.
#define COFFEE_ERASE(sector)
Erase.
#define COFFEE_MAX_OPEN_FILES
Number of file cache entries.
#define COFFEE_WRITE(buf, size, offset)
Write.
#define COFFEE_DYN_SIZE
Default reserved file size.
#define COFFEE_READ(buf, size, offset)
Read.
#define COFFEE_FD_SET_SIZE
Number of file descriptor entries.
#define COFFEE_LOG_SIZE
Default micro-log size.
#define COFFEE_NAME_LENGTH
Maximal filename length.
#define COFFEE_LOG_TABLE_LIMIT
Maximal amount of log table entries read in one batch.
#define COFFEE_PAGE_SIZE
Logical page size.
int cfs_coffee_configure_log(const char *filename, unsigned log_size, unsigned log_record_size)
Configure the on-demand log file.
#define CFS_READ
Specify that cfs_open() should open a file for reading.
Definition cfs.h:95
int cfs_read(int f, void *buf, unsigned int len)
Read data from an open file.
Definition cfs-cooja.c:85
int cfs_coffee_format(void)
Format the storage area assigned to Coffee.
int cfs_coffee_reserve(const char *name, cfs_offset_t size)
Reserve space for a file.
#define CFS_APPEND
Specify that cfs_open() should append written data to the file rather than overwriting it.
Definition cfs.h:123
#define CFS_COFFEE_IO_FLASH_AWARE
Instruct Coffee that the access pattern to this file is adapted to flash I/O semantics by design,...
Definition cfs-coffee.h:53
int cfs_remove(const char *name)
Remove a file.
int cfs_readdir(struct cfs_dir *dir, struct cfs_dirent *record)
Read a directory entry.
int cfs_open(const char *name, int flags)
Open a file.
#define CFS_COFFEE_IO_FIRM_SIZE
Instruct Coffee not to attempt to extend the file upon a request to write past the reserved file size...
Definition cfs-coffee.h:64
#define CFS_SEEK_SET
Specify that cfs_seek() should compute the offset from the beginning of the file.
Definition cfs.h:132
#define CFS_WRITE
Specify that cfs_open() should open a file for writing.
Definition cfs.h:109
#define CFS_SEEK_CUR
Specify that cfs_seek() should compute the offset from the current position of the file pointer.
Definition cfs.h:141
cfs_offset_t cfs_seek(int fd, cfs_offset_t offset, int whence)
Seek to a specified position in an open file.
#define CFS_SEEK_END
Specify that cfs_seek() should compute the offset from the end of the file.
Definition cfs.h:150
int cfs_write(int f, const void *buf, unsigned int len)
Write data to an open file.
Definition cfs-cooja.c:102
void cfs_closedir(struct cfs_dir *dir)
Close a directory opened with cfs_opendir().
void cfs_close(int fd)
Close an open file.
int cfs_coffee_set_io_semantics(int fd, unsigned flags)
Set the I/O semantics for accessing a file.
int cfs_offset_t
CFS directory entry name length.
Definition cfs.h:65
#define CFS_COFFEE_IO_ENSURE_READ_LENGTH
Instruct Coffee to set unused bytes in the destination buffer to zero.
Definition cfs-coffee.h:73
int cfs_opendir(struct cfs_dir *dir, const char *name)
Open a directory for reading directory entries.
static void start(void)
Start measurement.