Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(native): loop unmount until /mnt stack is clean + tmpfs debug log

Post-mortem from lemon-martin's /mnt/install-debug.log showed the real
root cause of the BLKRRPART EBUSY loop: /mnt has a TWO-LEVEL mount
stack on the X1 Nano. init.sh found config.json on /dev/nvme0n1p1
first (USB enumeration slow) and mounted it at /mnt. Then ac-native's
setup_logging() mounted /dev/sda1 on top of /mnt (mount stacking).
/proc/mounts showed only one nvme entry (the underlying one) but the
TOPMOST fs at /mnt was sda1.

force_unmount_disk() grabbed the nvme target from /proc/mounts and
called umount2("/mnt", MNT_DETACH). But umount2 by PATH unmounts the
topmost mount at that path — which was sda1, not nvme. After one
unmount sda1 was gone but nvme was still the topmost at /mnt, still
referenced, still making BLKRRPART return EBUSY.

Fix:
1. force_unmount_disk now LOOPS (up to 16 iterations):
- Re-read /proc/mounts each iteration
- If any entry still matches /dev/<parent>, umount2 its target
- Continue until no matching entries remain
Each iteration pops one layer of the stack.
2. Log the full /proc/mounts (not just filtered) at the start and end
of force_unmount_disk so post-mortem can see the complete picture.
3. DLOG moved from /mnt/install-debug.log to /tmp/install-debug.log.
Tmpfs can't be affected by the nvme repartition, so the log
survives even if /mnt gets fully popped off the stack. After the
install attempt finishes (success or failure), copy the tmpfs log
back to /mnt/install-debug.log for next-boot post-mortem.

This should finally clear the BLKRRPART EBUSY on the X1 Nano and
similar laptops whose init.sh accidentally finds a stale config.json
on an internal ESP during slow USB enumeration.

