Skip to content

Commit 8571175

Browse files
committed
system/nxpkg: fix concurrency and versioning bugs found in review.
- pkg_sync(): index.jsn and repo.url were updated as two independent atomic writes with nothing serializing the pair against a second, concurrent pkg_sync() call - each write stayed internally consistent, but the pair didn't, so one sync's index could end up on disk next to a different sync's source URL. Add a dedicated sync lock (pkg_repo_acquire_sync_lock(), mirroring the existing installed- db lock's blocking-retry-with-stale-reclaim pattern) around the whole read-fetch-write sequence. Also renew the lock's mtime as data actually arrives (pkg_repo_sink(), via a new renew_lock_path field threaded through pkg_acquire_source()) rather than only stamping it once at acquire time - a lock acquired once and then measured against a fixed 10-minute staleness window could otherwise be reclaimed mid-download on a large-enough file over a slow-enough link, even though the download was still genuinely in progress. pkg_reclaim_stale_lock() (renamed from pkg_install_reclaim_stale_lock, made public) is shared between both lock kinds rather than duplicated. - pkg_install_prune_oldest_version(): deleted the pruned version's on-disk payload directory before the updated installed database was even durably saved. If pkg_metadata_save_installed() subsequently failed, the payload was already gone but the last successfully-saved instpkg.jsn could still list that version as installed. The victim version is now handed back to the caller (threaded through pkg_install_add_version()/pkg_install_update_installed()) so pkg_install() can defer the actual directory removal until after the save succeeds. - pkg_metadata_version_token_cmp(): two version tokens with equal numeric prefixes (e.g. "1a" and "1b", both parsing as 1) compared as equal instead of falling back to a lexical comparison of what follows the number, contradicting this function's own documented behavior and silently treating genuinely different versions as the same one. Compare the non-numeric remainder lexically when the numeric prefixes match instead of falling through. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
1 parent 455af49 commit 8571175

4 files changed

Lines changed: 246 additions & 70 deletions

File tree

system/nxpkg/pkg.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ int pkg_resolve_artifact_source(FAR char *buffer, size_t size,
259259
FAR const struct pkg_manifest_s *manifest);
260260
int pkg_resolve_icon_source(FAR char *buffer, size_t size,
261261
FAR const struct pkg_manifest_s *manifest);
262-
int pkg_acquire_source(FAR const char *source, FAR const char *dest);
262+
int pkg_acquire_source(FAR const char *source, FAR const char *dest,
263+
FAR const char *renew_lock_path);
264+
void pkg_reclaim_stale_lock(FAR const char *path);
263265
int pkg_sync(FAR const char *source);
264266
int pkg_install(FAR const char *name);
265267
int pkg_uninstall(FAR const char *name);

system/nxpkg/pkg_install.c

Lines changed: 81 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,39 +40,6 @@
4040
* Private Functions
4141
****************************************************************************/
4242

43-
/****************************************************************************
44-
* Name: pkg_install_reclaim_stale_lock
45-
*
46-
* Description:
47-
* Remove "path" if it is old enough that it cannot belong to a
48-
* still-running install (see PKG_LOCK_STALE_SECONDS). Best-effort: any
49-
* stat()/unlink() failure just falls through to the normal -EBUSY
50-
* result, since a lock we can't inspect should be treated as held.
51-
*
52-
****************************************************************************/
53-
54-
static void pkg_install_reclaim_stale_lock(FAR const char *path)
55-
{
56-
struct stat st;
57-
time_t now;
58-
59-
if (stat(path, &st) < 0)
60-
{
61-
return;
62-
}
63-
64-
now = time(NULL);
65-
if (now < st.st_mtime ||
66-
(now - st.st_mtime) < PKG_LOCK_STALE_SECONDS)
67-
{
68-
return;
69-
}
70-
71-
pkg_error("reclaiming stale lock '%s' (age %ld s)",
72-
path, (long)(now - st.st_mtime));
73-
unlink(path);
74-
}
75-
7643
static int pkg_install_acquire_lock(FAR const char *name, FAR char *path,
7744
size_t size)
7845
{
@@ -94,7 +61,7 @@ static int pkg_install_acquire_lock(FAR const char *name, FAR char *path,
9461
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
9562
if (fd < 0 && errno == EEXIST)
9663
{
97-
pkg_install_reclaim_stale_lock(path);
64+
pkg_reclaim_stale_lock(path);
9865
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
9966
}
10067

@@ -176,7 +143,7 @@ static int pkg_install_acquire_installed_lock(FAR char *path, size_t size)
176143
return -errno;
177144
}
178145

179-
pkg_install_reclaim_stale_lock(path);
146+
pkg_reclaim_stale_lock(path);
180147
usleep(20 * 1000);
181148
}
182149

