На Mac с диском 256–512 ГБ место заканчивается регулярно. Обычный путь известен: открыть анализатор диска, найти самое большое, перетащить на внешний SSD и удалить оригинал. Если разобраться, что при этом ломается, окажется, что ломается много чего, и чаще всего тихо.
Ниже — грабли, которые я собрал, пока писал инструмент для такого переноса, и как их обойти. Всё это пригодится и при переносе руками.
«Скопировалось» ещё не значит «скопировалось правильно»
Finder и cp сообщают об успехе, когда данные отданы системе. Дошли ли они до диска целиком, никто не проверяет. На дешёвом USB-контроллере, плохом кабеле или выдернутом раньше времени диске копия может отличаться от оригинала, а оригинал вы уже удалили.
Надёжный перенос идёт в четыре шага:
копирование;
копия перечитывается с диска в обход кеша и сверяется по SHA-256 с оригиналом;
проверка, что оригинал не менялся, пока шло копирование: размеры, время изменения, состав папок;
только после этого — удаление оригинала.
Про второй шаг легко забыть: если сразу после копирования посчитать хеш, macOS отдаст данные из страничного кеша, и сверка пройдёт, даже если на диск записался мусор. Читать нужно в обход кеша — флагом F_NOCACHE:
let fd = open(path, O_RDONLY) _ = fcntl(fd, F_NOCACHE, 1) // читать с диска, а не из памяти
В командной строке ближе всего к этому — отключить диск, подключить снова и только потом сверять:
cd /Volumes/External/backup && shasum -a 256 -c checksums.txt
Список контрольных сумм стоит класть рядом с копией в формате shasum -c: тогда её можно проверить через пять лет на другом компьютере без всяких утилит.
exFAT теряет больше, чем кажется
Внешние диски почти всегда отформатированы в exFAT, чтобы работать и с Windows. exFAT не хранит права доступа и расширенные атрибуты. Скопируйте туда проект со скриптами, верните обратно — и ./build.sh перестанёт запускаться: бит исполнения потерян.
Что помогает: перед переносом сохранить права всех файлов в отдельный файл рядом с архивом и восстановить их при возврате. Если такого списка нет, разумные права по умолчанию: папки 755, файлы 644, а скрипты и бинарники (их можно узнать по #! в начале и по заголовку Mach-O) — исполняемые.
Вторая неприятность — файлы ._имя, которые macOS создаёт на exFAT для хранения атрибутов. После сверки их можно удалять. А если у вас есть настоящий файл, имя которого начинается с ._, на exFAT его лучше не класть вовсе: macOS молча перезапишет его своими данными.
Метки Finder, комментарии и жёсткие ссылки при таком переносе тоже не сохраняются. Если они важны, об этом надо знать до удаления оригинала, а не после.
Некоторые папки переносить нельзя совсем
Самый неприятный случай — UTM. Переносите папку с виртуальными машинами на внешний диск, возвращаете обратно, а UTM показывает машины как «Недоступно». Данные целы, но приложение хранит ссылки на файлы, и после переноса они ведут в никуда. То же самое с медиатекой «Фото».
Правило простое: пакеты со внутренними ссылками (.utm, .photoslibrary и подобные) не переносить, даже если они лежат глубоко внутри выбранной папки. Место под них освобождается в самом приложении: в UTM — его собственным удалением или переносом машины. ~/Library, ~/.ssh и ~/.config тоже лучше не трогать.
Docker умеет съесть диск, пока вы его чистите
Задача: заархивировать неиспользуемые тома Docker на внешний диск. Логичный способ — запустить контейнер, который делает tar тома в stdout:
docker run --rm -v myvolume:/data alpine tar -C /data -cf - . > /Volumes/External/myvolume.tar
Проблема: Docker по умолчанию пишет весь вывод контейнера в свой лог внутри Docker.raw. Поток архива на 30 ГБ сначала целиком ложится в этот лог, и диск заканчивается ровно в тот момент, когда вы пытаетесь его освободить.
Что нужно:
запускать контейнер с
--log-driver none(и заодно--network none: сеть ему не нужна);давать контейнеру имя и при отмене останавливать сам контейнер, а не только клиент
docker: иначе контейнер продолжает писать;следить за размером
Docker.rawи прерывать операцию, если он начинает расти.
docker run --rm --name vol-archive --log-driver none --network none \ -v myvolume:/data:ro alpine tar -C /data -cf - . > /Volumes/External/myvolume.tar
При обычной очистке тома лучше не трогать совсем: в них данные. Безопасно удаляются кеш сборки и образы без имени (<none>, остатки пересборок). Собранный вами и никуда не отправленный образ заново не скачать, поэтому его стоит удалять только осознанно.
Шифрованный контейнер без своей криптографии
Если на внешний диск уходят проекты и ключи, их стоит шифровать. Изобретать для этого ничего не нужно: штатный hdiutil создаёт образ AES-256 с APFS внутри, а шифрование написала и проверяет Apple.
hdiutil create -size 200g -type SPARSEBUNDLE -fs APFS -encryption AES-256 \ -stdinpass -volname Vault /Volumes/External/Vault.sparsebundle
Пара деталей, которые оказались важными при работе с ним из программы:
пароль передаётся только через stdin (
-stdinpass), не в аргументах: аргументы видны любому процессу черезps;до открытия образа стоит проверить, что он действительно зашифрован (
hdiutil isencrypted), а после — что система смонтировала его как зашифрованный (hdiutil info, полеimage-encrypted). Иначе подменённый образ без шифрования откроется молча, и всё уйдёт в него открытым текстом;некоторые вызовы
hdiutilсами показывают системное окно с запросом пароля. Из программы их лучше не вызывать, чтобы пароль вводился в одном предсказуемом месте;образ стоит закрывать при сне, блокировке экрана и простое.
Скрытых томов и правдоподобного отрицания у такого контейнера нет. Если нужно именно это — VeraCrypt.
Итого
Безопасный перенос — это не «скопировать и удалить», а:
сверка копии, перечитанной с диска;
проверка, что оригинал не менялся;
сохранённые права доступа рядом с архивом;
список того, что переносить нельзя;
для Docker — отключённый лог контейнера.
Всё это я собрал в открытой утилите для macOS, код и проверки — на GitHub: https://github.com/audit0/Offload
Расскажите в комментариях, на какие грабли с переносом данных наступали вы.

