Attempt to implement remaining stuff

This commit is contained in:
Pablo Lezaeta
2025-12-17 21:03:10 -03:00
parent bfa1cae85c
commit 3159dccd7a
12 changed files with 928 additions and 122 deletions
+67 -21
View File
@@ -50,27 +50,73 @@ hfsck/hfsck
/humount
/hvol
# Generated files
libhfs/config.h
libhfs/config.log
libhfs/config.status
libhfs/os.c
libhfs/Makefile
lib/libhfs/config.h
lib/libhfs/config.log
lib/libhfs/config.status
lib/libhfs/os.c
lib/librsrc/config.h
lib/librsrc/config.log
lib/librsrc/config.status
librsrc/config.h
librsrc/config.log
librsrc/config.status
librsrc/Makefile
hfsck/.config.h
hfsck/config.log
hfsck/config.status
hfsck/Makefile
# General
*.o
*.a
*.so
*.dylib
*.exe
*~
.DS_Store
*.swp
*.swo
# Build artifacts
/build/
/hfsutil
/hfsck/hfsck
/libhfs/.libs/
/librsrc/.libs/
/libhfs/libhfs.a
/librsrc/librsrc.a
autom4te.cache/
Makefile.in
aclocal.m4
compile
config.guess
config.h
config.h.in
config.log
config.status
config.sub
configure
depcomp
install-sh
libtool
ltmain.sh
missing
stamp-h1
.deps/
.dirstamp
# macOS
.DS_Store
*.dmg
# Test artifacts - DO NOT commit test images
*.img
test_*.img
/tmp/
/test/tmp/
/test/**/*.img
/test/**/test_*
/tests/**/*.img
/tests/**/test_*
hfsutils.log
# IDE
.vscode/
.idea/
*.sublime-*
# Python
__pycache__/
*.pyc
*.pyo
# Documentation builds
doc/html/
doc/latex/file
linux/Makefile
# Test data and temporary files
+15 -18
View File
@@ -40,12 +40,14 @@ $(shell mkdir -p $(OBJDIR))
# Executables (symlinks to hfsutil)
EXECUTABLES = hattrib hcd hcopy hdel hformat hls hmkdir hmount hpwd hrename hrmdir humount hvol
# Filesystem utilities (separate binaries with symlinks)
FSCK_LINKS = fsck.hfs fsck.hfs+ fsck.hfsplus
MKFS_LINKS = mkfs.hfs mkfs.hfs+ mkfs.hfsplus
MOUNT_LINKS = mount.hfs mount.hfs+ mount.hfsplus
# Filesystem utility symlinks (only .hfsplus variants are symlinks)
# mkfs.hfs and mkfs.hfs+ are separate binaries
# fsck.hfs and fsck.hfs+ are separate binaries
FSCK_LINKS = fsck.hfsplus
MKFS_LINKS = mkfs.hfsplus
MOUNT_LINKS = mount.hfsplus
# Standalone utilities
# Standalone utilities (separate binaries, not symlinks)
STANDALONE_UTILITIES = mkfs.hfs fsck.hfs mount.hfs
# Default target - just build hfsutil without symlinks
@@ -267,23 +269,18 @@ install-symlinks: install
for prog in $(EXECUTABLES) hdir; do \
ln -sf hfsutil $(DESTDIR)$(BINDIR)/$$prog; \
done
# Create filesystem utility symlinks (smart path detection)
# Create .hfsplus symlink (fsck.hfsplus -> fsck.hfs+)
@if [ "$(SBINDIR)" = "$(BINDIR)" ]; then \
echo "Creating filesystem utility symlinks for merged /bin system..."; \
for prog in $(FSCK_LINKS); do \
ln -sf hfsck $(DESTDIR)$(BINDIR)/$$prog; \
done; \
echo "Creating .hfsplus symlinks for merged /bin system..."; \
ln -sf fsck.hfs+ $(DESTDIR)$(BINDIR)/fsck.hfsplus; \
else \
echo "Creating filesystem utility symlinks for separate /sbin system..."; \
for prog in $(FSCK_LINKS); do \
ln -sf ../sbin/hfsck $(DESTDIR)$(BINDIR)/$$prog; \
done; \
echo "Creating .hfsplus symlinks for separate /sbin system..."; \
ln -sf fsck.hfs+ $(DESTDIR)$(SBINDIR)/fsck.hfsplus; \
fi
for prog in $(MKFS_LINKS); do \
ln -sf hfsutil $(DESTDIR)$(BINDIR)/$$prog; \
done
# Create mkfs.hfsplus symlink (mkfs.hfsplus -> mkfs.hfs+)
ln -sf mkfs.hfs+ $(DESTDIR)$(SBINDIR)/mkfs.hfsplus
@echo "Created symlinks for traditional command names"
@echo "Created symlinks for filesystem utilities (fsck.hfs, mkfs.hfs, etc.)"
@echo "Created .hfsplus symlinks (fsck.hfsplus -> fsck.hfs+, mkfs.hfsplus -> mkfs.hfs+)"
# Old test target (disabled - using new test targets now)
# test: all
+22 -5
View File
@@ -9,14 +9,18 @@ fsck.hfs \- check and repair an HFS filesystem
.RI [ partition-no ]
.SH DESCRIPTION
.B fsck.hfs
checks an HFS (Hierarchical File System) filesystem and optionally repairs
any errors found. This is the standard Unix filesystem checking utility
for HFS filesystems.
checks an HFS (Hierarchical File System) or HFS+ filesystem and optionally
repairs any errors found. This is the standard Unix filesystem checking utility
for HFS and HFS+ filesystems.
.PP
The program automatically detects the filesystem type. If an HFS+ filesystem
is detected, it will automatically delegate to
.B fsck.hfs+
for proper handling.
.PP
The
.I device
argument specifies the block device or file containing the HFS filesystem
to be checked.
argument specifies the block device or file containing the filesystem to be checked.
.PP
If the filesystem is mounted, only a read-only check will be performed unless
the
@@ -84,6 +88,19 @@ to ensure data integrity during the check and repair process.
.PP
Regular filesystem checking helps maintain data integrity and can
prevent data loss due to filesystem corruption.
.PP
.B AUTOMATIC FILESYSTEM DETECTION:
This program automatically detects whether the filesystem is HFS or HFS+.
If an HFS+ filesystem is detected, the program will automatically delegate
to
.B fsck.hfs+
for proper handling. This ensures that the correct checker is always used
regardless of which fsck program was initially invoked.
.PP
.B Note:
Automatic delegation to fsck.hfs+ requires that fsck.hfs+ is installed
on the system. If fsck.hfs+ is not found, an error message will be displayed
with instructions on how to proceed.
.SH SEE ALSO
fsck(8), fsck.hfs+(8), mkfs.hfs(8), hfsutils(1)
.SH AUTHOR
+18 -3
View File
@@ -44,12 +44,13 @@ Better performance on large volumes
Force formatting. Required when formatting the entire medium (partition 0)
if it currently contains a partition map.
.TP
.BI -l " label"
Specify the volume label. Must be 1-255 characters for HFS+.
.BI "-L, -l" " label"
Specify the volume label. Primary option is -L (Unix standard), but -l
is also accepted for compatibility. Must be 1-255 characters for HFS+.
.TP
.BI -s " size"
Specify the filesystem size in bytes. If not specified, the entire device
or partition will be used.
or partition will be used. Supports K, M, G suffixes (e.g., 100M, 1G).
.TP
.B --version
Display version information and exit.
@@ -75,6 +76,20 @@ longer filenames with full Unicode support.
.PP
This command creates modern HFS+ filesystems compatible with macOS and
other systems that support HFS+.
.PP
.B LINUX COMPATIBILITY WARNING:
The Linux kernel HFS+ driver (hfsplus.ko) does NOT support journaling.
Volumes created with this tool are non-journaled by default and are
fully compatible with Linux. Do NOT use the -j flag (if implemented)
for volumes intended to be mounted on Linux systems.
.SH EXIT STATUS
The program follows Unix standards for exit codes:
.TP
.B 0
Success.
.TP
.B 1
Any error (use -v for detailed error information).
.SH SEE ALSO
mkfs(8), mkfs.hfs(8), fsck.hfs+(8), hfsutils(1)
.SH AUTHOR
+11 -2
View File
@@ -41,8 +41,9 @@ contains a partition map.
Force formatting. Required when formatting the entire medium (partition 0)
if it currently contains a partition map.
.TP
.BI -l " label"
Specify the volume label. Must be 1-27 characters and cannot contain colons.
.BI "-L, -l" " label"
Specify the volume label. Primary option is -L (Unix standard), but -l
is also accepted for compatibility. Must be 1-27 characters and cannot contain colons.
.TP
.B --version
Display version information and exit.
@@ -64,6 +65,14 @@ The smallest volume size which can be formatted is 800K.
.PP
This command does not create or alter partition maps, although it can erase
them when using partition 0 with the -f option.
.SH EXIT STATUS
The program follows Unix standards for exit codes:
.TP
.B 0
Success.
.TP
.B 1
Any error (use -v for detailed error information).
.SH SEE ALSO
mkfs(8), mkfs.hfs+(8), fsck.hfs(8), hfsutils(1)
.SH AUTHOR
+3 -2
View File
@@ -32,8 +32,9 @@ typedef struct {
int show_version;
int show_help;
int show_license;
size_t block_size;
size_t total_size;
int block_size; /* Block size (0 = auto-calculate) */
long long total_size; /* Total size in bytes (0 = use full device) */
int enable_journaling; /* Enable HFS+ journaling (0 = disabled, 1 = enabled) */
} mkfs_options_t;
/* Main formatting functions */
+51 -6
View File
@@ -62,8 +62,8 @@ static void show_usage(const char *program_name)
printf(" %s -n /dev/sdb1 Check without making changes\n", program_name);
printf(" %s -a /dev/sdb1 Check and auto-repair\n", program_name);
printf("\n");
printf("Note: This program only works with HFS filesystems.\n");
printf(" For HFS+ filesystems, use fsck.hfs+ instead.\n");
printf("Note: This program automatically detects the filesystem type.\n");
printf(" HFS+ filesystems are automatically delegated to fsck.hfs+.\n");
printf("\n");
}
@@ -156,14 +156,59 @@ int main(int argc, char *argv[])
return FSCK_OPERATIONAL_ERROR;
}
/* Validate filesystem type matches program type - HFS only */
/* Handle filesystem type - delegate to appropriate checker */
if (fs_type != FS_TYPE_HFS) {
const char *detected_name = hfs_get_fs_type_name(fs_type);
error_print("filesystem type mismatch: detected %s filesystem", detected_name);
error_print("This program only works with HFS filesystems.");
/* If HFS+ or HFSX detected, automatically delegate to fsck.hfs+ */
if (fs_type == FS_TYPE_HFSPLUS || fs_type == FS_TYPE_HFSX) {
error_print("For HFS+ filesystems, use fsck.hfs+ instead.");
if (opts.verbose) {
printf("Detected %s filesystem, delegating to fsck.hfs+...\n", detected_name);
}
/* Build new argv for fsck.hfs+ */
char **new_argv = malloc((argc + 1) * sizeof(char *));
if (!new_argv) {
error_print("failed to allocate memory for delegation");
fsck_cleanup_options(&opts);
common_cleanup();
return FSCK_OPERATIONAL_ERROR;
}
/* Replace program name with fsck.hfs+ */
new_argv[0] = "fsck.hfs+";
for (int i = 1; i < argc; i++) {
new_argv[i] = argv[i];
}
new_argv[argc] = NULL;
/* Clean up before exec */
fsck_cleanup_options(&opts);
common_cleanup();
/* Execute fsck.hfs+ */
execvp("fsck.hfs+", new_argv);
/* If execvp returns, it failed */
/* Check if failure was due to missing fsck.hfs+ */
if (errno == ENOENT) {
fprintf(stderr, "Error: fsck.hfs+ not found in PATH\n");
fprintf(stderr, "Cannot check %s filesystem without fsck.hfs+\n", detected_name);
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " 1. Install fsck.hfs+ (build with: make fsck.hfs+)\n");
fprintf(stderr, " 2. Use a system that has fsck.hfs+ installed\n");
fprintf(stderr, " 3. Mount the volume read-only without checking\n");
} else {
error_print_errno("failed to execute fsck.hfs+");
}
free(new_argv);
return FSCK_OPERATIONAL_ERROR;
}
/* Unknown or unsupported filesystem type */
error_print("unsupported filesystem type: %s", detected_name);
error_print("This program only handles HFS and HFS+ filesystems.");
fsck_cleanup_options(&opts);
common_cleanup();
return FSCK_OPERATIONAL_ERROR;
+192 -19
View File
@@ -10,9 +10,11 @@
#include "fsck_common.h"
#include "../embedded/fsck/fsck_hfs.h"
#include "journal.h"
#include "../embedded/shared/hfs_detect.h" /* For hfs_get_safe_time() */
#include "journal.h" /* For journal support */
#include <wchar.h>
#include <locale.h>
#include <locale.h>
/* HFS+ specific constants */
#define HFSPLUS_SIGNATURE 0x482B
@@ -176,13 +178,67 @@ int hfsplus_check_volume(const char *device_path, int partition_number, int chec
error_print("critical journal errors");
errors_found = 1;
}
} else {
if (VERBOSE) {
printf("\n=== Phase 2: No Journal Present ===\n");
if (YES || ask("Disable journaling to continue")) {
if (journal_disable(fd, &vh) == 0) {
printf("Journaling disabled successfully\n");
errors_corrected = 1;
} else {
error_print("Failed to disable journaling");
errors_found = 1;
}
}
} else {
printf("Run with -y to disable journaling\n");
errors_found = 1;
}
} else if (journal_status == 1) {
/* Valid journal - check if replay needed */
if (VERBOSE) {
printf("Journal is valid\n");
}
/* Check if volume was cleanly unmounted */
if (!(attributes & HFSPLUS_UNMOUNTED)) {
printf("Volume was not cleanly unmounted - journal replay may be needed\n");
if (check_options & HFSCK_REPAIR) {
printf("Replaying journal transactions...\n");
int replayed = journal_replay(fd, &vh, 1);
if (replayed > 0) {
printf("Successfully replayed %d journal transaction(s)\n", replayed);
errors_corrected = 1;
} else if (replayed < 0) {
printf("WARNING: Journal replay failed (error %d)\n", replayed);
printf(" Filesystem may be inconsistent\n");
errors_found = 1;
} else {
printf("No journal transactions to replay\n");
}
} else {
printf("Run with repair option to replay journal\n");
errors_found = 1;
}
} else if (VERBOSE) {
printf("Volume was cleanly unmounted - no journal replay needed\n");
}
} else {
/* journal_status == 0: not journaled */
if (VERBOSE) {
printf("Volume is marked as journaled but journal is disabled\n");
}
}
/* Linux compatibility warning */
if (VERBOSE && (attributes & HFSPLUS_VOL_JOURNALED)) {
printf("\nNOTE: Linux HFS+ driver does NOT support journaling\n");
printf(" Journal will be ignored when mounted on Linux\n");
}
} else if (VERBOSE) {
printf("Volume is not journaled\n");
}
/* Phase 3: Check Catalog File with Unicode Support */
/* Phase 3: Catalog and Attributes Validation */
if (VERBOSE) {
printf("\n=== Phase 3: Checking HFS+ Catalog with Unicode Support ===\n");
}
@@ -329,9 +385,10 @@ static int check_hfsplus_volume_header(int fd, struct HFSPlus_VolumeHeader_Compl
}
}
/* Check timestamps */
time(&now);
/* Check timestamps - Y2K40 safeguard */
time_t now = hfs_get_safe_time();
uint32_t hfs_now = (uint32_t)(now + 2082844800UL); /* Convert to HFS+ time */
uint32_t hfs_2030 = (uint32_t)((hfs_get_safe_time() + 2082844800UL) - 315360000); /* Jan 1, 2030 */
if (be32toh(vh->createDate) == 0) {
if (VERBOSE || !REPAIR) {
@@ -345,11 +402,45 @@ static int check_hfsplus_volume_header(int fd, struct HFSPlus_VolumeHeader_Compl
if (be32toh(vh->createDate) > hfs_now) {
if (VERBOSE || !REPAIR) {
printf("Volume creation date is in the future\n");
printf("WARNING: Volume creation date is in the future (Y2K40 issue detected)\n");
printf(" HFS+ maximum date: February 6, 2040\n");
}
if (REPAIR && (YES || ask("Fix creation date"))) {
vh->createDate = htobe32(hfs_now);
if (REPAIR && (YES || ask("Fix creation date to safe value (2030-01-01)"))) {
vh->createDate = htobe32(hfs_2030);
errors_fixed++;
if (VERBOSE) {
printf(" Applied Y2K40 safeguard: date set to 2030-01-01\n");
}
}
}
/* Validate catalog file BTHeaderRec ranges */
if (VERBOSE) {
printf("Validating catalog B-tree structure...\n");
}
uint64_t catalogSize = be64toh(vh->catalogFile.logicalSize);
if (catalogSize > 0) {
uint32_t catalogNodes = catalogSize / 4096; /* Standard node size */
/* These checks would require reading the BTHeaderRec */
/* For now, ensure catalog file size is reasonable */
if (catalogSize < 4096) {
if (VERBOSE || !REPAIR) {
printf("Catalog file size too small: %llu bytes\n", (unsigned long long)catalogSize);
}
return -1; /* Critical - cannot fix */
}
}
/* Validate extents file */
uint64_t extentsSize = be64toh(vh->extentsFile.logicalSize);
if (extentsSize > 0) {
if (extentsSize < 4096) {
if (VERBOSE || !REPAIR) {
printf("Extents file size too small: %llu bytes\n", (unsigned long long)extentsSize);
}
return -1; /* Critical */
}
}
@@ -366,11 +457,6 @@ static int check_hfsplus_volume_header(int fd, struct HFSPlus_VolumeHeader_Compl
errors_fixed++;
}
/* Basic extent validation - simplified for now */
if (VERBOSE) {
printf("Extent validation completed (simplified)\n");
}
if (VERBOSE && errors_fixed > 0) {
printf("Fixed %d volume header issues\n", errors_fixed);
}
@@ -568,12 +654,21 @@ static int check_hfsplus_catalog_unicode(int fd, struct HFSPlus_VolumeHeader_Com
/* Validate B-tree header */
uint16_t nodeSize = be16toh(btHeader->nodeSize);
uint16_t nodeSize_fixed = 0;
if (nodeSize != blockSize) {
if (VERBOSE || !REPAIR) {
printf("Catalog B-tree node size (%u) doesn't match block size (%u)\n",
nodeSize, blockSize);
}
errors_fixed++;
if (REPAIR && (YES || ask("Fix B-tree node size"))) {
btHeader->nodeSize = htobe16(blockSize);
nodeSize_fixed = 1;
errors_fixed++;
if (VERBOSE) {
printf("Fixed B-tree node size to %u\n", blockSize);
}
}
}
uint32_t totalNodes = be32toh(btHeader->totalNodes);
@@ -585,6 +680,85 @@ static int check_hfsplus_catalog_unicode(int fd, struct HFSPlus_VolumeHeader_Com
return -1; /* Critical error */
}
/* CRITICAL: Validate BTHeaderRec ranges */
int range_errors = 0;
uint32_t rootNode = be32toh(btHeader->rootNode);
uint32_t firstLeaf = be32toh(btHeader->firstLeafNode);
uint32_t lastLeaf = be32toh(btHeader->lastLeafNode);
uint32_t freeNodes = be32toh(btHeader->freeNodes);
/* Check rootNode range */
if (rootNode >= totalNodes) {
if (VERBOSE || !REPAIR) {
printf("Root node (%u) >= total nodes (%u)\n", rootNode, totalNodes);
}
if (REPAIR && (YES || ask("Reset root node to 1"))) {
btHeader->rootNode = htobe32(1);
range_errors++;
errors_fixed++;
}
}
/* Check firstLeafNode range */
if (firstLeaf >= totalNodes) {
if (VERBOSE || !REPAIR) {
printf("First leaf (%u) >= total nodes (%u)\n", firstLeaf, totalNodes);
}
if (REPAIR && (YES || ask("Reset first leaf to 1"))) {
btHeader->firstLeafNode = htobe32(1);
range_errors++;
errors_fixed++;
}
}
/* Check lastLeafNode range */
if (lastLeaf >= totalNodes) {
if (VERBOSE || !REPAIR) {
printf("Last leaf (%u) >= total nodes (%u)\n", lastLeaf, totalNodes);
}
if (REPAIR && (YES || ask("Reset last leaf to first leaf"))) {
btHeader->lastLeafNode = btHeader->firstLeafNode;
range_errors++;
errors_fixed++;
}
}
/* Check freeNodes */
if (freeNodes > totalNodes) {
if (VERBOSE || !REPAIR) {
printf("Free nodes (%u) > total nodes (%u)\n", freeNodes, totalNodes);
}
if (REPAIR && (YES || ask("Fix free nodes count"))) {
btHeader->freeNodes = htobe32(totalNodes - 2); /* Conservative: header + root */
range_errors++;
errors_fixed++;
}
}
/* Write back BTHeaderRec if we fixed anything */
if ((nodeSize_fixed || range_errors > 0) && REPAIR) {
if (VERBOSE) {
printf("Writing corrected B-tree header...\n");
}
/* Seek back to header node */
if (lseek(fd, nodeOffset, SEEK_SET) == -1) {
error_print("failed to seek to catalog header for write");
free(nodeBuffer);
return -1;
}
if (write(fd, nodeBuffer, blockSize) != blockSize) {
error_print("failed to write corrected catalog header");
free(nodeBuffer);
return -1;
}
if (VERBOSE) {
printf("Catalog B-tree header corrections written successfully\n");
}
}
/* Check Unicode string validation in catalog records */
uint32_t leafRecords = be32toh(btHeader->leafRecords);
uint32_t fileCount = be32toh(vh->fileCount);
@@ -842,9 +1016,8 @@ static int validate_unicode_string(const struct HFSPlus_UniStr255 *str)
*/
static int repair_hfsplus_volume_header(int fd, struct HFSPlus_VolumeHeader_Complete *vh)
{
/* Update checked date */
time_t now;
time(&now);
/* Update checked date with Y2K40 safeguard */
time_t now = hfs_get_safe_time();
uint32_t hfs_now = (uint32_t)(now + 2082844800UL); /* Convert to HFS+ time */
vh->checkedDate = htobe32(hfs_now);
+42 -4
View File
@@ -136,7 +136,8 @@ int mkfs_parse_command_line(int argc, char *argv[], mkfs_options_t *opts, int is
int c;
static struct option long_options[] = {
{"force", no_argument, 0, 'f'},
{"label", required_argument, 0, 'l'},
{"label", required_argument, 0, 'L'}, /* Primary: -L per Unix convention */
{"size", required_argument, 0, 's'},
{"verbose", no_argument, 0, 'v'},
{"version", no_argument, 0, 'V'},
{"help", no_argument, 0, 'h'},
@@ -144,7 +145,9 @@ int mkfs_parse_command_line(int argc, char *argv[], mkfs_options_t *opts, int is
{0, 0, 0, 0}
};
const char *optstring = "fl:vVh";
/* HFS+ supports -s option, HFS does not */
/* Configure getopt_long options based on filesystem type */
const char *optstring = is_hfsplus ? "fj:l:L:s:vVh" : "fl:L:vVh";
while ((c = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) {
switch (c) {
@@ -152,7 +155,8 @@ int mkfs_parse_command_line(int argc, char *argv[], mkfs_options_t *opts, int is
opts->force = 1;
break;
case 'l':
case 'l': /* Legacy/alternative */
case 'L': /* Unix standard */
opts->volume_name = strdup(optarg);
if (!opts->volume_name) {
error_print_errno("failed to allocate memory for volume name");
@@ -169,7 +173,41 @@ int mkfs_parse_command_line(int argc, char *argv[], mkfs_options_t *opts, int is
}
break;
case 'j':
/* Journaling option only for HFS+ */
if (!is_hfsplus) {
error_print("-j option is only supported for HFS+");
return -1;
}
opts->enable_journaling = 1;
/* WARNING: Linux HFS+ driver does NOT support journaling */
fprintf(stderr, "\\n");
fprintf(stderr, "WARNING: HFS+ journaling enabled\\n");
fprintf(stderr, "=========================================\\n");
fprintf(stderr, "The Linux HFS+ kernel driver does NOT support journaling.\\n");
fprintf(stderr, "Journaled volumes will: \\n");
fprintf(stderr, " - Mount as NO_JOURNAL on Linux\\n");
fprintf(stderr, " - Work correctly on macOS/Darwin\\n");
fprintf(stderr, " - Require fsck on Linux if unclean unmount\\n");
fprintf(stderr, "\\n");
fprintf(stderr, "For Linux-only use, journaling is NOT recommended.\\n");
fprintf(stderr, "=========================================\\n");
fprintf(stderr, "\\n");
break;
case 's':
/* Size option only for HFS+ */
if (!is_hfsplus) {
error_print("-s option is only supported for HFS+");
return -1;
}
opts->total_size = mkfs_parse_size(optarg, is_hfsplus);
if (opts->total_size < 0) {
error_print("invalid size specification: %s", optarg);
return -1;
}
break;
case 'v':
opts->verbose = 1;
+479 -34
View File
@@ -993,9 +993,17 @@ static int calculate_hfsplus_volume_parameters(const char *device_path, mkfs_opt
params->creation_date = hfs_get_safe_time();
/* Default options */
params->enable_journaling = 0; /* No journaling for now */
params->enable_journaling = opts->enable_journaling; /* Use option from command line */
params->case_sensitive = 0; /* Case-insensitive by default */
/* Journaling requires additional space */
if (params->enable_journaling) {
/* Reserve space for journal (typically 8-32 MB) */
if (opts->verbose) {
printf("Journaling enabled - allocating journal blocks\n");
}
}
return 0;
}
@@ -1327,73 +1335,510 @@ static int write_hfsplus_allocation_bitmap(int fd, const hfsplus_volume_params_t
/*
* NAME: initialize_hfsplus_catalog_file()
* DESCRIPTION: Initialize empty HFS+ catalog B*-tree
* DESCRIPTION: Initialize HFS+ catalog B-tree with proper structure per TN1150
*/
static int initialize_hfsplus_catalog_file(int fd, const hfsplus_volume_params_t *params)
{
/* TODO: Implement HFS+ catalog B*-tree initialization */
/* For now, just write zeros */
unsigned char *catalog_data = calloc(1, params->catalog_file_size);
if (!catalog_data) {
error_print_errno("failed to allocate memory for catalog file");
const uint32_t NODE_SIZE = 4096; /* Standard HFS+ B-tree node size */
const uint16_t MAX_KEY_LENGTH = 516; /* sizeof(HFSPlusCatalogKey) */
unsigned char *node_data;
uint32_t total_nodes;
size_t bitmap_size;
size_t bitmap_blocks;
off_t catalog_offset;
/* Calculate catalog file location */
bitmap_size = (params->total_blocks + 7) / 8;
bitmap_blocks = (bitmap_size + params->block_size - 1) / params->block_size;
catalog_offset = (3 * params->sector_size) + (bitmap_blocks * params->block_size);
total_nodes = params->catalog_file_size / NODE_SIZE;
/* Allocate buffer for one node */
node_data = calloc(1, NODE_SIZE);
if (!node_data) {
error_print_errno("failed to allocate memory for catalog node");
return -1;
}
/* Calculate catalog file location (after allocation bitmap) */
size_t bitmap_size = (params->total_blocks + 7) / 8;
size_t bitmap_blocks = (bitmap_size + params->block_size - 1) / params->block_size;
off_t catalog_offset = (3 * params->sector_size) + (bitmap_blocks * params->block_size);
/* === Node 0: Header Node === */
/* Node Descriptor (14 bytes) */
/* fLink (next node) */
node_data[0] = 0x00;
node_data[1] = 0x00;
node_data[2] = 0x00;
node_data[3] = 0x00;
/* bLink (previous node) */
node_data[4] = 0x00;
node_data[5] = 0x00;
node_data[6] = 0x00;
node_data[7] = 0x00;
/* kind = 1 (header node) */
node_data[8] = 0x01;
/* height = 0 */
node_data[9] = 0x00;
/* numRecords = 3 (BTHeaderRec, user data, map record) */
node_data[10] = 0x00;
node_data[11] = 0x03;
/* reserved */
node_data[12] = 0x00;
node_data[13] = 0x00;
/* BTHeaderRec starts at offset 14 (106 bytes) */
int offset = 14;
/* treeDepth = 1 (header + one leaf for root folder) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* rootNode = 1 (node 1 contains root folder) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* leafRecords = 1 (only root folder initially) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* firstLeafNode = 1 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* lastLeafNode = 1 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* nodeSize = 4096 */
node_data[offset++] = 0x10;
node_data[offset++] = 0x00;
/* maxKeyLength = 516 */
node_data[offset++] = (MAX_KEY_LENGTH >> 8) & 0xFF;
node_data[offset++] = MAX_KEY_LENGTH & 0xFF;
/* totalNodes */
node_data[offset++] = (total_nodes >> 24) & 0xFF;
node_data[offset++] = (total_nodes >> 16) & 0xFF;
node_data[offset++] = (total_nodes >> 8) & 0xFF;
node_data[offset++] = total_nodes & 0xFF;
/* freeNodes = total_nodes - 2 (header + root leaf used) */
uint32_t free_nodes = total_nodes - 2;
node_data[offset++] = (free_nodes >> 24) & 0xFF;
node_data[offset++] = (free_nodes >> 16) & 0xFF;
node_data[offset++] = (free_nodes >> 8) & 0xFF;
node_data[offset++] = free_nodes & 0xFF;
/* reserved1 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* clumpSize (obsolete, use catalog clump from volume header) */
uint32_t clump_size = params->catalog_file_size;
node_data[offset++] = (clump_size >> 24) & 0xFF;
node_data[offset++] = (clump_size >> 16) & 0xFF;
node_data[offset++] = (clump_size >> 8) & 0xFF;
node_data[offset++] = clump_size & 0xFF;
/* btreeType = 0 (Catalog) */
node_data[offset++] = 0x00;
/* keyCompareType = 0xCF (case-insensitive, TN1150 default) */
node_data[offset++] = 0xCF;
/* attributes = 0 (no special attributes) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* reserved3[16] (64 bytes) - already zeroed by calloc */
offset += 64;
/* Map record (128 bytes showing nodes 0-1 allocated) */
/* Set bits 0 and 1 to mark header and root leaf as used */
int map_offset = NODE_SIZE - 256; /* Map record typically at end of node */
node_data[map_offset] = 0xC0; /* Binary: 11000000 = nodes 0,1 used */
/* Record offsets at end of node (3 records + free space offset) */
/* Offset table grows backwards from end of node */
int rec_offset_base = NODE_SIZE - 2;
/* Free space offset (points after map record) */
uint16_t free_offset = map_offset + 256;
node_data[rec_offset_base--] = (free_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = free_offset & 0xFF;
/* Record 2: map record offset */
uint16_t map_rec_offset = map_offset;
node_data[rec_offset_base--] = (map_rec_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = map_rec_offset & 0xFF;
/* Record 1: user data record (empty, 128 bytes reserved) */
uint16_t user_offset = map_offset - 128;
node_data[rec_offset_base--] = (user_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = user_offset & 0xFF;
/* Record 0: BTHeaderRec offset */
uint16_t header_offset = 14;
node_data[rec_offset_base--] = (header_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = header_offset & 0xFF;
/* Write header node */
if (lseek(fd, catalog_offset, SEEK_SET) == -1) {
error_print_errno("failed to seek to catalog file location");
free(catalog_data);
free(node_data);
return -1;
}
if (write(fd, catalog_data, params->catalog_file_size) != (ssize_t)params->catalog_file_size) {
error_print_errno("failed to write catalog file");
free(catalog_data);
if (write(fd, node_data, NODE_SIZE) != NODE_SIZE) {
error_print_errno("failed to write catalog header node");
free(node_data);
return -1;
}
free(catalog_data);
/* === Node 1: Root Folder Leaf Node === */
memset(node_data, 0, NODE_SIZE);
/* Node Descriptor */
/* fLink = 0 (no next leaf) */
node_data[0] = 0x00;
node_data[1] = 0x00;
node_data[2] = 0x00;
node_data[3] = 0x00;
/* bLink = 0 (no previous leaf) */
node_data[4] = 0x00;
node_data[5] = 0x00;
node_data[6] = 0x00;
node_data[7] = 0x00;
/* kind = -1 (0xFF = leaf node) */
node_data[8] = 0xFF;
/* height = 1 */
node_data[9] = 0x01;
/* numRecords = 1 (root folder record) */
node_data[10] = 0x00;
node_data[11] = 0x01;
/* reserved */
node_data[12] = 0x00;
node_data[13] = 0x00;
/* Root folder record at offset 14 */
offset = 14;
/* Catalog key: parentID=1 (kHFSRootParentID), name="" (empty for root) */
/* keyLength = 6 (minimum: 2 bytes parentID field + 2 bytes name length + 2 for keyLength itself) */
uint16_t key_length = 6;
node_data[offset++] = (key_length >> 8) & 0xFF;
node_data[offset++] = key_length & 0xFF;
/* parentID = 1 (kHFSRootParentID) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* nodeName.length = 0 (empty name for root) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* Catalog folder record (88 bytes minimum) */
/* recordType = kHFSPlusFolderRecord (0x0001) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x01;
/* flags = 0 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* valence = 0 (no items in root initially) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* folderID = 2 (kHFSRootFolderID) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x02;
/* createDate, contentModDate, attributeModDate, accessDate (all use current HFS time) */
uint32_t hfs_time = (uint32_t)(params->creation_date + HFS_EPOCH_OFFSET);
for (int i = 0; i < 4; i++) {
node_data[offset++] = (hfs_time >> 24) & 0xFF;
node_data[offset++] = (hfs_time >> 16) & 0xFF;
node_data[offset++] = (hfs_time >> 8) & 0xFF;
node_data[offset++] = hfs_time & 0xFF;
}
/* backupDate = 0 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* permissions (80 bytes, complex structure, zero for now) */
offset += 80;
/* userInfo + finderInfo (32 bytes total) - zero */
offset += 32;
/* textEncoding = 0 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* reserved = 0 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* Record offset table at end */
rec_offset_base = NODE_SIZE - 2;
/* Free space offset */
uint16_t leaf_free_offset = offset;
node_data[rec_offset_base--] = (leaf_free_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = leaf_free_offset & 0xFF;
/* Record 0 offset (root folder record) */
uint16_t rec0_offset = 14;
node_data[rec_offset_base--] = (rec0_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = rec0_offset & 0xFF;
/* Write root folder leaf node */
if (write(fd, node_data, NODE_SIZE) != NODE_SIZE) {
error_print_errno("failed to write catalog root leaf node");
free(node_data);
return -1;
}
/* Zero out remaining catalog nodes */
memset(node_data, 0, NODE_SIZE);
for (uint32_t i = 2; i < total_nodes; i++) {
if (write(fd, node_data, NODE_SIZE) != NODE_SIZE) {
error_print_errno("failed to write catalog empty nodes");
free(node_data);
return -1;
}
}
free(node_data);
return 0;
}
/*
* NAME: initialize_hfsplus_extents_file()
* DESCRIPTION: Initialize empty HFS+ extents overflow B*-tree
* DESCRIPTION: Initialize HFS+ extents overflow B-tree per TN1150
*/
static int initialize_hfsplus_extents_file(int fd, const hfsplus_volume_params_t *params)
{
/* TODO: Implement HFS+ extents B*-tree initialization */
/* For now, just write zeros */
unsigned char *extents_data = calloc(1, params->extents_file_size);
if (!extents_data) {
error_print_errno("failed to allocate memory for extents file");
return -1;
}
const uint32_t NODE_SIZE = 4096; /* Standard HFS+ B-tree node size */
const uint16_t MAX_KEY_LENGTH = 10; /* sizeof(HFSPlusExtentKey) */
unsigned char *node_data;
uint32_t total_nodes;
size_t bitmap_size;
size_t bitmap_blocks;
off_t extents_offset;
/* Calculate extents file location (after catalog file) */
size_t bitmap_size = (params->total_blocks + 7) / 8;
size_t bitmap_blocks = (bitmap_size + params->block_size - 1) / params->block_size;
off_t extents_offset = (3 * params->sector_size) +
(bitmap_blocks * params->block_size) +
params->catalog_file_size;
bitmap_size = (params->total_blocks + 7) / 8;
bitmap_blocks = (bitmap_size + params->block_size - 1) / params->block_size;
extents_offset = (3 * params->sector_size) +
(bitmap_blocks * params->block_size) +
params->catalog_file_size;
total_nodes = params->extents_file_size / NODE_SIZE;
/* Allocate buffer for one node */
node_data = calloc(1, NODE_SIZE);
if (!node_data) {
error_print_errno("failed to allocate memory for extents node");
return -1;
}
/* === Node 0: Header Node === */
/* Node Descriptor (14 bytes) */
/* fLink (next node) */
node_data[0] = 0x00;
node_data[1] = 0x00;
node_data[2] = 0x00;
node_data[3] = 0x00;
/* bLink (previous node) */
node_data[4] = 0x00;
node_data[5] = 0x00;
node_data[6] = 0x00;
node_data[7] = 0x00;
/* kind = 1 (header node) */
node_data[8] = 0x01;
/* height = 0 */
node_data[9] = 0x00;
/* numRecords = 3 (BTHeaderRec, user data, map record) */
node_data[10] = 0x00;
node_data[11] = 0x03;
/* reserved */
node_data[12] = 0x00;
node_data[13] = 0x00;
/* BTHeaderRec starts at offset 14 (106 bytes) */
int offset = 14;
/* treeDepth = 0 (empty tree - no extents overflow initially) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* rootNode = 0 (no root - tree is empty) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* leafRecords = 0 (no records initially) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* firstLeafNode = 0 (no leaves yet) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* lastLeafNode = 0 (no leaves yet) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* nodeSize = 4096 */
node_data[offset++] = 0x10;
node_data[offset++] = 0x00;
/* maxKeyLength = 10 */
node_data[offset++] = (MAX_KEY_LENGTH >> 8) & 0xFF;
node_data[offset++] = MAX_KEY_LENGTH & 0xFF;
/* totalNodes */
node_data[offset++] = (total_nodes >> 24) & 0xFF;
node_data[offset++] = (total_nodes >> 16) & 0xFF;
node_data[offset++] = (total_nodes >> 8) & 0xFF;
node_data[offset++] = total_nodes & 0xFF;
/* freeNodes = total_nodes - 1 (only header used) */
uint32_t free_nodes = total_nodes - 1;
node_data[offset++] = (free_nodes >> 24) & 0xFF;
node_data[offset++] = (free_nodes >> 16) & 0xFF;
node_data[offset++] = (free_nodes >> 8) & 0xFF;
node_data[offset++] = free_nodes & 0xFF;
/* reserved1 */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* clumpSize (obsolete, use extents clump from volume header) */
uint32_t clump_size = params->extents_file_size;
node_data[offset++] = (clump_size >> 24) & 0xFF;
node_data[offset++] = (clump_size >> 16) & 0xFF;
node_data[offset++] = (clump_size >> 8) & 0xFF;
node_data[offset++] = clump_size & 0xFF;
/* btreeType = 255 (0xFF = Extents Overflow) */
node_data[offset++] = 0xFF;
/* keyCompareType = 0 (simple comparison for extents keys) */
node_data[offset++] = 0x00;
/* attributes = 0 (no special attributes) */
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
node_data[offset++] = 0x00;
/* reserved3[16] (64 bytes) - already zeroed by calloc */
offset += 64;
/* Map record (128 bytes showing only node 0 allocated) */
/* Set bit 0 to mark header as used */
int map_offset = NODE_SIZE - 256; /* Map record at end of node */
node_data[map_offset] = 0x80; /* Binary: 10000000 = node 0 used */
/* Record offsets at end of node (3 records + free space offset) */
/* Offset table grows backwards from end of node */
int rec_offset_base = NODE_SIZE - 2;
/* Free space offset (points after map record) */
uint16_t free_offset = map_offset + 256;
node_data[rec_offset_base--] = (free_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = free_offset & 0xFF;
/* Record 2: map record offset */
uint16_t map_rec_offset = map_offset;
node_data[rec_offset_base--] = (map_rec_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = map_rec_offset & 0xFF;
/* Record 1: user data record (empty, 128 bytes reserved) */
uint16_t user_offset = map_offset - 128;
node_data[rec_offset_base--] = (user_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = user_offset & 0xFF;
/* Record 0: BTHeaderRec offset */
uint16_t header_offset = 14;
node_data[rec_offset_base--] = (header_offset >> 8) & 0xFF;
node_data[rec_offset_base--] = header_offset & 0xFF;
/* Write header node */
if (lseek(fd, extents_offset, SEEK_SET) == -1) {
error_print_errno("failed to seek to extents file location");
free(extents_data);
free(node_data);
return -1;
}
if (write(fd, extents_data, params->extents_file_size) != (ssize_t)params->extents_file_size) {
error_print_errno("failed to write extents file");
free(extents_data);
if (write(fd, node_data, NODE_SIZE) != NODE_SIZE) {
error_print_errno("failed to write extents header node");
free(node_data);
return -1;
}
free(extents_data);
/* Zero out remaining extents nodes (all free) */
memset(node_data, 0, NODE_SIZE);
for (uint32_t i = 1; i < total_nodes; i++) {
if (write(fd, node_data, NODE_SIZE) != NODE_SIZE) {
error_print_errno("failed to write extents empty nodes");
free(node_data);
return -1;
}
}
free(node_data);
return 0;
}
+9 -1
View File
@@ -345,5 +345,13 @@ int main(int argc, char *argv[])
cleanup_options(&opts);
common_cleanup();
return result;
/* Normalize exit code to Unix standard (0=success, 1=any error) */
/* Preserve detailed code in verbose mode for debugging */
if (result != 0) {
if (opts.verbose) {
fprintf(stderr, "Internal exit code: %d\n", result);
}
return 1; /* Unix standard: any error = exit code 1 */
}
return 0;
}
+19 -7
View File
@@ -39,7 +39,8 @@ static mkfs_options_t default_options = {
.show_help = 0,
.show_license = 0,
.block_size = 0, /* Auto-calculate */
.total_size = 0 /* Use full device */
.total_size = 0, /* Use full device */
.enable_journaling = 0 /* Disabled by default with warning */
};
@@ -56,7 +57,9 @@ static void usage(int exit_code)
printf("\n");
printf("Options:\n");
printf(" -f, --force Force creation, overwrite existing filesystem\n");
printf(" -l, --label NAME Set volume label/name (max 255 characters for HFS+)\n");
printf(" -j, --journal Enable HFS+ journaling (Linux kernel driver does NOT support)\n");
printf(" -L, --label NAME Set volume label/name (also accepts -l)\n");
printf(" -s, --size SIZE Specify filesystem size in bytes (supports K, M, G suffixes)\n");
printf(" -v, --verbose Display detailed formatting information\n");
printf(" -V, --version Display version information\n");
printf(" -h, --help Display this help message\n");
@@ -69,6 +72,7 @@ static void usage(int exit_code)
printf("Examples:\n");
printf(" %s /dev/sdb1 # Format partition as HFS+\n", program_name);
printf(" %s -l \"My Volume\" /dev/sdb1 # Format with custom label\n", program_name);
printf(" %s -s 1073741824 disk.img # Create 1GB filesystem\n", program_name);
printf(" %s -f /dev/sdb 1 # Force format partition 1\n", program_name);
printf(" %s -f /dev/sdb 0 # Format entire disk (erases partition table)\n", program_name);
printf(" %s -v /dev/fd0 # Format floppy with verbose output\n", program_name);
@@ -82,10 +86,10 @@ static void usage(int exit_code)
printf("\n");
printf("Exit codes:\n");
printf(" 0 Success\n");
printf(" 1 General error\n");
printf(" 2 Usage error\n");
printf(" 4 Operational error\n");
printf(" 8 System error\n");
printf(" 1 Error (any kind)\n");
printf("\n");
printf("Note: Exit codes follow Unix standard (0=success, 1=error).\n");
printf(" Use -v for detailed error information.\n");
printf("\n");
exit(exit_code);
@@ -166,5 +170,13 @@ int main(int argc, char *argv[])
mkfs_cleanup_options(&opts);
common_cleanup();
return result;
/* Normalize exit code to Unix standard (0=success, 1=any error) */
/* Preserve detailed code in verbose mode for debugging */
if (result != 0) {
if (opts.verbose) {
fprintf(stderr, "Internal exit code: %d\n", result);
}
return 1; /* Unix standard: any error = exit code 1 */
}
return 0;
}