fix(libgit2): make the loose ODB persist on FATFS via POSIX shims
The loose ODB silently dropped every write on device: utimes() returned 0 unconditionally, so libgit2's freshen probe (git_futils_touch -> p_utimes) always reported "object exists" and git_odb_write skipped the write entirely -- blobs/trees/commits never hit disk and write_tree failed with "invalid object specified". Gate utimes on file existence (present -> 0, absent -> ENOENT). Also add a remove-then-rename p_rename (FATFS f_rename can't replace an existing target, no hardlinks), with posix.c's original scoped out in the component CMakeLists, plus symlink/gai_strerror link stubs git_push pulls in. p_rename was verified on hardware (cleared the rename error); the utimes fix is diagnosed from the failure + libgit2 source and build-verified, on-device confirmation still pending.
This commit is contained in:
@@ -106,3 +106,14 @@ target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||
-Wno-error=incompatible-pointer-types
|
||||
-Wno-error=implicit-int
|
||||
)
|
||||
|
||||
# FATFS can't do POSIX rename-replace (f_rename fails if the target exists) and
|
||||
# has no hardlinks, so libgit2's p_rename (link-then-rename) can't overwrite the
|
||||
# config/refs/HEAD/index files its lock→commit writes depend on. Compile
|
||||
# posix.c's p_rename under a throwaway name — scoped to this ONE file so no other
|
||||
# TU is touched — and provide our own remove-then-rename p_rename in esp_stubs.c
|
||||
# (p_rename is the single atomic-rename path: filebuf/futils/indexer/refdb_fs).
|
||||
set_source_files_properties(
|
||||
"${LG2}/src/util/posix.c"
|
||||
PROPERTIES COMPILE_DEFINITIONS "p_rename=libgit2_unused_p_rename"
|
||||
)
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <pwd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h> /* stat() for the existence-gated utimes() below */
|
||||
#include <stdio.h> /* remove(), rename() for the p_rename replacement */
|
||||
|
||||
/* One implicit root user/group. */
|
||||
uid_t getuid(void) { return 0; }
|
||||
@@ -48,10 +50,59 @@ ssize_t readlink(const char *path, char *buf, size_t bufsiz)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* VFS has no utimes(); accept and ignore (file mtime is cosmetic here). */
|
||||
/* No symlinks on FAT: creating one is unsupported. libgit2 calls this only in
|
||||
* its "does this filesystem support symlinks?" probe (fs_path.c) and while
|
||||
* copying trees (futils.c) — a clean failure is the honest answer, and libgit2
|
||||
* treats the target as a normal file. (git_push referenced it; git_smoke, which
|
||||
* only touched the ODB, did not — hence it surfaces now.) */
|
||||
int symlink(const char *target, const char *linkpath)
|
||||
{
|
||||
(void)target;
|
||||
(void)linkpath;
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* FATFS/VFS can't set file times — but utimes() MUST still fail for a path that
|
||||
* doesn't exist. libgit2's git_futils_touch() is p_utimes(), and that is how the
|
||||
* loose ODB's `freshen` probe answers "does this object already exist?":
|
||||
* git_odb_write() SKIPS the write entirely when freshen succeeds (odb.c:1629).
|
||||
* A blanket `return 0` made every freshen succeed, so libgit2 believed every
|
||||
* object was already on disk and silently dropped ALL loose-object writes —
|
||||
* blobs/trees/commits never persisted, and write_tree then failed with
|
||||
* "invalid object specified". Gate success on existence: present → 0 (actually
|
||||
* setting the time is a cosmetic no-op we skip), absent → -1/ENOENT so freshen
|
||||
* reports "not found" and the real write proceeds. */
|
||||
int utimes(const char *path, const struct timeval times[2])
|
||||
{
|
||||
(void)path;
|
||||
struct stat st;
|
||||
(void)times;
|
||||
if (stat(path, &st) != 0)
|
||||
return -1; /* stat set errno (ENOENT for a missing object) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* lwip implements getaddrinfo() but not gai_strerror(); libgit2's socket stream
|
||||
* (streams/socket.c) uses it only to format a connect-error message, so a
|
||||
* constant string is sufficient. Deliberately no <netdb.h> include: the symbol
|
||||
* is undefined at link (no macro/decl shadows it), so a plain definition is
|
||||
* safe, and the ABI (pointer vs. implicit-int return) matches on xtensa. */
|
||||
const char *gai_strerror(int ecode)
|
||||
{
|
||||
(void)ecode;
|
||||
return "getaddrinfo failure";
|
||||
}
|
||||
|
||||
/* POSIX rename() atomically replaces an existing target; FATFS f_rename does
|
||||
* NOT (it fails with EEXIST) and FAT has no hardlinks, so libgit2's own
|
||||
* p_rename (link-then-rename, in posix.c) can't overwrite config/refs/HEAD/index
|
||||
* during their lock→commit. Provide replace semantics: drop the target, then
|
||||
* rename. Not crash-atomic (a crash between the two loses `to`), but FAT offers
|
||||
* no atomic replace — acceptable for the working copy. posix.c's original is
|
||||
* compiled as libgit2_unused_p_rename (see the component CMakeLists), so this is
|
||||
* the p_rename every caller links against. */
|
||||
int p_rename(const char *from, const char *to)
|
||||
{
|
||||
(void)remove(to); /* ignore ENOENT when `to` doesn't exist yet */
|
||||
return rename(from, to) == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user