@@ -25,16 +25,17 @@ static bool file_hdr_is_uninitialized(SettingsFileHeader *file_hdr) {
2525 && (file_hdr -> flags == 0xffff );
2626}
2727
28- static status_t prv_open (SettingsFile * file , const char * name , uint8_t flags , int max_used_space ) {
28+ static status_t prv_open (SettingsFile * file , const char * name , uint8_t flags ,
29+ int max_used_space , int alloc_used_space ,
30+ int min_alloc_used_space ) {
2931 // Making the max_space_total at least a little bit larger than the
30- // max_used_space allows us to avoid thrashing. Without it, if
31- // max_space_total == max_used_space , then if the file is full, changing a
32+ // alloc_used_space allows us to avoid thrashing. Without it, if
33+ // max_space_total == alloc_used_space , then if the file is full, changing a
3234 // single value would force the whole file to be rewritten- every single
3335 // time! It's probably worth it to "waste" a bit of flash space to avoid
3436 // this pathalogical case.
35- int max_space_total = pfs_sector_optimal_size (max_used_space * 12 / 10 , strlen (name ));
37+ int max_space_total = pfs_sector_optimal_size (alloc_used_space * 12 / 10 , strlen (name ));
3638
37- // TODO: Dynamically sized files?
3839 int fd = pfs_open (name , flags , FILE_TYPE_STATIC , max_space_total );
3940 if (fd < 0 ) {
4041 PBL_LOG_ERR ("Could not open settings file '%s', %d" , name , fd );
@@ -50,6 +51,8 @@ static status_t prv_open(SettingsFile *file, const char *name, uint8_t flags, in
5051 * file = (SettingsFile ) {
5152 .name = kernel_strdup_check (name ),
5253 .max_used_space = max_used_space ,
54+ .alloc_used_space = alloc_used_space ,
55+ .min_alloc_used_space = min_alloc_used_space ,
5356 .max_space_total = max_space_total ,
5457 };
5558
@@ -73,32 +76,37 @@ static status_t prv_open(SettingsFile *file, const char *name, uint8_t flags, in
7376 PBL_LOG_WRN ("Unrecognized version %d for file %s, removing..." ,
7477 file_hdr .version , name );
7578 pfs_close_and_remove (fd );
76- return prv_open (file , name , flags , max_used_space );
79+ return prv_open (file , name , flags , max_used_space , alloc_used_space , min_alloc_used_space );
80+ }
81+
82+ // For growable files, adopt the actual file size before bootup_check so that
83+ // any compaction during recovery uses the correct (grown) allocation size.
84+ int actual_size = pfs_get_file_size (file -> iter .fd );
85+ if (alloc_used_space < max_used_space && actual_size > max_space_total ) {
86+ file -> alloc_used_space = actual_size * 10 / 12 ;
87+ file -> max_space_total = actual_size ;
7788 }
7889
7990 status_t status = bootup_check (file );
8091 if (status < 0 ) {
8192 PBL_LOG_ERR ("Bootup check failed (%" PRId32 "), not good. "
8293 "Attempting to recover by deleting %s..." , status , name );
8394 pfs_close_and_remove (fd );
84- return prv_open (file , name , flags , max_used_space );
95+ return prv_open (file , name , flags , max_used_space , alloc_used_space , min_alloc_used_space );
8596 }
8697
8798 // There's a chance that the caller increased the desired size of the settings file since
8899 // the file was originally created (i.e. the file was created in an earlier version of the
89100 // firmware). If we detect that situation, let's re-write the file to the new larger requested
90101 // size.
91- int actual_size = pfs_get_file_size (file -> iter .fd );
92- if (actual_size < max_space_total ) {
102+ if (alloc_used_space >= max_used_space && actual_size < max_space_total ) {
93103 PBL_LOG_INFO ("Re-writing settings file %s to increase its size from %d to %d." ,
94104 name , actual_size , max_space_total );
95- // The settings_file_rewrite_filtered call creates a new file based on file->max_used_space
96- // and copies the contents of the existing file into it.
97105 status = settings_file_rewrite_filtered (file , NULL , NULL );
98106 if (status < 0 ) {
99107 PBL_LOG_ERR ("Could not resize file %s (error %" PRId32 "). Creating new one" ,
100108 name , status );
101- return prv_open (file , name , flags , max_used_space );
109+ return prv_open (file , name , flags , max_used_space , alloc_used_space , min_alloc_used_space );
102110 }
103111 }
104112
@@ -109,7 +117,16 @@ static status_t prv_open(SettingsFile *file, const char *name, uint8_t flags, in
109117
110118status_t settings_file_open (SettingsFile * file , const char * name ,
111119 int max_used_space ) {
112- return prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE , max_used_space );
120+ return prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE ,
121+ max_used_space , max_used_space , max_used_space );
122+ }
123+
124+ status_t settings_file_open_growable (SettingsFile * file , const char * name ,
125+ int max_used_space , int initial_alloc_size ) {
126+ // prv_grow doubles alloc_used_space; a zero seed would loop forever.
127+ PBL_ASSERTN (initial_alloc_size > 0 );
128+ return prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE ,
129+ max_used_space , initial_alloc_size , initial_alloc_size );
113130}
114131
115132void settings_file_close (SettingsFile * file ) {
@@ -199,7 +216,8 @@ status_t settings_file_rewrite_filtered(
199216
200217 SettingsFile new_file ;
201218 status_t status = prv_open (& new_file , file -> name , OP_FLAG_OVERWRITE | OP_FLAG_READ ,
202- file -> max_used_space );
219+ file -> max_used_space , file -> alloc_used_space ,
220+ file -> min_alloc_used_space );
203221 if (status < 0 ) {
204222 PBL_LOG_ERR ("Could not open temporary file to compact settings file. Error %" PRIi32 "." ,
205223 status );
@@ -253,8 +271,11 @@ status_t settings_file_rewrite_filtered(
253271 // old file. After the close suceeds, we will end up reading the new
254272 // (compacted) file.
255273 char * name = kernel_strdup (new_file .name );
274+ int alloc_used_space = new_file .alloc_used_space ;
275+ int min_alloc_used_space = new_file .min_alloc_used_space ;
256276 settings_file_close (& new_file );
257- status = prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE , file -> max_used_space );
277+ status = prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE ,
278+ file -> max_used_space , alloc_used_space , min_alloc_used_space );
258279 kernel_free (name );
259280
260281 // FIRM-1649: instrumentation. See note at the top of this function.
@@ -272,7 +293,29 @@ void settings_file_set_change_callback(SettingsFileChangeCallback callback) {
272293}
273294
274295T_STATIC status_t settings_file_compact (SettingsFile * file ) {
275- return settings_file_rewrite_filtered (file , NULL , NULL );
296+ // For growable files, drop alloc_used_space toward the floor when live data
297+ // is much smaller than the current allocation. Without this, a file that
298+ // burst to e.g. 256 KiB and then idled would hold that flash forever even
299+ // after the records were deleted, slowly bleeding free PFS space across
300+ // device lifetime. Aim for the smallest doubling step that leaves ~2x
301+ // headroom over used_space; the next grow cycle will re-expand if needed.
302+ const int old_alloc = file -> alloc_used_space ;
303+ if (file -> alloc_used_space > file -> min_alloc_used_space ) {
304+ int target = file -> min_alloc_used_space ;
305+ while (target < file -> used_space * 2 && target < file -> alloc_used_space ) {
306+ target *= 2 ;
307+ }
308+ if (target < file -> alloc_used_space ) {
309+ file -> alloc_used_space = target ;
310+ }
311+ }
312+ status_t status = settings_file_rewrite_filtered (file , NULL , NULL );
313+ if (status < 0 ) {
314+ // rewrite_filtered fails before the swap if it fails at all; the on-disk
315+ // file is still at old_alloc, so put the in-memory book-keeping back.
316+ file -> alloc_used_space = old_alloc ;
317+ }
318+ return status ;
276319}
277320
278321static bool key_matches (SettingsRawIter * iter , const uint8_t * key , int key_len ) {
@@ -420,6 +463,27 @@ status_t settings_file_set_byte(SettingsFile *file, const void *key,
420463 return S_SUCCESS ;
421464}
422465
466+ static status_t prv_grow (SettingsFile * file , int needed_used_space ) {
467+ int new_alloc = file -> alloc_used_space ;
468+ while (new_alloc < needed_used_space && new_alloc < file -> max_used_space ) {
469+ new_alloc *= 2 ;
470+ }
471+ if (new_alloc > file -> max_used_space ) {
472+ new_alloc = file -> max_used_space ;
473+ }
474+ if (new_alloc < needed_used_space ) {
475+ return E_OUT_OF_STORAGE ;
476+ }
477+
478+ int old_alloc = file -> alloc_used_space ;
479+ file -> alloc_used_space = new_alloc ;
480+ status_t status = settings_file_rewrite_filtered (file , NULL , NULL );
481+ if (status < 0 ) {
482+ file -> alloc_used_space = old_alloc ;
483+ }
484+ return status ;
485+ }
486+
423487// Internal implementation that takes a timestamp parameter
424488// Note that this operation is designed to be atomic from the perspective of
425489// an outside observer. That is, either the new value will be completely
@@ -443,7 +507,14 @@ static status_t prv_settings_file_set_internal(SettingsFile *file, const void *k
443507 return E_OUT_OF_STORAGE ;
444508 }
445509 if (file -> used_space + file -> dead_space + rec_size > file -> max_space_total ) {
446- status_t status = settings_file_compact (file );
510+ bool needs_growth = (file -> used_space + rec_size > file -> max_space_total ) &&
511+ (file -> alloc_used_space < file -> max_used_space );
512+ status_t status ;
513+ if (needs_growth ) {
514+ status = prv_grow (file , file -> used_space + rec_size );
515+ } else {
516+ status = settings_file_compact (file );
517+ }
447518 if (status < 0 ) {
448519 return status ;
449520 }
@@ -612,7 +683,8 @@ status_t settings_file_rewrite(SettingsFile *file,
612683 SettingsFile new_file ;
613684 status_t status = prv_open (& new_file , file -> name ,
614685 OP_FLAG_OVERWRITE | OP_FLAG_READ ,
615- file -> max_used_space );
686+ file -> max_used_space , file -> alloc_used_space ,
687+ file -> min_alloc_used_space );
616688 if (status < 0 ) {
617689 return status ;
618690 }
@@ -628,8 +700,11 @@ status_t settings_file_rewrite(SettingsFile *file,
628700 // old file. After the close suceeds, we will end up reading the new
629701 // (compacted) file.
630702 char * name = kernel_strdup (new_file .name );
703+ int alloc_used_space = new_file .alloc_used_space ;
704+ int min_alloc_used_space = new_file .min_alloc_used_space ;
631705 settings_file_close (& new_file );
632- status = prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE , file -> max_used_space );
706+ status = prv_open (file , name , OP_FLAG_READ | OP_FLAG_WRITE ,
707+ file -> max_used_space , alloc_used_space , min_alloc_used_space );
633708 kernel_free (name );
634709
635710 return status ;
0 commit comments