From 409acd3082a40c111091075fec085a116d99fa5c Mon Sep 17 00:00:00 2001 From: Frank Loesche Date: Tue, 3 Mar 2026 18:14:59 -0500 Subject: [PATCH] try to remove attributes before writing to FAT32 --- .../fixtures/prepare_sd_card_crossplatform.m | 62 +++++-------------- tests/test_sd_card_deployment.m | 28 ++++----- 2 files changed, 29 insertions(+), 61 deletions(-) diff --git a/tests/fixtures/prepare_sd_card_crossplatform.m b/tests/fixtures/prepare_sd_card_crossplatform.m index 0488a2c..48d7693 100644 --- a/tests/fixtures/prepare_sd_card_crossplatform.m +++ b/tests/fixtures/prepare_sd_card_crossplatform.m @@ -62,8 +62,8 @@ % - Format option: Windows (auto), Mac (with confirmation prompt) % - ValidateDriveName: checks 'PATSD' on both Windows (vol) and Mac (mount path) % - On Mac, formatting clears the FAT table, ensuring reliable dirIndex order -% - macOS dot-files (._*) are automatically cleaned from FAT32 volumes -% (AppleDouble resource forks corrupt G4.1 controller dirIndex ordering) +% - On macOS, extended attributes are stripped (xattr -c) before copying +% to FAT32, preventing AppleDouble ._* files that corrupt dirIndex ordering % % See also: prepare_sd_card, deploy_experiments_to_sd @@ -407,6 +407,17 @@ end end + %% Strip extended attributes (prevents macOS AppleDouble ._* files on FAT32) + if ismac + for i = 1:num_patterns + src = fullfile(staging_dir, 'patterns', sprintf('pat%04d.pat', i)); + system(sprintf('xattr -c "%s"', src)); + end + system(sprintf('xattr -c "%s"', bin_path)); + system(sprintf('xattr -c "%s"', txt_path)); + fprintf(' ✓ Cleared extended attributes (prevents ._* files on FAT32)\n'); + end + %% Copy patterns to SD card (FIRST - for correct dirIndex order) fprintf(' Copying %d patterns...\n', num_patterns); try @@ -421,30 +432,6 @@ end fprintf(' ✓ Copied %d patterns\n', num_patterns); - %% Clean up macOS resource fork files (._* files) - % macOS creates AppleDouble "._" files when copying to FAT32 volumes. - % These are invisible in Finder but occupy FAT32 directory entries, - % which shifts dirIndex ordering and causes the G4.1 controller to - % load wrong patterns. We must remove them before writing manifests. - if ismac - dot_files = dir(fullfile(target_dir, '._*')); - if ~isempty(dot_files) - fprintf(' Cleaning %d macOS resource fork files (._*)...\n', length(dot_files)); - for i = 1:length(dot_files) - delete(fullfile(target_dir, dot_files(i).name)); - end - fprintf(' ✓ Removed %d dot-files (prevents dirIndex corruption)\n', length(dot_files)); - end - - % Also clean root-level dot-files (in case manifests create them) - dot_files_root = dir(fullfile(sd_root, '._*')); - if ~isempty(dot_files_root) - for i = 1:length(dot_files_root) - delete(fullfile(sd_root, dot_files_root(i).name)); - end - end - end - %% Copy manifest files to SD card (AFTER patterns for correct dirIndex) try copyfile(bin_path, fullfile(sd_root, 'MANIFEST.bin')); @@ -454,29 +441,10 @@ return; end fprintf(' ✓ Copied manifest files\n'); - - %% Final macOS dot-file cleanup (manifests may have created new ones) - if ismac - % Clean patterns dir - dot_final = dir(fullfile(target_dir, '._*')); - for i = 1:length(dot_final) - delete(fullfile(target_dir, dot_final(i).name)); - end - % Clean root dir - dot_final_root = dir(fullfile(sd_root, '._*')); - for i = 1:length(dot_final_root) - delete(fullfile(sd_root, dot_final_root(i).name)); - end - if ~isempty(dot_final) || ~isempty(dot_final_root) - fprintf(' ✓ Final dot-file cleanup: removed %d files\n', ... - length(dot_final) + length(dot_final_root)); - end - end - %% Verify (exclude macOS ._* resource fork files from count) + %% Verify all_pat = dir(fullfile(target_dir, '*.pat')); - real_pat = all_pat(~startsWith({all_pat.name}, '._')); - verify_count = length(real_pat); + verify_count = length(all_pat); if verify_count ~= num_patterns mapping.error = sprintf('Verification failed: expected %d patterns, found %d on SD card', ... num_patterns, verify_count); diff --git a/tests/test_sd_card_deployment.m b/tests/test_sd_card_deployment.m index 0290bea..18c9b6b 100644 --- a/tests/test_sd_card_deployment.m +++ b/tests/test_sd_card_deployment.m @@ -12,7 +12,7 @@ % 2. MANIFEST.bin binary format (uint16 count + uint32 timestamp) % 3. MANIFEST.txt human-readable mapping % 4. Verification count accuracy -% 5. macOS dot-file cleanup (AppleDouble resource fork removal) +% 5. macOS dot-file prevention (xattr -c before copy to FAT32) % 6. ValidateDriveName on Mac (volume name from mount path) % 7. detect_sd_card utility (when UseRealSD=true) % 8. Format + deploy pipeline (when UseRealSD=true, DESTRUCTIVE) @@ -220,34 +220,34 @@ record_fail('1.7 Mapping struct fields', 'Mapping not available'); end - % Test 1.8: macOS dot-file cleanup simulation + % Test 1.8: macOS dot-file prevention (xattr -c) if ismac - fprintf('\n [Mac-specific] Testing dot-file cleanup...\n'); + fprintf('\n [Mac-specific] Testing dot-file prevention...\n'); - % Clean and redo with injected dot-files + % Clean and redo if isfolder(fake_sd), rmdir(fake_sd, 's'); end mkdir(fake_sd); mapping2 = prepare_sd_card_crossplatform(pattern_paths, fake_sd, ... 'ValidateDriveName', false); - % Check no dot-files remain + % Check no dot-files were created dot_files_pat = dir(fullfile(fake_sd, 'patterns', '._*')); dot_files_root = dir(fullfile(fake_sd, '._*')); total_dots = length(dot_files_pat) + length(dot_files_root); if total_dots == 0 && mapping2.success - record_pass('1.8 macOS dot-file cleanup', ... - 'No ._* files remain on fake SD'); + record_pass('1.8 macOS dot-file prevention', ... + 'No ._* files on fake SD (xattr -c worked)'); elseif ~mapping2.success - record_fail('1.8 macOS dot-file cleanup', ... + record_fail('1.8 macOS dot-file prevention', ... sprintf('Deployment failed: %s', mapping2.error)); else - record_fail('1.8 macOS dot-file cleanup', ... + record_fail('1.8 macOS dot-file prevention', ... sprintf('%d dot-files still present', total_dots)); end else - record_pass('1.8 macOS dot-file cleanup', 'Skipped (not macOS)'); + record_pass('1.8 macOS dot-file prevention', 'Skipped (not macOS)'); end % Test 1.9: ValidateDriveName with wrong name @@ -356,15 +356,15 @@ real_target = mapping_real.target_dir; dot_check = dir(fullfile(real_target, '._*')); if isempty(dot_check) - record_pass('3.3 Real SD dot-file clean', ... + record_pass('3.3 Real SD no dot-files', ... 'No ._* files on SD card'); else - record_fail('3.3 Real SD dot-file clean', ... + record_fail('3.3 Real SD no dot-files', ... sprintf('%d dot-files found — dirIndex may be corrupted', ... length(dot_check))); end else - record_pass('3.3 Real SD dot-file clean', 'Skipped (not macOS)'); + record_pass('3.3 Real SD no dot-files', 'Skipped (not macOS)'); end % Test 3.4: Verify pattern count on real SD @@ -379,7 +379,7 @@ end else record_fail('3.2 Real SD deployment', mapping_real.error); - record_fail('3.3 Real SD dot-file clean', 'Deployment failed'); + record_fail('3.3 Real SD no dot-files', 'Deployment failed'); record_fail('3.4 Real SD pattern count', 'Deployment failed'); end end