aboutsummaryrefslogtreecommitdiff
path: root/fs/lfs
diff options
context:
space:
mode:
authorEnji Cooper <ngie@FreeBSD.org>2026-02-15 01:57:42 +0000
committerEnji Cooper <ngie@FreeBSD.org>2026-02-15 02:12:44 +0000
commite8dbf2b6df199526a660f81de07d17925cfd8518 (patch)
treecd0c09449bea5df56ef67059e797737d70587070 /fs/lfs
parent56a7ce8416d181a2060d7a428aed9c3c6a431e6d (diff)
Diffstat (limited to 'fs/lfs')
-rw-r--r--fs/lfs/t_basic.c79
-rw-r--r--fs/lfs/t_fcntl.c407
-rw-r--r--fs/lfs/t_orphan.c203
-rw-r--r--fs/lfs/t_resize.c180
-rw-r--r--fs/lfs/util.c181
-rw-r--r--fs/lfs/util.h28
6 files changed, 1078 insertions, 0 deletions
diff --git a/fs/lfs/t_basic.c b/fs/lfs/t_basic.c
new file mode 100644
index 000000000000..3a01a5a43569
--- /dev/null
+++ b/fs/lfs/t_basic.c
@@ -0,0 +1,79 @@
+/* $NetBSD: t_basic.c,v 1.1 2025/10/13 00:44:35 perseant Exp $ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <rump/rump.h>
+#include <rump/rump_syscalls.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/lfs/lfs.h>
+#include <ufs/lfs/lfs_extern.h>
+
+#include "h_macros.h"
+#include "util.h"
+
+#define FSSIZE 10000
+
+/* Actually run the test */
+void test(int);
+
+ATF_TC(newfs_fsck32);
+ATF_TC_HEAD(newfs_fsck32, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS32 newfs_lfs produces a filesystem that passes fsck_lfs");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC(newfs_fsck64);
+ATF_TC_HEAD(newfs_fsck64, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS64 newfs_lfs produces a filesystem that passes fsck_lfs");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC_BODY(newfs_fsck32, tc)
+{
+ test(32);
+}
+
+ATF_TC_BODY(newfs_fsck64, tc)
+{
+ test(64);
+}
+
+void test(int width)
+{
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ /*
+ * Initialize.
+ */
+
+ /* Create image file larger than filesystem */
+ create_lfs(FSSIZE, FSSIZE, width, 1);
+
+ if (fsck())
+ atf_tc_fail_errno("fsck found errors after first unmount");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, newfs_fsck32);
+ ATF_TP_ADD_TC(tp, newfs_fsck64);
+ return atf_no_error();
+}
diff --git a/fs/lfs/t_fcntl.c b/fs/lfs/t_fcntl.c
new file mode 100644
index 000000000000..0d7c1f86d79f
--- /dev/null
+++ b/fs/lfs/t_fcntl.c
@@ -0,0 +1,407 @@
+/* $NetBSD: t_fcntl.c,v 1.6 2025/12/10 03:08:52 perseant Exp $ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <rump/rump.h>
+#include <rump/rump_syscalls.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/lfs/lfs.h>
+#include <ufs/lfs/lfs_extern.h>
+
+#include "h_macros.h"
+#include "util.h"
+
+/* Debugging conditions */
+/* #define FORCE_SUCCESS */ /* Don't actually revert, everything worked */
+/* #define USE_DUMPLFS */ /* Dump the filesystem at certain steps */
+
+#define UNCHANGED_CONTROL MP "/3-a-random-file"
+#define FSSIZE 10000 /* In sectors */
+#define A_FEW_BLOCKS 6500 /* In bytes; a few blocks worth */
+#define MORE_THAN_A_SEGMENT (4 * SEGSIZE) /* In bytes; > SEGSIZE */
+
+/* Set up filesystem with a file in it */
+int setup(int, struct ufs_args *, off_t);
+
+/* Actually run the test */
+void coalesce(int);
+void cleanseg(int);
+void autoclean(int);
+
+/* Unmount and check fsck */
+void teardown(int, struct ufs_args *, off_t);
+
+ATF_TC(coalesce32);
+ATF_TC_HEAD(coalesce32, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS32 LFCNSCRAMBLE/LFCNREWRITEFILE");
+ /* atf_tc_set_md_var(tc, "timeout", "20"); */
+}
+
+ATF_TC(coalesce64);
+ATF_TC_HEAD(coalesce64, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS64 LFCNSCRAMBLE/LFCNREWRITEFILE");
+ /* atf_tc_set_md_var(tc, "timeout", "20"); */
+}
+
+ATF_TC(cleanseg32);
+ATF_TC_HEAD(cleanseg32, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS32 LFCNREWRITESEG");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC(cleanseg64);
+ATF_TC_HEAD(cleanseg64, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS64 LFCNREWRITESEG");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC(autoclean32);
+ATF_TC_HEAD(autoclean32, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS32 LFCNAUTOCLEAN");
+ /* atf_tc_set_md_var(tc, "timeout", "20"); */
+}
+
+ATF_TC(autoclean64);
+ATF_TC_HEAD(autoclean64, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS64 LFCNAUTOCLEAN");
+ /* atf_tc_set_md_var(tc, "timeout", "20"); */
+}
+
+ATF_TC_BODY(coalesce32, tc)
+{
+ coalesce(32);
+}
+
+ATF_TC_BODY(coalesce64, tc)
+{
+ coalesce(64);
+}
+
+ATF_TC_BODY(cleanseg32, tc)
+{
+ cleanseg(32);
+}
+
+ATF_TC_BODY(cleanseg64, tc)
+{
+ cleanseg(64);
+}
+
+ATF_TC_BODY(autoclean32, tc)
+{
+ autoclean(32);
+}
+
+ATF_TC_BODY(autoclean64, tc)
+{
+ autoclean(64);
+}
+
+int setup(int width, struct ufs_args *argsp, off_t filesize)
+{
+ int fd;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ /*
+ * Initialize.
+ */
+
+ /* Create image file */
+ create_lfs(FSSIZE, FSSIZE, width, 1);
+
+ /* Mount filesystem */
+ fprintf(stderr, "* Mount fs [1]\n");
+ memset(argsp, 0, sizeof *argsp);
+ argsp->fspec = __UNCONST(FAKEBLK);
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, argsp, sizeof *argsp) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed");
+
+ /* Payload */
+ fprintf(stderr, "* Initial payload\n");
+ write_file(UNCHANGED_CONTROL, filesize, 1, 3);
+
+ /* Make the data go to disk */
+ fprintf(stderr, "* Double sync\n");
+ rump_sys_sync();
+ rump_sys_sync();
+
+ /* Get a handle to the root of the file system */
+ fd = rump_sys_open(MP, O_RDONLY);
+ if (fd < 0)
+ atf_tc_fail_errno("rump_sys_open mount point root failed");
+
+ return fd;
+}
+
+void teardown(int fd, struct ufs_args *argsp, off_t filesize)
+{
+ /* Close descriptor */
+ rump_sys_close(fd);
+
+ /* Unmount */
+ if (rump_sys_unmount(MP, 0) < 0)
+ atf_tc_fail_errno("rump_sys_unmount failed[1]");
+
+ /* Fsck */
+ fprintf(stderr, "* Fsck after final unmount\n");
+ if (fsck())
+ atf_tc_fail("fsck found errors after final unmount");
+
+ /*
+ * Check file system contents
+ */
+
+ /* Mount filesystem one last time */
+ fprintf(stderr, "* Mount fs again to check contents\n");
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, argsp, sizeof *argsp) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed [4]");
+
+ if (check_file(UNCHANGED_CONTROL, filesize, 3) != 0)
+ atf_tc_fail("Unchanged control file differs(!)");
+
+ /* Umount filesystem */
+ if (rump_sys_unmount(MP, 0) < 0)
+ atf_tc_fail_errno("rump_sys_unmount failed[2]");
+
+ /* Final fsck to double check */
+ fprintf(stderr, "* Fsck after final unmount\n");
+ if (fsck())
+ atf_tc_fail("fsck found errors after final unmount");
+}
+
+void coalesce(int width)
+{
+ struct ufs_args args;
+ struct stat statbuf;
+ struct lfs_filestat_req fsr;
+ struct lfs_filestats fss_before, fss_scrambled, fss_after;
+ struct lfs_inode_array inotbl;
+ int fd;
+ ino_t ino;
+
+ fd = setup(width, &args, A_FEW_BLOCKS);
+
+ /* Get our file's inode number */
+ if (rump_sys_stat(UNCHANGED_CONTROL, &statbuf) != 0)
+ atf_tc_fail_errno("rump_sys_stat failed");
+ ino = statbuf.st_ino;
+
+ fprintf(stderr, "Treating inode %d\n", (int)ino);
+
+ /* Retrieve fragmentation statistics */
+ memset(&fss_before, 0, sizeof fss_before);
+ memset(&fss_scrambled, 0, sizeof fss_scrambled);
+ memset(&fss_after, 0, sizeof fss_after);
+
+ fsr.ino = ino;
+ fsr.len = 1;
+ fsr.fss = &fss_before;
+ if (rump_sys_fcntl(fd, LFCNFILESTATS, &fsr) < 0)
+ atf_tc_fail_errno("LFCNFILESTATS[1] failed");
+
+ inotbl.len = 1;
+ inotbl.inodes = &ino;
+
+ fprintf(stderr, "Start ino %d nblk %d count %d total %d\n",
+ (int)ino, (int)fss_before.nblk, (int)fss_before.dc_count,
+ (int)fss_before.dc_sum);
+
+ /* Scramble */
+ if (rump_sys_fcntl(fd, LFCNSCRAMBLE, &inotbl) < 0)
+ atf_tc_fail_errno("LFCNSCRAMBLE failed");
+ fsr.fss = &fss_scrambled;
+ if (rump_sys_fcntl(fd, LFCNFILESTATS, &fsr) < 0)
+ atf_tc_fail_errno("LFCNFILESTATS[2] failed");
+ if (fss_scrambled.dc_count <= fss_before.dc_count)
+ atf_tc_fail("Scramble did not worsen gap count");
+ if (fss_scrambled.dc_sum <= fss_before.dc_sum)
+ atf_tc_fail("Scramble did not worsen total gap length");
+
+ fprintf(stderr, "Scrambled ino %d nblk %d count %d total %d\n",
+ (int)ino, (int)fss_scrambled.nblk, (int)fss_scrambled.dc_count,
+ (int)fss_scrambled.dc_sum);
+
+ /* Coalesce */
+ if (rump_sys_fcntl(fd, LFCNREWRITEFILE, &inotbl) < 0)
+ atf_tc_fail_errno("LFCNREWRITEFILE failed");
+ fsr.fss = &fss_after;
+ if (rump_sys_fcntl(fd, LFCNFILESTATS, &fsr) < 0)
+ atf_tc_fail_errno("LFCNFILESTATS[3] failed");
+ if (fss_scrambled.dc_count <= fss_after.dc_count)
+ atf_tc_fail("Rewrite did not improve gap count");
+ if (fss_scrambled.dc_sum <= fss_after.dc_sum)
+ atf_tc_fail("Rewrite did not improve total gap length");
+
+ fprintf(stderr, "Coalesced ino %d nblk %d count %d total %d\n",
+ (int)ino, (int)fss_after.nblk, (int)fss_after.dc_count,
+ (int)fss_after.dc_sum);
+
+ teardown(fd, &args, A_FEW_BLOCKS);
+}
+
+void cleanseg(int width)
+{
+ struct ufs_args args;
+ struct lfs_segnum_array sna;
+ struct lfs_seguse_array sua;
+ int fd, sn;
+
+ fd = setup(width, &args, MORE_THAN_A_SEGMENT);
+
+ fprintf(stderr, "* Get seguse\n");
+ sua.len = LFS_SEGUSE_MAXCNT;
+ sua.start = 0;
+ sua.seguse = malloc(LFS_SEGUSE_MAXCNT * sizeof(*sua.seguse));
+ if (rump_sys_fcntl(fd, LFCNSEGUSE, &sua) < 0)
+ atf_tc_fail_errno("LFCNSEGUSE[1] failed");
+
+ for (sn = 0; sn < (int)sua.len; ++sn) {
+ if (sua.seguse[sn].su_nbytes == 0)
+ continue;
+ if ((sua.seguse[sn].su_flags & (SEGUSE_DIRTY | SEGUSE_ACTIVE))
+ != SEGUSE_DIRTY)
+ continue;
+ break;
+ }
+ if (sn == (int)sua.len)
+ atf_tc_fail("No segments found to clean");
+
+ fprintf(stderr, "* Cleaning segment #%d\n", sn);
+
+ memset(&sna, 0, sizeof sna);
+ sna.len = 1;
+ sna.segments = &sn;
+ if (rump_sys_fcntl(fd, LFCNREWRITESEGS, &sna) < 0)
+ atf_tc_fail_errno("LFCNREWRITESEGS failed");
+
+ fprintf(stderr, "* Double sync\n");
+ rump_sys_sync();
+ rump_sys_sync();
+
+ fprintf(stderr, "* Get seguse again\n");
+ sua.len = 1;
+ sua.start = sn;
+ if (rump_sys_fcntl(fd, LFCNSEGUSE, &sua) < 0)
+ atf_tc_fail_errno("LFCNSEGUSE[2] failed");
+
+ if (sua.seguse[0].su_nbytes > 0)
+ atf_tc_fail("Empty bytes after clean");
+
+ fprintf(stderr, "* Teardown\n");
+ teardown(fd, &args, MORE_THAN_A_SEGMENT);
+}
+
+void autoclean(int width)
+{
+ struct ufs_args args;
+ struct lfs_autoclean_params autoclean;
+ int i, fd;
+ char filename[1024];
+ struct statvfs statbuf;
+ CLEANERINFO64 ci_before, ci_after;
+ unsigned long target;
+
+ fd = setup(width, &args, CHUNKSIZE);
+
+ rump_sys_fstatvfs1(fd, &statbuf, ST_WAIT);
+ target = 7 * statbuf.f_blocks / 8;
+
+ /* Write a number of files, deleting every other one. */
+ fprintf(stderr, "* Write numbered files\n");
+ for (i = 4; ; i++) {
+ fprintf(stderr, "* create %d\n", i);
+ sprintf(filename, "%s/%d", MP, i);
+ write_file(filename, SEGSIZE / 3, 1, 0);
+ rump_sys_fstatvfs1(fd, &statbuf, ST_WAIT);
+ if (statbuf.f_bfree < target)
+ break;
+ fprintf(stderr, " %ld blocks vs target %ld\n",
+ (long)statbuf.f_bfree, (long)target);
+ }
+ rump_sys_sync();
+ rump_sys_sync();
+
+ /* Record cleanerinfo */
+ fprintf(stderr, "* Get cleanerinfo\n");
+ if (rump_sys_fcntl(fd, LFCNCLEANERINFO, &ci_before) < 0)
+ atf_tc_fail_errno("LFCNCLEANERINFO[1] failed");
+
+ /* Start autocleaner */
+ autoclean.size = sizeof(autoclean);
+ autoclean.mode = LFS_CLEANMODE_GREEDY;
+ autoclean.thresh = -1;
+ autoclean.target = -1;
+ if (rump_sys_fcntl(fd, LFCNAUTOCLEAN, &autoclean) < 0)
+ atf_tc_fail_errno("LFCNAUTOCLEAN[1] failed");
+
+ /* Make many segments almost empty */
+ fprintf(stderr, "* Delete most files\n");
+ for (; i > 0; i--) {
+ if (i % 3 == 0)
+ continue;
+ fprintf(stderr, "* delete %d\n", i);
+ sprintf(filename, "%s/%d", MP, i);
+ rump_sys_unlink(filename);
+ }
+ rump_sys_sync();
+ rump_sys_sync();
+
+ /* Give the cleaner a chance to run */
+ fprintf(stderr, "* Sleep\n");
+ sleep(5);
+
+ /* Auto reclaim freed segments */
+ fprintf(stderr, "* Sync\n");
+ rump_sys_sync();
+ rump_sys_sync();
+
+ /* Record cleanerinfo again */
+ fprintf(stderr, "* Get cleanerinfo again\n");
+ if (rump_sys_fcntl(fd, LFCNCLEANERINFO, &ci_after) < 0)
+ atf_tc_fail_errno("LFCNCLEANERINFO[2] failed");
+
+ /* Compare */
+ if (ci_before.avail >= ci_after.avail)
+ atf_tc_fail("No improvement");
+
+ teardown(fd, &args, CHUNKSIZE);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, coalesce32);
+ ATF_TP_ADD_TC(tp, coalesce64);
+ ATF_TP_ADD_TC(tp, cleanseg32);
+ ATF_TP_ADD_TC(tp, cleanseg64);
+ ATF_TP_ADD_TC(tp, autoclean32);
+ ATF_TP_ADD_TC(tp, autoclean64);
+ return atf_no_error();
+}
diff --git a/fs/lfs/t_orphan.c b/fs/lfs/t_orphan.c
new file mode 100644
index 000000000000..c1cf7f1f71b8
--- /dev/null
+++ b/fs/lfs/t_orphan.c
@@ -0,0 +1,203 @@
+/* $NetBSD: t_orphan.c,v 1.4 2025/12/19 20:58:08 perseant Exp $ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <rump/rump.h>
+#include <rump/rump_syscalls.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/lfs/lfs.h>
+#include <ufs/lfs/lfs_extern.h>
+
+#include "h_macros.h"
+#include "util.h"
+
+/* Debugging conditions */
+/* #define FORCE_SUCCESS */ /* Don't actually revert, everything worked */
+/* #define USE_DUMPLFS */ /* Dump the filesystem at certain steps */
+
+#define UNCHANGED_CONTROL MP "/3-a-random-file"
+#define FSSIZE 10000 /* In sectors */
+#define FILE_SIZE 65000 /* In bytes; a few blocks worth */
+#define TMPFILE "filehandle"
+
+/* Actually run the test */
+void orphan(int);
+
+ATF_TC(orphan32);
+ATF_TC_HEAD(orphan32, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS32 orphan removal");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC(orphan64);
+ATF_TC_HEAD(orphan64, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS64 orphan removal");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC_BODY(orphan32, tc)
+{
+ orphan(32);
+}
+
+ATF_TC_BODY(orphan64, tc)
+{
+ orphan(64);
+}
+
+void orphan(int width)
+{
+ char s[MAXLINE];
+ struct ufs_args args;
+ struct stat statbuf;
+ int fd, status;
+ pid_t childpid;
+ FILE *fp;
+ int thisinum, version, found;
+ ino_t inum;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ /*
+ * Initialize.
+ */
+
+ /* Create image file */
+ create_lfs(FSSIZE, FSSIZE, width, 0);
+
+ /* Prepare to mount */
+ memset(&args, 0, sizeof args);
+ args.fspec = __UNCONST(FAKEBLK);
+
+ if ((childpid = fork()) == 0) {
+ /* Set up rump */
+ rump_init();
+ if (rump_sys_mkdir(MP, 0777) == -1)
+ atf_tc_fail_errno("cannot create mountpoint");
+ rump_pub_etfs_register(FAKEBLK, IMGNAME, RUMP_ETFS_BLK);
+
+ /* Mount filesystem */
+ fprintf(stderr, "* Mount fs [1]\n");
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, &args, sizeof args) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed");
+
+ /* Payload */
+ fprintf(stderr, "* Initial payload\n");
+ fd = write_file(UNCHANGED_CONTROL, FILE_SIZE, 0, 0);
+
+ /* Make the data go to disk */
+ rump_sys_sync();
+ rump_sys_sync();
+
+ /* Write the inode number into a temporary file */
+ rump_sys_fstat(fd, &statbuf);
+ fprintf(stderr, "Inode is => %d <=\n", (int)statbuf.st_ino);
+
+ fp = fopen(TMPFILE, "wb");
+ fwrite(&statbuf.st_ino, sizeof(ino_t), 1, fp);
+ fclose(fp);
+
+ /* Delete while still open */
+ if (rump_sys_unlink(UNCHANGED_CONTROL) != 0)
+ atf_tc_fail_errno("rump_sys_unlink failed");
+
+ /* Sanity check values */
+ if (statbuf.st_size != FILE_SIZE)
+ atf_tc_fail("wrong size in initial stat");
+ if (statbuf.st_nlink <= 0)
+ atf_tc_fail("file already deleted");
+ if (statbuf.st_blocks <= 0)
+ atf_tc_fail("no blocks");
+
+ /* Make the data go to disk */
+ rump_sys_sync();
+ rump_sys_sync();
+
+ /* Simulate a system crash */
+ exit(0);
+ }
+
+ /* Wait for child to terminate */
+ waitpid(childpid, &status, 0);
+
+ /* If it died, die ourselves */
+ if (WEXITSTATUS(status))
+ exit(WEXITSTATUS(status));
+
+ /* Fsck */
+ fprintf(stderr, "* Fsck after crash\n");
+ if (fsck())
+ atf_tc_fail("fsck found errors after crash");
+
+ /* Read the inode number from temporary file */
+ fp = fopen(TMPFILE, "rb");
+ fread(&inum, sizeof(ino_t), 1, fp);
+ fclose(fp);
+
+ fprintf(stderr, "Seeking inum => %d <=\n", (int)inum);
+
+ /* Set up rump */
+ rump_init();
+ if (rump_sys_mkdir(MP, 0777) == -1)
+ atf_tc_fail_errno("cannot create mountpoint");
+ rump_pub_etfs_register(FAKEBLK, IMGNAME, RUMP_ETFS_BLK);
+
+ /* Remount */
+ fprintf(stderr, "* Mount fs [2]\n");
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, &args, sizeof args) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed[2]");
+
+ /* At this point the orphan should be deleted. */
+
+ /* Unmount */
+ fprintf(stderr, "* Unmount\n");
+ if (rump_sys_unmount(MP, 0) != 0)
+ atf_tc_fail_errno("rump_sys_unmount failed[1]");
+
+ /* Check that it was in fact deleted. */
+ fprintf(stderr, "* Check for orphaned file\n");
+ found = 0;
+ fp = popen("dumplfs -i -s9 ./" IMGNAME, "r");
+ while (fgets(s, MAXLINE, fp) != NULL) {
+ if (sscanf(s, "%d FREE %d", &thisinum, &version) == 2
+ && (ino_t)thisinum == inum) {
+ found = 1;
+ break;
+ }
+ }
+ fclose(fp);
+ if (!found)
+ atf_tc_fail("orphaned inode not freed on subsequent mount");
+
+ /* Fsck */
+ fprintf(stderr, "* Fsck after final unmount\n");
+ if (fsck())
+ atf_tc_fail("fsck found errors after final unmount");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, orphan32);
+ ATF_TP_ADD_TC(tp, orphan64);
+ return atf_no_error();
+}
+
diff --git a/fs/lfs/t_resize.c b/fs/lfs/t_resize.c
new file mode 100644
index 000000000000..0dae56429261
--- /dev/null
+++ b/fs/lfs/t_resize.c
@@ -0,0 +1,180 @@
+/* $NetBSD: t_resize.c,v 1.2 2025/10/30 15:30:17 perseant Exp $ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <rump/rump.h>
+#include <rump/rump_syscalls.h>
+
+#include <ufs/ufs/ufsmount.h>
+#include <ufs/lfs/lfs.h>
+#include <ufs/lfs/lfs_extern.h>
+
+#include "h_macros.h"
+#include "util.h"
+
+/* Debugging conditions */
+/* #define FORCE_SUCCESS */ /* Don't actually revert, everything worked */
+/* #define USE_DUMPLFS */ /* Dump the filesystem at certain steps */
+
+#define UNCHANGED_CONTROL MP "/3-a-random-file"
+#define BIGSIZE 15000
+#define SMALLSIZE 10000
+__CTASSERT(BIGSIZE > SMALLSIZE);
+
+/* Resize filesystem */
+void resize(int, size_t);
+
+/* Actually run the test */
+void test(int);
+
+ATF_TC(resize32);
+ATF_TC_HEAD(resize32, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS32 resize_lfs creates an inconsistent filesystem");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC(resize64);
+ATF_TC_HEAD(resize64, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "LFS64 resize_lfs creates an inconsistent filesystem");
+ atf_tc_set_md_var(tc, "timeout", "20");
+}
+
+ATF_TC_BODY(resize32, tc)
+{
+ test(32);
+}
+
+ATF_TC_BODY(resize64, tc)
+{
+ test(64);
+}
+
+void test(int width)
+{
+ struct ufs_args args;
+ int fd;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ /*
+ * Initialize.
+ */
+
+ /* Create image file larger than filesystem */
+ create_lfs(BIGSIZE, SMALLSIZE, width, 1);
+
+ /* Mount filesystem */
+ fprintf(stderr, "* Mount fs [1]\n");
+ memset(&args, 0, sizeof(args));
+ args.fspec = __UNCONST(FAKEBLK);
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, &args, sizeof(args)) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed");
+
+ /* Payload */
+ fprintf(stderr, "* Initial payload\n");
+ write_file(UNCHANGED_CONTROL, CHUNKSIZE, 1, 0);
+
+ /* Unmount */
+ rump_sys_unmount(MP, 0);
+ if (fsck())
+ atf_tc_fail_errno("fsck found errors after first unmount");
+
+ /*
+ * Remount and resize.
+ */
+
+ /* Reconfigure and mount filesystem again */
+ fprintf(stderr, "* Remount fs [2, to enlarge]\n");
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, &args, sizeof(args)) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed [2]");
+
+ /* Get a handle to the root of the file system */
+ fd = rump_sys_open(MP, O_RDONLY);
+ if (fd < 0)
+ atf_tc_fail_errno("rump_sys_open mount point root failed");
+
+ /* Enlarge filesystem */
+ fprintf(stderr, "* Resize (enlarge)\n");
+ resize(fd, BIGSIZE);
+
+ /* Unmount fs and check */
+ rump_sys_close(fd);
+ rump_sys_unmount(MP, 0);
+ if (fsck())
+ atf_tc_fail_errno("fsck found errors after enlarge");
+
+ /* Mount filesystem for shrink */
+ fprintf(stderr, "* Mount fs [3, to shrink]\n");
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, &args, sizeof(args)) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed [3]");
+
+ /* Get a handle to the root of the file system */
+ fd = rump_sys_open(MP, O_RDONLY);
+ if (fd < 0)
+ atf_tc_fail_errno("rump_sys_open mount point root failed");
+
+ /* Shrink filesystem */
+ fprintf(stderr, "* Resize (shrink)\n");
+ resize(fd, SMALLSIZE);
+
+ /* Unmount and check again */
+ rump_sys_close(fd);
+ if (rump_sys_unmount(MP, 0) != 0)
+ atf_tc_fail_errno("rump_sys_umount failed after shrink");
+ fprintf(stderr, "* Fsck after shrink\n");
+ if (fsck())
+ atf_tc_fail("fsck found errors after shrink");
+
+ /*
+ * Check file system contents
+ */
+
+ /* Mount filesystem one last time */
+ fprintf(stderr, "* Mount fs [4, to check contents]\n");
+ if (rump_sys_mount(MOUNT_LFS, MP, 0, &args, sizeof(args)) == -1)
+ atf_tc_fail_errno("rump_sys_mount failed [4]");
+
+ if (check_file(UNCHANGED_CONTROL, CHUNKSIZE, 0) != 0)
+ atf_tc_fail("Unchanged control file differs(!)");
+
+ /* Umount filesystem */
+ rump_sys_unmount(MP, 0);
+
+ /* Final fsck to double check */
+ fprintf(stderr, "* Fsck after final unmount\n");
+ if (fsck())
+ atf_tc_fail("fsck found errors after final unmount");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, resize32);
+ ATF_TP_ADD_TC(tp, resize64);
+ return atf_no_error();
+}
+
+void
+resize(int fd, size_t size)
+{
+ int newnseg = (size * DEV_BSIZE) / SEGSIZE;
+
+ if (rump_sys_fcntl(fd, LFCNRESIZE, &newnseg) != 0)
+ atf_tc_fail_errno("LFCNRESIZE failed");
+}
diff --git a/fs/lfs/util.c b/fs/lfs/util.c
new file mode 100644
index 000000000000..75ca59983596
--- /dev/null
+++ b/fs/lfs/util.c
@@ -0,0 +1,181 @@
+/* $NetBSD: util.c,v 1.6 2025/12/19 20:58:08 perseant Exp $ */
+
+#include <sys/mount.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+
+#include <rump/rump.h>
+#include <rump/rump_syscalls.h>
+
+#include "h_macros.h"
+#include "util.h"
+
+long long sbaddr[2] = { -1, -1 };
+
+/* Create filesystem, note superblock locations */
+void create_lfs(size_t imgsize, size_t fssize, int width, int do_setup)
+{
+ FILE *pipe;
+ char cmd[MAXLINE];
+ char buf[MAXLINE];
+
+ /* Create image file larger than filesystem */
+ sprintf(cmd, "dd if=/dev/zero of=%s bs=512 count=%zd",
+ IMGNAME, imgsize);
+ if (system(cmd) == -1)
+ atf_tc_fail_errno("create image failed");
+
+ /* Create filesystem */
+ fprintf(stderr, "* Create file system\n");
+ sprintf(cmd, "newfs_lfs -D -F -B %d -s %zd -w%d ./%s > %s",
+ SEGSIZE, fssize, width, IMGNAME, LOGFILE);
+ if (system(cmd) == -1)
+ atf_tc_fail_errno("newfs failed");
+ pipe = fopen(LOGFILE, "r");
+ if (pipe == NULL)
+ atf_tc_fail_errno("newfs failed to execute");
+ while (fgets(buf, MAXLINE, pipe) != NULL) {
+ if (sscanf(buf, "%lld,%lld", sbaddr, sbaddr + 1) == 2)
+ break;
+ }
+ while (fgets(buf, MAXLINE, pipe) != NULL)
+ ;
+ fclose(pipe);
+ if (sbaddr[0] < 0 || sbaddr[1] < 0)
+ atf_tc_fail("superblock not found");
+ fprintf(stderr, "* Superblocks at %lld and %lld\n",
+ sbaddr[0], sbaddr[1]);
+
+ if (do_setup) {
+ /* Set up rump */
+ rump_init();
+ if (rump_sys_mkdir(MP, 0777) == -1)
+ atf_tc_fail_errno("cannot create mountpoint");
+ rump_pub_etfs_register(FAKEBLK, IMGNAME, RUMP_ETFS_BLK);
+ }
+}
+
+/* Write some data into a file */
+int write_file(const char *filename, off_t len, int close, unsigned int seed)
+{
+ int fd;
+ unsigned i, j;
+ struct stat statbuf;
+ int flags = O_CREAT|O_WRONLY;
+ char buf[1024];
+ off_t size;
+
+ srandom(seed);
+ if (rump_sys_stat(filename, &statbuf) < 0)
+ size = 0;
+ else {
+ size = statbuf.st_size;
+ flags |= O_APPEND;
+
+ /* Reset randomness */
+ for (i = 0; i < size; i++)
+ random();
+ }
+
+ fd = rump_sys_open(filename, flags);
+
+ for (i = 0; i < len; i+= sizeof(buf)) {
+ for (j = 0; j < sizeof(buf); j++)
+ buf[j] = ((unsigned)random()) & 0xff;
+ rump_sys_write(fd, buf, MIN(len - i, (off_t)sizeof(buf)));
+ }
+
+ if (close) {
+ rump_sys_close(fd);
+ fd = -1;
+ }
+
+ return fd;
+}
+
+/* Check file's existence, size and contents */
+int check_file(const char *filename, int size, unsigned int seed)
+{
+ int fd, i, j, res;
+ struct stat statbuf;
+ unsigned char b, buf[1024];
+
+ if (rump_sys_stat(filename, &statbuf) < 0) {
+ fprintf(stderr, "%s: stat failed\n", filename);
+ return 1;
+ }
+ if (size != statbuf.st_size) {
+ fprintf(stderr, "%s: expected %d bytes, found %d\n",
+ filename, size, (int)statbuf.st_size);
+ return 2;
+ }
+
+ fd = rump_sys_open(filename, O_RDONLY);
+
+ srandom(seed);
+ for (i = 0; i < size; i += sizeof(buf)) {
+ res = MIN(size - i, (off_t)sizeof(buf));
+ rump_sys_read(fd, buf, res);
+ for (j = 0; j < res; j++) {
+ b = (((unsigned)random()) & 0xff);
+ if (buf[j] != b) {
+ fprintf(stderr, "%s: byte %d:"
+ " expected %hhx found %hhx\n",
+ filename, i + j,
+ b, buf[j]);
+ rump_sys_close(fd);
+ return 3;
+ }
+ }
+ }
+ rump_sys_close(fd);
+ fprintf(stderr, "%s: no problem\n", filename);
+ return 0;
+}
+
+/* Run a file system consistency check */
+int fsck(void)
+{
+ char s[MAXLINE];
+ int i, errors = 0;
+ FILE *pipe;
+ char cmd[MAXLINE];
+
+ for (i = 0; i < 2; i++) {
+ sprintf(cmd, "fsck_lfs -n -a -b %jd -f " IMGNAME,
+ (intmax_t)sbaddr[i]);
+ pipe = popen(cmd, "r");
+ while (fgets(s, MAXLINE, pipe) != NULL) {
+ if (isdigit((int)s[0])) /* "5 files ... " */
+ continue;
+ if (isspace((int)s[0]) || s[0] == '*')
+ continue;
+ if (strncmp(s, "Alternate", 9) == 0)
+ continue;
+ if (strncmp(s, "ROLL ", 5) == 0)
+ continue;
+ fprintf(stderr, "FSCK[sb@%lld]: %s", sbaddr[i], s);
+ ++errors;
+ }
+ pclose(pipe);
+ if (errors) {
+ break;
+ }
+ }
+
+ return errors;
+}
+
+/* Run dumplfs */
+void dumplfs()
+{
+ char s[MAXLINE];
+ FILE *pipe;
+
+ pipe = popen("dumplfs -S -s 2 -s 1 -s 0 " IMGNAME, "r");
+ while (fgets(s, MAXLINE, pipe) != NULL)
+ fprintf(stderr, "DUMPLFS: %s", s);
+ pclose(pipe);
+}
+
diff --git a/fs/lfs/util.h b/fs/lfs/util.h
new file mode 100644
index 000000000000..b33f89d084ab
--- /dev/null
+++ b/fs/lfs/util.h
@@ -0,0 +1,28 @@
+/* $NetBSD: util.h,v 1.4 2025/10/30 15:30:17 perseant Exp $ */
+
+/* Create test image and filesystem, record superblock locations */
+void create_lfs(size_t, size_t, int, int);
+
+/* Write a well-known byte pattern into a file, appending if it exists */
+int write_file(const char *, off_t, int, unsigned int);
+
+/* Check the byte pattern and size of the file */
+int check_file(const char *, int, unsigned int);
+
+/* Check the file system for consistency */
+int fsck(void);
+
+/* Run dumplfs; for debugging */
+void dumplfs(void);
+
+#define MAXLINE 132
+#define CHUNKSIZE 300
+#define SEGSIZE 32768
+
+#define IMGNAME "disk.img"
+#define FAKEBLK "/dev/blk"
+#define LOGFILE "newfs.log"
+
+#define MP "/mp"
+
+extern long long sbaddr[2];