홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

macOS에서 NTFS 외장하드에 쓰기 — fuse-t로 ntfs-3g 빌드하기

정기창·2026년 7월 14일

얼마 전 로컬 디스크를 15GB에서 82GB까지 비운 일이 있었습니다. 그러려면 밀린 파일들을 어딘가로 옮겨 백업해야 했는데, 마침 손에 있던 것이 WD Elements 외장하드였습니다. 문제는 이 디스크가 NTFS로 포맷돼 있었다는 점입니다. macOS는 NTFS를 읽기 전용으로만 마운트해 줍니다. 읽을 수는 있어도 쓸 수가 없으니, 백업 용도로는 무용지물이었습니다.

유료 앱을 사면 간단히 풀리는 문제이긴 합니다. 다만 이왕이면 커널 확장(kext) 없이, 그리고 가능하면 sudo도 최소로 해결해 보고 싶었습니다. 그 과정에서 밟은 함정들을 순서대로 남깁니다.

익숙한 길은 전부 막혀 있었습니다

NTFS 쓰기라고 하면 보통 ntfs-3g를 떠올립니다. 그런데 Homebrew의 ntfs-3g(core)는 빌드 자체를 거부했습니다.

Error: ntfs-3g: Linux is required for this software.

대안으로 알려진 gromgit/fuse/ntfs-3g-mac은 macFUSE, 즉 kext를 하드하게 요구합니다. 요즘 macOS에서 kext는 보안 정책상 부담이 크고, 저는 애초에 그걸 피하려던 참이었습니다. 익숙한 두 길이 모두 막힌 셈입니다.

fuse-t와 소스 빌드로 우회했습니다

출구는 fuse-t였습니다. fuse-t는 kext 없이 NFS를 기반으로 동작하는 macOS용 FUSE 구현입니다. 여기에 ntfs-3g는 Homebrew 대신 소스에서 직접 빌드하기로 했습니다. 빌드 자체에는 sudo가 필요 없고, 사용자 홈 아래 $HOME/.local/ntfs-3g를 prefix로 잡으면 됩니다. 다만 configure를 통과시키기까지 네 개의 함정을 차례로 넘어야 했습니다.

1) fuse.pc 버전 게이트. ntfs-3g의 configure는 pkg-config로 fuse >= 2.6.0을 요구합니다. 그런데 fuse-t.pc의 Version은 1.2.7이라 이 검사를 통과하지 못합니다. 실제 ABI는 fuse-t가 제공하는 fuse2 헤더(/usr/local/include/fuse)로 호환되므로, 버전 문자열만 손보면 됩니다. fuse-t.pc를 ~/.local/pkgconfig/fuse.pc로 복사한 뒤 두 줄을 고쳤습니다.

Name: fuse-t   →  Name: fuse
Version: 1.2.7 →  Version: 2.9.9

2) _FILE_OFFSET_BITS 컴파일 에러. fuse_common.h는 이 매크로가 정의돼 있지 않으면 하드하게 #error를 냅니다. CFLAGS에 -D_FILE_OFFSET_BITS=64를 넣어 해결했습니다.

3) install-exec-hook의 엉뚱한 이동. make install 단계에서 훅이 .so 파일을 루트의 /lib로 옮기려다 실패했습니다. autoconf가 그 시점에 exec_prefix를 literal "NONE"으로 읽어버리는 탓입니다. --exec-prefix를 --prefix와 똑같이 명시해 주면 이 이동이 no-op가 되어 조용히 지나갑니다.

4) glibtoolize PATH shim. libtoolize는 macOS에서 이름이 glibtoolize라, PATH에 /opt/homebrew/opt/libtool/libexec/gnubin을 얹어 shim을 잡아줘야 했습니다.

네 가지를 모두 반영한 최종 configure는 다음과 같습니다.

export PATH="/opt/homebrew/opt/libtool/libexec/gnubin:$PATH"
PKG_CONFIG_PATH=~/.local/pkgconfig:/usr/local/lib/pkgconfig \
CFLAGS="-I/usr/local/include/fuse -D_FILE_OFFSET_BITS=64" \
LDFLAGS="-L/usr/local/lib" \
./configure --prefix=$HOME/.local/ntfs-3g --exec-prefix=$HOME/.local/ntfs-3g \
  --with-fuse=external --disable-ldconfig

빌드가 끝난 뒤 검증은 버전 출력과 링크 확인으로 했습니다.

ntfs-3g --version
# ntfs-3g 2026.2.25 external FUSE 29

otool -L ~/.local/ntfs-3g/bin/ntfs-3g
# @rpath/libfuse-t.dylib 로 링크, LC_RPATH=/usr/local/lib 내장

마운트에서 만난 raw와 block의 차이

여기서부터는 sudo가 필요합니다. 디바이스 노드가 root:operator 소유라 마운트만큼은 권한이 있어야 하기 때문입니다. 그리고 이 단계에서 이번 삽질의 가장 값진 함정을 만났습니다. raw 디바이스로 마운트하면 안 된다는 것입니다.

처음에 /dev/rdisk6s1(raw 디바이스)로 마운트했더니, 정렬되지 않은 read가 곧바로 깨졌습니다.

ntfs_pread failed: Invalid argument
Failed to read NTFS $Bitmap

