Случайно затертые или замененные файлы — бич абсолютно всех пользователей Linux. Любой из нас хоть раз в жизни лишался важных данных из-за банальной халатности: одна команда — и данные, над которым работал многие дни и недели, исчезает в одно мгновение. И в большинстве случаев их уже не вернуть. В этой статье я расскажу, как избежать таких ситуаций.

 

Введение в проблему

В каких случаях мы обычно теряем файлы? Я бы выделил два наиболее популярных сценария:

  1. «Что это за старое файло? В мусорку!». Зачастую мы удаляем важные файлы просто по ошибке, или думая, что они уже не содержат важной для нас информации. Это стандартный сценарий, знакомый всем.
  2. «Щас я исправлю этот файл, и та штука заработает быстрее». Более сложный вариант, при котором человек хочет сделать лучше, но получает худший вариант, а когда пытается вернуть все на место, уже точно не помнит, что содержал файл изначально. Это типичная проблема кодеров, сисадминов, веб-дизайнеров и просто экспериментаторов. И одна из причин появления систем контроля версий.

Как избежать таких ситуаций? Очень просто: не попадать в них. А если серьезно, то нам нужна какая-то система, которая бы помнила содержимое всех наших файлов и хранила бы их прошлые версии (включая те, оригиналы которых были удалены). Тогда в любой момент времени мы сможем вернуть все на место без потерь. Еще лучше, если система предоставит способ синхронизировать этот архив на удаленную машину, чтобы данные остались с нами даже в случае тотального краха всего и вся.

 

Простейшая система отката

Не заморачиваясь с чем-то более сложным и требующем настройки, попробуем представить себе, как может выглядеть простейшая система отката файлов к предыдущим версиям. Скорее всего, это будет нечто вроде этого:

// Хммм, я хочу отредактировать файл confi g.cfg, но не хочу его потерять
$ cp confi g.cfg confi g.cfg.bak
// Окей, теперь можно редактировать
$ vim confi g.cfg
// Бррр, теперь ничего не работает, придется вернуть оригинал
$ mv confi g.cfg.bak confi g.cfg

Готов поспорить, что хоть раз в жизни ты так делал. Я тоже. И это хорошо, однако вводить лишние команды перед каждым редактированием файла оказывается не очень-то удобно. Со временем начинаешь просто забивать на резервные копии.

Мы можем развить идею и написать функцию, которая будет делать бэкап автоматически, прямо во время открытия файла на редактирование. Также можно написать функцию, которая вернет файл из бэкапа.

Выглядеть они могут так:

vim() {
FILE=$1
DATE=date +'%F'
BAK=.$FILE.bak
cp $FILE $BAK-$DATE
rm -f $BAK
ln -s $BAK-DATE $BAK
vim $FILE
}

mv .$1.bak $1
}

Поместив их в файл ~/.bashrc, ты получишь команду vim, которая будет бэкапить файл при каждом его открытии (делая его скрытым и помечая датой), и команду ret, которая позволит вернуть файл из самого свежего бэкапа на место. Это довольно действенный способ, и я использую его для конфигурирования удаленных серверов, но на домашней машине он окажется не очень эффективным. Здесь файлы могут быть далеко не текстовыми, а в качестве редактора использоваться куча разных программ. Более гибкая система должна перехватывать любые попытки изменить файл на уровне системы и делать бэкап автоматически.

 

Доверимся ядру

Трудно найти более подходящий инструмент для тотальной слежки за файлами, чем Inotify. Работая внутри Linuxядра, эта подсистема не пропустит ни одного изменения, открытия или закрытия файла, а мы сможем узнать об этом с помощью простой консольной команды под названием inotifywait.

Inotifywait(которая входит в состав пакета inotify-tools) — примитивная утилита, она ждет указанного события, связанного с определенным файлом, а затем завершается или пишет в лог о том, что произошло. Ее очень удобно использовать в скриптах: достаточно просто добавить вызов команды в начало скрипта, а далее поместить код, манипулирующий файлом или каталогом. Для нашей задачи inotifywait подходит идеально, в качестве события мы можем указать не просто доступ к файлу, а завершение операции его изменения, тем самым защитив себя от ситуаций, когда файл открывается просто «на посмотреть»:

$ vi ~/bin/in-back.sh
#!/bin/sh
DIR=pwd
while inotifywait -r -e modify $DIR; do
cp $DIR ~/bak/.$DIR.bak
done;

Этот скрипт будет делать резервную копию текущего каталога каждый раз, когда один из файлов внутри него будет изменен. Это самая простая реализация скрипта, которая не учитывает сообщения самого inotifywait и поэтому делает работу очень грязным методом, просто копируя весь имеющийся каталог. Более сложная реализация может выглядеть так:

