forked from apache/mynewt-nffs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
design.txt
885 lines (712 loc) · 34.5 KB
/
design.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
***** NFFS
*** HISTORY
Rev. Date Changes
6 2015/10/12 Add directory-reading API.
5 2015/09/28 Rename to Newtron Flash File System.
4 2015/09/08 Fix some badly-specified CRC behavior; clarification of
behavior during restore of corrupt disk.
3 2015/08/20 More cache specifics; updated nffs_read() function
type.
2 2015/08/17 Addition of crc16; clarification of sweep phase.
1 2015/08/13
*** SUMMARY
Newtron Flash File System (nffs) is a flash file system with the following
priorities:
* Minimal RAM usage
* Reliability
*** DISK STRUCTURE
At the top level, an nffs disk is partitioned into areas. An area is a region
of disk with the following properties:
(1) An area can be fully erased without affecting any other areas.
(2) A write to one area does not restrict writes to other areas.
Clarification of point (2): some flash hardware divides its memory space into
"blocks." Writes within a block must be sequential, but writes to one block
have no effect on what parts of other blocks can be written. Thus, for flash
hardware with such a restriction, each area must comprise a discrete number of
blocks.
While not strictly necessary, it is recommended that all areas have the same
size.
On disk, each area is prefixed with the following header:
/** On-disk representation of an area header. */
struct nffs_disk_area {
uint32_t nda_magic[4]; /* NFFS_AREA_MAGIC{0,1,2,3} */
uint32_t nda_length; /* Total size of area, in bytes. */
uint8_t nda_ver; /* Current nffs version: 0 */
uint8_t nda_gc_seq; /* Garbage collection count. */
uint8_t reserved8;
uint8_t nda_id; /* 0xff if scratch area. */
};
Beyond its header, an area contains a sequence of disk objects, representing
the contents of the file system. There are two types of objects: inodes and
data blocks. An inode represents a file or directory; a data block represents
part of a file's contents.
/** On-disk representation of an inode (file or directory). */
struct nffs_disk_inode {
uint32_t ndi_magic; /* NFFS_INODE_MAGIC */
uint32_t ndi_id; /* Unique object ID. */
uint32_t ndi_seq; /* Sequence number; greater supersedes
lesser. */
uint32_t ndi_parent_id; /* Object ID of parent directory inode. */
uint8_t reserved8;
uint8_t ndi_filename_len; /* Length of filename, in bytes. */
uint16_t ndi_crc16; /* Covers rest of header and filename. */
/* Followed by filename. */
};
An inode filename's length cannot exceed 256 bytes. The filename is not
null-terminated. The following ASCII characters are not allowed in a
filename:
* / (slash character)
* \0 (NUL character)
/** On-disk representation of a data block. */
struct nffs_disk_block {
uint32_t ndb_magic; /* NFFS_BLOCK_MAGIC */
uint32_t ndb_id; /* Unique object ID. */
uint32_t ndb_seq; /* Sequence number; greater supersedes lesser. */
uint32_t ndb_inode_id; /* Object ID of owning inode. */
uint32_t ndb_prev_id; /* Object ID of previous block in file;
NFFS_ID_NONE if this is the first block. */
uint16_t ndb_data_len; /* Length of data contents, in bytes. */
uint16_t ndb_crc16; /* Covers rest of header and data. */
/* Followed by 'ndb_data_len' bytes of data. */
};
Each data block contains the ID of the previous data block in the file.
Together, the set of blocks in a file form a reverse singly-linked list.
The maximum number of data bytes that a block can contain is determined at
initialization-time. The result is the greatest number which satisfies all of
the following restrictions:
o No more than 2048.
o At least two maximum-sized blocks can fit in the smallest area.
The 2048 number was chosen somewhat arbitrarily, and may change in the future.
*** ID SPACE
All disk objects have a unique 32-bit ID. The ID space is partitioned as
follows:
* 0x00000000 - 0x0fffffff: Directory inodes.
* 0x10000000 - 0x7fffffff: File inodes.
* 0x80000000 - 0xfffffffe: Data blocks.
* 0xffffffff : Reserved (NFFS_ID_NONE)
*** SCRATCH AREA
A valid nffs file system must contain a single "scratch area." The scratch
area does not contain any objects of its own, and is only used during garbage
collection. The scratch area must have a size greater than or equal to each
of the other areas in flash.
*** RAM REPRESENTATION
The file system comprises a set of objects of the following two types:
1) inode
2) data block
Every object in the file system is stored in a 256-entry hash table. An
object's hash key is derived from its 32-bit ID. Each list in the hash table
is sorted by time of use; most-recently-used is at the front of the list. All
objects are represented by the following structure:
/**
* What gets stored in the hash table. Each entry represents a data block or
* an inode.
*/
struct nffs_hash_entry {
SLIST_ENTRY(nffs_hash_entry) nhe_next;
uint32_t nhe_id; /* 0 - 0x7fffffff if inode; else if block. */
uint32_t nhe_flash_loc; /* Upper-byte = area idx; rest = area offset. */
};
For each data block, the above structure is all that is stored in RAM. To
acquire more information about a data block, the block header must be read
from flash.
Inodes require a fuller RAM representation to capture the structure of the
file system. There are two types of inodes: files and directories. Each
inode hash entry is actually an instance of the following structure:
/** Each inode hash entry is actually one of these. */
struct nffs_inode_entry {
struct nffs_hash_entry nie_hash_entry;
SLIST_ENTRY(nffs_inode_entry) nie_sibling_next;
union {
struct nffs_inode_list nie_child_list; /* If directory */
struct nffs_hash_entry *nie_last_block_entry; /* If file */
};
uint8_t nie_refcnt;
};
A directory inode contains a list of its child files and directories
(fie_child_list). These entries are sorted alphabetically using the ASCII
character set.
A file inode contains a pointer to the last data block in the file
(nie_last_block_entry). For most file operations, the reversed block list must
be walked backwards. This introduces a number of speed inefficiencies:
* All data blocks must be read to determine the length of the file.
* Data blocks often need to be processed sequentially. The reversed
nature of the block list transforms this from linear time to an O(n^2)
operation.
Furthermore, obtaining information about any constituent data block requires a
separate flash read.
*** INODE CACHE AND DATA BLOCK CACHE
The speed issues are addressed by a pair of caches. Cached inodes entries
contain the file length and a much more convenient doubly-linked list of
cached data blocks. The benefit of using caches is that the size of the
caches need not be proportional to the size of the file system. In other
words, caches can address speed efficiency concerns without negatively
impacting the file system's scalability.
nffs requires both caches during normal operation, so it is not possible to
disable them. However, the cache sizes are configurable, and both caches can
be configured with a size of one if RAM usage must be minimized.
The following data structures are used in the inode and data block caches.
/** Full data block representation; not stored permanently in RAM. */
struct nffs_block {
struct nffs_hash_entry *nb_hash_entry; /* Points to real block entry. */
uint32_t nb_seq; /* Sequence number; greater
supersedes lesser. */
struct nffs_inode_entry *nb_inode_entry; /* Owning inode. */
struct nffs_hash_entry *nb_prev; /* Previous block in file. */
uint16_t nb_data_len; /* # of data bytes in block. */
uint16_t reserved16;
};
/** Represents a single cached data block. */
struct nffs_cache_block {
TAILQ_ENTRY(nffs_cache_block) ncb_link; /* Next / prev cached block. */
struct nffs_block ncb_block; /* Full data block. */
uint32_t ncb_file_offset; /* File offset of this block. */
};
/** Full inode representation; not stored permanently in RAM. */
struct nffs_inode {
struct nffs_inode_entry *ni_inode_entry; /* Points to real inode entry. */
uint32_t ni_seq; /* Sequence number; greater
supersedes lesser. */
struct nffs_inode_entry *ni_parent; /* Points to parent directory. */
uint8_t ni_filename_len; /* # chars in filename. */
uint8_t ni_filename[NFFS_SHORT_FILENAME_LEN]; /* First 3 bytes. */
};
/** Doubly-linked tail queue of cached blocks; contained in cached inodes. */
TAILQ_HEAD(nffs_block_cache_list, nffs_block_cache_entry);
/** Represents a single cached file inode. */
struct nffs_cache_inode {
TAILQ_ENTRY(nffs_cache_inode) nci_link; /* Sorted; LRU at tail. */
struct nffs_inode nci_inode; /* Full inode. */
struct nffs_cache_block_list nci_block_list; /* List of cached blocks. */
uint32_t nci_file_size; /* Total file size. */
};
Only file inodes are cached; directory inodes are never cached.
Within a cached inode, all cached data blocks are contiguous. E.g., if the
start and end of a file are cached, then the middle must also be cached. A
data block is only cached if its owning file is also cached.
Internally, cached inodes are stored in a singly-linked list, ordered by time
of use. The most-recently-used entry is the first element in the list. If a
new inode needs to be cached, but the inode cache is full, the
least-recently-used entry is freed to make room for the new one. The
following operations cause an inode to be cached:
* Querying a file's length.
* Seeking within a file.
* Reading from a file.
* Writing to a file.
The following operations cause a data block to be cached:
* Reading from the block.
* Writing to the block.
If one of the above operations is applied to a data block that is not currently
cached, nffs uses the following procedure to cache the necessary block:
1. If none of the owning inode's blocks are currently cached, allocate a
cached block entry corresponding to the requested block and insert it
into the inode's list.
2. Else if the requested file offset is less than that of the first cached
block, bridge the gap between the inode's sequence of cached blocks and
the block that now needs to be cached. This is accomplished by caching
each block in the gap, finishing with the requested block.
3. Else (the requested offset is beyond the end of the cache),
a. If the requested offset belongs to the block that immediately
follows the end of the cache, cache the block and append it to the
list.
b. Else, clear the cache, and populate it with the single entry
corresponding to the requested block.
If the system is unable to allocate a cached block entry at any point during
the above procedure, the system frees up other blocks currently in the cache.
This is accomplished as follows:
1. Iterate the inode cache in reverse (i.e., start with the
least-recently-used entry). For each entry:
a. If the entry's cached block list is empty, advance to the next
entry.
b. Else, free all the cached blocks in the entry's list.
Because the system imposes a minimum block cache size of one, the above
procedure will always reclaim at least one cache block entry. The above
procedure may result in the freeing of the block list that belongs to the very
inode being operated on. This is OK, as the final block to get cached is
always the block being requested.
*** CONFIGURATION
The file system is configured by populating fields in a global structure.
Each field in the structure corresponds to a setting. All configuration must
be done prior to calling nffs_init(). The configuration structure is defined
as follows:
struct nffs_config {
/** Maximum number of inodes; default=1024. */
uint32_t nc_num_inodes;
/** Maximum number of data blocks; default=4096. */
uint32_t nc_num_blocks;
/** Maximum number of open files; default=4. */
uint32_t nc_num_files;
/** Inode cache size; default=4. */
uint32_t nc_num_cache_inodes;
/** Data block cache size; default=64. */
uint32_t nc_num_cache_blocks;
};
extern struct nffs_config nffs_config;
Any fields that are set to 0 (or not set at all) inherit the corresponding
default value. This means that it is impossible to configure any setting with
a value of zero.
*** INITIALIZATION
There are two means of initializing an nffs file system:
(1) Restore an existing file system via detection.
(2) Create a new file system via formatting.
Both methods require the user to describe how the flash memory is divided into
areas. This is accomplished with an array of struct nffs_area_desc, defined as
follows:
struct nffs_area_desc {
uint32_t nad_offset; /* Flash offset of start of area. */
uint32_t nad_length; /* Size of area, in bytes. */
};
An array of area descriptors is terminated by an entry with a fad_length field
of 0.
One common initialization sequence is the following:
(1) Detect an nffs file system anywhere in flash.
(2) If no file system detected, format a new file system in a specific
region of flash.
*** DETECTION
The file system detection process consists of scanning a specified set of
flash regions for valid nffs areas, and then populating the RAM representation
of the file system with the detected objects. Detection is initiated with the
following function:
/**
* Searches for a valid nffs file system among the specified areas. This
* function succeeds if a file system is detected among any subset of the
* supplied areas. If the area set does not contain a valid file system,
* a new one can be created via a separate call to nffs_format().
*
* @param area_descs The area set to search. This array must be
* terminated with a 0-length area.
*
* @return 0 on success;
* NFFS_ECORRUPT if no valid file system was detected;
* other nonzero on error.
*/
int nffs_detect(const struct nffs_area_desc *area_descs);
As indicated, not every area descriptor needs to reference a valid nffs area.
Detection is successful as long as a complete file system is detected
somewhere in the specified regions of flash. If an application is unsure
where a file system might be located, it can initiate detection across the
entire flash region.
A detected file system is valid if:
(1) At least one non-scratch area is present.
(2) At least one scratch area is present (only the first gets used if
there is more than one).
(3) The root directory inode is present.
During detection, each indicated region of flash is checked for a valid area
header. The contents of each valid non-scratch area are then restored into
the nffs RAM representation. The following procedure is applied to each object
in the area:
(1) Verify the object's integrity via a crc16 check. If invalid, the
object is discarded and the procedure restarts on the next object in
the area.
(2) Convert the disk object into its corresponding RAM representation and
insert it into the hash table. If the object is an inode, its
reference count is initialized to 1, indicating ownership by its
parent directory.
(3) If an object with the same ID is already present, then one supersedes
the other. Accept the object with the greater sequence number and
discard the other.
(4) If the object references a nonexistant inode (parent directory in the
case of an inode; owning file in the case of a data block), insert a
temporary "dummy" inode into the hash table so that inter-object links
can be maintained until the absent inode is eventually restored. Dummy
inodes are identified by a reference count of 0.
(5) If a delete record for an inode is encountered, the inode's parent
pointer is set to null to indicate that it should be removed from RAM.
If nffs encounters an object that cannot be identified (i.e., its magic number
is not valid), it scans the remainder of the flash area for the next valid
magic number. Upon encountering a valid object, nffs resumes the procedure
described above.
After all areas have been restored, a sweep is performed across the entire RAM
representation so that invalid inodes can be deleted from memory.
For each directory inode:
* If its reference count is 0 (i.e., it is a dummy), migrate its children
to the /lost+found directory, and delete it from the RAM representation.
This should only happen in the case of file system corruption.
* If its parent reference is null (i.e., it was deleted), delete it and all
its children from the RAM representation.
For each file inode:
* If its reference count is 0 (i.e., it is a dummy), delete it from the RAM
representation. This should only happen in the case of file system
corruption. (We should try to migrate the file to the lost+found
directory in this case, as mentioned in the todo section).
When an object is deleted during this sweep, it is only deleted from the RAM
representation; nothing is written to disk.
When objects are migrated to the lost+found directory, their parent inode
reference is permanently updated on the disk.
In addition, a single scratch area is identified during the detection process.
The first area whose 'fda_id' value is set to 0xff is designated as the file
system scratch area. If no valid scratch area is found, the cause could be
that the system was restarted while a garbage collection cycle was in progress.
Such a condition is identified by the presence of two areas with the same ID.
In such a case, the shorter of the two areas is erased and designated as the
scratch area.
*** FORMATTING
A new file system is created via formatting. Formatting is achieved via the
following function:
/**
* Erases all the specified areas and initializes them with a clean nffs
* file system.
*
* @param area_descs The set of areas to format.
*
* @return 0 on success;
* nonzero on failure.
*/
int nffs_format(const struct nffs_area_desc *area_descs);
On success, an area header is written to each of the specified locations. The
largest area in the set is designated as the initial scratch area.
*** FLASH WRITES
The nffs implementation always writes in a strictly sequential fashion within an
area. For each area, the system keeps track of the current offset. Whenever
an object gets written to an area, it gets written to that area's current
offset, and the offset is increased by the object's disk size.
When a write needs to be performed, the nffs implementation selects the
appropriate destination area by iterating though each area until one with
sufficient free space is encountered.
There is no write buffering. Each call to a write function results in a write
operation being sent to the flash hardware.
*** NEW OBJECTS
Whenever a new object is written to disk, it is assigned the following
properties:
* ID: A unique value is selected from the 32-bit ID space, as appropriate
for the object's type.
* Sequence number: 0
When a new file or directory is created, a corresponding inode is written to
flash. Likewise, a new data block also results in the writing of a
corresponding disk object.
*** MOVING / RENAMING FILES AND DIRECTORIES
When a file or directory is moved or renamed, its corresponding inode is
rewritten to flash with the following properties:
* ID: Unchanged
* Sequence number: Previous value plus one.
* Parent inode: As specified by the move / rename operation.
* Filename: As specified by the move / rename operation.
Because the inode's ID is unchanged, all dependent objects remain valid.
*** UNLINKING FILES AND DIRECTORIES
When a file or directory is unlinked from its parent directory, a deletion
record for the unlinked inode gets written to flash. The deletion record is an
inode with the following properties:
* ID: Unchanged
* Sequence number: Previous value plus one.
* Parent inode ID: NFFS_ID_NONE
When an inode is unlinked, no deletion records need to be written for the
inode's dependent objects (constituent data blocks or child inodes). During
the next file system detection, it is recognized that the objects belong to
a deleted inode, so they are not restored into the RAM representation.
If a file has an open handle at the time it gets unlinked, application code
can continued to use the file handle to read and write data. All files retain
a reference count, and a file isn't deleted from the RAM representation until
its reference code drops to 0. Any attempt to open an unlinked file fails,
even if the file is referenced by other file handles.
*** WRITING TO A FILE
The following procedure is used whenever the application code writes to a file.
First, if the write operation specifies too much data to fit into a single
block, the operation is split into several separate write operations. Then,
for each write operation:
(1) Determine which existing blocks the write operation overlaps
(n = number of overwritten blocks).
(2) If n = 0, this is an append operation. Write a data block with the
following properties:
* ID: New unique value.
* Sequence number: 0.
(3) Else (n > 1), this write overlaps existing data.
(a) For each block in [1, 2, ... n-1], write a new block
containing the updated contents. Each new block supersedes the
block it overwrites. That is, each block has the following
properties:
* ID: Unchanged
* Sequence number: Previous value plus one.
(b) Write the nth block. The nth block includes all appended data,
if any. As with the other blocks, its ID is unchanged and its
sequence number is incremented.
Appended data can only be written to the end of the file. That is, "holes" are
not supported.
*** GARBAGE COLLECTION
When the file system is too full to accomodate a write operation, the system
must perform garbage collection to make room. The garbage collection
procedure is described below:
(1) The non-scratch area with the lowest garbage collection sequence
number is selected as the "source area." If there are other areas
with the same sequence number, the one with the smallest flash offset
is selected.
(2) The source area's ID is written to the scratch area's header,
transforming it into a non-scratch ID. This former scratch area is now
known as the "destination area."
(3) The RAM representation is exhaustively searched for collectible
objects. The following procedure is applied to each inode in the
system:
(a) If the inode is resident in the source area, copy the inode record
to the destination area.
(b) If the inode is a file inode, walk the inode's list of data blocks,
starting with the last block in the file. Each block that is
resident in the source area is copied to the destination area. If
there is a run of two or more blocks that are resident in the
source area, they are consolidated and copied to the destination
area as a single new block (subject to the maximum block size
restriction).
(4) The source area is reformatted as a scratch sector (i.e., is is fully
erased, and its header is rewritten with an ID of 0xff). The area's
garbage collection sequence number is incremented prior to rewriting
the header. This area is now the new scratch sector.
*** MISC
* RAM usage:
o 24 bytes per inode
o 12 bytes per data block
o 36 bytes per inode cache entry
o 32 bytes per data block cache entry
* Maximum filename size: 256 characters (no null terminator required)
* Disallowed filename characters: '/' and '\0'
*** FUTURE ENHANCEMENTS
* API function to traverse a directory.
* Migrate corrupt files to the /lost+found directory during restore, rather
than discarding them from RAM.
* Error correction.
* Encryption.
* Compression.
*** API
struct nffs_file;
/**
* Opens a file at the specified path. The result of opening a nonexistent
* file depends on the access flags specified. All intermediate directories
* must already exist.
*
* The mode strings passed to fopen() map to nffs_open()'s access flags as
* follows:
* "r" - NFFS_ACCESS_READ
* "r+" - NFFS_ACCESS_READ | NFFS_ACCESS_WRITE
* "w" - NFFS_ACCESS_WRITE | NFFS_ACCESS_TRUNCATE
* "w+" - NFFS_ACCESS_READ | NFFS_ACCESS_WRITE | NFFS_ACCESS_TRUNCATE
* "a" - NFFS_ACCESS_WRITE | NFFS_ACCESS_APPEND
* "a+" - NFFS_ACCESS_READ | NFFS_ACCESS_WRITE | NFFS_ACCESS_APPEND
*
* @param out_file On success, a pointer to the newly-created file
* handle gets written here.
* @param path The path of the file to open.
* @param access_flags Flags controlling file access; see above table.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_open(const char *path, uint8_t access_flags,
struct nffs_file **out_file);
/**
* Closes the specified file and invalidates the file handle. If the file has
* already been unlinked, and this is the last open handle to the file, this
* operation causes the file to be deleted from disk.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_close(struct nffs_file *file);
/**
* Positions a file's read and write pointer at the specified offset. The
* offset is expressed as the number of bytes from the start of the file (i.e.,
* seeking to offset 0 places the pointer at the first byte in the file).
*
* @param file The file to reposition.
* @param offset The 0-based file offset to seek to.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_seek(struct nffs_file *file, uint32_t offset);
/**
* Retrieves the current read and write position of the specified open file.
*
* @param file The file to query.
*
* @return The file offset, in bytes.
*/
uint32_t nffs_getpos(const struct nffs_file *file);
/**
* Retrieves the current length of the specified open file.
*
* @param file The file to query.
* @param out_len On success, the number of bytes in the file gets
* written here.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_file_len(const struct nffs_file *file, uint32_t *out_len)
/**
* Reads data from the specified file. If more data is requested than remains
* in the file, all available data is retrieved. Note: this type of short read
* results in a success return code.
*
* @param file The file to read from.
* @param len The number of bytes to attempt to read.
* @param out_data The destination buffer to read into.
* @param out_len On success, the number of bytes actually read gets
* written here. Pass null if you don't care.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_read(struct nffs_file *file, uint32_t len, void *out_data,
uint32_t *out_len)
/**
* Writes the supplied data to the current offset of the specified file handle.
*
* @param file The file to write to.
* @param data The data to write.
* @param len The number of bytes to write.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_write(struct nffs_file *file, const void *data, int len);
/**
* Unlinks the file or directory at the specified path. If the path refers to
* a directory, all the directory's descendants are recursively unlinked. Any
* open file handles refering to an unlinked file remain valid, and can be
* read from and written to.
*
* @path The path of the file or directory to unlink.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_unlink(const char *path);
/**
* Performs a rename and / or move of the specified source path to the
* specified destination. The source path can refer to either a file or a
* directory. All intermediate directories in the destination path must
* already exist. If the source path refers to a file, the destination path
* must contain a full filename path, rather than just the new parent
* directory. If an object already exists at the specified destination path,
* this function causes it to be unlinked prior to the rename (i.e., the
* destination gets clobbered).
*
* @param from The source path.
* @param to The destination path.
*
* @return 0 on success;
* nonzero on failure.
*/
int nffs_rename(const char *from, const char *to);
/**
* Creates the directory represented by the specified path. All intermediate
* directories must already exist. The specified path must start with a '/'
* character.
*
* @param path The directory to create.
*
* @return 0 on success;
* nonzero on failure.
*/
int nffs_mkdir(const char *path);
/**
* Erases all the specified areas and initializes them with a clean nffs
* file system.
*
* @param area_descs The set of areas to format.
*
* @return 0 on success;
* nonzero on failure.
*/
int nffs_format(const struct nffs_area_desc *area_descs);
/**
* Opens the directory at the specified path. The directory's contents can be
* read with subsequent calls to nffs_readdir(). When you are done with the
* directory handle, close it with nffs_closedir().
*
* Unlinking files from the directory while it is open may result in
* unpredictable behavior. New files can be created inside the directory.
*
* @param path The directory to open.
* @param out_dir On success, points to the directory handle.
*
* @return 0 on success;
* NFFS_ENOENT if the specified directory does not
* exist;
* other nonzero on error.
*/
int nffs_opendir(const char *path, struct nffs_dir **out_dir);
/**
* Reads the next entry in an open directory.
*
* @param dir The directory handle to read from.
* @param out_dirent On success, points to the next child entry in
* the specified directory.
*
* @return 0 on success;
* NFFS_ENOENT if there are no more entries in the
* parent directory;
* other nonzero on error.
*/
int nffs_readdir(struct nffs_dir *dir, struct nffs_dirent **out_dirent);
/**
* Closes the specified directory handle.
*
* @param dir The directory to close.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_closedir(struct nffs_dir *dir);
/**
* Retrieves the filename of the specified directory entry. The retrieved
* filename is always null-terminated. To ensure enough space to hold the full
* filename plus a null-termintor, a destination buffer of size
* (NFFS_FILENAME_MAX_LEN + 1) should be used.
*
* @param dirent The directory entry to query.
* @param max_len The size of the "out_name" character buffer.
* @param out_name On success, the entry's filename is written
* here; always null-terminated.
* @param out_name_len On success, contains the actual length of the
* filename, NOT including the
* null-terminator.
*
* @return 0 on success; nonzero on failure.
*/
int nffs_dirent_name(struct nffs_dirent *dirent, size_t max_len,
char *out_name, uint8_t *out_name_len);
/**
* Tells you whether the specified directory entry is a sub-directory or a
* regular file.
*
* @param dirent The directory entry to query.
*
* @return 1: The entry is a directory;
* 0: The entry is a regular file.
*/
int nffs_dirent_is_dir(const struct nffs_dirent *dirent);
/**
* Searches for a valid nffs file system among the specified areas. This
* function succeeds if a file system is detected among any subset of the
* supplied areas. If the area set does not contain a valid file system,
* a new one can be created via a call to nffs_format().
*
* @param area_descs The area set to search. This array must be
* terminated with a 0-length area.
*
* @return 0 on success;
* NFFS_ECORRUPT if no valid file system was detected;
* other nonzero on error.
*/
int nffs_detect(const struct nffs_area_desc *area_descs);
/**
* Indicates whether a valid filesystem has been initialized, either via
* detection or formatting.
*
* @return 1 if a file system is present; 0 otherwise.
*/
int nffs_ready(void);
/**
* Initializes the nffs memory and data structures. This must be called before
* any nffs operations are attempted.
*
* @return 0 on success; nonzero on error.
*/
int nffs_init(void);