From d135aa828e9788de0874e6e79fcd254a4b3cc651 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 19:56:56 +0530 Subject: [PATCH 01/26] Remove partial Git mirror after clone failure --- src/pkg.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pkg.c b/src/pkg.c index fb01878..cdfcb5d 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -861,8 +861,14 @@ static bool pkg_ensure_mirror(const char *mirror, (char *)mirror, NULL}; if (pkg_git(argv, NULL, 1) != 0) { + /* + * git can leave a partial destination when a clone fails. + * Remove it so the next attempt can start cleanly rather + * than failing forever on a non-empty cache directory. + */ + (void)pkg_remove_tree(mirror); snprintf(error, error_size, "cannot clone '%s'", url); - return NULL; + return false; } return true; } From af0eaaafc70ff36317ca92bb9594a758c1275949 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 19:56:58 +0530 Subject: [PATCH 02/26] Remove partial Git mirror after clone failure --- rust/flintlang/c-src/src/pkg.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index aca7b39..cae05ff 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -861,8 +861,14 @@ static bool pkg_ensure_mirror(const char *mirror, (char *)mirror, NULL}; if (pkg_git(argv, NULL, 1) != 0) { + /* + * git can leave a partial destination when a clone fails. + * Remove it so the next attempt can start cleanly rather + * than failing forever on a non-empty cache directory. + */ + (void)pkg_remove_tree(mirror); snprintf(error, error_size, "cannot clone '%s'", url); - return NULL; + return false; } return true; } From 2e3ec8621f2f41cb6bc0e0b9e84b082accd03c4d Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 19:57:07 +0530 Subject: [PATCH 03/26] Test recovery from an incomplete Git mirror cache --- tests/pkg_test.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 7d1d2d6..2f73f3f 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -158,6 +158,14 @@ expect "git import works" "HEY zed" "$("$FLINT" use.fl 2>&1)" pin=$(sed -n '/^commit = /s/commit = "\(.*\)"/\1/p' flint.lock) expect "lock pins a 40-char commit" "40" "${#pin}" +# A damaged mirror cache must heal instead of making every retry fail. +mirror=$(find "$HOME/.flint/git" -mindepth 1 -maxdepth 1 -type d | head -n 1) +expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" +rm -f "$mirror/HEAD" +out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? +expect "install recovers a mirror missing HEAD" "0" "$code" +expect "recovered mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" + cat > "$WORK/gitsrc/main.fl" <<'EOF' export fn loud(who) { return "LOUDER " + who From 018b729499a9543214d276c8dba16da6459d45ab Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 19:59:01 +0530 Subject: [PATCH 04/26] Make mirror recovery regression test portable and retry-aware --- tests/pkg_test.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 2f73f3f..f3022d0 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -159,11 +159,16 @@ pin=$(sed -n '/^commit = /s/commit = "\(.*\)"/\1/p' flint.lock) expect "lock pins a 40-char commit" "40" "${#pin}" # A damaged mirror cache must heal instead of making every retry fail. -mirror=$(find "$HOME/.flint/git" -mindepth 1 -maxdepth 1 -type d | head -n 1) +mirror= +for candidate in "$HOME"/.flint/git/*; do + [ -d "$candidate" ] && { mirror=$candidate; break; } +done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" rm -f "$mirror/HEAD" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? -expect "install recovers a mirror missing HEAD" "0" "$code" +expect "damaged mirror install fails while clearing cache" "1" "$([ "$code" -ne 0 ] && echo 1 || echo 0)" +out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? +expect "next install recovers the mirror" "0" "$code" expect "recovered mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" cat > "$WORK/gitsrc/main.fl" <<'EOF' From 1c7c58aa3510f1853297450acfe2e9d43c816dee Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:00:07 +0530 Subject: [PATCH 05/26] Target the installed package mirror in recovery test --- tests/pkg_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index f3022d0..420ed67 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -160,7 +160,7 @@ expect "lock pins a 40-char commit" "40" "${#pin}" # A damaged mirror cache must heal instead of making every retry fail. mirror= -for candidate in "$HOME"/.flint/git/*; do +for candidate in "$HOME"/.flint/git/shout-*; do [ -d "$candidate" ] && { mirror=$candidate; break; } done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" From 5525fa49ec373541165954800ba369156416d0bf Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:01:15 +0530 Subject: [PATCH 06/26] Select the mirror using its URL-derived cache slug --- tests/pkg_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 420ed67..f2dcf30 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -160,7 +160,7 @@ expect "lock pins a 40-char commit" "40" "${#pin}" # A damaged mirror cache must heal instead of making every retry fail. mirror= -for candidate in "$HOME"/.flint/git/shout-*; do +for candidate in "$HOME"/.flint/git/gitsrc-*; do [ -d "$candidate" ] && { mirror=$candidate; break; } done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" From 2fb90816c5eced9a0410017d51d65e8ce326248f Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:02:07 +0530 Subject: [PATCH 07/26] Simulate a genuinely incomplete bare Git mirror --- tests/pkg_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index f2dcf30..4032ab2 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -164,7 +164,7 @@ for candidate in "$HOME"/.flint/git/gitsrc-*; do [ -d "$candidate" ] && { mirror=$candidate; break; } done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" -rm -f "$mirror/HEAD" +rm -f "$mirror/HEAD" "$mirror/config" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? expect "damaged mirror install fails while clearing cache" "1" "$([ "$code" -ne 0 ] && echo 1 || echo 0)" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? From bb81f3386516a9ed4ea948e25812753c269b5917 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:02:23 +0530 Subject: [PATCH 08/26] Retry Git mirror clone after cleaning partial cache --- src/pkg.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pkg.c b/src/pkg.c index cdfcb5d..ba542fb 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -863,12 +863,19 @@ static bool pkg_ensure_mirror(const char *mirror, if (pkg_git(argv, NULL, 1) != 0) { /* * git can leave a partial destination when a clone fails. - * Remove it so the next attempt can start cleanly rather - * than failing forever on a non-empty cache directory. + * Remove it and retry once so a damaged cache heals during + * this install. A bad URL still fails, but leaves no poison + * directory behind for the next attempt. */ - (void)pkg_remove_tree(mirror); - snprintf(error, error_size, "cannot clone '%s'", url); - return false; + if (!pkg_remove_tree(mirror)) { + snprintf(error, error_size, "cannot clone '%s'", url); + return false; + } + if (pkg_git(argv, NULL, 1) != 0) { + (void)pkg_remove_tree(mirror); + snprintf(error, error_size, "cannot clone '%s'", url); + return false; + } } return true; } From 54ce549934914fb7407f14379d4b0cf7a0082ff6 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:02:27 +0530 Subject: [PATCH 09/26] Retry Git mirror clone after cleaning partial cache --- rust/flintlang/c-src/src/pkg.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index cae05ff..c3acd7d 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -863,12 +863,19 @@ static bool pkg_ensure_mirror(const char *mirror, if (pkg_git(argv, NULL, 1) != 0) { /* * git can leave a partial destination when a clone fails. - * Remove it so the next attempt can start cleanly rather - * than failing forever on a non-empty cache directory. + * Remove it and retry once so a damaged cache heals during + * this install. A bad URL still fails, but leaves no poison + * directory behind for the next attempt. */ - (void)pkg_remove_tree(mirror); - snprintf(error, error_size, "cannot clone '%s'", url); - return false; + if (!pkg_remove_tree(mirror)) { + snprintf(error, error_size, "cannot clone '%s'", url); + return false; + } + if (pkg_git(argv, NULL, 1) != 0) { + (void)pkg_remove_tree(mirror); + snprintf(error, error_size, "cannot clone '%s'", url); + return false; + } } return true; } From f6b8fe0c1de53e77cf32ab83a59d0930367501f1 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:02:29 +0530 Subject: [PATCH 10/26] Assert package install repairs an incomplete mirror in one run --- tests/pkg_test.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 4032ab2..a5db0de 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -166,9 +166,7 @@ done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" rm -f "$mirror/HEAD" "$mirror/config" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? -expect "damaged mirror install fails while clearing cache" "1" "$([ "$code" -ne 0 ] && echo 1 || echo 0)" -out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? -expect "next install recovers the mirror" "0" "$code" +expect "install recovers an incomplete mirror" "0" "$code" expect "recovered mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" cat > "$WORK/gitsrc/main.fl" <<'EOF' From b35b96f205c4b9967b4bfc89efae6e2f25ace321 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:02:54 +0530 Subject: [PATCH 11/26] Clean partial Git mirror after clone failure --- src/pkg.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/pkg.c b/src/pkg.c index ba542fb..cdfcb5d 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -863,19 +863,12 @@ static bool pkg_ensure_mirror(const char *mirror, if (pkg_git(argv, NULL, 1) != 0) { /* * git can leave a partial destination when a clone fails. - * Remove it and retry once so a damaged cache heals during - * this install. A bad URL still fails, but leaves no poison - * directory behind for the next attempt. + * Remove it so the next attempt can start cleanly rather + * than failing forever on a non-empty cache directory. */ - if (!pkg_remove_tree(mirror)) { - snprintf(error, error_size, "cannot clone '%s'", url); - return false; - } - if (pkg_git(argv, NULL, 1) != 0) { - (void)pkg_remove_tree(mirror); - snprintf(error, error_size, "cannot clone '%s'", url); - return false; - } + (void)pkg_remove_tree(mirror); + snprintf(error, error_size, "cannot clone '%s'", url); + return false; } return true; } From a8df58019d32cca3730c8e9e54fb62abe13fad35 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:02:58 +0530 Subject: [PATCH 12/26] Clean partial Git mirror after clone failure --- rust/flintlang/c-src/src/pkg.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index c3acd7d..cae05ff 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -863,19 +863,12 @@ static bool pkg_ensure_mirror(const char *mirror, if (pkg_git(argv, NULL, 1) != 0) { /* * git can leave a partial destination when a clone fails. - * Remove it and retry once so a damaged cache heals during - * this install. A bad URL still fails, but leaves no poison - * directory behind for the next attempt. + * Remove it so the next attempt can start cleanly rather + * than failing forever on a non-empty cache directory. */ - if (!pkg_remove_tree(mirror)) { - snprintf(error, error_size, "cannot clone '%s'", url); - return false; - } - if (pkg_git(argv, NULL, 1) != 0) { - (void)pkg_remove_tree(mirror); - snprintf(error, error_size, "cannot clone '%s'", url); - return false; - } + (void)pkg_remove_tree(mirror); + snprintf(error, error_size, "cannot clone '%s'", url); + return false; } return true; } From 5f17a0f31c4ae4b63039da2ef3218c8028483016 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:03:00 +0530 Subject: [PATCH 13/26] Verify an incomplete mirror is cleaned and retry succeeds --- tests/pkg_test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index a5db0de..4032ab2 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -166,7 +166,9 @@ done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" rm -f "$mirror/HEAD" "$mirror/config" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? -expect "install recovers an incomplete mirror" "0" "$code" +expect "damaged mirror install fails while clearing cache" "1" "$([ "$code" -ne 0 ] && echo 1 || echo 0)" +out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? +expect "next install recovers the mirror" "0" "$code" expect "recovered mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" cat > "$WORK/gitsrc/main.fl" <<'EOF' From ba3a1e00dab5289a949b20649487bcfb250aa445 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:08:29 +0530 Subject: [PATCH 14/26] Make mirror repair symlink-safe and serialize concurrent installs --- src/pkg.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/src/pkg.c b/src/pkg.c index cdfcb5d..ec1772e 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -836,7 +837,7 @@ static bool pkg_satisfies(const char *requirement, * ours saying which URL it was for -- git already explains itself, and * wrapping that twice helps nobody. */ -static bool pkg_ensure_mirror(const char *mirror, +static bool pkg_ensure_mirror_unlocked(const char *mirror, const char *url, bool fetch, const char *commit, @@ -848,9 +849,35 @@ static bool pkg_ensure_mirror(const char *mirror, if (snprintf(head, sizeof(head), "%s/HEAD", mirror) >= (int)sizeof(head)) { snprintf(error, error_size, "path too long"); - return NULL; + return false; } if (access(head, F_OK) != 0) { + char staging[4352]; + bool made_staging = false; + for (unsigned int attempt = 0; attempt < 100; attempt++) { + int n = snprintf(staging, + sizeof(staging), + "%s.tmp.%ld.%u", + mirror, + (long)getpid(), + attempt); + if (n < 0 || (size_t)n >= sizeof(staging)) { + snprintf(error, error_size, "path too long"); + return false; + } + if (mkdir(staging, 0700) == 0) { + made_staging = true; + break; + } + if (errno != EEXIST) { + snprintf(error, error_size, "cannot create mirror staging directory"); + return false; + } + } + if (!made_staging) { + snprintf(error, error_size, "cannot create mirror staging directory"); + return false; + } char *argv[] = {"git", "-c", "protocol.file.allow=always", @@ -858,18 +885,31 @@ static bool pkg_ensure_mirror(const char *mirror, "--mirror", "--", (char *)url, - (char *)mirror, + staging, NULL}; if (pkg_git(argv, NULL, 1) != 0) { - /* - * git can leave a partial destination when a clone fails. - * Remove it so the next attempt can start cleanly rather - * than failing forever on a non-empty cache directory. - */ - (void)pkg_remove_tree(mirror); + (void)pkg_remove_tree(staging); snprintf(error, error_size, "cannot clone '%s'", url); return false; } + + struct stat st; + if (lstat(mirror, &st) == 0) { + if (!pkg_remove_tree(mirror)) { + (void)pkg_remove_tree(staging); + snprintf(error, error_size, "cannot remove damaged mirror '%s'", mirror); + return false; + } + } else if (errno != ENOENT) { + (void)pkg_remove_tree(staging); + snprintf(error, error_size, "cannot inspect mirror '%s'", mirror); + return false; + } + if (rename(staging, mirror) != 0) { + (void)pkg_remove_tree(staging); + snprintf(error, error_size, "cannot publish mirror '%s'", mirror); + return false; + } return true; } bool need_fetch = fetch; @@ -889,7 +929,7 @@ static bool pkg_ensure_mirror(const char *mirror, NULL}; if (pkg_git(argv, NULL, 1) != 0) { snprintf(error, error_size, "cannot fetch '%s'", url); - return NULL; + return false; } } return true; @@ -903,6 +943,51 @@ static bool pkg_ensure_mirror(const char *mirror, #endif } +static bool pkg_ensure_mirror(const char *mirror, + const char *url, + bool fetch, + const char *commit, + char *error, + size_t error_size) +{ +#ifndef _WIN32 + char lock_path[4352]; + if (snprintf(lock_path, sizeof(lock_path), "%s.lock", mirror) >= + (int)sizeof(lock_path)) { + snprintf(error, error_size, "path too long"); + return false; + } + int lock_fd = open(lock_path, O_CREAT | O_RDWR, 0600); + if (lock_fd < 0) { + snprintf(error, error_size, "cannot open mirror lock '%s'", lock_path); + return false; + } + struct flock lock = {0}; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + while (fcntl(lock_fd, F_SETLKW, &lock) != 0) { + if (errno == EINTR) + continue; + close(lock_fd); + snprintf(error, error_size, "cannot lock mirror '%s'", mirror); + return false; + } + bool ok = pkg_ensure_mirror_unlocked( + mirror, url, fetch, commit, error, error_size); + lock.l_type = F_UNLCK; + (void)fcntl(lock_fd, F_SETLK, &lock); + close(lock_fd); + return ok; +#else + (void)mirror; + (void)url; + (void)fetch; + (void)commit; + snprintf(error, error_size, "git dependencies need a POSIX system"); + return false; +#endif +} + /* copy a commit out of a mirror into dest: clone, detach at the pin, * drop the .git, so what lands in flint_modules/ is source, not a * repository. dest must not exist; the caller clears it first. */ @@ -1129,9 +1214,16 @@ static bool pkg_remove_entry(const char *child) static bool pkg_remove_tree(const char *path) { + struct stat st; + int sr = pkg_stat_entry(path, &st); + if (sr < 0) + return false; + if (sr != 0 || !S_ISDIR(st.st_mode)) + return remove(path) == 0; + DIR *dir = opendir(path); if (dir == NULL) - return remove(path) == 0; + return false; bool ok = true; struct dirent *entry; while (ok && (entry = readdir(dir)) != NULL) { From f6d2a7782fef1abd40040a56315825424b1fd962 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:08:33 +0530 Subject: [PATCH 15/26] Make mirror repair symlink-safe and serialize concurrent installs --- rust/flintlang/c-src/src/pkg.c | 114 +++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 11 deletions(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index cae05ff..e7f484c 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -836,7 +837,7 @@ static bool pkg_satisfies(const char *requirement, * ours saying which URL it was for -- git already explains itself, and * wrapping that twice helps nobody. */ -static bool pkg_ensure_mirror(const char *mirror, +static bool pkg_ensure_mirror_unlocked(const char *mirror, const char *url, bool fetch, const char *commit, @@ -848,9 +849,35 @@ static bool pkg_ensure_mirror(const char *mirror, if (snprintf(head, sizeof(head), "%s/HEAD", mirror) >= (int)sizeof(head)) { snprintf(error, error_size, "path too long"); - return NULL; + return false; } if (access(head, F_OK) != 0) { + char staging[4352]; + bool made_staging = false; + for (unsigned int attempt = 0; attempt < 100; attempt++) { + int n = snprintf(staging, + sizeof(staging), + "%s.tmp.%ld.%u", + mirror, + (long)getpid(), + attempt); + if (n < 0 || (size_t)n >= sizeof(staging)) { + snprintf(error, error_size, "path too long"); + return false; + } + if (mkdir(staging, 0700) == 0) { + made_staging = true; + break; + } + if (errno != EEXIST) { + snprintf(error, error_size, "cannot create mirror staging directory"); + return false; + } + } + if (!made_staging) { + snprintf(error, error_size, "cannot create mirror staging directory"); + return false; + } char *argv[] = {"git", "-c", "protocol.file.allow=always", @@ -858,18 +885,31 @@ static bool pkg_ensure_mirror(const char *mirror, "--mirror", "--", (char *)url, - (char *)mirror, + staging, NULL}; if (pkg_git(argv, NULL, 1) != 0) { - /* - * git can leave a partial destination when a clone fails. - * Remove it so the next attempt can start cleanly rather - * than failing forever on a non-empty cache directory. - */ - (void)pkg_remove_tree(mirror); + (void)pkg_remove_tree(staging); snprintf(error, error_size, "cannot clone '%s'", url); return false; } + + struct stat st; + if (lstat(mirror, &st) == 0) { + if (!pkg_remove_tree(mirror)) { + (void)pkg_remove_tree(staging); + snprintf(error, error_size, "cannot remove damaged mirror '%s'", mirror); + return false; + } + } else if (errno != ENOENT) { + (void)pkg_remove_tree(staging); + snprintf(error, error_size, "cannot inspect mirror '%s'", mirror); + return false; + } + if (rename(staging, mirror) != 0) { + (void)pkg_remove_tree(staging); + snprintf(error, error_size, "cannot publish mirror '%s'", mirror); + return false; + } return true; } bool need_fetch = fetch; @@ -889,7 +929,7 @@ static bool pkg_ensure_mirror(const char *mirror, NULL}; if (pkg_git(argv, NULL, 1) != 0) { snprintf(error, error_size, "cannot fetch '%s'", url); - return NULL; + return false; } } return true; @@ -903,6 +943,51 @@ static bool pkg_ensure_mirror(const char *mirror, #endif } +static bool pkg_ensure_mirror(const char *mirror, + const char *url, + bool fetch, + const char *commit, + char *error, + size_t error_size) +{ +#ifndef _WIN32 + char lock_path[4352]; + if (snprintf(lock_path, sizeof(lock_path), "%s.lock", mirror) >= + (int)sizeof(lock_path)) { + snprintf(error, error_size, "path too long"); + return false; + } + int lock_fd = open(lock_path, O_CREAT | O_RDWR, 0600); + if (lock_fd < 0) { + snprintf(error, error_size, "cannot open mirror lock '%s'", lock_path); + return false; + } + struct flock lock = {0}; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + while (fcntl(lock_fd, F_SETLKW, &lock) != 0) { + if (errno == EINTR) + continue; + close(lock_fd); + snprintf(error, error_size, "cannot lock mirror '%s'", mirror); + return false; + } + bool ok = pkg_ensure_mirror_unlocked( + mirror, url, fetch, commit, error, error_size); + lock.l_type = F_UNLCK; + (void)fcntl(lock_fd, F_SETLK, &lock); + close(lock_fd); + return ok; +#else + (void)mirror; + (void)url; + (void)fetch; + (void)commit; + snprintf(error, error_size, "git dependencies need a POSIX system"); + return false; +#endif +} + /* copy a commit out of a mirror into dest: clone, detach at the pin, * drop the .git, so what lands in flint_modules/ is source, not a * repository. dest must not exist; the caller clears it first. */ @@ -1129,9 +1214,16 @@ static bool pkg_remove_entry(const char *child) static bool pkg_remove_tree(const char *path) { + struct stat st; + int sr = pkg_stat_entry(path, &st); + if (sr < 0) + return false; + if (sr != 0 || !S_ISDIR(st.st_mode)) + return remove(path) == 0; + DIR *dir = opendir(path); if (dir == NULL) - return remove(path) == 0; + return false; bool ok = true; struct dirent *entry; while (ok && (entry = readdir(dir)) != NULL) { From 665ad27750bd0c8719b41ba86a0d0cc27043ac61 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:08:58 +0530 Subject: [PATCH 16/26] Test single-run mirror recovery and symlink safety --- tests/pkg_test.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 4032ab2..1af86dc 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -158,19 +158,29 @@ expect "git import works" "HEY zed" "$("$FLINT" use.fl 2>&1)" pin=$(sed -n '/^commit = /s/commit = "\(.*\)"/\1/p' flint.lock) expect "lock pins a 40-char commit" "40" "${#pin}" -# A damaged mirror cache must heal instead of making every retry fail. +# A damaged mirror is replaced in one install, without following symlinks. mirror= for candidate in "$HOME"/.flint/git/gitsrc-*; do [ -d "$candidate" ] && { mirror=$candidate; break; } done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" +rm -rf flint_modules/shout rm -f "$mirror/HEAD" "$mirror/config" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? -expect "damaged mirror install fails while clearing cache" "1" "$([ "$code" -ne 0 ] && echo 1 || echo 0)" -out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? -expect "next install recovers the mirror" "0" "$code" +expect "damaged mirror recovers in one install" "0" "$code" expect "recovered mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" +# A symlink at the cache path must be unlinked, never recursively followed. +target="$WORK/mirror-target" +mkdir -p "$target" +echo "keep me" > "$target/marker" +rm -rf flint_modules/shout "$mirror" +ln -s "$target" "$mirror" +out=$("$FLINT" pkg install 2>&1) && code=0 || code=$? +expect "symlink mirror is safely replaced" "0" "$code" +expect "symlink target data is preserved" "keep me" "$(cat "$target/marker")" +expect "replacement mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" + cat > "$WORK/gitsrc/main.fl" <<'EOF' export fn loud(who) { return "LOUDER " + who From 1f611691def36811e07c4dcbad89537ebc748972 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:09:11 +0530 Subject: [PATCH 17/26] Cover concurrent mirror initialization --- tests/pkg_test.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 1af86dc..3d2a25e 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -181,6 +181,29 @@ expect "symlink mirror is safely replaced" "0" "$code" expect "symlink target data is preserved" "keep me" "$(cat "$target/marker")" expect "replacement mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)" +# Two projects may initialize the same mirror concurrently. +mkdir -p "$WORK/concurrent/a" "$WORK/concurrent/b" +for project in a b; do + cat > "$WORK/concurrent/$project/flint.toml" <"$WORK/concurrent/a.out" 2>&1 & +pid_a=$! +(cd "$WORK/concurrent/b" && "$FLINT" pkg add "file://$WORK/gitsrc") \ + >"$WORK/concurrent/b.out" 2>&1 & +pid_b=$! +if wait "$pid_a"; then code_a=0; else code_a=$?; fi +if wait "$pid_b"; then code_b=0; else code_b=$?; fi +expect "first concurrent install succeeds" "0" "$code_a" +expect "second concurrent install succeeds" "0" "$code_b" +expect_contains "first project got the package" "installed shout 0.3.0" "$(cat "$WORK/concurrent/a.out")" +expect_contains "second project got the package" "installed shout 0.3.0" "$(cat "$WORK/concurrent/b.out")" + cat > "$WORK/gitsrc/main.fl" <<'EOF' export fn loud(who) { return "LOUDER " + who From 9abae29fd1a7493be21e84229aa8a6abcc81b775 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:09:28 +0530 Subject: [PATCH 18/26] Reject symlinked mirror paths before reuse --- src/pkg.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pkg.c b/src/pkg.c index ec1772e..d67862a 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -851,7 +851,10 @@ static bool pkg_ensure_mirror_unlocked(const char *mirror, snprintf(error, error_size, "path too long"); return false; } - if (access(head, F_OK) != 0) { + struct stat mirror_stat; + bool mirror_is_link = lstat(mirror, &mirror_stat) == 0 && + S_ISLNK(mirror_stat.st_mode); + if (mirror_is_link || access(head, F_OK) != 0) { char staging[4352]; bool made_staging = false; for (unsigned int attempt = 0; attempt < 100; attempt++) { From 910f1a08ab83d6deb9df1667cb0462776a4998de Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:09:32 +0530 Subject: [PATCH 19/26] Reject symlinked mirror paths before reuse --- rust/flintlang/c-src/src/pkg.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index e7f484c..5044f7c 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -851,7 +851,10 @@ static bool pkg_ensure_mirror_unlocked(const char *mirror, snprintf(error, error_size, "path too long"); return false; } - if (access(head, F_OK) != 0) { + struct stat mirror_stat; + bool mirror_is_link = lstat(mirror, &mirror_stat) == 0 && + S_ISLNK(mirror_stat.st_mode); + if (mirror_is_link || access(head, F_OK) != 0) { char staging[4352]; bool made_staging = false; for (unsigned int attempt = 0; attempt < 100; attempt++) { From 5be5eb05c95481a3690b1b1e740a703d3eabdb44 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:09:41 +0530 Subject: [PATCH 20/26] Format mirror symlink check --- src/pkg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pkg.c b/src/pkg.c index d67862a..75c945c 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -852,8 +852,8 @@ static bool pkg_ensure_mirror_unlocked(const char *mirror, return false; } struct stat mirror_stat; - bool mirror_is_link = lstat(mirror, &mirror_stat) == 0 && - S_ISLNK(mirror_stat.st_mode); + bool mirror_is_link = lstat(mirror, &mirror_stat) == 0 && + S_ISLNK(mirror_stat.st_mode); if (mirror_is_link || access(head, F_OK) != 0) { char staging[4352]; bool made_staging = false; From b329515f13e3163488f221074f9519499f7b9827 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:09:45 +0530 Subject: [PATCH 21/26] Format mirror symlink check --- rust/flintlang/c-src/src/pkg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index 5044f7c..ffce5d1 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -852,8 +852,8 @@ static bool pkg_ensure_mirror_unlocked(const char *mirror, return false; } struct stat mirror_stat; - bool mirror_is_link = lstat(mirror, &mirror_stat) == 0 && - S_ISLNK(mirror_stat.st_mode); + bool mirror_is_link = lstat(mirror, &mirror_stat) == 0 && + S_ISLNK(mirror_stat.st_mode); if (mirror_is_link || access(head, F_OK) != 0) { char staging[4352]; bool made_staging = false; From 1b273962ff52f779b69f676a2ca7667b7123cd3d Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:10:59 +0530 Subject: [PATCH 22/26] Fix GCC format-truncation warnings in mirror errors --- src/pkg.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pkg.c b/src/pkg.c index 75c945c..c12c9f2 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -900,17 +900,17 @@ static bool pkg_ensure_mirror_unlocked(const char *mirror, if (lstat(mirror, &st) == 0) { if (!pkg_remove_tree(mirror)) { (void)pkg_remove_tree(staging); - snprintf(error, error_size, "cannot remove damaged mirror '%s'", mirror); + snprintf(error, error_size, "cannot remove damaged mirror"); return false; } } else if (errno != ENOENT) { (void)pkg_remove_tree(staging); - snprintf(error, error_size, "cannot inspect mirror '%s'", mirror); + snprintf(error, error_size, "cannot inspect mirror"); return false; } if (rename(staging, mirror) != 0) { (void)pkg_remove_tree(staging); - snprintf(error, error_size, "cannot publish mirror '%s'", mirror); + snprintf(error, error_size, "cannot publish mirror"); return false; } return true; @@ -962,7 +962,7 @@ static bool pkg_ensure_mirror(const char *mirror, } int lock_fd = open(lock_path, O_CREAT | O_RDWR, 0600); if (lock_fd < 0) { - snprintf(error, error_size, "cannot open mirror lock '%s'", lock_path); + snprintf(error, error_size, "cannot open mirror lock"); return false; } struct flock lock = {0}; @@ -972,7 +972,7 @@ static bool pkg_ensure_mirror(const char *mirror, if (errno == EINTR) continue; close(lock_fd); - snprintf(error, error_size, "cannot lock mirror '%s'", mirror); + snprintf(error, error_size, "cannot lock mirror"); return false; } bool ok = pkg_ensure_mirror_unlocked( From 46801b1b50bde635c82fb531908ed0a4bc332b43 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:11:03 +0530 Subject: [PATCH 23/26] Fix GCC format-truncation warnings in mirror errors --- rust/flintlang/c-src/src/pkg.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index ffce5d1..13c21e2 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -900,17 +900,17 @@ static bool pkg_ensure_mirror_unlocked(const char *mirror, if (lstat(mirror, &st) == 0) { if (!pkg_remove_tree(mirror)) { (void)pkg_remove_tree(staging); - snprintf(error, error_size, "cannot remove damaged mirror '%s'", mirror); + snprintf(error, error_size, "cannot remove damaged mirror"); return false; } } else if (errno != ENOENT) { (void)pkg_remove_tree(staging); - snprintf(error, error_size, "cannot inspect mirror '%s'", mirror); + snprintf(error, error_size, "cannot inspect mirror"); return false; } if (rename(staging, mirror) != 0) { (void)pkg_remove_tree(staging); - snprintf(error, error_size, "cannot publish mirror '%s'", mirror); + snprintf(error, error_size, "cannot publish mirror"); return false; } return true; @@ -962,7 +962,7 @@ static bool pkg_ensure_mirror(const char *mirror, } int lock_fd = open(lock_path, O_CREAT | O_RDWR, 0600); if (lock_fd < 0) { - snprintf(error, error_size, "cannot open mirror lock '%s'", lock_path); + snprintf(error, error_size, "cannot open mirror lock"); return false; } struct flock lock = {0}; @@ -972,7 +972,7 @@ static bool pkg_ensure_mirror(const char *mirror, if (errno == EINTR) continue; close(lock_fd); - snprintf(error, error_size, "cannot lock mirror '%s'", mirror); + snprintf(error, error_size, "cannot lock mirror"); return false; } bool ok = pkg_ensure_mirror_unlocked( From 5e346b5858c4cbf1115f7e57447645a5b7bd4796 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:14:03 +0530 Subject: [PATCH 24/26] Keep Windows builds warning-clean --- src/pkg.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pkg.c b/src/pkg.c index c12c9f2..2c64d50 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -982,12 +982,8 @@ static bool pkg_ensure_mirror(const char *mirror, close(lock_fd); return ok; #else - (void)mirror; - (void)url; - (void)fetch; - (void)commit; - snprintf(error, error_size, "git dependencies need a POSIX system"); - return false; + return pkg_ensure_mirror_unlocked( + mirror, url, fetch, commit, error, error_size); #endif } From c8d2acbd16f476492fa6b511d68e29060fd637d2 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:14:07 +0530 Subject: [PATCH 25/26] Keep Windows builds warning-clean --- rust/flintlang/c-src/src/pkg.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rust/flintlang/c-src/src/pkg.c b/rust/flintlang/c-src/src/pkg.c index 13c21e2..6ea98f5 100644 --- a/rust/flintlang/c-src/src/pkg.c +++ b/rust/flintlang/c-src/src/pkg.c @@ -982,12 +982,8 @@ static bool pkg_ensure_mirror(const char *mirror, close(lock_fd); return ok; #else - (void)mirror; - (void)url; - (void)fetch; - (void)commit; - snprintf(error, error_size, "git dependencies need a POSIX system"); - return false; + return pkg_ensure_mirror_unlocked( + mirror, url, fetch, commit, error, error_size); #endif } From 9d81e6fd4d05d0fb2ce70897fae50f0dbf9612e9 Mon Sep 17 00:00:00 2001 From: soumalya das Date: Sat, 10 Oct 2026 20:23:18 +0530 Subject: [PATCH 26/26] Guard mirror recovery test against an empty path --- tests/pkg_test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/pkg_test.sh b/tests/pkg_test.sh index 3d2a25e..e153aa0 100644 --- a/tests/pkg_test.sh +++ b/tests/pkg_test.sh @@ -164,6 +164,10 @@ for candidate in "$HOME"/.flint/git/gitsrc-*; do [ -d "$candidate" ] && { mirror=$candidate; break; } done expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)" +if [ -z "$mirror" ]; then + echo "cannot test mirror recovery without a mirror path" >&2 + exit 1 +fi rm -rf flint_modules/shout rm -f "$mirror/HEAD" "$mirror/config" out=$("$FLINT" pkg install 2>&1) && code=0 || code=$?