+113 -36
+113 -36
fedac/native/src/ac-native.c
··· 784 784 static char install_fail_reason[256] = ""; 785 785 static char install_fail_detail[256] = ""; 786 786 787 - // Unmount every entry in /proc/mounts whose SOURCE device matches /dev/<parent> 788 - // or any of its partitions (/dev/<parent>p*, /dev/<parent>*). We use umount2 789 - // with MNT_DETACH for the initial pass (lazy, always succeeds if the path is 790 - // reachable) and log each action to the given log file. Returns the number of 791 - // successful unmounts. 787 + // Unmount every entry in /proc/mounts whose SOURCE device matches 788 + // /dev/<parent> or any of its partitions. Loops until /proc/mounts no longer 789 + // shows any matching entry — necessary for mount-stacked /mnt where the 790 + // topmost fs might not be the one we want to unmount. Each iteration finds 791 + // ONE matching target, umount2()'s it (MNT_DETACH pops the topmost mount at 792 + // that path, which may or may not be the one we targeted — but after enough 793 + // iterations the stack empties of any matching entries). Returns total 794 + // successful umounts. 792 795 static int force_unmount_disk(const char *parent_blk, const char *dlog_path) { 793 796 int unmounted = 0; 794 - FILE *mp = fopen("/proc/mounts", "r"); 795 - if (!mp) return 0; 796 - // Collect targets first (don't unmount while iterating — /proc/mounts changes) 797 - char targets[16][128]; 798 - int n_targets = 0; 799 - char line[512]; 800 - while (n_targets < 16 && fgets(line, sizeof(line), mp)) { 801 - // /proc/mounts format: source target fstype options ... 802 - char src[128], tgt[128], fst[64]; 803 - if (sscanf(line, "%127s %127s %63s", src, tgt, fst) != 3) continue; 804 - // Match /dev/<parent>, /dev/<parent>p1, /dev/<parent>1, etc. 805 - if (strncmp(src, "/dev/", 5) != 0) continue; 806 - const char *sbase = src + 5; 807 - if (strncmp(sbase, parent_blk, strlen(parent_blk)) != 0) continue; 808 - strncpy(targets[n_targets], tgt, sizeof(targets[0]) - 1); 809 - targets[n_targets][sizeof(targets[0]) - 1] = 0; 810 - n_targets++; 797 + FILE *dl = dlog_path ? fopen(dlog_path, "a") : NULL; 798 + if (dl) fprintf(dl, "--- force_unmount_disk(%s) start ---\n", parent_blk); 799 + 800 + // First pass: log the full /proc/mounts so we can see the initial stack 801 + { 802 + FILE *mp = fopen("/proc/mounts", "r"); 803 + if (mp && dl) { 804 + fprintf(dl, "--- initial /proc/mounts ---\n"); 805 + char line[512]; 806 + while (fgets(line, sizeof(line), mp)) fputs(line, dl); 807 + fprintf(dl, "--- end /proc/mounts ---\n"); 808 + fclose(mp); 809 + } else if (mp) { 810 + fclose(mp); 811 + } 812 + } 813 + 814 + const int MAX_ITER = 16; // belt-and-suspenders against mount-stack depth 815 + for (int iter = 0; iter < MAX_ITER; iter++) { 816 + // Find ONE target whose source starts with /dev/<parent> 817 + char target[128] = ""; 818 + char source[128] = ""; 819 + FILE *mp = fopen("/proc/mounts", "r"); 820 + if (!mp) break; 821 + char line[512]; 822 + while (fgets(line, sizeof(line), mp)) { 823 + char src[128], tgt[128], fst[64]; 824 + if (sscanf(line, "%127s %127s %63s", src, tgt, fst) != 3) continue; 825 + if (strncmp(src, "/dev/", 5) != 0) continue; 826 + if (strncmp(src + 5, parent_blk, strlen(parent_blk)) != 0) continue; 827 + strncpy(target, tgt, sizeof(target) - 1); 828 + target[sizeof(target) - 1] = 0; 829 + strncpy(source, src, sizeof(source) - 1); 830 + source[sizeof(source) - 1] = 0; 831 + break; 832 + } 833 + fclose(mp); 834 + if (!target[0]) { 835 + if (dl) fprintf(dl, "iter=%d: no matching mounts remain — done\n", iter); 836 + break; 837 + } 838 + // Umount by path (MNT_DETACH pops the topmost mount, which may not be 839 + // the one we scanned — that's ok, we loop and re-scan). 840 + int r = umount2(target, MNT_DETACH); 841 + if (dl) fprintf(dl, "iter=%d: umount2(%s, MNT_DETACH) src=%s = %d errno=%d\n", 842 + iter, target, source, r, r < 0 ? errno : 0); 843 + ac_log("[install] iter=%d umount2(%s) src=%s rc=%d errno=%d\n", 844 + iter, target, source, r, r < 0 ? errno : 0); 845 + if (r == 0) { 846 + unmounted++; 847 + } else { 848 + // Try without MNT_DETACH as a fallback (plain umount). 849 + r = umount2(target, 0); 850 + if (dl) fprintf(dl, "iter=%d: umount2(%s, 0) fallback = %d errno=%d\n", 851 + iter, target, r, r < 0 ? errno : 0); 852 + if (r != 0) break; 853 + unmounted++; 854 + } 855 + // Tiny yield so the kernel can finish detaching before we re-scan. 856 + usleep(50000); 811 857 } 812 - fclose(mp); 813 - FILE *dl = dlog_path ? fopen(dlog_path, "a") : NULL; 814 - if (dl) fprintf(dl, "--- force_unmount_disk(%s) found %d targets ---\n", parent_blk, n_targets); 815 - for (int i = 0; i < n_targets; i++) { 816 - // Try MNT_DETACH first (lazy — always works if target is valid). 817 - int r = umount2(targets[i], MNT_DETACH); 818 - if (dl) fprintf(dl, "umount2(%s, MNT_DETACH) = %d errno=%d\n", 819 - targets[i], r, r < 0 ? errno : 0); 820 - ac_log("[install] umount2(%s, MNT_DETACH) = %d errno=%d\n", 821 - targets[i], r, r < 0 ? errno : 0); 822 - if (r == 0) unmounted++; 858 + 859 + if (dl) { 860 + fprintf(dl, "--- force_unmount_disk(%s) total=%d ---\n", parent_blk, unmounted); 861 + // Log post-unmount /proc/mounts so we can verify the stack is clean. 862 + FILE *mp = fopen("/proc/mounts", "r"); 863 + if (mp) { 864 + fprintf(dl, "--- post /proc/mounts ---\n"); 865 + char line[512]; 866 + while (fgets(line, sizeof(line), mp)) fputs(line, dl); 867 + fprintf(dl, "--- end /proc/mounts ---\n"); 868 + fclose(mp); 869 + } 870 + fclose(dl); 823 871 } 824 - if (dl) fclose(dl); 825 872 return unmounted; 826 873 } 827 874 ··· 1098 1145 // explicitly, then retry BLKRRPART with backoff. 1099 1146 umount("/tmp/hd"); 1100 1147 umount2("/tmp/hd", MNT_DETACH); 1101 - const char *DLOG = "/mnt/install-debug.log"; 1148 + // Write install-debug.log to TMPFS so it survives even 1149 + // if the unmount loop pops the whole /mnt stack. We copy 1150 + // it back to /mnt/install-debug.log at the end so the 1151 + // USB has a post-mortem trace. 1152 + const char *DLOG = "/tmp/install-debug.log"; 1102 1153 // Fresh log per attempt 1103 1154 FILE *__dl = fopen(DLOG, "w"); 1104 - if (__dl) { fprintf(__dl, "=== install-debug starting ===\n"); fclose(__dl); } 1155 + if (__dl) { 1156 + fprintf(__dl, "=== install-debug starting ===\n"); 1157 + fprintf(__dl, "parent_blk=%s devpath=%s\n", parent_blk, devpath); 1158 + fclose(__dl); 1159 + } 1105 1160 // Unmount every mount currently referencing this disk. 1106 1161 int n_unmounted = force_unmount_disk(parent_blk, DLOG); 1107 1162 ac_log("[install] force_unmount_disk(%s) released %d mounts\n", ··· 1325 1380 } 1326 1381 } 1327 1382 if (source_mounted_tmp) umount("/tmp/src"); 1383 + 1384 + // Copy the tmpfs install-debug.log back to /mnt so it survives the boot 1385 + // session (tmpfs is wiped on reboot). Best effort — if /mnt got trashed 1386 + // by the repartition attempt we simply skip. The log is most useful on 1387 + // failure but we copy on success too so "what worked" is visible. 1388 + { 1389 + FILE *src = fopen("/tmp/install-debug.log", "rb"); 1390 + if (src) { 1391 + FILE *dst = fopen("/mnt/install-debug.log", "wb"); 1392 + if (dst) { 1393 + char buf[4096]; size_t n; 1394 + while ((n = fread(buf, 1, sizeof(buf), src)) > 0) 1395 + fwrite(buf, 1, n, dst); 1396 + fflush(dst); 1397 + fsync(fileno(dst)); 1398 + fclose(dst); 1399 + ac_log("[install] copied /tmp/install-debug.log → /mnt/install-debug.log\n"); 1400 + } 1401 + fclose(src); 1402 + } 1403 + } 1404 + 1328 1405 return installed; 1329 1406 } 1330 1407