Contiki-Inga 3.x
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 "cfs-coffee-arch.h"
58 #include "contiki-conf.h"
59 #include "cfs/cfs.h"
60 #include "cfs-coffee-arch.h"
61 #include "cfs/cfs-coffee.h"
62 
63 /* Micro logs enable modifications on storage types that do not support
64  in-place updates. This applies primarily to flash memories. */
65 #ifndef COFFEE_MICRO_LOGS
66 #define COFFEE_MICRO_LOGS 1
67 #endif
68 
69 /* If the files are expected to be appended to only, this parameter
70  can be set to save some code space. */
71 #ifndef COFFEE_APPEND_ONLY
72 #define COFFEE_APPEND_ONLY 0
73 #endif
74 
75 #if COFFEE_MICRO_LOGS && COFFEE_APPEND_ONLY
76 #error "Cannot have COFFEE_APPEND_ONLY set when COFFEE_MICRO_LOGS is set."
77 #endif
78 
79 /* I/O semantics can be set on file descriptors in order to optimize
80  file access on certain storage types. */
81 #ifndef COFFEE_IO_SEMANTICS
82 #define COFFEE_IO_SEMANTICS 0
83 #endif
84 
85 /*
86  * Prevent sectors from being erased directly after file removal.
87  * This will level the wear across sectors better, but may lead
88  * to longer garbage collection procedures.
89  */
90 #ifndef COFFEE_EXTENDED_WEAR_LEVELLING
91 #define COFFEE_EXTENDED_WEAR_LEVELLING 1
92 #endif
93 
94 #if COFFEE_START & (COFFEE_SECTOR_SIZE - 1)
95 #error COFFEE_START must point to the first byte in a sector.
96 #endif
97 
98 #define COFFEE_FD_FREE 0x0
99 #define COFFEE_FD_READ 0x1
100 #define COFFEE_FD_WRITE 0x2
101 #define COFFEE_FD_APPEND 0x4
102 
103 #define COFFEE_FILE_MODIFIED 0x1
104 
105 #define INVALID_PAGE ((coffee_page_t)-1)
106 #define UNKNOWN_OFFSET ((cfs_offset_t)-1)
107 
108 #define REMOVE_LOG 1
109 #define CLOSE_FDS 1
110 #define ALLOW_GC 1
111 
112 /* "Greedy" garbage collection erases as many sectors as possible. */
113 #define GC_GREEDY 0
114 /* "Reluctant" garbage collection stops after erasing one sector. */
115 #define GC_RELUCTANT 1
116 
117 /* File descriptor macros. */
118 #define FD_VALID(fd) \
119  ((fd) >= 0 && (fd) < COFFEE_FD_SET_SIZE && \
120  coffee_fd_set[(fd)].flags != COFFEE_FD_FREE)
121 #define FD_READABLE(fd) (coffee_fd_set[(fd)].flags & CFS_READ)
122 #define FD_WRITABLE(fd) (coffee_fd_set[(fd)].flags & CFS_WRITE)
123 #define FD_APPENDABLE(fd) (coffee_fd_set[(fd)].flags & CFS_APPEND)
124 
125 /* File object macros. */
126 #define FILE_MODIFIED(file) ((file)->flags & COFFEE_FILE_MODIFIED)
127 #define FILE_FREE(file) ((file)->max_pages == 0)
128 #define FILE_UNREFERENCED(file) ((file)->references == 0)
129 
130 /* File header flags. */
131 #define HDR_FLAG_VALID 0x1 /* Completely written header. */
132 #define HDR_FLAG_ALLOCATED 0x2 /* Allocated file. */
133 #define HDR_FLAG_OBSOLETE 0x4 /* File marked for GC. */
134 #define HDR_FLAG_MODIFIED 0x8 /* Modified file, log exists. */
135 #define HDR_FLAG_LOG 0x10 /* Log file. */
136 #define HDR_FLAG_ISOLATED 0x20 /* Isolated page. */
137 
138 /* File header macros. */
139 #define CHECK_FLAG(hdr, flag) ((hdr).flags & (flag))
140 #define HDR_VALID(hdr) CHECK_FLAG(hdr, HDR_FLAG_VALID)
141 #define HDR_ALLOCATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ALLOCATED)
142 #define HDR_FREE(hdr) !HDR_ALLOCATED(hdr)
143 #define HDR_LOG(hdr) CHECK_FLAG(hdr, HDR_FLAG_LOG)
144 #define HDR_MODIFIED(hdr) CHECK_FLAG(hdr, HDR_FLAG_MODIFIED)
145 #define HDR_ISOLATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ISOLATED)
146 #define HDR_OBSOLETE(hdr) CHECK_FLAG(hdr, HDR_FLAG_OBSOLETE)
147 #define HDR_ACTIVE(hdr) (HDR_ALLOCATED(hdr) && \
148  !HDR_OBSOLETE(hdr) && \
149  !HDR_ISOLATED(hdr))
150 
151 /* Shortcuts derived from the hardware-dependent configuration of Coffee. */
152 #define COFFEE_SECTOR_COUNT (unsigned)(COFFEE_SIZE / COFFEE_SECTOR_SIZE)
153 #define COFFEE_PAGE_COUNT \
154  ((coffee_page_t)(COFFEE_SIZE / COFFEE_PAGE_SIZE))
155 #define COFFEE_PAGES_PER_SECTOR \
156  ((coffee_page_t)(COFFEE_SECTOR_SIZE / COFFEE_PAGE_SIZE))
157 
158 /* This structure is used for garbage collection statistics. */
159 struct sector_status {
160  coffee_page_t active;
161  coffee_page_t obsolete;
162  coffee_page_t free;
163 };
164 
165 /* The structure of cached file objects. */
166 struct file {
167  cfs_offset_t end;
168  coffee_page_t page;
169  coffee_page_t max_pages;
170  int16_t record_count;
171  uint8_t references;
172  uint8_t flags;
173 };
174 
175 /* The file descriptor structure. */
176 struct file_desc {
177  cfs_offset_t offset;
178  struct file *file;
179  uint8_t flags;
180 #if COFFEE_IO_SEMANTICS
181  uint8_t io_flags;
182 #endif
183 };
184 
185 /* The file header structure mimics the representation of file headers
186  in the physical storage medium. */
187 struct file_header {
188  coffee_page_t log_page;
189  uint16_t log_records;
190  uint16_t log_record_size;
191  coffee_page_t max_pages;
192  uint8_t deprecated_eof_hint;
193  uint8_t flags;
194  char name[COFFEE_NAME_LENGTH];
195 };
196 
197 /* This is needed because of a buggy compiler. */
198 struct log_param {
199  cfs_offset_t offset;
200  const char *buf;
201  uint16_t size;
202 };
203 
204 /*
205  * The protected memory consists of structures that should not be
206  * overwritten during system checkpointing because they may be used by
207  * the checkpointing implementation. These structures need not be
208  * protected if checkpointing is not used.
209  */
210 static struct protected_mem_t {
211  struct file coffee_files[COFFEE_MAX_OPEN_FILES];
212  struct file_desc coffee_fd_set[COFFEE_FD_SET_SIZE];
213  coffee_page_t next_free;
214  char gc_wait;
215 } protected_mem;
216 static struct file * const coffee_files = protected_mem.coffee_files;
217 static struct file_desc * const coffee_fd_set = protected_mem.coffee_fd_set;
218 static coffee_page_t * const next_free = &protected_mem.next_free;
219 static char * const gc_wait = &protected_mem.gc_wait;
220 
221 /*---------------------------------------------------------------------------*/
222 static void
223 write_header(struct file_header *hdr, coffee_page_t page)
224 {
225  hdr->flags |= HDR_FLAG_VALID;
226  COFFEE_WRITE(hdr, sizeof(*hdr), page * COFFEE_PAGE_SIZE);
227  PRINTF("hdr.flags = %x\n", hdr->flags);
228 }
229 /*---------------------------------------------------------------------------*/
230 static void
231 read_header(struct file_header *hdr, coffee_page_t page)
232 {
233  COFFEE_READ(hdr, sizeof(*hdr), page * COFFEE_PAGE_SIZE);
234 #if DEBUG
235  if(HDR_ACTIVE(*hdr) && !HDR_VALID(*hdr)) {
236  PRINTF("Invalid header at page %u!\n", (unsigned)page);
237  PRINTF("hdr.flags = %x\n", hdr->flags);
238  }
239 #endif
240 }
241 /*---------------------------------------------------------------------------*/
242 static cfs_offset_t
243 absolute_offset(coffee_page_t page, cfs_offset_t offset)
244 {
245  return page * COFFEE_PAGE_SIZE + sizeof(struct file_header) + offset;
246 }
247 /*---------------------------------------------------------------------------*/
248 static coffee_page_t
249 get_sector_status(uint16_t sector, struct sector_status *stats)
250 {
251  static coffee_page_t skip_pages;
252  static char last_pages_are_active;
253  struct file_header hdr;
254  coffee_page_t active, obsolete, free;
255  coffee_page_t sector_start, sector_end;
256  coffee_page_t page;
257 
258  memset(stats, 0, sizeof(*stats));
259  active = obsolete = free = 0;
260 
261  /*
262  * get_sector_status() is an iterative function using local static
263  * state. It therefore requires that the caller starts iterating from
264  * sector 0 in order to reset the internal state.
265  */
266  if(sector == 0) {
267  skip_pages = 0;
268  last_pages_are_active = 0;
269  }
270 
271  sector_start = sector * COFFEE_PAGES_PER_SECTOR;
272  sector_end = sector_start + COFFEE_PAGES_PER_SECTOR;
273 
274  /*
275  * Account for pages belonging to a file starting in a previous
276  * segment that extends into this segment. If the whole segment is
277  * covered, we do not need to continue counting pages in this iteration.
278  */
279  if(last_pages_are_active) {
280  if(skip_pages >= COFFEE_PAGES_PER_SECTOR) {
281  stats->active = COFFEE_PAGES_PER_SECTOR;
282  skip_pages -= COFFEE_PAGES_PER_SECTOR;
283  return 0;
284  }
285  active = skip_pages;
286  } else {
287  if(skip_pages >= COFFEE_PAGES_PER_SECTOR) {
288  stats->obsolete = COFFEE_PAGES_PER_SECTOR;
289  skip_pages -= COFFEE_PAGES_PER_SECTOR;
290  return skip_pages >= COFFEE_PAGES_PER_SECTOR ? 0 : skip_pages;
291  }
292  obsolete = skip_pages;
293  }
294 
295  /* Determine the amount of pages of each type that have not been
296  accounted for yet in the current sector. */
297  for(page = sector_start + skip_pages; page < sector_end;) {
298  read_header(&hdr, page);
299  last_pages_are_active = 0;
300  if(HDR_ACTIVE(hdr)) {
301  last_pages_are_active = 1;
302  page += hdr.max_pages;
303  active += hdr.max_pages;
304  } else if(HDR_ISOLATED(hdr)) {
305  page++;
306  obsolete++;
307  } else if(HDR_OBSOLETE(hdr)) {
308  page += hdr.max_pages;
309  obsolete += hdr.max_pages;
310  } else {
311  free = sector_end - page;
312  break;
313  }
314  }
315 
316  /*
317  * Determine the amount of pages in the following sectors that
318  * should be remembered for the next iteration. This is necessary
319  * because no page except the first of a file contains information
320  * about what type of page it is. A side effect of remembering this
321  * amount is that there is no need to read in the headers of each
322  * of these pages from the storage.
323  */
324  skip_pages = active + obsolete + free - COFFEE_PAGES_PER_SECTOR;
325  if(skip_pages > 0) {
326  if(last_pages_are_active) {
327  active = COFFEE_PAGES_PER_SECTOR - obsolete;
328  } else {
329  obsolete = COFFEE_PAGES_PER_SECTOR - active;
330  }
331  }
332 
333  stats->active = active;
334  stats->obsolete = obsolete;
335  stats->free = free;
336 
337  /*
338  * To avoid unnecessary page isolation, we notify the caller that
339  * "skip_pages" pages should be isolated only if the current file extent
340  * ends in the next sector. If the file extent ends in a more distant
341  * sector, however, the garbage collection can free the next sector
342  * immediately without requiring page isolation.
343  */
344  return (last_pages_are_active || (skip_pages >= COFFEE_PAGES_PER_SECTOR)) ?
345  0 : skip_pages;
346 }
347 /*---------------------------------------------------------------------------*/
348 static void
349 isolate_pages(coffee_page_t start, coffee_page_t skip_pages)
350 {
351  struct file_header hdr;
352  coffee_page_t page;
353 
354  /* Split an obsolete file starting in the previous sector and mark
355  the following pages as isolated. */
356  memset(&hdr, 0, sizeof(hdr));
357  hdr.flags = HDR_FLAG_ALLOCATED | HDR_FLAG_ISOLATED;
358 
359  /* Isolation starts from the next sector. */
360  for(page = 0; page < skip_pages; page++) {
361  write_header(&hdr, start + page);
362  }
363  PRINTF("Coffee: Isolated %u pages starting in sector %d\n",
364  (unsigned)skip_pages, (int)start / COFFEE_PAGES_PER_SECTOR);
365 
366 }
367 /*---------------------------------------------------------------------------*/
368 static void
369 collect_garbage(int mode)
370 {
371  uint16_t sector;
372  struct sector_status stats;
373  coffee_page_t first_page, isolation_count;
374 
375  PRINTF("Coffee: Running the file system garbage collector in %s mode\n",
376  mode == GC_RELUCTANT ? "reluctant" : "greedy");
377  /*
378  * The garbage collector erases as many sectors as possible. A sector is
379  * erasable if there are only free or obsolete pages in it.
380  */
381  for(sector = 0; sector < COFFEE_SECTOR_COUNT; sector++) {
382  isolation_count = get_sector_status(sector, &stats);
383  PRINTF("Coffee: Sector %u has %u active, %u obsolete, and %u free pages.\n",
384  sector, (unsigned)stats.active,
385  (unsigned)stats.obsolete, (unsigned)stats.free);
386 
387  if(stats.active > 0) {
388  continue;
389  }
390 
391  if((mode == GC_RELUCTANT && stats.free == 0) ||
392  (mode == GC_GREEDY && stats.obsolete > 0)) {
393  first_page = sector * COFFEE_PAGES_PER_SECTOR;
394  if(first_page < *next_free) {
395  *next_free = first_page;
396  }
397 
398  if(isolation_count > 0) {
399  isolate_pages(first_page + COFFEE_PAGES_PER_SECTOR, isolation_count);
400  }
401 
402  COFFEE_ERASE(sector);
403  PRINTF("Coffee: Erased sector %d!\n", sector);
404 
405  if(mode == GC_RELUCTANT && isolation_count > 0) {
406  break;
407  }
408  }
409  }
410 }
411 /*---------------------------------------------------------------------------*/
412 static coffee_page_t
413 next_file(coffee_page_t page, struct file_header *hdr)
414 {
415  /*
416  * The quick-skip algorithm for finding file extents is the most
417  * essential part of Coffee. The file allocation rules enables this
418  * algorithm to quickly jump over free areas and allocated extents
419  * after reading single headers and determining their status.
420  *
421  * The worst-case performance occurs when we encounter multiple long
422  * sequences of isolated pages, but such sequences are uncommon and
423  * always shorter than a sector.
424  */
425  if(HDR_FREE(*hdr)) {
426  return (page + COFFEE_PAGES_PER_SECTOR) & ~(COFFEE_PAGES_PER_SECTOR - 1);
427  } else if(HDR_ISOLATED(*hdr)) {
428  return page + 1;
429  }
430  return page + hdr->max_pages;
431 }
432 /*---------------------------------------------------------------------------*/
433 static struct file *
434 load_file(coffee_page_t start, struct file_header *hdr)
435 {
436  int i, unreferenced, free;
437  struct file *file;
438 
439  /*
440  * We prefer to overwrite a free slot since unreferenced ones
441  * contain usable data. Free slots are designated by the page
442  * value INVALID_PAGE.
443  */
444  for(i = 0, unreferenced = free = -1; i < COFFEE_MAX_OPEN_FILES; i++) {
445  if(FILE_FREE(&coffee_files[i])) {
446  free = i;
447  break;
448  } else if(FILE_UNREFERENCED(&coffee_files[i])) {
449  unreferenced = i;
450  }
451  }
452 
453  if(free == -1) {
454  if(unreferenced != -1) {
455  i = unreferenced;
456  } else {
457  return NULL;
458  }
459  }
460 
461  file = &coffee_files[i];
462  file->page = start;
463  file->end = UNKNOWN_OFFSET;
464  file->max_pages = hdr->max_pages;
465  file->flags = 0;
466  if(HDR_MODIFIED(*hdr)) {
467  file->flags |= COFFEE_FILE_MODIFIED;
468  }
469  /* We don't know the amount of records yet. */
470  file->record_count = -1;
471 
472  return file;
473 }
474 /*---------------------------------------------------------------------------*/
475 static struct file *
476 find_file(const char *name)
477 {
478  int i;
479  struct file_header hdr;
480  coffee_page_t page;
481 
482  /* First check if the file metadata is cached. */
483  for(i = 0; i < COFFEE_MAX_OPEN_FILES; i++) {
484  if(FILE_FREE(&coffee_files[i])) {
485  continue;
486  }
487 
488  read_header(&hdr, coffee_files[i].page);
489  if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) {
490  return &coffee_files[i];
491  }
492  }
493 
494  /* Scan the flash memory sequentially otherwise. */
495  for(page = 0; page < COFFEE_PAGE_COUNT; page = next_file(page, &hdr)) {
496  read_header(&hdr, page);
497  if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) {
498  return load_file(page, &hdr);
499  }
500  }
501 
502  return NULL;
503 }
504 /*---------------------------------------------------------------------------*/
505 static cfs_offset_t
506 file_end(coffee_page_t start)
507 {
508  struct file_header hdr;
509  unsigned char buf[COFFEE_PAGE_SIZE];
510  coffee_page_t page;
511  int i;
512 
513  read_header(&hdr, start);
514 
515  /*
516  * Move from the end of the range towards the beginning and look for
517  * a byte that has been modified.
518  *
519  * An important implication of this is that if the last written bytes
520  * are zeroes, then these are skipped from the calculation.
521  */
522 
523  for(page = hdr.max_pages - 1; page >= 0; page--) {
524  COFFEE_READ(buf, sizeof(buf), (start + page) * COFFEE_PAGE_SIZE);
525  for(i = COFFEE_PAGE_SIZE - 1; i >= 0; i--) {
526  if(buf[i] != 0) {
527  if(page == 0 && i < sizeof(hdr)) {
528  return 0;
529  }
530  return 1 + i + (page * COFFEE_PAGE_SIZE) - sizeof(hdr);
531  }
532  }
533  }
534 
535  /* All bytes are writable. */
536  return 0;
537 }
538 /*---------------------------------------------------------------------------*/
539 static coffee_page_t
540 find_contiguous_pages(coffee_page_t amount)
541 {
542  coffee_page_t page, start;
543  struct file_header hdr;
544 
545  start = INVALID_PAGE;
546  for(page = *next_free; page < COFFEE_PAGE_COUNT;) {
547  read_header(&hdr, page);
548  if(HDR_FREE(hdr)) {
549  if(start == INVALID_PAGE) {
550  start = page;
551  if(start + amount >= COFFEE_PAGE_COUNT) {
552  /* We can stop immediately if the remaining pages are not enough. */
553  break;
554  }
555  }
556 
557  /* All remaining pages in this sector are free --
558  jump to the next sector. */
559  page = next_file(page, &hdr);
560 
561  if(start + amount <= page) {
562  if(start == *next_free) {
563  *next_free = start + amount;
564  }
565  return start;
566  }
567  } else {
568  start = INVALID_PAGE;
569  page = next_file(page, &hdr);
570  }
571  }
572  return INVALID_PAGE;
573 }
574 /*---------------------------------------------------------------------------*/
575 static int
576 remove_by_page(coffee_page_t page, int remove_log, int close_fds,
577  int gc_allowed)
578 {
579  struct file_header hdr;
580  int i;
581 
582  read_header(&hdr, page);
583  if(!HDR_ACTIVE(hdr)) {
584  return -1;
585  }
586 
587  if(remove_log && HDR_MODIFIED(hdr)) {
588  if(remove_by_page(hdr.log_page, !REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC) < 0) {
589  return -1;
590  }
591  }
592 
593  hdr.flags |= HDR_FLAG_OBSOLETE;
594  write_header(&hdr, page);
595 
596  *gc_wait = 0;
597 
598  /* Close all file descriptors that reference the removed file. */
599  if(close_fds) {
600  for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
601  if(coffee_fd_set[i].file != NULL && coffee_fd_set[i].file->page == page) {
602  coffee_fd_set[i].flags = COFFEE_FD_FREE;
603  }
604  }
605  }
606 
607  for(i = 0; i < COFFEE_MAX_OPEN_FILES; i++) {
608  if(coffee_files[i].page == page) {
609  coffee_files[i].page = INVALID_PAGE;
610  coffee_files[i].references = 0;
611  coffee_files[i].max_pages = 0;
612  }
613  }
614 
615 #if !COFFEE_EXTENDED_WEAR_LEVELLING
616  if(gc_allowed) {
617  collect_garbage(GC_RELUCTANT);
618  }
619 #endif
620 
621  return 0;
622 }
623 /*---------------------------------------------------------------------------*/
624 static coffee_page_t
625 page_count(cfs_offset_t size)
626 {
627  return (size + sizeof(struct file_header) + COFFEE_PAGE_SIZE - 1) /
628  COFFEE_PAGE_SIZE;
629 }
630 /*---------------------------------------------------------------------------*/
631 static struct file *
632 reserve(const char *name, coffee_page_t pages,
633  int allow_duplicates, unsigned flags)
634 {
635  struct file_header hdr;
636  coffee_page_t page;
637  struct file *file;
638 
639  if(!allow_duplicates && find_file(name) != NULL) {
640  return NULL;
641  }
642 
643  page = find_contiguous_pages(pages);
644  if(page == INVALID_PAGE) {
645  if(*gc_wait) {
646  return NULL;
647  }
648  collect_garbage(GC_GREEDY);
649  page = find_contiguous_pages(pages);
650  if(page == INVALID_PAGE) {
651  *gc_wait = 1;
652  return NULL;
653  }
654  }
655 
656  memset(&hdr, 0, sizeof(hdr));
657  memcpy(hdr.name, name, sizeof(hdr.name) - 1);
658  hdr.max_pages = pages;
659  hdr.flags = HDR_FLAG_ALLOCATED | flags;
660  write_header(&hdr, page);
661 
662  PRINTF("Coffee: Reserved %u pages starting from %u for file %s\n",
663  pages, page, name);
664 
665  file = load_file(page, &hdr);
666  if(file != NULL) {
667  file->end = 0;
668  }
669 
670  return file;
671 }
672 /*---------------------------------------------------------------------------*/
673 #if COFFEE_MICRO_LOGS
674 static void
675 adjust_log_config(struct file_header *hdr,
676  uint16_t *log_record_size, uint16_t *log_records)
677 {
678  *log_record_size = hdr->log_record_size == 0 ?
679  COFFEE_PAGE_SIZE : hdr->log_record_size;
680  *log_records = hdr->log_records == 0 ?
681  COFFEE_LOG_SIZE / *log_record_size : hdr->log_records;
682 }
683 #endif /* COFFEE_MICRO_LOGS */
684 /*---------------------------------------------------------------------------*/
685 #if COFFEE_MICRO_LOGS
686 static uint16_t
687 modify_log_buffer(uint16_t log_record_size,
688  cfs_offset_t *offset, uint16_t *size)
689 {
690  uint16_t region;
691 
692  region = *offset / log_record_size;
693  *offset %= log_record_size;
694 
695  if(*size > log_record_size - *offset) {
696  *size = log_record_size - *offset;
697  }
698 
699  return region;
700 }
701 #endif /* COFFEE_MICRO_LOGS */
702 /*---------------------------------------------------------------------------*/
703 #if COFFEE_MICRO_LOGS
704 static int
705 get_record_index(coffee_page_t log_page, uint16_t search_records,
706  uint16_t region)
707 {
708  cfs_offset_t base;
709  uint16_t processed;
710  uint16_t batch_size;
711  int16_t match_index, i;
712 
713  base = absolute_offset(log_page, sizeof(uint16_t) * search_records);
714  batch_size = search_records > COFFEE_LOG_TABLE_LIMIT ?
715  COFFEE_LOG_TABLE_LIMIT : search_records;
716  processed = 0;
717  match_index = -1;
718 
719  {
720  uint16_t indices[batch_size];
721 
722  while(processed < search_records && match_index < 0) {
723  if(batch_size + processed > search_records) {
724  batch_size = search_records - processed;
725  }
726 
727  base -= batch_size * sizeof(indices[0]);
728  COFFEE_READ(&indices, sizeof(indices[0]) * batch_size, base);
729 
730  for(i = batch_size - 1; i >= 0; i--) {
731  if(indices[i] - 1 == region) {
732  match_index = search_records - processed - (batch_size - i);
733  break;
734  }
735  }
736 
737  processed += batch_size;
738  }
739  }
740 
741  return match_index;
742 }
743 #endif /* COFFEE_MICRO_LOGS */
744 /*---------------------------------------------------------------------------*/
745 #if COFFEE_MICRO_LOGS
746 static int
747 read_log_page(struct file_header *hdr, int16_t record_count,
748  struct log_param *lp)
749 {
750  uint16_t region;
751  int16_t match_index;
752  uint16_t log_record_size;
753  uint16_t log_records;
754  cfs_offset_t base;
755  uint16_t search_records;
756 
757  adjust_log_config(hdr, &log_record_size, &log_records);
758  region = modify_log_buffer(log_record_size, &lp->offset, &lp->size);
759 
760  search_records = record_count < 0 ? log_records : record_count;
761  match_index = get_record_index(hdr->log_page, search_records, region);
762  if(match_index < 0) {
763  return -1;
764  }
765 
766  base = absolute_offset(hdr->log_page, log_records * sizeof(region));
767  base += (cfs_offset_t)match_index * log_record_size;
768  base += lp->offset;
769  COFFEE_READ(lp->buf, lp->size, base);
770 
771  return lp->size;
772 }
773 #endif /* COFFEE_MICRO_LOGS */
774 /*---------------------------------------------------------------------------*/
775 #if COFFEE_MICRO_LOGS
776 static coffee_page_t
777 create_log(struct file *file, struct file_header *hdr)
778 {
779  uint16_t log_record_size, log_records;
780  cfs_offset_t size;
781  struct file *log_file;
782 
783  adjust_log_config(hdr, &log_record_size, &log_records);
784 
785  /* Log index size + log data size. */
786  size = log_records * (sizeof(uint16_t) + log_record_size);
787 
788  log_file = reserve(hdr->name, page_count(size), 1, HDR_FLAG_LOG);
789  if(log_file == NULL) {
790  return INVALID_PAGE;
791  }
792 
793  hdr->flags |= HDR_FLAG_MODIFIED;
794  hdr->log_page = log_file->page;
795  write_header(hdr, file->page);
796 
797  file->flags |= COFFEE_FILE_MODIFIED;
798  return log_file->page;
799 }
800 #endif /* COFFEE_MICRO_LOGS */
801 /*---------------------------------------------------------------------------*/
802 static int
803 merge_log(coffee_page_t file_page, int extend)
804 {
805  struct file_header hdr, hdr2;
806  int fd, n;
807  cfs_offset_t offset;
808  coffee_page_t max_pages;
809  struct file *new_file;
810  int i;
811 
812  read_header(&hdr, file_page);
813 
814  fd = cfs_open(hdr.name, CFS_READ);
815  if(fd < 0) {
816  return -1;
817  }
818 
819  /*
820  * The reservation function adds extra space for the header, which has
821  * already been accounted for in the previous reservation.
822  */
823  max_pages = hdr.max_pages << extend;
824  new_file = reserve(hdr.name, max_pages, 1, 0);
825  if(new_file == NULL) {
826  cfs_close(fd);
827  return -1;
828  }
829 
830  offset = 0;
831  do {
832  char buf[hdr.log_record_size == 0 ? COFFEE_PAGE_SIZE : hdr.log_record_size];
833  n = cfs_read(fd, buf, sizeof(buf));
834  if(n < 0) {
835  remove_by_page(new_file->page, !REMOVE_LOG, !CLOSE_FDS, ALLOW_GC);
836  cfs_close(fd);
837  return -1;
838  } else if(n > 0) {
839  COFFEE_WRITE(buf, n, absolute_offset(new_file->page, offset));
840  offset += n;
841  }
842  } while(n != 0);
843 
844  for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
845  if(coffee_fd_set[i].flags != COFFEE_FD_FREE &&
846  coffee_fd_set[i].file->page == file_page) {
847  coffee_fd_set[i].file = new_file;
848  new_file->references++;
849  }
850  }
851 
852  if(remove_by_page(file_page, REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC) < 0) {
853  remove_by_page(new_file->page, !REMOVE_LOG, !CLOSE_FDS, !ALLOW_GC);
854  cfs_close(fd);
855  return -1;
856  }
857 
858  /* Copy the log configuration and the EOF hint. */
859  read_header(&hdr2, new_file->page);
860  hdr2.log_record_size = hdr.log_record_size;
861  hdr2.log_records = hdr.log_records;
862  write_header(&hdr2, new_file->page);
863 
864  new_file->flags &= ~COFFEE_FILE_MODIFIED;
865  new_file->end = offset;
866 
867  cfs_close(fd);
868 
869  return 0;
870 }
871 /*---------------------------------------------------------------------------*/
872 #if COFFEE_MICRO_LOGS
873 static int
874 find_next_record(struct file *file, coffee_page_t log_page,
875  int log_records)
876 {
877  int log_record, preferred_batch_size;
878 
879  if(file->record_count >= 0) {
880  return file->record_count;
881  }
882 
883  preferred_batch_size = log_records > COFFEE_LOG_TABLE_LIMIT ?
884  COFFEE_LOG_TABLE_LIMIT : log_records;
885  {
886  /* The next log record is unknown at this point; search for it. */
887  uint16_t indices[preferred_batch_size];
888  uint16_t processed;
889  uint16_t batch_size;
890 
891  log_record = log_records;
892  for(processed = 0; processed < log_records; processed += batch_size) {
893  batch_size = log_records - processed >= preferred_batch_size ?
894  preferred_batch_size : log_records - processed;
895 
896  COFFEE_READ(&indices, batch_size * sizeof(indices[0]),
897  absolute_offset(log_page, processed * sizeof(indices[0])));
898  for(log_record = 0; log_record < batch_size; log_record++) {
899  if(indices[log_record] == 0) {
900  log_record += processed;
901  break;
902  }
903  }
904  }
905  }
906 
907  return log_record;
908 }
909 #endif /* COFFEE_MICRO_LOGS */
910 /*---------------------------------------------------------------------------*/
911 #if COFFEE_MICRO_LOGS
912 static int
913 write_log_page(struct file *file, struct log_param *lp)
914 {
915  struct file_header hdr;
916  uint16_t region;
917  coffee_page_t log_page;
918  int16_t log_record;
919  uint16_t log_record_size;
920  uint16_t log_records;
921  cfs_offset_t offset;
922  struct log_param lp_out;
923 
924  read_header(&hdr, file->page);
925 
926  adjust_log_config(&hdr, &log_record_size, &log_records);
927  region = modify_log_buffer(log_record_size, &lp->offset, &lp->size);
928 
929  log_page = 0;
930  if(HDR_MODIFIED(hdr)) {
931  /* A log structure has already been created. */
932  log_page = hdr.log_page;
933  log_record = find_next_record(file, log_page, log_records);
934  if(log_record >= log_records) {
935  /* The log is full; merge the log. */
936  PRINTF("Coffee: Merging the file %s with its log\n", hdr.name);
937  return merge_log(file->page, 0);
938  }
939  } else {
940  /* Create a log structure. */
941  log_page = create_log(file, &hdr);
942  if(log_page == INVALID_PAGE) {
943  return -1;
944  }
945  PRINTF("Coffee: Created a log structure for file %s at page %u\n",
946  hdr.name, (unsigned)log_page);
947  hdr.log_page = log_page;
948  log_record = 0;
949  }
950 
951  {
952  char copy_buf[log_record_size];
953 
954  lp_out.offset = offset = region * log_record_size;
955  lp_out.buf = copy_buf;
956  lp_out.size = log_record_size;
957 
958  if((lp->offset > 0 || lp->size != log_record_size) &&
959  read_log_page(&hdr, log_record, &lp_out) < 0) {
960  COFFEE_READ(copy_buf, sizeof(copy_buf),
961  absolute_offset(file->page, offset));
962  }
963 
964  memcpy(&copy_buf[lp->offset], lp->buf, lp->size);
965 
966  /*
967  * Write the region number in the region index table.
968  * The region number is incremented to avoid values of zero.
969  */
970  offset = absolute_offset(log_page, 0);
971  ++region;
972  COFFEE_WRITE(&region, sizeof(region),
973  offset + log_record * sizeof(region));
974 
975  offset += log_records * sizeof(region);
976  COFFEE_WRITE(copy_buf, sizeof(copy_buf),
977  offset + log_record * log_record_size);
978  file->record_count = log_record + 1;
979  }
980 
981  return lp->size;
982 }
983 #endif /* COFFEE_MICRO_LOGS */
984 /*---------------------------------------------------------------------------*/
985 static int
986 get_available_fd(void)
987 {
988  PRINTF("get_available_fd()\n");
989  int i;
990 
991  for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
992  if(coffee_fd_set[i].flags == COFFEE_FD_FREE) {
993  return i;
994  }
995  }
996  return -1;
997 }
998 /*---------------------------------------------------------------------------*/
999 int
1000 cfs_open(const char *name, int flags)
1001 {
1002  int fd;
1003  struct file_desc *fdp;
1004 
1005  fd = get_available_fd();
1006  if(fd < 0) {
1007  PRINTF("Coffee: Failed to allocate a new file descriptor!\n");
1008  return -1;
1009  }
1010 
1011  fdp = &coffee_fd_set[fd];
1012  fdp->flags = 0;
1013 
1014  fdp->file = find_file(name);
1015  if(fdp->file == NULL) {
1016  if((flags & (CFS_READ | CFS_WRITE)) == CFS_READ) {
1017  return -1;
1018  }
1019  fdp->file = reserve(name, page_count(COFFEE_DYN_SIZE), 1, 0);
1020  if(fdp->file == NULL) {
1021  return -1;
1022  }
1023  fdp->file->end = 0;
1024  } else if(fdp->file->end == UNKNOWN_OFFSET) {
1025  fdp->file->end = file_end(fdp->file->page);
1026  }
1027 
1028  fdp->flags |= flags;
1029  fdp->offset = flags & CFS_APPEND ? fdp->file->end : 0;
1030  fdp->file->references++;
1031 
1032  return fd;
1033 }
1034 /*---------------------------------------------------------------------------*/
1035 void
1036 cfs_close(int fd)
1037 {
1038  if(FD_VALID(fd)) {
1039  coffee_fd_set[fd].flags = COFFEE_FD_FREE;
1040  coffee_fd_set[fd].file->references--;
1041  coffee_fd_set[fd].file = NULL;
1042  }
1043 }
1044 /*---------------------------------------------------------------------------*/
1045 cfs_offset_t
1046 cfs_seek(int fd, cfs_offset_t offset, int whence)
1047 {
1048  struct file_desc *fdp;
1049  cfs_offset_t new_offset;
1050 
1051  if(!FD_VALID(fd)) {
1052  return -1;
1053  }
1054  fdp = &coffee_fd_set[fd];
1055 
1056  if(whence == CFS_SEEK_SET) {
1057  new_offset = offset;
1058  } else if(whence == CFS_SEEK_END) {
1059  new_offset = fdp->file->end + offset;
1060  } else if(whence == CFS_SEEK_CUR) {
1061  new_offset = fdp->offset + offset;
1062  } else {
1063  return (cfs_offset_t)-1;
1064  }
1065 
1066  if(new_offset < 0 || new_offset > fdp->file->max_pages * COFFEE_PAGE_SIZE) {
1067  return -1;
1068  }
1069 
1070  if(fdp->file->end < new_offset) {
1071  fdp->file->end = new_offset;
1072  }
1073 
1074  return fdp->offset = new_offset;
1075 }
1076 /*---------------------------------------------------------------------------*/
1077 int
1078 cfs_remove(const char *name)
1079 {
1080  struct file *file;
1081 
1082  /*
1083  * Coffee removes files by marking them as obsolete. The space
1084  * is not guaranteed to be reclaimed immediately, but must be
1085  * sweeped by the garbage collector. The garbage collector is
1086  * called once a file reservation request cannot be granted.
1087  */
1088  file = find_file(name);
1089  if(file == NULL) {
1090  return -1;
1091  }
1092 
1093  return remove_by_page(file->page, REMOVE_LOG, CLOSE_FDS, ALLOW_GC);
1094 }
1095 /*---------------------------------------------------------------------------*/
1096 int
1097 cfs_read(int fd, void *buf, unsigned size)
1098 {
1099  struct file_desc *fdp;
1100  struct file *file;
1101 #if COFFEE_MICRO_LOGS
1102  struct file_header hdr;
1103  struct log_param lp;
1104  unsigned bytes_left;
1105  int r;
1106 #endif
1107 
1108  if(!(FD_VALID(fd) && FD_READABLE(fd))) {
1109  return -1;
1110  }
1111 
1112  fdp = &coffee_fd_set[fd];
1113  file = fdp->file;
1114  if(fdp->offset + size > file->end) {
1115  size = file->end - fdp->offset;
1116  }
1117 
1118  /* If the file is allocated, read directly in the file. */
1119  if(!FILE_MODIFIED(file)) {
1120  COFFEE_READ(buf, size, absolute_offset(file->page, fdp->offset));
1121  fdp->offset += size;
1122  return size;
1123  }
1124 
1125 #if COFFEE_MICRO_LOGS
1126  read_header(&hdr, file->page);
1127 
1128  /*
1129  * Fill the buffer by copying from the log in first hand, or the
1130  * ordinary file if the page has no log record.
1131  */
1132  for(bytes_left = size; bytes_left > 0; bytes_left -= r) {
1133  r = -1;
1134 
1135  lp.offset = fdp->offset;
1136  lp.buf = buf;
1137  lp.size = bytes_left;
1138  r = read_log_page(&hdr, file->record_count, &lp);
1139 
1140  /* Read from the original file if we cannot find the data in the log. */
1141  if(r < 0) {
1142  COFFEE_READ(buf, lp.size, absolute_offset(file->page, fdp->offset));
1143  r = lp.size;
1144  }
1145  fdp->offset += r;
1146  buf = (char *)buf + r;
1147  }
1148 #endif /* COFFEE_MICRO_LOGS */
1149 
1150  return size;
1151 }
1152 /*---------------------------------------------------------------------------*/
1153 int
1154 cfs_write(int fd, const void *buf, unsigned size)
1155 {
1156  struct file_desc *fdp;
1157  struct file *file;
1158 #if COFFEE_MICRO_LOGS
1159  int i;
1160  struct log_param lp;
1161  cfs_offset_t bytes_left;
1162  int8_t need_dummy_write;
1163  const char dummy[1] = { 0xff };
1164 #endif
1165 
1166  if(!(FD_VALID(fd) && FD_WRITABLE(fd))) {
1167  return -1;
1168  }
1169 
1170  fdp = &coffee_fd_set[fd];
1171  file = fdp->file;
1172 
1173  /* Attempt to extend the file if we try to write past the end. */
1174 #if COFFEE_IO_SEMANTICS
1175  if(!(fdp->io_flags & CFS_COFFEE_IO_FIRM_SIZE)) {
1176 #endif
1177  while(size + fdp->offset + sizeof(struct file_header) >
1178  (file->max_pages * COFFEE_PAGE_SIZE)) {
1179  if(merge_log(file->page, 1) < 0) {
1180  return -1;
1181  }
1182  file = fdp->file;
1183  PRINTF("Extended the file at page %u\n", (unsigned)file->page);
1184  }
1185 #if COFFEE_IO_SEMANTICS
1186  }
1187 #endif
1188 
1189 #if COFFEE_MICRO_LOGS
1190 #if COFFEE_IO_SEMANTICS
1191  if(!(fdp->io_flags & CFS_COFFEE_IO_FLASH_AWARE) &&
1192  (FILE_MODIFIED(file) || fdp->offset < file->end)) {
1193 #else
1194  if(FILE_MODIFIED(file) || fdp->offset < file->end) {
1195 #endif
1196  need_dummy_write = 0;
1197  for(bytes_left = size; bytes_left > 0;) {
1198  lp.offset = fdp->offset;
1199  lp.buf = buf;
1200  lp.size = bytes_left;
1201  i = write_log_page(file, &lp);
1202  if(i < 0) {
1203  /* Return -1 if we wrote nothing because the log write failed. */
1204  if(size == bytes_left) {
1205  return -1;
1206  }
1207  break;
1208  } else if(i == 0) {
1209  /* The file was merged with the log. */
1210  file = fdp->file;
1211  } else {
1212  /* A log record was written. */
1213  bytes_left -= i;
1214  fdp->offset += i;
1215  buf = (char *)buf + i;
1216 
1217  /* Update the file end for a potential log merge that might
1218  occur while writing log records. */
1219  if(fdp->offset > file->end) {
1220  file->end = fdp->offset;
1221  need_dummy_write = 1;
1222  }
1223  }
1224  }
1225 
1226  if(need_dummy_write) {
1227  /*
1228  * A log record has been written at an offset beyond the original
1229  * extent's end. Consequently, we need to write a dummy value at the
1230  * corresponding end offset in the original extent to ensure that
1231  * the correct file size is calculated when opening the file again.
1232  */
1233  COFFEE_WRITE(dummy, 1, absolute_offset(file->page, fdp->offset - 1));
1234  }
1235  } else {
1236 #endif /* COFFEE_MICRO_LOGS */
1237 #if COFFEE_APPEND_ONLY
1238  if(fdp->offset < file->end) {
1239  return -1;
1240  }
1241 #endif /* COFFEE_APPEND_ONLY */
1242 
1243  COFFEE_WRITE(buf, size, absolute_offset(file->page, fdp->offset));
1244  fdp->offset += size;
1245 #if COFFEE_MICRO_LOGS
1246  }
1247 #endif /* COFFEE_MICRO_LOGS */
1248 
1249  if(fdp->offset > file->end) {
1250  file->end = fdp->offset;
1251  }
1252 
1253  return size;
1254 }
1255 /*---------------------------------------------------------------------------*/
1256 int
1257 cfs_opendir(struct cfs_dir *dir, const char *name)
1258 {
1259  /*
1260  * Coffee is only guaranteed to support "/" and ".", but it does not
1261  * currently enforce this.
1262  */
1263  memset(dir->dummy_space, 0, sizeof(coffee_page_t));
1264  return 0;
1265 }
1266 /*---------------------------------------------------------------------------*/
1267 int
1268 cfs_readdir(struct cfs_dir *dir, struct cfs_dirent *record)
1269 {
1270  struct file_header hdr;
1271  coffee_page_t page;
1272 
1273  memcpy(&page, dir->dummy_space, sizeof(coffee_page_t));
1274 
1275  while(page < COFFEE_PAGE_COUNT) {
1276  read_header(&hdr, page);
1277  if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr)) {
1278  coffee_page_t next_page;
1279  memcpy(record->name, hdr.name, sizeof(record->name));
1280  record->name[sizeof(record->name) - 1] = '\0';
1281  record->size = file_end(page);
1282 
1283  next_page = next_file(page, &hdr);
1284  memcpy(dir->dummy_space, &next_page, sizeof(coffee_page_t));
1285  return 0;
1286  }
1287  page = next_file(page, &hdr);
1288  }
1289 
1290  return -1;
1291 }
1292 /*---------------------------------------------------------------------------*/
1293 void
1294 cfs_closedir(struct cfs_dir *dir)
1295 {
1296  return;
1297 }
1298 /*---------------------------------------------------------------------------*/
1299 int
1300 cfs_coffee_reserve(const char *name, cfs_offset_t size)
1301 {
1302  return reserve(name, page_count(size), 0, 0) == NULL ? -1 : 0;
1303 }
1304 /*---------------------------------------------------------------------------*/
1305 int
1306 cfs_coffee_configure_log(const char *filename, unsigned log_size,
1307  unsigned log_record_size)
1308 {
1309  struct file *file;
1310  struct file_header hdr;
1311 
1312  if(log_record_size == 0 || log_record_size > COFFEE_PAGE_SIZE ||
1313  log_size < log_record_size) {
1314  return -1;
1315  }
1316 
1317  file = find_file(filename);
1318  if(file == NULL) {
1319  return -1;
1320  }
1321 
1322  read_header(&hdr, file->page);
1323  if(HDR_MODIFIED(hdr)) {
1324  /* Too late to customize the log. */
1325  return -1;
1326  }
1327 
1328  hdr.log_records = log_size / log_record_size;
1329  hdr.log_record_size = log_record_size;
1330  write_header(&hdr, file->page);
1331 
1332  return 0;
1333 }
1334 /*---------------------------------------------------------------------------*/
1335 #if COFFEE_IO_SEMANTICS
1336 int
1337 cfs_coffee_set_io_semantics(int fd, unsigned flags)
1338 {
1339  if(!FD_VALID(fd)) {
1340  return -1;
1341  }
1342 
1343  coffee_fd_set[fd].io_flags |= flags;
1344 
1345  return 0;
1346 }
1347 #endif
1348 /*---------------------------------------------------------------------------*/
1349 int
1351 {
1352  unsigned i;
1353 
1354  PRINTF("Coffee: Formatting %u sectors", COFFEE_SECTOR_COUNT);
1355 
1356  *next_free = 0;
1357 
1358  for(i = 0; i < COFFEE_SECTOR_COUNT; i++) {
1359  COFFEE_ERASE(i);
1360  PRINTF(".");
1361  }
1362 
1363  /* Formatting invalidates the file information. */
1364  memset(&protected_mem, 0, sizeof(protected_mem));
1365 
1366  PRINTF(" done!\n");
1367 
1368  return 0;
1369 }
1370 /*---------------------------------------------------------------------------*/
1371 void *
1373 {
1374  *size = sizeof(protected_mem);
1375  return &protected_mem;
1376 }