raw 디바이스는 블록 단위 정렬을 요구해서, ntfs-3g가 임의 위치를 읽으려 하면 커널이 거부합니다. 반면 블록 디바이스인 /dev/disk6s1은 캐시 계층이 정렬을 흡수해 줍니다. 접두어 하나(r) 차이였습니다.

sudo ~/.local/ntfs-3g/bin/ntfs-3g /dev/disk6s1 /Volumes/ElementsRW \
  -o uid=501,gid=20,allow_other,noatime,volname=ElementsRW

마운트가 성공하면 fuse-t답게 NFS로 붙습니다. mount 출력에 fuse-t:/ElementsRW ... (nfs)로 찍히는 게 그 증거입니다. 쓰기 속도는 실측으로 약 76MB/s가 나왔습니다. 외장 HDD 백업 용도로는 충분한 속도였습니다.

언마운트도 순순히 되지 않았습니다

백업 검증을 위해 백그라운드에서 unzip을 돌려두고 있었는데, 그 프로세스가 볼륨을 붙잡고 있어 언마운트가 "Resource busy"로 실패했습니다. lsof로 볼륨을 잡은 프로세스를 찾아 종료한 뒤에야 풀렸습니다. 한 가지 더, 여기서는 umount가 아니라 diskutil을 써야 했습니다. umount는 같은 Resource busy로 실패합니다.

lsof /Volumes/ElementsRW               # 볼륨을 잡은 프로세스 확인 후 종료
diskutil unmount /Volumes/ElementsRW   # umount 는 Resource busy 로 실패
diskutil eject disk6                   # 파티션이 아니라 물리 디스크를 eject

한글 파일명이 어긋나 보인 진짜 이유

마지막으로 백업이 온전한지 대조하다가 한 번 더 당황했습니다. zip으로 묶어 옮긴 뒤 파일 개수를 세어봤더니 원본과 어긋나 보였던 것입니다. 파일이 누락된 줄 알고 한참을 헤맸지만, 범인은 한글 파일명의 정규화 방식이었습니다.

macOS는 한글을 NFD, 즉 자모를 분리한 형태로 저장합니다. '한'을 'ㅎ+ㅏ+ㄴ'으로 다루는 식입니다. 반면 zipinfo는 이걸 또 이스케이프해서 보여주기 때문에, 겉보기 문자열만 비교하면 서로 다른 파일처럼 보입니다. NFC(결합형)로 정규화한 뒤 basename을 대조하니 개수가 완전히 일치했고, 실제로 몇 개를 추출해 원본과 바이트 단위까지 같은지 확인했습니다.

사실 한글 폴더를 개별 파일로 복사하지 않고 굳이 zip으로 묶어 옮긴 이유도 같은 정규화 문제 때문이었습니다. NFD 상태 그대로 NTFS를 거쳐 Windows에서 열면 자모가 분리된 채로 보일 수 있어서, 한 겹 감싸 두는 편이 안전하다는 판단이었습니다.

정리하며 — kext 없는 macOS NTFS 쓰기

돌이켜보면 요즘 macOS에서 NTFS 쓰기는 유료 앱 없이도, kext 없이도 fuse-t로 충분히 가능합니다. 다만 그 길은 매끄러운 포장도로가 아니라, pkg-config 버전 게이트와 raw/block 디바이스의 차이, 그리고 한글 정규화 같은 함정이 곳곳에 놓인 오솔길에 가깝습니다. 저처럼 디스크를 비우려다 얼떨결에 이 길로 들어선 분에게, 이 기록이 몇 개의 함정이라도 미리 알려주는 지도가 되면 좋겠다는 생각이 듭니다.

macOSNTFSfuse-tntfs-3gFUSE외장하드소스 빌드

관련 글

RAM 32GB가 남는데 프로세스가 죽었다 — macOS 스왑과 Jetsam OOM

RAM 32GB가 넉넉히 남아도 macOS 프로세스는 SIGKILL로 죽을 수 있습니다. 진짜 원인은 디스크 여유 공간 부족으로 동적 스왑을 늘리지 못한 것이었고, Jetsam이 개입하는 위험의 임계는 사용률 %가 아니라 절대 여유 GB라는 사실을 실측으로 확인했습니다.

관련도 87%

맥북에서 jest가 OOM 났는데, 알고 보니 macOS 메모리 지표가 거짓말이었다 — ts-jest에서 @swc/jest로

jest가 OOM 날 것 같아 중단했지만, 알고 보니 macOS의 Pages free 지표가 거짓말이었습니다. memory_pressure로 본 실제 여유는 87%. 진짜 원인은 Jest 기본 워커 수와 ts-jest의 V8 heap 부담이었고, @swc/jest로 이관해 heap 50%·속도 5배 개선한 기록입니다.

관련도 86%

Vrew autosave_backup 96GB 자동 정리 — Windows 자동화 함정 5가지

Vrew 의 autosave_backup 폴더가 Windows 의 %APPDATA% 에 96.5GB 까지 누적되는 사례를 PowerShell + Task Scheduler 로 30 분마다 자동 정리했습니다. UTF-8 BOM, vertical tab, SSH ACL 같은 Windows 자동화 함정 다섯 가지도 함께 정리합니다.

관련도 86%