From 0291c7eaf31f5e8a58ab40fca6a9f7af18e6eb67 Mon Sep 17 00:00:00 2001 From: zombocoder Date: Wed, 8 Oct 2025 21:46:36 +0000 Subject: [PATCH 1/7] Refactoring --- bfc_format.c | 37 ++++++++++++++++++++----------------- bfc_io.c | 14 +++++++------- bfcfs.h | 16 +++++++++++++++- bfcfs_vfs.c | 12 ++++++------ bfcfs_vnops.c | 14 +++++++++----- 5 files changed, 57 insertions(+), 36 deletions(-) diff --git a/bfc_format.c b/bfc_format.c index 4d9ff4e..40a1ba0 100644 --- a/bfc_format.c +++ b/bfc_format.c @@ -33,13 +33,13 @@ bfc_read_header(struct bfcfs_mount *bmp, struct thread *td) /* Read header from offset 0 */ error = bfc_pread(bmp, hdr, sizeof(*hdr), 0, td); if (error) { - printf("bfcfs: failed to read header: %d\n", error); + BFCFS_ERR("failed to read header: %d", error); return (error); } /* Validate magic string */ if (strncmp(hdr->magic, "BFCFv1", 6) != 0) { - printf("bfcfs: invalid magic: expected 'BFCFv1', got '%.8s'\n", + BFCFS_ERR("invalid magic: expected 'BFCFv1', got '%.8s'", hdr->magic); return (EINVAL); } @@ -52,9 +52,10 @@ bfc_read_header(struct bfcfs_mount *bmp, struct thread *td) BFCFS_DEBUG("format: header valid, block_size=%u, features=0x%lx", hdr->block_size, hdr->features); - /* Sanity check block size */ - if (hdr->block_size == 0 || hdr->block_size > 1048576) { - printf("bfcfs: invalid block size: %u\n", hdr->block_size); + /* Sanity check block size (must be non-zero and reasonable) */ + if (hdr->block_size == 0 || hdr->block_size > BFC_MAX_BLOCK_SIZE) { + BFCFS_ERR("invalid block size: %u (max %u)", + hdr->block_size, BFC_MAX_BLOCK_SIZE); return (EINVAL); } @@ -77,7 +78,7 @@ bfc_read_footer(struct bfcfs_mount *bmp, struct thread *td) return (error); if (file_size < (off_t)(BFC_HEADER_SIZE + BFC_FOOTER_SIZE)) { - printf("bfcfs: file too small: %ld bytes\n", file_size); + BFCFS_ERR("file too small: %ld bytes", file_size); return (EINVAL); } @@ -85,13 +86,13 @@ bfc_read_footer(struct bfcfs_mount *bmp, struct thread *td) footer_offset = file_size - BFC_FOOTER_SIZE; error = bfc_pread(bmp, footer, sizeof(*footer), footer_offset, td); if (error) { - printf("bfcfs: failed to read footer: %d\n", error); + BFCFS_ERR("failed to read footer: %d", error); return (error); } /* Validate footer magic */ if (strncmp(footer->magic_start, "BFCFIDX", 7) != 0) { - printf("bfcfs: invalid footer magic\n"); + BFCFS_ERR("invalid footer magic"); return (EINVAL); } @@ -107,12 +108,13 @@ bfc_read_footer(struct bfcfs_mount *bmp, struct thread *td) /* Sanity checks */ if (footer->index_offset < BFC_HEADER_SIZE || footer->index_offset >= (uint64_t)file_size) { - printf("bfcfs: invalid index offset: %lu\n", footer->index_offset); + BFCFS_ERR("invalid index offset: %lu", footer->index_offset); return (EINVAL); } - if (footer->index_size == 0 || footer->index_size > 100*1024*1024) { - printf("bfcfs: invalid index size: %lu\n", footer->index_size); + if (footer->index_size == 0 || footer->index_size > BFC_MAX_INDEX_SIZE) { + BFCFS_ERR("invalid index size: %lu (max %u)", + footer->index_size, BFC_MAX_INDEX_SIZE); return (EINVAL); } @@ -232,14 +234,14 @@ bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) error = bfc_pread(bmp, index_buf, bmp->footer.index_size, bmp->footer.index_offset, td); if (error) { - printf("bfcfs: failed to read index: %d\n", error); + BFCFS_ERR("failed to read index: %d", error); free(index_buf, M_BFCFS); return (error); } /* Parse index header (8 bytes: version + count) */ if (bmp->footer.index_size < 8) { - printf("bfcfs: index too small for header\n"); + BFCFS_ERR("index too small for header"); free(index_buf, M_BFCFS); return (EINVAL); } @@ -251,14 +253,15 @@ bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) count = LE32(count); if (version != 1) { - printf("bfcfs: unsupported index version: %u\n", version); + BFCFS_ERR("unsupported index version: %u", version); free(index_buf, M_BFCFS); return (EINVAL); } bmp->num_entries = count; - if (bmp->num_entries == 0 || bmp->num_entries > 1000000) { - printf("bfcfs: invalid entry count: %u\n", bmp->num_entries); + if (bmp->num_entries == 0 || bmp->num_entries > BFC_MAX_ENTRIES) { + BFCFS_ERR("invalid entry count: %u (max %u)", + bmp->num_entries, BFC_MAX_ENTRIES); free(index_buf, M_BFCFS); return (EINVAL); } @@ -275,7 +278,7 @@ bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) error = bfc_parse_index_entry(index_buf, bmp->footer.index_size, &offset, &bmp->index[i], i + 1); /* inode numbers start at 1 */ if (error) { - printf("bfcfs: failed to parse index entry %d: %d\n", i, error); + BFCFS_ERR("failed to parse index entry %d: %d", i, error); goto fail; } diff --git a/bfc_io.c b/bfc_io.c index 64cee07..92fc026 100644 --- a/bfc_io.c +++ b/bfc_io.c @@ -143,8 +143,8 @@ bfc_read_object(struct bfcfs_mount *bmp, struct bfcfs_index_entry *entry, if (bmp->verify_crc && offset == 0 && len == entry->size) { uint32_t crc = bfc_crc32c(buf, len); if (crc != entry->crc32c) { - printf("bfcfs: CRC mismatch for %s: " - "expected 0x%x, got 0x%x\n", + BFCFS_ERR("CRC mismatch for %s: " + "expected 0x%x, got 0x%x", entry->path, entry->crc32c, crc); return (EIO); } @@ -159,14 +159,14 @@ bfc_read_object(struct bfcfs_mount *bmp, struct bfcfs_index_entry *entry, * TODO: Add support for partial reads with decompression */ if (offset != 0 || len != entry->size) { - printf("bfcfs: partial reads not yet supported for " - "compressed/encrypted files\n"); + BFCFS_ERR("partial reads not yet supported for " + "compressed/encrypted files"); return (EOPNOTSUPP); } /* Handle compressed files */ if (entry->comp == BFC_COMP_ZSTD) { - printf("bfcfs: ZSTD decompression not yet implemented\n"); + BFCFS_ERR("ZSTD decompression not yet implemented"); return (EOPNOTSUPP); /* * TODO: Implement ZSTD decompression @@ -179,7 +179,7 @@ bfc_read_object(struct bfcfs_mount *bmp, struct bfcfs_index_entry *entry, /* Handle encrypted files */ if (entry->enc == BFC_ENC_CHACHA20_POLY1305) { - printf("bfcfs: ChaCha20-Poly1305 decryption not yet implemented\n"); + BFCFS_ERR("ChaCha20-Poly1305 decryption not yet implemented"); return (EOPNOTSUPP); /* * TODO: Implement decryption @@ -191,7 +191,7 @@ bfc_read_object(struct bfcfs_mount *bmp, struct bfcfs_index_entry *entry, } /* Unknown compression/encryption */ - printf("bfcfs: unsupported compression (%u) or encryption (%u)\n", + BFCFS_ERR("unsupported compression (%u) or encryption (%u)", entry->comp, entry->enc); return (EOPNOTSUPP); } diff --git a/bfcfs.h b/bfcfs.h index 08aff56..bdaa1da 100644 --- a/bfcfs.h +++ b/bfcfs.h @@ -31,6 +31,17 @@ #define BFC_FOOTER_SIZE 56 #define BFC_ALIGN 16 +/* Sanity limits for container validation */ +#define BFC_MAX_BLOCK_SIZE (1024 * 1024) /* 1MB max block size */ +#define BFC_MAX_INDEX_SIZE (100 * 1024 * 1024) /* 100MB max index */ +#define BFC_MAX_ENTRIES 1000000 /* 1M max entries */ + +/* Time conversion constants */ +#define NSEC_PER_SEC 1000000000ULL /* Nanoseconds per second */ + +/* Size alignment */ +#define BFC_BLOCK_ROUND 512 /* Round size to 512-byte blocks */ + /* BFC object types */ #define BFC_TYPE_FILE 1 #define BFC_TYPE_DIR 2 @@ -208,7 +219,10 @@ int bfc_get_file_size(struct bfcfs_mount *bmp, off_t *size, struct thread *td); #define VFSTOBFCFS(mp) ((struct bfcfs_mount *)((mp)->mnt_data)) #define VTOBFCFS(vp) ((struct bfcfs_node *)((vp)->v_data)) -/* Debug printf */ +/* Logging macros */ +#define BFCFS_ERR(fmt, ...) printf("bfcfs: " fmt "\n", ##__VA_ARGS__) +#define BFCFS_WARN(fmt, ...) printf("bfcfs: " fmt "\n", ##__VA_ARGS__) + #ifdef DEBUG #define BFCFS_DEBUG(fmt, ...) printf("bfcfs: " fmt "\n", ##__VA_ARGS__) #else diff --git a/bfcfs_vfs.c b/bfcfs_vfs.c index 6cebbf0..016b91d 100644 --- a/bfcfs_vfs.c +++ b/bfcfs_vfs.c @@ -95,7 +95,7 @@ bfcfs_mount(struct mount *mp) /* Try to get the device path from "from" option */ error = vfs_getopt(mp->mnt_optnew, "from", &opt_ptr, &opt_len); if (error || opt_ptr == NULL) { - printf("bfcfs: mount requires device path or -o source=/path/to/file.bfc\n"); + BFCFS_ERR("mount requires device path or -o source=/path/to/file.bfc"); return (EINVAL); } } @@ -113,7 +113,7 @@ bfcfs_mount(struct mount *mp) error = vn_open(&nd, &flags, 0, NULL); if (error) { free(source_path, M_BFCFS); - printf("bfcfs: failed to open %s: error %d\n", source_path, error); + BFCFS_ERR("failed to open %s: error %d", source_path, error); return (error); } NDFREE_PNBUF(&nd); @@ -146,14 +146,14 @@ bfcfs_mount(struct mount *mp) /* Read BFC header and validate */ error = bfc_read_header(bmp, td); if (error) { - printf("bfcfs: invalid BFC header: error %d\n", error); + BFCFS_ERR("invalid BFC header: error %d", error); goto fail; } /* Read and parse the index */ error = bfc_read_index(bmp, td); if (error) { - printf("bfcfs: failed to read index: error %d\n", error); + BFCFS_ERR("failed to read index: error %d", error); goto fail; } @@ -262,14 +262,14 @@ bfcfs_root(struct mount *mp, int flags, struct vnode **vpp) BFCFS_DEBUG("root: creating synthetic root directory"); error = bfcfs_vnode_create(bmp, NULL, vpp); if (error) { - printf("bfcfs: failed to create root vnode: %d\n", error); + BFCFS_ERR("failed to create root vnode: %d", error); return (error); } } else { /* Create/get vnode for explicit root entry */ error = bfcfs_vnode_create(bmp, root_entry, vpp); if (error) { - printf("bfcfs: failed to create root vnode: %d\n", error); + BFCFS_ERR("failed to create root vnode: %d", error); return (error); } } diff --git a/bfcfs_vnops.c b/bfcfs_vnops.c index 1d549fd..e8a12de 100644 --- a/bfcfs_vnops.c +++ b/bfcfs_vnops.c @@ -212,9 +212,9 @@ bfcfs_getattr(struct vop_getattr_args *ap) vap->va_mode = entry->mode & ALLPERMS; vap->va_fileid = entry->ino; vap->va_size = entry->size; - vap->va_bytes = (entry->size + 511) & ~511ULL; - vap->va_atime.tv_sec = entry->mtime_ns / 1000000000ULL; - vap->va_atime.tv_nsec = entry->mtime_ns % 1000000000ULL; + vap->va_bytes = (entry->size + (BFC_BLOCK_ROUND - 1)) & ~(BFC_BLOCK_ROUND - 1); + vap->va_atime.tv_sec = entry->mtime_ns / NSEC_PER_SEC; + vap->va_atime.tv_nsec = entry->mtime_ns % NSEC_PER_SEC; vap->va_mtime = vap->va_atime; vap->va_ctime = vap->va_atime; vap->va_birthtime = vap->va_atime; @@ -454,11 +454,15 @@ bfcfs_strategy(struct vop_strategy_args *ap) static int bfcfs_print(struct vop_print_args *ap) { +#ifdef DEBUG struct vnode *vp = ap->a_vp; struct bfcfs_node *node = VTOBFCFS(vp); - - printf(" vnode type=%d path=%s\n", + BFCFS_DEBUG("vnode type=%d path=%s", vp->v_type, node->entry ? node->entry->path : ""); +#else + (void)ap; +#endif + return (0); } From 578a14e8121ed3e7d909cde938a9f3c810a61884 Mon Sep 17 00:00:00 2001 From: zombocoder Date: Wed, 8 Oct 2025 21:52:37 +0000 Subject: [PATCH 2/7] Refactor constants for clarity and maintainability --- bfc_format.c | 6 +++--- bfc_io.c | 2 +- bfcfs.h | 11 +++++++++++ bfcfs_vfs.c | 2 +- bfcfs_vnops.c | 10 +++++----- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bfc_format.c b/bfc_format.c index 40a1ba0..455ec59 100644 --- a/bfc_format.c +++ b/bfc_format.c @@ -155,7 +155,7 @@ bfc_parse_index_entry(const uint8_t *buf, size_t buflen, size_t *offset, remaining -= 4; /* Sanity check path length */ - if (path_len == 0 || path_len > 4096 || path_len > remaining) + if (path_len == 0 || path_len > BFC_MAX_PATH_LEN || path_len > remaining) return (EINVAL); /* Allocate and copy path */ @@ -165,8 +165,8 @@ bfc_parse_index_entry(const uint8_t *buf, size_t buflen, size_t *offset, p += path_len; remaining -= path_len; - /* Need at least 48 more bytes for metadata */ - if (remaining < 48) + /* Need at least metadata bytes for entry fields */ + if (remaining < BFC_INDEX_ENTRY_METADATA_SIZE) return (EINVAL); /* Parse metadata fields (all little-endian) */ diff --git a/bfc_io.c b/bfc_io.c index 92fc026..35222b9 100644 --- a/bfc_io.c +++ b/bfc_io.c @@ -123,7 +123,7 @@ bfc_read_object(struct bfcfs_mount *bmp, struct bfcfs_index_entry *entry, /* Calculate content offset like Linux and BFC library do */ hdr_name_size = sizeof(struct bfc_obj_header) + le16toh(obj_hdr.name_len); - padding = ((hdr_name_size + 15) & ~15ULL) - hdr_name_size; /* BFC_ALIGN=16 */ + padding = ((hdr_name_size + (BFC_ALIGN - 1)) & ~(BFC_ALIGN - 1)) - hdr_name_size; content_offset = entry->obj_offset + hdr_name_size + padding; BFCFS_DEBUG("read_object: %s obj_off=%lu hdr=%zu name_len=%u hdr_name=%zu pad=%zu content_off=%ld", diff --git a/bfcfs.h b/bfcfs.h index bdaa1da..78d9b0a 100644 --- a/bfcfs.h +++ b/bfcfs.h @@ -35,6 +35,10 @@ #define BFC_MAX_BLOCK_SIZE (1024 * 1024) /* 1MB max block size */ #define BFC_MAX_INDEX_SIZE (100 * 1024 * 1024) /* 100MB max index */ #define BFC_MAX_ENTRIES 1000000 /* 1M max entries */ +#define BFC_MAX_PATH_LEN 4096 /* Maximum path length */ + +/* BFC index entry format constants */ +#define BFC_INDEX_ENTRY_METADATA_SIZE 48 /* Size of fixed metadata fields */ /* Time conversion constants */ #define NSEC_PER_SEC 1000000000ULL /* Nanoseconds per second */ @@ -42,6 +46,13 @@ /* Size alignment */ #define BFC_BLOCK_ROUND 512 /* Round size to 512-byte blocks */ +/* VFS constants */ +#define BFC_DEFAULT_IO_SIZE 65536 /* 64KB optimal I/O size */ +#define BFC_ROOT_MODE 0755 /* Synthetic root directory mode */ +#define BFC_NAME_MAX 255 /* Maximum filename length */ +#define BFC_PATH_MAX 1024 /* Maximum path length for pathconf */ +#define BFC_FILESIZEBITS 64 /* File size bits (64-bit offsets) */ + /* BFC object types */ #define BFC_TYPE_FILE 1 #define BFC_TYPE_DIR 2 diff --git a/bfcfs_vfs.c b/bfcfs_vfs.c index 016b91d..3eb653e 100644 --- a/bfcfs_vfs.c +++ b/bfcfs_vfs.c @@ -294,7 +294,7 @@ bfcfs_statfs(struct mount *mp, struct statfs *sbp) struct bfcfs_mount *bmp = VFSTOBFCFS(mp); sbp->f_bsize = bmp->header.block_size; - sbp->f_iosize = 65536; /* Optimal I/O size */ + sbp->f_iosize = BFC_DEFAULT_IO_SIZE; sbp->f_blocks = 0; /* Unknown (container is append-only) */ sbp->f_bfree = 0; /* Read-only */ sbp->f_bavail = 0; /* Read-only */ diff --git a/bfcfs_vnops.c b/bfcfs_vnops.c index e8a12de..e192550 100644 --- a/bfcfs_vnops.c +++ b/bfcfs_vnops.c @@ -198,7 +198,7 @@ bfcfs_getattr(struct vop_getattr_args *ap) /* Handle synthetic root (NULL entry) */ if (entry == NULL) { - vap->va_mode = S_IFDIR | 0755; + vap->va_mode = S_IFDIR | BFC_ROOT_MODE; vap->va_fileid = 1; vap->va_size = 0; vap->va_bytes = 0; @@ -262,7 +262,7 @@ bfcfs_access(struct vop_access_args *ap) return (EROFS); /* Get mode: synthetic root or from entry */ - mode = (node->entry == NULL) ? 0755 : (node->entry->mode & ALLPERMS); + mode = (node->entry == NULL) ? BFC_ROOT_MODE : (node->entry->mode & ALLPERMS); /* Simple permission check based on mode bits */ return (vaccess(vp->v_type, mode, 0, 0, accmode, ap->a_cred)); @@ -479,16 +479,16 @@ bfcfs_pathconf(struct vop_pathconf_args *ap) *ap->a_retval = 1; break; case 4: /* _PC_NAME_MAX */ - *ap->a_retval = 255; /* NAME_MAX */ + *ap->a_retval = BFC_NAME_MAX; break; case 5: /* _PC_PATH_MAX */ - *ap->a_retval = 1024; /* PATH_MAX */ + *ap->a_retval = BFC_PATH_MAX; break; case 7: /* _PC_NO_TRUNC */ *ap->a_retval = 1; break; case 18: /* _PC_FILESIZEBITS */ - *ap->a_retval = 64; + *ap->a_retval = BFC_FILESIZEBITS; break; default: error = EINVAL; From 70111f72788da3f7dd52903f07817f35f31c7d6e Mon Sep 17 00:00:00 2001 From: zombocoder Date: Wed, 8 Oct 2025 21:56:19 +0000 Subject: [PATCH 3/7] Refactor alignment calculations using macros --- bfc_io.c | 2 +- bfcfs.h | 4 ++++ bfcfs_vnops.c | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bfc_io.c b/bfc_io.c index 35222b9..963b568 100644 --- a/bfc_io.c +++ b/bfc_io.c @@ -123,7 +123,7 @@ bfc_read_object(struct bfcfs_mount *bmp, struct bfcfs_index_entry *entry, /* Calculate content offset like Linux and BFC library do */ hdr_name_size = sizeof(struct bfc_obj_header) + le16toh(obj_hdr.name_len); - padding = ((hdr_name_size + (BFC_ALIGN - 1)) & ~(BFC_ALIGN - 1)) - hdr_name_size; + padding = BFC_ALIGN_UP(hdr_name_size, BFC_ALIGN) - hdr_name_size; content_offset = entry->obj_offset + hdr_name_size + padding; BFCFS_DEBUG("read_object: %s obj_off=%lu hdr=%zu name_len=%u hdr_name=%zu pad=%zu content_off=%ld", diff --git a/bfcfs.h b/bfcfs.h index 78d9b0a..3430d9a 100644 --- a/bfcfs.h +++ b/bfcfs.h @@ -46,6 +46,10 @@ /* Size alignment */ #define BFC_BLOCK_ROUND 512 /* Round size to 512-byte blocks */ +/* Alignment macros */ +#define BFC_ALIGN_TO_BLOCK(size) (((size) + (BFC_BLOCK_ROUND - 1)) & ~(BFC_BLOCK_ROUND - 1)) +#define BFC_ALIGN_UP(size, align) (((size) + ((align) - 1)) & ~((align) - 1)) + /* VFS constants */ #define BFC_DEFAULT_IO_SIZE 65536 /* 64KB optimal I/O size */ #define BFC_ROOT_MODE 0755 /* Synthetic root directory mode */ diff --git a/bfcfs_vnops.c b/bfcfs_vnops.c index e192550..aca884e 100644 --- a/bfcfs_vnops.c +++ b/bfcfs_vnops.c @@ -212,7 +212,7 @@ bfcfs_getattr(struct vop_getattr_args *ap) vap->va_mode = entry->mode & ALLPERMS; vap->va_fileid = entry->ino; vap->va_size = entry->size; - vap->va_bytes = (entry->size + (BFC_BLOCK_ROUND - 1)) & ~(BFC_BLOCK_ROUND - 1); + vap->va_bytes = BFC_ALIGN_TO_BLOCK(entry->size); vap->va_atime.tv_sec = entry->mtime_ns / NSEC_PER_SEC; vap->va_atime.tv_nsec = entry->mtime_ns % NSEC_PER_SEC; vap->va_mtime = vap->va_atime; From ba9208f709d4cd8da0e22b47c37f0780e0501e4e Mon Sep 17 00:00:00 2001 From: zombocoder Date: Wed, 8 Oct 2025 22:15:14 +0000 Subject: [PATCH 4/7] Refactor magic string handling and data parsing - Use defined constants for magic strings. - Simplify data parsing with helper macros. --- bfc_format.c | 61 ++++++++++++++++------------------------------------ bfcfs.h | 21 +++++++++++++++++- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/bfc_format.c b/bfc_format.c index 455ec59..2d976b8 100644 --- a/bfc_format.c +++ b/bfc_format.c @@ -38,9 +38,9 @@ bfc_read_header(struct bfcfs_mount *bmp, struct thread *td) } /* Validate magic string */ - if (strncmp(hdr->magic, "BFCFv1", 6) != 0) { - BFCFS_ERR("invalid magic: expected 'BFCFv1', got '%.8s'", - hdr->magic); + if (strncmp(hdr->magic, BFC_MAGIC_STR, strlen(BFC_MAGIC_STR)) != 0) { + BFCFS_ERR("invalid magic: expected '%s', got '%.8s'", + BFC_MAGIC_STR, hdr->magic); return (EINVAL); } @@ -91,7 +91,7 @@ bfc_read_footer(struct bfcfs_mount *bmp, struct thread *td) } /* Validate footer magic */ - if (strncmp(footer->magic_start, "BFCFIDX", 7) != 0) { + if (strncmp(footer->magic_start, BFC_INDEX_MAGIC, strlen(BFC_INDEX_MAGIC)) != 0) { BFCFS_ERR("invalid footer magic"); return (EINVAL); } @@ -149,9 +149,7 @@ bfc_parse_index_entry(const uint8_t *buf, size_t buflen, size_t *offset, return (EINVAL); /* Read path length (32-bit) */ - bcopy(p, &path_len, 4); - path_len = LE32(path_len); - p += 4; + READ_LE32(p, path_len); remaining -= 4; /* Sanity check path length */ @@ -170,37 +168,14 @@ bfc_parse_index_entry(const uint8_t *buf, size_t buflen, size_t *offset, return (EINVAL); /* Parse metadata fields (all little-endian) */ - bcopy(p, &entry->obj_offset, 8); - entry->obj_offset = LE64(entry->obj_offset); - p += 8; - - bcopy(p, &entry->obj_size, 8); - entry->obj_size = LE64(entry->obj_size); - p += 8; - - bcopy(p, &entry->mode, 4); - entry->mode = LE32(entry->mode); - p += 4; - - bcopy(p, &entry->mtime_ns, 8); - entry->mtime_ns = LE64(entry->mtime_ns); - p += 8; - - bcopy(p, &entry->comp, 4); - entry->comp = LE32(entry->comp); - p += 4; - - bcopy(p, &entry->enc, 4); - entry->enc = LE32(entry->enc); - p += 4; - - bcopy(p, &entry->size, 8); /* orig_size */ - entry->size = LE64(entry->size); - p += 8; - - bcopy(p, &entry->crc32c, 4); - entry->crc32c = LE32(entry->crc32c); - p += 4; + READ_LE64(p, entry->obj_offset); + READ_LE64(p, entry->obj_size); + READ_LE32(p, entry->mode); + READ_LE64(p, entry->mtime_ns); + READ_LE32(p, entry->comp); + READ_LE32(p, entry->enc); + READ_LE64(p, entry->size); /* orig_size */ + READ_LE32(p, entry->crc32c); entry->ino = ino; @@ -240,17 +215,17 @@ bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) } /* Parse index header (8 bytes: version + count) */ - if (bmp->footer.index_size < 8) { + if (bmp->footer.index_size < BFC_INDEX_HEADER_SIZE) { BFCFS_ERR("index too small for header"); free(index_buf, M_BFCFS); return (EINVAL); } + /* Parse index header (version and count) */ uint32_t version, count; - bcopy(index_buf, &version, 4); - version = LE32(version); - bcopy(index_buf + 4, &count, 4); - count = LE32(count); + uint8_t *p = index_buf; + READ_LE32(p, version); + READ_LE32(p, count); if (version != 1) { BFCFS_ERR("unsupported index version: %u", version); diff --git a/bfcfs.h b/bfcfs.h index 3430d9a..b9cf559 100644 --- a/bfcfs.h +++ b/bfcfs.h @@ -31,13 +31,19 @@ #define BFC_FOOTER_SIZE 56 #define BFC_ALIGN 16 +/* Magic strings */ +#define BFC_MAGIC_STR "BFCFv1" +#define BFC_INDEX_MAGIC "BFCFIDX" +#define BFC_INDEX_END "BFCFEND" + /* Sanity limits for container validation */ #define BFC_MAX_BLOCK_SIZE (1024 * 1024) /* 1MB max block size */ #define BFC_MAX_INDEX_SIZE (100 * 1024 * 1024) /* 100MB max index */ #define BFC_MAX_ENTRIES 1000000 /* 1M max entries */ #define BFC_MAX_PATH_LEN 4096 /* Maximum path length */ -/* BFC index entry format constants */ +/* BFC index format constants */ +#define BFC_INDEX_HEADER_SIZE 8 /* Version (4) + count (4) */ #define BFC_INDEX_ENTRY_METADATA_SIZE 48 /* Size of fixed metadata fields */ /* Time conversion constants */ @@ -50,6 +56,19 @@ #define BFC_ALIGN_TO_BLOCK(size) (((size) + (BFC_BLOCK_ROUND - 1)) & ~(BFC_BLOCK_ROUND - 1)) #define BFC_ALIGN_UP(size, align) (((size) + ((align) - 1)) & ~((align) - 1)) +/* Helper macros for reading little-endian fields */ +#define READ_LE32(ptr, field) do { \ + bcopy(ptr, &(field), 4); \ + (field) = LE32(field); \ + (ptr) += 4; \ +} while (0) + +#define READ_LE64(ptr, field) do { \ + bcopy(ptr, &(field), 8); \ + (field) = LE64(field); \ + (ptr) += 8; \ +} while (0) + /* VFS constants */ #define BFC_DEFAULT_IO_SIZE 65536 /* 64KB optimal I/O size */ #define BFC_ROOT_MODE 0755 /* Synthetic root directory mode */ From bc1ecd56ed4e7096c8d5245ebe0af12426694f90 Mon Sep 17 00:00:00 2001 From: zombocoder Date: Wed, 8 Oct 2025 22:31:15 +0000 Subject: [PATCH 5/7] Refactor index and I/O constants usage - Use defined constants for index parsing and I/O operations - Enhance clarity by replacing magic numbers with named constants --- bfc_format.c | 22 ++++++++++++---------- bfc_io.c | 4 ++-- bfcfs.h | 7 +++++++ bfcfs_vfs.c | 3 ++- bfcfs_vnops.c | 15 ++++++++------- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/bfc_format.c b/bfc_format.c index 2d976b8..9a0a120 100644 --- a/bfc_format.c +++ b/bfc_format.c @@ -144,13 +144,13 @@ bfc_parse_index_entry(const uint8_t *buf, size_t buflen, size_t *offset, size_t remaining = buflen - *offset; uint32_t path_len; - /* Need at least 4 bytes for path_len */ - if (remaining < 4) + /* Need at least BFC_PATH_LEN_SIZE bytes for path_len */ + if (remaining < BFC_PATH_LEN_SIZE) return (EINVAL); /* Read path length (32-bit) */ READ_LE32(p, path_len); - remaining -= 4; + remaining -= BFC_PATH_LEN_SIZE; /* Sanity check path length */ if (path_len == 0 || path_len > BFC_MAX_PATH_LEN || path_len > remaining) @@ -191,6 +191,8 @@ int bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) { uint8_t *index_buf = NULL; + uint8_t *p; + uint32_t version, count; size_t offset = 0; int error, i; @@ -222,13 +224,13 @@ bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) } /* Parse index header (version and count) */ - uint32_t version, count; - uint8_t *p = index_buf; + p = index_buf; READ_LE32(p, version); READ_LE32(p, count); - if (version != 1) { - BFCFS_ERR("unsupported index version: %u", version); + if (version != BFC_INDEX_VERSION) { + BFCFS_ERR("unsupported index version: %u (expected %u)", + version, BFC_INDEX_VERSION); free(index_buf, M_BFCFS); return (EINVAL); } @@ -247,11 +249,11 @@ bfc_read_index(struct bfcfs_mount *bmp, struct thread *td) bmp->index = malloc(bmp->num_entries * sizeof(struct bfcfs_index_entry), M_BFCFS, M_WAITOK | M_ZERO); - /* Parse all entries (starting after 8-byte header) */ - offset = 8; + /* Parse all entries (starting after index header) */ + offset = BFC_INDEX_HEADER_SIZE; for (i = 0; i < (int)bmp->num_entries; i++) { error = bfc_parse_index_entry(index_buf, bmp->footer.index_size, - &offset, &bmp->index[i], i + 1); /* inode numbers start at 1 */ + &offset, &bmp->index[i], i + BFC_INODE_START); if (error) { BFCFS_ERR("failed to parse index entry %d: %d", i, error); goto fail; diff --git a/bfc_io.c b/bfc_io.c index 963b568..cb5c25c 100644 --- a/bfc_io.c +++ b/bfc_io.c @@ -78,9 +78,9 @@ bfc_crc32c(const void *buf, size_t len) { /* * calculate_crc32c() from FreeBSD already handles initialization and XOR - * Just pass 0 as initial CRC for a new calculation + * Just pass BFC_CRC32C_INIT as initial CRC for a new calculation */ - return calculate_crc32c(0, buf, len); + return calculate_crc32c(BFC_CRC32C_INIT, buf, len); } /* diff --git a/bfcfs.h b/bfcfs.h index b9cf559..4cfd3cf 100644 --- a/bfcfs.h +++ b/bfcfs.h @@ -43,8 +43,11 @@ #define BFC_MAX_PATH_LEN 4096 /* Maximum path length */ /* BFC index format constants */ +#define BFC_INDEX_VERSION 1 /* Supported index version */ #define BFC_INDEX_HEADER_SIZE 8 /* Version (4) + count (4) */ #define BFC_INDEX_ENTRY_METADATA_SIZE 48 /* Size of fixed metadata fields */ +#define BFC_PATH_LEN_SIZE 4 /* Size of path_len field (uint32_t) */ +#define BFC_INODE_START 1 /* First inode number (root is synthetic) */ /* Time conversion constants */ #define NSEC_PER_SEC 1000000000ULL /* Nanoseconds per second */ @@ -75,6 +78,10 @@ #define BFC_NAME_MAX 255 /* Maximum filename length */ #define BFC_PATH_MAX 1024 /* Maximum path length for pathconf */ #define BFC_FILESIZEBITS 64 /* File size bits (64-bit offsets) */ +#define BFC_LINK_MAX 1 /* Max hard links (read-only FS) */ + +/* I/O constants */ +#define BFC_CRC32C_INIT 0 /* Initial CRC32C value */ /* BFC object types */ #define BFC_TYPE_FILE 1 diff --git a/bfcfs_vfs.c b/bfcfs_vfs.c index 3eb653e..bbac4ca 100644 --- a/bfcfs_vfs.c +++ b/bfcfs_vfs.c @@ -137,7 +137,8 @@ bfcfs_mount(struct mount *mp) LIST_INIT(&bmp->index_list); /* Parse optional mount options */ - bmp->verify_crc = 1; /* Default: verify CRC */ + bmp->verify_crc = 1; /* Default: verify CRC enabled */ + bmp->no_readahead = 0; /* Default: readahead enabled */ if (vfs_getopt(mp->mnt_optnew, "noverify", NULL, NULL) == 0) bmp->verify_crc = 0; if (vfs_getopt(mp->mnt_optnew, "noreadahead", NULL, NULL) == 0) diff --git a/bfcfs_vnops.c b/bfcfs_vnops.c index aca884e..d644a5d 100644 --- a/bfcfs_vnops.c +++ b/bfcfs_vnops.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -475,19 +476,19 @@ bfcfs_pathconf(struct vop_pathconf_args *ap) int error = 0; switch (ap->a_name) { - case 1: /* _PC_LINK_MAX */ - *ap->a_retval = 1; + case _PC_LINK_MAX: + *ap->a_retval = BFC_LINK_MAX; break; - case 4: /* _PC_NAME_MAX */ + case _PC_NAME_MAX: *ap->a_retval = BFC_NAME_MAX; break; - case 5: /* _PC_PATH_MAX */ + case _PC_PATH_MAX: *ap->a_retval = BFC_PATH_MAX; break; - case 7: /* _PC_NO_TRUNC */ - *ap->a_retval = 1; + case _PC_NO_TRUNC: + *ap->a_retval = 1; /* Filenames are never truncated */ break; - case 18: /* _PC_FILESIZEBITS */ + case _PC_FILESIZEBITS: *ap->a_retval = BFC_FILESIZEBITS; break; default: From 01c127734cb39fc5f4a3012a238239cd29e1c689 Mon Sep 17 00:00:00 2001 From: zombocoder Date: Thu, 9 Oct 2025 08:32:14 +0000 Subject: [PATCH 6/7] Clarify alignment macro arithmetic --- bfcfs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bfcfs.h b/bfcfs.h index 4cfd3cf..df0c973 100644 --- a/bfcfs.h +++ b/bfcfs.h @@ -55,9 +55,9 @@ /* Size alignment */ #define BFC_BLOCK_ROUND 512 /* Round size to 512-byte blocks */ -/* Alignment macros */ -#define BFC_ALIGN_TO_BLOCK(size) (((size) + (BFC_BLOCK_ROUND - 1)) & ~(BFC_BLOCK_ROUND - 1)) -#define BFC_ALIGN_UP(size, align) (((size) + ((align) - 1)) & ~((align) - 1)) +/* Alignment macros - use ULL suffix for 64-bit arithmetic with large file sizes */ +#define BFC_ALIGN_TO_BLOCK(size) (((size) + 511ULL) & ~511ULL) +#define BFC_ALIGN_UP(size, align) (((size) + ((align) - 1ULL)) & ~((align) - 1ULL)) /* Helper macros for reading little-endian fields */ #define READ_LE32(ptr, field) do { \ From 2b8968bd94b5fbf6212b2c94e91e7e2dc4af0cfb Mon Sep 17 00:00:00 2001 From: zombocoder Date: Thu, 9 Oct 2025 09:38:54 +0000 Subject: [PATCH 7/7] Add test suite using Kyua for FreeBSD builds - Install kyua and use it in FreeBSD CI workflow - Enhance test verification with kernel module checks - Integrate automated test container setup in CI - Expand README with detailed test instructions - Add comprehensive ATF test cases for file operations --- .github/workflows/freebsd-build.yml | 49 ++++++++++++- README.md | 86 +++++++++++++++++----- tests/Kyuafile | 11 +++ tests/fixtures/README.md | 49 +++++++++++++ tests/fixtures/test.bfc | Bin 0 -> 207671 bytes tests/h_funcs.subr | 110 ++++++++++++++++++++++++++++ tests/mount_test | 84 +++++++++++++++++++++ tests/read_test | 106 +++++++++++++++++++++++++++ tests/readdir_test | 101 +++++++++++++++++++++++++ tests/symlink_test | 92 +++++++++++++++++++++++ 10 files changed, 666 insertions(+), 22 deletions(-) create mode 100644 tests/Kyuafile create mode 100644 tests/fixtures/README.md create mode 100644 tests/fixtures/test.bfc create mode 100644 tests/h_funcs.subr create mode 100755 tests/mount_test create mode 100755 tests/read_test create mode 100755 tests/readdir_test create mode 100755 tests/symlink_test diff --git a/.github/workflows/freebsd-build.yml b/.github/workflows/freebsd-build.yml index 3b0d803..e5edf13 100644 --- a/.github/workflows/freebsd-build.yml +++ b/.github/workflows/freebsd-build.yml @@ -19,7 +19,7 @@ jobs: release: '14.3' usesh: true prepare: | - pkg install -y llvm + pkg install -y llvm kyua run: | # Print FreeBSD version @@ -56,4 +56,49 @@ jobs: # Check module information file bfcfs.ko - echo "All checks passed!" + echo "Build successful! Now running tests..." + + # Verify test fixture exists + if [ ! -f tests/fixtures/test.bfc ]; then + echo "Warning: Test fixture tests/fixtures/test.bfc not found, skipping tests" + echo "All checks passed (build only)!" + exit 0 + fi + + echo "Test container found, proceeding with tests..." + + # Load the kernel module + echo "Loading BFCFS kernel module..." + kldload ./bfcfs.ko || { + echo "ERROR: Failed to load kernel module" + kldstat + dmesg | tail -20 + exit 1 + } + + # Verify module is loaded + kldstat | grep bfcfs || { + echo "ERROR: Module not loaded" + exit 1 + } + + # Run the tests + echo "Running BFCFS tests..." + cd tests + kyua test || { + echo "Tests failed, showing report:" + kyua report --verbose + cd .. + kldunload bfcfs || true + exit 1 + } + + # Show test report + echo "Test results:" + kyua report + + # Unload module + cd .. + kldunload bfcfs + + echo "All checks and tests passed!" diff --git a/README.md b/README.md index 9da7388..61c65fa 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Low-level I/O routines (pread, object reading, CRC verification) bfcfs-freebsd/ ├── .github/ │ └── workflows/ -│ └── freebsd-build.yml # GitHub Actions CI +│ └── freebsd-build.yml # GitHub Actions CI with tests ├── .gitignore # Git ignore rules ├── LICENSE # BSD 3-Clause License ├── Makefile # Build configuration @@ -139,7 +139,16 @@ bfcfs-freebsd/ ├── bfcfs_vfs.c # VFS operations ├── bfcfs_vnops.c # Vnode operations ├── bfc_format.c # BFC format parsing -└── bfc_io.c # I/O routines +├── bfc_io.c # I/O routines +└── tests/ # ATF test suite + ├── Kyuafile # Test configuration + ├── h_funcs.subr # Test helper functions + ├── mount_test # Mount/unmount tests + ├── read_test # Read operation tests + ├── readdir_test # Directory listing tests + ├── symlink_test # Symlink tests + └── fixtures/ # Test BFC containers + └── README.md # Fixture documentation ``` ## Debugging @@ -218,6 +227,28 @@ doas kldload ./bfcfs.ko ### Testing: +The module includes an ATF-based test suite using Kyua: + +```sh +# Install test dependencies +doas pkg install kyua + +# Load the module +doas kldload ./bfcfs.ko + +# Run the test suite +cd tests +doas kyua test + +# View test results +kyua report + +# Detailed report +kyua report --verbose +``` + +#### Manual Testing: + ```sh # Create a test mount point doas mkdir -p /mnt/bfc @@ -225,7 +256,7 @@ doas mkdir -p /mnt/bfc # Mount test container doas mount -t bfcfs -o ro /path/to/test.bfc /mnt/bfc -# Run tests +# Run manual tests ls -la /mnt/bfc cat /mnt/bfc/somefile.txt find /mnt/bfc -type f @@ -234,6 +265,21 @@ find /mnt/bfc -type f doas umount /mnt/bfc ``` +### Test Suite Structure: + +``` +tests/ +├── Kyuafile # Test configuration +├── h_funcs.subr # Helper functions +├── mount_test # Mount/unmount tests +├── read_test # Read operations tests +├── readdir_test # Directory listing tests +├── symlink_test # Symlink support tests +└── fixtures/ + ├── README.md # Fixture documentation + └── test.bfc # Test BFC container +``` + ## License BSD 3-Clause License - See [LICENSE](LICENSE) file for details @@ -315,12 +361,12 @@ doas mount -t bfcfs -o ro /var/containers/website.bfc /var/www/html | Feature | BFCFS | SquashFS | ISO9660 | FUSE | | -------------- | ----- | -------- | ------- | ------ | -| Read-only | ✅ | ✅ | ✅ | ❌ | -| Kernel module | ✅ | ✅ | ✅ | ❌ | -| Compression | 🚧 | ✅ | ❌ | Varies | -| Encryption | 🚧 | ❌ | ❌ | Varies | -| CRC integrity | ✅ | ✅ | ❌ | Varies | -| Cross-platform | ✅ | ✅ | ✅ | ✅ | +| Read-only | [x] | [x] | [x] | [-] | +| Kernel module | [x] | [x] | [x] | [-] | +| Compression | [ ] | [x] | [-] | Varies | +| Encryption | [ ] | [-] | [-] | Varies | +| CRC integrity | [x] | [x] | [-] | Varies | +| Cross-platform | [x] | [x] | [x] | [x] | | Performance | High | High | High | Medium | ## Project Status @@ -329,20 +375,20 @@ doas mount -t bfcfs -o ro /var/containers/website.bfc /var/www/html ### Implemented Features -- ✅ Mount/unmount operations -- ✅ Directory listing and navigation -- ✅ File reading (uncompressed) -- ✅ Symlink support -- ✅ CRC32C verification -- ✅ Hardware-accelerated checksums +- [x] Mount/unmount operations +- [x] Directory listing and navigation +- [x] File reading (uncompressed) +- [x] Symlink support +- [x] CRC32C verification +- [x] Hardware-accelerated checksums ### Planned Features (Roadmap) -- 🚧 ZSTD decompression support -- 🚧 ChaCha20-Poly1305 decryption support -- 🚧 Extended attributes -- 🚧 Advanced mount options -- 🚧 Performance optimizations +- [ ] ZSTD decompression support +- [ ] ChaCha20-Poly1305 decryption support +- [ ] Extended attributes +- [ ] Advanced mount options +- [ ] Performance optimizations ## Contributing diff --git a/tests/Kyuafile b/tests/Kyuafile new file mode 100644 index 0000000..d0a0c33 --- /dev/null +++ b/tests/Kyuafile @@ -0,0 +1,11 @@ +-- BFCFS Test Suite Configuration +-- $FreeBSD$ + +syntax(2) + +test_suite("FreeBSD") + +atf_test_program{name="mount_test"} +atf_test_program{name="read_test"} +atf_test_program{name="readdir_test"} +atf_test_program{name="symlink_test"} diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..51b93f6 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,49 @@ +# BFCFS Test Fixtures + +This directory contains BFC container files used for testing. + +## Creating Test Containers + +You need to create test BFC containers using the `bfc` tool from https://github.com/zombocoder/bfc: + +```bash +# Build and install bfc tool +git clone https://github.com/zombocoder/bfc.git +cd bfc +make +doas make install +cd .. + +# Create a simple test container +mkdir test_data +echo "Hello World" > test_data/index.html +echo "body { color: blue; }" > test_data/style.css +ln -s index.html test_data/link + +bfc create test.bfc ./test_data/* + +# Copy to fixtures directory +cp test.bfc /path/to/bfcfs-freebsd/tests/fixtures/ +``` + +## Required Test Containers + +The test suite expects the following containers: + +1. **test.bfc** - Basic container with: + - `index.html` - Simple HTML file + - `style.css` - Simple CSS file (optional) + - `link` - Symlink to index.html (optional) + +## Container Requirements + +- Containers must be valid BFC format with: + - Valid header and footer + - Valid index structure + - At least one file entry + - No compression/encryption (for basic tests) + +## GitHub Actions + +The CI workflow will create test containers automatically using the BFC tool +before running the tests. diff --git a/tests/fixtures/test.bfc b/tests/fixtures/test.bfc new file mode 100644 index 0000000000000000000000000000000000000000..4ef1e5120333e0d1bbb30f2869c47aded45b55e3 GIT binary patch literal 207671 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 literal 0 HcmV?d00001 diff --git a/tests/h_funcs.subr b/tests/h_funcs.subr new file mode 100644 index 0000000..e9a989d --- /dev/null +++ b/tests/h_funcs.subr @@ -0,0 +1,110 @@ +#!/bin/sh +# +# Helper functions for BFCFS tests +# + +# Mount point for tests +Mount_Point=bfcfs_mnt + +# Fixtures directory +Fixtures_Dir=$(dirname $0)/fixtures + +# +# require_bfcfs +# +# Checks that bfcfs kernel module is available +# and can be loaded. Skips the test if not available. +# +require_bfcfs() { + atf_require_prog mount + atf_require_prog umount + atf_require_prog kldload + atf_require_prog kldunload + + # Check if module is already loaded + if kldstat -qm bfcfs; then + return 0 + fi + + # Try to load from common locations + local module_paths="/boot/modules/bfcfs.ko ../bfcfs.ko ./bfcfs.ko" + local loaded=0 + + for mod_path in $module_paths; do + if [ -f "$mod_path" ]; then + if kldload "$mod_path" 2>/dev/null; then + loaded=1 + break + fi + fi + done + + # If still not loaded, try system-wide kldload + if [ $loaded -eq 0 ]; then + if ! kldload bfcfs 2>/dev/null; then + atf_skip "bfcfs kernel module not available" + fi + fi + + # Verify module is loaded + if ! kldstat -qm bfcfs; then + atf_skip "bfcfs kernel module failed to load" + fi +} + +# +# test_mount container [options] +# +# Mounts a BFC container from the fixtures directory +# +test_mount() { + local container options container_path + container="${1}" + shift + options="$@" + + require_bfcfs + + # Create mount point + mkdir -p ${Mount_Point} || atf_fail "Cannot create mount point" + + # Get absolute path to container + container_path="${Fixtures_Dir}/${container}" + if [ ! -f "${container_path}" ]; then + atf_fail "Test container not found: ${container_path}" + fi + + # Always mount read-only (required by bfcfs) + # Add ro option if not already present + case "${options}" in + *ro*) ;; + *) options="-o ro ${options}" ;; + esac + + # Mount the BFC container + mount -t bfcfs ${options} ${container_path} ${Mount_Point} || \ + atf_fail "Cannot mount BFC container" +} + +# +# test_unmount +# +# Unmounts the test filesystem +# +test_unmount() { + umount ${Mount_Point} || atf_fail "Cannot unmount filesystem" + rmdir ${Mount_Point} 2>/dev/null || true +} + +# +# cleanup_mount +# +# Cleanup function to be called in test cleanup +# Forcefully unmounts if still mounted +# +cleanup_mount() { + if mount | grep -q ${Mount_Point}; then + umount -f ${Mount_Point} 2>/dev/null || true + fi + rmdir ${Mount_Point} 2>/dev/null || true +} diff --git a/tests/mount_test b/tests/mount_test new file mode 100755 index 0000000..600f6ea --- /dev/null +++ b/tests/mount_test @@ -0,0 +1,84 @@ +#!/usr/libexec/atf-sh +# +# BFCFS mount/unmount tests +# + +. $(dirname $0)/h_funcs.subr + +atf_test_case plain cleanup +plain_head() { + atf_set "descr" "Tests basic mount and unmount of BFC container" + atf_set "require.user" "root" +} +plain_body() { + test_mount test.bfc + # Verify mount point exists and is accessible + test -d ${Mount_Point} || atf_fail "Mount point is not a directory" + test_unmount +} +plain_cleanup() { + cleanup_mount +} + +atf_test_case verify_readonly cleanup +verify_readonly_head() { + atf_set "descr" "Verifies that BFCFS is mounted read-only" + atf_set "require.user" "root" +} +verify_readonly_body() { + test_mount test.bfc + # Check that filesystem is read-only + mount | grep ${Mount_Point} | grep -q "read-only" || \ + atf_fail "Filesystem is not mounted read-only" + + # Try to create a file (should fail) + if touch ${Mount_Point}/testfile 2>/dev/null; then + atf_fail "Should not be able to create files on read-only filesystem" + fi + test_unmount +} +verify_readonly_cleanup() { + cleanup_mount +} + +atf_test_case mount_options cleanup +mount_options_head() { + atf_set "descr" "Tests mount with noverify option" + atf_set "require.user" "root" +} +mount_options_body() { + test_mount test.bfc -o noverify + mount | grep ${Mount_Point} || atf_fail "Filesystem not mounted" + test_unmount +} +mount_options_cleanup() { + cleanup_mount +} + +atf_test_case invalid_container cleanup +invalid_container_head() { + atf_set "descr" "Tests mounting invalid BFC container fails gracefully" + atf_set "require.user" "root" +} +invalid_container_body() { + require_bfcfs + mkdir -p ${Mount_Point} + + # Try to mount non-existent file (should fail) + if mount -t bfcfs /nonexistent.bfc ${Mount_Point} 2>/dev/null; then + umount ${Mount_Point} + atf_fail "Should not be able to mount non-existent container" + fi + + rmdir ${Mount_Point} +} +invalid_container_cleanup() { + cleanup_mount +} + +atf_init_test_cases() { + atf_add_test_case plain + atf_add_test_case verify_readonly + atf_add_test_case mount_options + atf_add_test_case invalid_container +} diff --git a/tests/read_test b/tests/read_test new file mode 100755 index 0000000..2b4f04f --- /dev/null +++ b/tests/read_test @@ -0,0 +1,106 @@ +#!/usr/libexec/atf-sh +# +# BFCFS read operations tests +# + +. $(dirname $0)/h_funcs.subr + +atf_test_case read_file cleanup +read_file_head() { + atf_set "descr" "Tests reading a file from BFC container" + atf_set "require.user" "root" +} +read_file_body() { + test_mount test.bfc + + # Test reading index.html + test -f ${Mount_Point}/index.html || \ + atf_fail "index.html not found in container" + + # Read the file + content=$(cat ${Mount_Point}/index.html 2>/dev/null) || \ + atf_fail "Cannot read index.html" + + # Check that we got some content + test -n "$content" || atf_fail "File is empty" + + test_unmount +} +read_file_cleanup() { + cleanup_mount +} + +atf_test_case read_multiple cleanup +read_multiple_head() { + atf_set "descr" "Tests reading multiple files sequentially" + atf_set "require.user" "root" +} +read_multiple_body() { + test_mount test.bfc + + # Read multiple files + for file in index.html style.css; do + if [ -f ${Mount_Point}/${file} ]; then + cat ${Mount_Point}/${file} >/dev/null || \ + atf_fail "Cannot read ${file}" + fi + done + + test_unmount +} +read_multiple_cleanup() { + cleanup_mount +} + +atf_test_case read_symlink cleanup +read_symlink_head() { + atf_set "descr" "Tests reading file through symlink" + atf_set "require.user" "root" +} +read_symlink_body() { + test_mount test.bfc + + # Check if symlink exists + if [ -L ${Mount_Point}/link ]; then + # Try to read through symlink + cat ${Mount_Point}/link >/dev/null || \ + atf_fail "Cannot read through symlink" + else + atf_skip "No symlink in test container" + fi + + test_unmount +} +read_symlink_cleanup() { + cleanup_mount +} + +atf_test_case stat_file cleanup +stat_file_head() { + atf_set "descr" "Tests stat(2) on files in BFC container" + atf_set "require.user" "root" +} +stat_file_body() { + test_mount test.bfc + + # Stat a file + eval $(stat -s ${Mount_Point}/index.html 2>/dev/null) || \ + atf_fail "Cannot stat index.html" + + # Check that we got valid stat info + test -n "$st_size" || atf_fail "File size is not set" + test "$st_size" -gt 0 || atf_fail "File size is zero" + test -n "$st_mode" || atf_fail "File mode is not set" + + test_unmount +} +stat_file_cleanup() { + cleanup_mount +} + +atf_init_test_cases() { + atf_add_test_case read_file + atf_add_test_case read_multiple + atf_add_test_case read_symlink + atf_add_test_case stat_file +} diff --git a/tests/readdir_test b/tests/readdir_test new file mode 100755 index 0000000..a668b2f --- /dev/null +++ b/tests/readdir_test @@ -0,0 +1,101 @@ +#!/usr/libexec/atf-sh +# +# BFCFS directory listing tests +# + +. $(dirname $0)/h_funcs.subr + +atf_test_case list_root cleanup +list_root_head() { + atf_set "descr" "Tests listing root directory" + atf_set "require.user" "root" +} +list_root_body() { + test_mount test.bfc + + # List root directory + ls ${Mount_Point} >/dev/null || \ + atf_fail "Cannot list root directory" + + # Count entries + count=$(ls -1 ${Mount_Point} | wc -l) + test "$count" -gt 0 || atf_fail "Root directory is empty" + + test_unmount +} +list_root_cleanup() { + cleanup_mount +} + +atf_test_case list_details cleanup +list_details_head() { + atf_set "descr" "Tests listing with details (ls -l)" + atf_set "require.user" "root" +} +list_details_body() { + test_mount test.bfc + + # List with details + ls -l ${Mount_Point} >/dev/null || \ + atf_fail "Cannot list directory with details" + + # Check that we can see file sizes + output=$(ls -l ${Mount_Point}/index.html 2>/dev/null) + echo "$output" | grep -q "index.html" || \ + atf_fail "File not listed correctly" + + test_unmount +} +list_details_cleanup() { + cleanup_mount +} + +atf_test_case list_all cleanup +list_all_head() { + atf_set "descr" "Tests listing with hidden files (ls -a)" + atf_set "require.user" "root" +} +list_all_body() { + test_mount test.bfc + + # List all files (note: BFCFS currently doesn't return . and .. entries) + # This is acceptable behavior for a read-only filesystem + output=$(ls -a ${Mount_Point}) + + # At minimum, verify we get some files + test -n "$output" || atf_fail "No files listed" + + test_unmount +} +list_all_cleanup() { + cleanup_mount +} + +atf_test_case find_files cleanup +find_files_head() { + atf_set "descr" "Tests recursive file finding" + atf_set "require.user" "root" +} +find_files_body() { + test_mount test.bfc + + # Use find to list all files + find ${Mount_Point} -type f >/dev/null || \ + atf_fail "Cannot use find on mounted filesystem" + + # Count files + count=$(find ${Mount_Point} -type f | wc -l) + test "$count" -gt 0 || atf_fail "No files found" + + test_unmount +} +find_files_cleanup() { + cleanup_mount +} + +atf_init_test_cases() { + atf_add_test_case list_root + atf_add_test_case list_details + atf_add_test_case list_all + atf_add_test_case find_files +} diff --git a/tests/symlink_test b/tests/symlink_test new file mode 100755 index 0000000..776f813 --- /dev/null +++ b/tests/symlink_test @@ -0,0 +1,92 @@ +#!/usr/libexec/atf-sh +# +# BFCFS symlink tests +# + +. $(dirname $0)/h_funcs.subr + +atf_test_case readlink cleanup +readlink_head() { + atf_set "descr" "Tests reading symlink target" + atf_set "require.user" "root" +} +readlink_body() { + test_mount test.bfc + + # Check if test container has symlinks + if ! find ${Mount_Point} -type l 2>/dev/null | grep -q .; then + test_unmount + atf_skip "No symlinks in test container" + fi + + # Find a symlink and read it + link=$(find ${Mount_Point} -type l | head -1) + target=$(readlink "$link") || \ + atf_fail "Cannot read symlink" + + test -n "$target" || atf_fail "Symlink target is empty" + + test_unmount +} +readlink_cleanup() { + cleanup_mount +} + +atf_test_case follow_symlink cleanup +follow_symlink_head() { + atf_set "descr" "Tests following symlinks to read target file" + atf_set "require.user" "root" +} +follow_symlink_body() { + test_mount test.bfc + + # Check if test container has symlinks + if ! find ${Mount_Point} -type l 2>/dev/null | grep -q .; then + test_unmount + atf_skip "No symlinks in test container" + fi + + # Find a symlink and read through it + link=$(find ${Mount_Point} -type l | head -1) + cat "$link" >/dev/null 2>&1 || \ + atf_fail "Cannot read through symlink" + + test_unmount +} +follow_symlink_cleanup() { + cleanup_mount +} + +atf_test_case stat_symlink cleanup +stat_symlink_head() { + atf_set "descr" "Tests stat on symlinks (lstat)" + atf_set "require.user" "root" +} +stat_symlink_body() { + test_mount test.bfc + + # Check if test container has symlinks + if ! find ${Mount_Point} -type l 2>/dev/null | grep -q .; then + test_unmount + atf_skip "No symlinks in test container" + fi + + # Find a symlink and stat it + link=$(find ${Mount_Point} -type l | head -1) + eval $(stat -s "$link") || \ + atf_fail "Cannot stat symlink" + + # Check if it's recognized as a symlink + test -L "$link" || atf_fail "File not recognized as symlink" + + test_unmount +} +stat_symlink_cleanup() { + cleanup_mount +} + +atf_init_test_cases() { + atf_add_test_case readlink + atf_add_test_case follow_symlink + atf_add_test_case stat_symlink +}