From 8c621b8e20735aa4389f0af1e2f737ecfa434e86 Mon Sep 17 00:00:00 2001 From: "jerry.meng" Date: Thu, 18 Jun 2026 15:49:15 +0800 Subject: [PATCH] file: fix concurrent load/commit race condition Root cause With blocking LOCK_SH, a reader could open an old inode first, then wait for the writer lock; after the writer renamed and unlocked, the reader still operated on that stale inode, causing stale reads or lost updates under contention. Solution Changed the read path to use non-blocking shared lock attempts and, on lock contention, close and reopen the file before retrying so it tracks the latest inode behind the path. Signed-off-by: jerry.meng --- file.c | 4 ++-- util.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/file.c b/file.c index 6840cfc..72de612 100644 --- a/file.c +++ b/file.c @@ -816,15 +816,15 @@ static void uci_file_commit(struct uci_context *ctx, struct uci_package **packag done: free(name); free(path); - uci_close_stream(f1); if (do_rename) { path = realpath(p->path, NULL); if (!path || stat(path, &statbuf) || chmod(filename, statbuf.st_mode) || rename(filename, path)) { unlink(filename); - UCI_THROW(ctx, UCI_ERR_IO); + ctx->err = UCI_ERR_IO; } free(path); } + uci_close_stream(f1); if (ctx->err) UCI_THROW(ctx, ctx->err); } diff --git a/util.c b/util.c index 61e42cd..2149894 100644 --- a/util.c +++ b/util.c @@ -218,9 +218,32 @@ __private FILE *uci_open_stream(struct uci_context *ctx, const char *filename, c if (fd < 0) goto error; - ret = flock(fd, (write ? LOCK_EX : LOCK_SH)); - if ((ret < 0) && (errno != ENOSYS)) - goto error_close; + if (write) { + ret = flock(fd, LOCK_EX); + if ((ret < 0) && (errno != ENOSYS)) + goto error_close; + } else { + while (1) { + ret = flock(fd, LOCK_SH | LOCK_NB); + if (ret == 0 || errno == ENOSYS) + break; + + if (errno == EINTR) + continue; + + if (errno == EWOULDBLOCK || errno == EAGAIN) { + /* Reopen to follow a potentially replaced inode while writer holds lock */ + close(fd); + usleep(1000); + fd = open(filename, flags, mode); + if (fd < 0) + goto error; + continue; + } + + goto error_close; + } + } ret = lseek(fd, 0, pos);