$ vi ~/bin/in-back2.sh
#!/bin/sh
DIR=pwd
inotifywait -mr --timefmt '%d-%m-%y %H-%M'
--format'%T %f' -e close_write $DIR |
while read DATE TIME FILE; do
cp $FILE .$FILE.bak-$DATE-$TIME
done

Это своего рода универсальная версия подхода, рассмотренного в начале статьи. С помощью флага '-m' мы заставили inotifywait писать лог изменений файлов в стандартный вывод, с помощью опций '--timefmt' и '--format' мы изменили ее вывод так, чтобы в лог попадали дата, время, а также имя изменяемого файла. Далее мы сделали цикл, который читает этот лог и копирует измененные файлы, делая их скрытыми и добавляя к имени дату и время изменения. Впоследствии все сохраненные таким образом файлы можно будет увидеть с помощью такой команды:

$ ls -la | grep -e '.*.bak-.*'

А удалить — с помощью такой:

$ rm -rf *.bak-*

Во второй части статьи, когда мы будем говорить о способах синхронизации файлов между машинами, я покажу более развитую версию этого скрипта, а пока рассмотрим инструменты incron и fsniper, которые делают работу с inotify более удобной.

 

Планировщик для файлов

Утилита inotifywait удобна и проста в использовании, она хорошо подходит для тривиальных скриптов, но реализовать систему тотальной слежки за файлами с ее помощью довольно сложно. Поэтому мы воспользуемся более высокоуровневым инструментом под названием incron.

Демон incron, как нетрудно догадаться по названию, это Inotify-версия стандартного cron. Он читает список правил, затем переходит в фон и ждет, пока не наступит описанное в правилах событие. Когда это происходит, запускается указанное приложение/скрипт, которому могут быть переданы такие аргументы, как время модификации, имя файла и каталога и другие. Всего их четыре:

  • $@– каталог/файл, за которым ведется наблюдение
  • $#– имя файла, с которым связано произошедшее событие
  • $%– флаги события (в текстовом формате)
  • $& – флаги события (в числовом формате)

Для добавления событий и правил используется cron-подобная утилита incrontab, вызов которой с флагом '-i' приведет к распечатке текущего списка правил. Для добавления новых правил используем уже знакомый по cron флаг '-e'. Откроется редактор, в который можно вписать правила и закрепленные за ними команды, используя следующий шаблон:

[путь] [действие] [команда]

Здесь «путь» — это путь до файла/каталога, «действие» — операция, совершаемая над файлом, а «команда» — это команда, которая будет выполнена в случае возникновения действия по отношению к указанному файлу (в качестве аргументов могут быть использованы приведенные выше метапеременные).

Список поддерживаемых действий полностью совпадает со списком действий самой подсистемы inotify и команды inotifywait. Вот он:

  • IN_ACCESS — Произошло обращение к файлу (например, чтение)
  • IN_ATTRIB — Метаданные файла (такие как владелец или права доступа) были изменены
  • IN_CLOSE_WRITE File — Файл, открытый для записи, был успешно закрыт
  • IN_CLOSE_NOWRITE File — Файл, открытый не для записи, был закрыт
  • IN_CREATE — В наблюдаемом каталоге был создан файл
  • IN_DELETE — Файл был удален из наблюдаемого каталога
  • IN_DELETE_SELF — Был удален сам наблюдаемый каталог
  • IN_MODIFY — Файл был изменен
  • IN_MOVE_SELF — Наблюдаемый каталог/файл был перемещен
  • IN_MOVED_FROM — Файл был перемещен за границы наблюдаемого каталога
  • IN_MOVED_TO — Файл был перемещен в наблюдаемый каталог
  • IN_OPEN — Файл был открыт

Для управления тем, кто может добавлять правила, используются файлы /etc/incron.allow и /etc/incron.deny, которые содержат список разрешенных и заблокированных пользователей. По умолчанию эти файлы не существуют, поэтому создать новое правило от своего имени может любой пользователь.

Демон incrond очень удобен для решения нашей задачи. Он стартует во время старта ОС и постоянно находится в фоне, а в правильных дистрибутивах еще и перезапускается после падения. Проблемы скриптов, отваливающихся от терминала, его не касаются.

Вот простейший пример того, как можно использовать incrond для слежения и бэкапа файлов каталога /etc. Запускаем редактор правил:

$ export EDITOR=vim
$ sudo crontab -e

И пишем следующую команду:

/etc IN_CLOSE_WRITE /bin/cp $@/$# $@/.$#.bak-`/bin/date +'%F'`

Это все, теперь после каждого редактирования конфигурационного файла будет создаваться его скрытая и помеченная датой модификации копия, почти так же, как и во всех приведенных ранее примерах.

 

Каждому файлу — свое место