@@ -201,7 +168,8 @@ static bool pkg_install_has_version(
201168
}
202169

203170
static int pkg_install_prune_oldest_version(
204-
FAR struct pkg_installed_entry_s *entry)
171+
FAR struct pkg_installed_entry_s *entry,
172+
FAR char *pruned_version, size_t pruned_version_size)
205173
{
206174
size_t victim = entry->version_count;
207175
size_t i;
@@ -228,7 +196,19 @@ static int pkg_install_prune_oldest_version(
228196
return -E2BIG;
229197
}
230198

231-
pkg_store_remove_version_dir(entry->name, entry->versions[victim]);
199+
/* Deleting the pruned version's on-disk directory here, before this
200+
* in-memory db update is even durably saved, left a real inconsistency
201+
* window: if pkg_metadata_save_installed() subsequently failed (full
202+
* SD card, I/O error), the payload was already gone but the last
203+
* successfully-saved instpkg.jsn could still list that version as
204+
* installed. Hand the victim's version string back to the caller
205+
* instead, so it can defer the actual directory removal until after
206+
* the save succeeds - mirroring how this file already treats the
207+
* installed db as authoritative everywhere else.
208+
*/
209+
210+
snprintf(pruned_version, pruned_version_size, "%s",
211+
entry->versions[victim]);
232212

233213
for (i = victim; i + 1 < entry->version_count; i++)
234214
{
@@ -241,7 +221,9 @@ static int pkg_install_prune_oldest_version(
241221
}
242222

243223
static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
244-
FAR const char *version)
224+
FAR const char *version,
225+
FAR char *pruned_version,
226+
size_t pruned_version_size)
245227
{
246228
int ret;
247229

@@ -252,7 +234,8 @@ static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
252234

253235
if (entry->version_count >= PKG_INSTALLED_VERSIONS_MAX)
254236
{
255-
ret = pkg_install_prune_oldest_version(entry);
237+
ret = pkg_install_prune_oldest_version(entry, pruned_version,
238+
pruned_version_size);
256239
if (ret < 0)
257240
{
258241
return ret;
@@ -278,7 +261,9 @@ static int pkg_install_add_version(FAR struct pkg_installed_entry_s *entry,
278261

279262
static int pkg_install_update_installed(FAR struct pkg_installed_db_s *db,
280263
FAR const struct pkg_manifest_s
281-
*manifest)
264+
*manifest,
265+
FAR char *pruned_version,
266+
size_t pruned_version_size)
282267
{
283268
FAR struct pkg_installed_entry_s *entry;
284269
int ret;
@@ -332,7 +317,8 @@ static int pkg_install_update_installed(FAR struct pkg_installed_db_s *db,
332317
}
333318

334319
entry->type = manifest->type;
335-
return pkg_install_add_version(entry, manifest->version);
320+
return pkg_install_add_version(entry, manifest->version, pruned_version,
321+
pruned_version_size);
336322
}
337323

338324
static int pkg_install_write_pointers(
@@ -380,6 +366,42 @@ static int pkg_install_write_pointers(
380366
* Public Functions
381367
****************************************************************************/
382368

369+
/****************************************************************************
370+
* Name: pkg_reclaim_stale_lock
371+
*
372+
* Description:
373+
* Remove "path" if it is old enough that it cannot belong to a still-
374+
* running holder (see PKG_LOCK_STALE_SECONDS). Best-effort: any
375+
* stat()/unlink() failure just falls through to the normal -EBUSY
376+
* result, since a lock we can't inspect should be treated as held.
377+
* Generic across every lock file this package uses (per-package
378+
* install locks, the shared installed-db lock, pkg_repo.c's sync
379+
* lock) - not install-specific despite living in this file.
380+
*
381+
****************************************************************************/
382+
383+
void pkg_reclaim_stale_lock(FAR const char *path)
384+
{
385+
struct stat st;
386+
time_t now;
387+
388+
if (stat(path, &st) < 0)
389+
{
390+
return;
391+
}
392+
393+
now = time(NULL);
394+
if (now < st.st_mtime ||
395+
(now - st.st_mtime) < PKG_LOCK_STALE_SECONDS)
396+
{
397+
return;
398+
}
399+
400+
pkg_error("reclaiming stale lock '%s' (age %ld s)",
401+
path, (long)(now - st.st_mtime));
402+
unlink(path);
403+
}
404+
383405
int pkg_install(FAR const char *name)
384406
{
385407
FAR struct pkg_index_s *index;
@@ -393,11 +415,14 @@ int pkg_install(FAR const char *name)
393415
FAR char *installed_lock;
394416
FAR const char *artifact;
395417
char digest[PKG_HASH_HEX_LEN + 1];
418+
char pruned_version[PKG_VERSION_MAX + 1];
396419
bool staged_to_tmp;
397420
bool version_dir_created;
398421
bool installed_lock_held;
399422
int ret;
400423

424+
pruned_version[0] = '\0';
425+
401426
index = pkg_zalloc(sizeof(*index));
402427
installed = pkg_zalloc(sizeof(*installed));
403428
source = pkg_path_alloc();
@@ -525,7 +550,7 @@ int pkg_install(FAR const char *name)
525550
goto errout;
526551
}
527552

528-
ret = pkg_acquire_source(source, tmp);
553+
ret = pkg_acquire_source(source, tmp, lock);
529554
if (ret < 0)
530555
{
531556
pkg_error("acquire source failed: %d", ret);
@@ -667,7 +692,8 @@ int pkg_install(FAR const char *name)
667692
goto errout;
668693
}
669694

670-
ret = pkg_install_update_installed(installed, manifest);
695+
ret = pkg_install_update_installed(installed, manifest, pruned_version,
696+
sizeof(pruned_version));
671697
if (ret < 0)
672698
{
673699
pkg_error("update installed metadata failed: %d", ret);
@@ -681,6 +707,18 @@ int pkg_install(FAR const char *name)
681707
goto errout;
682708
}
683709

710+
/* Only remove the pruned version's payload directory now that the db
711+
* update naming it gone is durably saved - see
712+
* pkg_install_prune_oldest_version()'s comment for why doing this
713+
* before the save could leave a saved db entry pointing at an
714+
* already-deleted version if the save had failed instead.
715+
*/
716+
717+
if (pruned_version[0] != '\0')
718+
{
719+
pkg_store_remove_version_dir(manifest->name, pruned_version);
720+
}
721+
684722
ret = pkg_install_write_pointers(installed, manifest->name);
685723
if (ret < 0)
686724
{

system/nxpkg/pkg_metadata.c

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,9 @@ static int pkg_metadata_version_token_cmp(FAR const char *lhs,
404404
long rightnum;
405405
FAR char *leftend;
406406
FAR char *rightend;
407+
FAR const char *cmpleft;
408+
FAR const char *cmpright;
409+
int ret;
407410

408411
leftnum = strtol(lhs, &leftend, 10);
409412
rightnum = strtol(rhs, &rightend, 10);
@@ -419,22 +422,36 @@ static int pkg_metadata_version_token_cmp(FAR const char *lhs,
419422
{
420423
return 1;
421424
}
425+
426+
/* Equal numeric prefixes don't make the tokens equal if what
427+
* follows the number differs - e.g. "1a" and "1b" both parse as
428+
* 1, but are documented to compare lexically by their non-numeric
429+
* component. Falling through to "equal" here (as this used to)
430+
* silently treated any such pair as the same version, which is
431+
* exactly backwards for tokens whose whole point is to be
432+
* distinguishable. Compare what's left after the numeric prefix
433+
* - leftend/rightend - lexically instead of falling through.
434+
*/
435+
436+
cmpleft = leftend;
437+
cmpright = rightend;
422438
}
423439
else
424440
{
425-
int ret;
441+
cmpleft = lhs;
442+
cmpright = rhs;
443+
}
426444

427-
ret = pkg_string_cmp(lhs, PKG_VERSION_MAX + 1,
428-
rhs, PKG_VERSION_MAX + 1);
429-
if (ret < 0)
430-
{
431-
return -1;
432-
}
445+
ret = pkg_string_cmp(cmpleft, PKG_VERSION_MAX + 1,
446+
cmpright, PKG_VERSION_MAX + 1);
447+
if (ret < 0)
448+
{
449+
return -1;
450+
}
433451

434-
if (ret > 0)
435-
{
436-
return 1;
437-
}
452+
if (ret > 0)
453+
{
454+
return 1;
438455
}
439456

440457
return 0;

0 commit comments

Comments
 (0)