Существует еще более интересная inotify-утилита под названием fsniper (freshmeat.net/projects/fsniper). Для решения нашей задачи она будет не столь полезна, но я просто не могу обойти ее стороной.

Программа fsniper была написана с целью упорядочить и автоматизировать управление файлами. Аналогично incron она ждет событий, находясь в фоне, но вместо того чтобы позволить пользователю самому задавать тип событий, она умеет обрабатывать только вновь созданные файлы и на основе маски их имени определять совершаемые над ними действия.

Чтобы понять, зачем это нужно, представь, что у тебя есть каталог, в который складываются все скачанные из интернета файлы (спорю, что так оно и есть). Время от времени скопившуюся кучу информации приходится разгребать, перемещая изображения в каталог ~/images, видеофайлы — в ~/video, музыку — в ~/music и т.д. Так вот, fsniper берет на себя всю эту работу, руководствуясь составленным тобой списком правил. Один раз написав правила, ты можешь навсегда забыть о ручном труде и наслаждаться автоматической расфасовкой. Сами правила довольно просты в составлении и чтении, поэтому процесс написания правильного конфигурационного файла не займет много времени. Все, что нужно для этого сделать, это установить fsniper:

$ sudo apt-get install fsniper

Создать каталог для конфига:

$ mkdir ~/.confi g/fsniper

И поместить в него файл config примерно следующего содержания:

$ vi ~/.confi g/fsniper/confi g
watch {

Наблюдаемый каталог

~/downloads {
image/* {
handler = cp %% ~/images
}
video/* {
handler = cp %% ~/video
}
audio/* {
handler = cp %% ~/music
}
}
}

Эти правила описывают именно ту ситуацию, о которой я говорил выше, разные типы данных помещаются в разные каталоги. Обрати внимание, что в качестве метода классификации мы использовали mime-тип, также допускается использование масок файлов (например, .avi) или регулярные выражения (.HDRip.*).
Теперь можно запустить fsniper в режиме демона и наслаждаться результатом:

$ fsniper --daemon

Единственное, что нужно учесть, это то, что в отличие от incron, fsniper работает от обычного пользователя, а потому он должен быть запущен во время входа пользователя в систему или запуска графической оболочки. Пользователи Gnome и KDE могут воспользоваться встроенными конфигураторами для выполнения этой операции, для всех остальных есть файл инициализации ~/.xsession:

$ vi ~/.xsession
fsniper --daemon &

 

Путь назад

Итак, мы рассмотрели несколько hand made-способов откатить файлы к предыдущим состояниям, и теперь настало время выяснить, есть ли в Linux более унифицированные и стандартизованные способы сделать это. Есть ли здесь файловые системы, которые из коробки предоставят способ делать резервные копии файлов и восстанавливать их.

Как оказалось, такие системы есть, и их не две-три, а десяток. Одна из самых удобных и интересных из них носит имя wayback (wayback.sourceforge.net). Ее преимущество в том, что она работает поверх существующей файловой системы, а значит, не требует пересоздания файловой системы и каких бы то ни было манипуляций с существующими файлами. Достаточно просто установить wayback, используя пакетный менеджер:

$ sudo apt-get install wayback

И смонтировать ФС к нужному каталогу с помощью команды mount.wayback:

$ mount.wayback /оригинальный/каталог /точка/монтирования

Все, теперь любое изменение файла во втором каталоге приведет к прозрачному созданию его резервной копии, а следующее изменение — к появлению еще одной копии. Для просмотра списка всех бэкапов выбранного файла можно использовать команду vstat:

$ vstat файл

А чтобы вернуть его к одной из предыдущих версий — команду vrevert:

$ vrevert -d 12:00:00 файл

Так файл снова станет таким, каким он был в 12 часов. Время можно указать и поточнее, например, добавить дату:

$ vrevert -d 2011:01:01:0:00:00 файл

Хотя, скорее всего, проще будет использовать номер сохраненной версии, который выводит описанная ранее команда vstat:

$ vrevert -n 5 файл

В конце концов, чтобы все эти резервные копии не захламляли жесткий диск, их можно потереть командой vrm:

$ vrm файл

И продолжать спокойно изменять файлы, в результате чего будут плодиться все новые и новые версии. Просто, не так ли?

 

Назад, в будущее

Все рассмотренные ранее подходы используют версионность файлов для обеспечения возможности их отката к прошлым версиям. Однако иногда снапшоты могут стать более действенным способом хранения оригиналов.

Суть снапшотов состоит в том, чтобы дать пользователю возможность сделать снимок состояния файловой системы и позволить вернуть ФС к этому состоянию в любой момент времени. Обычно механизм снапшотов встроен прямо в файловую систему, поэтому абсолютно прозрачен для пользователя и удобен в использовании. Стандартные файловые системы ext3 и ext4 не поддерживают этот механизм (в последней он должен появиться в ближайшее время), зато его поддержка есть в btrfs, которая хоть и считается нестабильной, но включена в Linux-ядро (начиная с версии 2.6.29-rc). Поэтому если на твоей машине установлен достаточно свежий дистрибутив, а также имеется свободный раздел для экспериментов, настоятельно рекомендую воспользоваться этой возможностью.

Для работы с btrfs нужны утилиты, распространяемые в пакете btrfsprogs (в некоторых дистрибутивах — btrfs-progs-unstable). Их нужно установить в первую очередь:

$ sudo apt-get install btrfs-progs

Далее следует выбрать подходящий раздел, создать на нем файловую систему и смонтировать ее:

$ sudo mkfs.btrfs /dev/sdXX
$ sudo mount /dev/sdXX /mnt

Теперь файловую систему можно наполнить данными, а затем сделать снапшот с помощью следующей команды:

$ sudo btrfsctl -s fi rst_snapshot /mnt

Через какое-то время можно сделать следующий снапшот:

$ sudo btrfsctl -s second_snapshot /mnt

Количество снапшотов не ограничено, поэтому перед каждым важным изменением файлов можно делать все новые и новые снапшоты. Чтобы вернуть файловую систему к тому состоянию, в котором она находилась во время одного из снапшотов, необходимо просто смонтировать ее с опцией «subvol=имя_снапшота»:

$ sudo umount /mnt
$ sudo mount -o subvol=fi rst_snapshot /dev/hdXX /mnt

Но гораздо удобнее сразу монтировать файловую систему с опцией «subvol=.», благодаря чему все снапшоты будут видны в точке монтирования как простые каталоги:

$ sudo umount /mnt
$ mount -o subvol=. /dev/hdXX /mnt
$ ls -1
default
fi rst_snapshot
second_snapshot

При работе со снапшотами btrfs использует механизм copy-onwrite (копирование при записи), так что дополнительное пространство будут занимать только те файлы, содержимое которых было реально изменено. Неизменившиеся файлы будут иметь только одну копию.

 

Удаленная сторона

Несмотря на все преимущества описанных ранее подходов, хранение резервных копий на локальной машине не самая удачная идея. Если жесткий диск выйдет из строя, данные и их бережно сохраненные копии могут быть безвозвратно потеряны. Мы должны позаботиться о том, чтобы данные сохранялись на каком-то удаленном хосте.

Самый простой и эффективный способ сделать удаленное резервное копирование — это воспользоваться инструментом под названием rsync. Эта система синхронизации/копирования файлов может быть использована как для локального бэкапа файлов, так и для их передачи на удаленную сторону. При этом синхронизируемые файлы могут иметь инкрементальные бэкапы, а это значит, что при многократном бэкапе одних и тех же данных будут сохранены только их измененные части, а не весь файл целиком, как это происходит при использовании скриптов, описанных в первой части статьи.

Инструмент rsync может быть использован как для организации обратной системы бэкапа, когда сервер собирает файлы с нужных сетевых машин и сохраняет их на своем жестком диске, так и для прямого бэкапа на удаленную сторону. В рамках этой статьи мы рассмотрим только второй метод.

Самый простой способ использовать rsync — это доверить ему копирование файлов на удаленную машину по протоколу SSH. Устанавливаем и запускаем программу:

$ sudo apt-get install rsync
$ rsync -a --delete -e ssh /путь/до/каталога
юзер@хост:/путь/до/каталога

Чтобы команда не запрашивала пароль, необходимо настроить аутентификацию на базе публичного ключа. Для настройки немедленного бэкапа на удаленную машину сделаем модифицированную версию уже рассмотренного ранее скрипта:

$ vi ~/bin/in-rsync.sh
#!/bin/sh
DIR=pwd

Имя пользователя на удаленном хосте и каталог для бэкапа

USER="vasya"
HOST="host.com"
REMOTEDIR="/backup"
inotifywait -mr --timefmt '%d-%m-%y %H-%M'
--format'%T %f' -e close_write $DIR |
while read DATE TIME FILE; do
rsync -a --delete -e ssh ${DIR}/${FILE}
${USER}@${HOST}:${REMOTEDIR}
done

Теперь после каждого изменения файлы будут бэкапиться на удаленную машину. Было бы хорошо добавить к этой схеме еще и хранение версий файлов, но эта задача довольно просто решается с помощью инструмента rsnapshot, обзор которого выходит за рамки данной статьи.

 

Выводы

Вернуть файлы из небытия можно множеством разных способов, сегодня мы рассмотрели только часть из них. Какой способ использовать — решать тебе, главное помнить, что без резервных копий не обойтись.

 

Links

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии