Восстановление данных с NTFS

У хорошего знакомого посыпался жесткий диск, в «мастерской» сказали, что всё безнадежно. При подключении жесткого диска BIOS отказывался у них его определять. Предложил ему свои услуги на безвозмездной основе и без каких-либо гарантий к восстановлению данных.

Первые три шага стандартные:
1. подключение накопителя через кабель USB-2-SATA.
2. заглянуть в SMART и лишний раз убедиться, что проблема с накопителем, а не с файловой системой:

smartctl -a /dev/sdb

3. снять копию накопителя программой ddrescue, чтобы работать с копией и не насиловать и без того подайший признаки смерти жесткий диск

ddrescue /dev/sdb COPY.image

Здесь маленькая ремарка изначально мною была сделана копия с помощью

dd if=/dev/sdb of=COPY.image conv=sync,noerror

Однако ddrescue удалось создать более полноценный образ.

Десятки мегабайт были поврежденными. Поэтому программа

testdisk COPY.image

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

UPDATE: образ, полученный ddrescue, удалось прочитать всё-таки testdisk. Однако при попытке получить список файлов он сыпался с SIGSEGV. Версия для разработчиков c официального сайта TestDisk смогла прочитать полностью дерево, что существенно упростило восстановление требуемых файлов.

uwsgi separate stdout/stderr logging via logfile

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

  req-logger = file:/path/to/log/access.log
  logger     = file:/path/to/log/error.log

Однако при указаннии привиденных директив уверенно возникала ошибка:

Thu May 15 16:56:57 2014 - unable to find logger file

при подключении syslog логирования ошибка менялась на:

Thu May 15 16:58:21 2014 - unable to find logger syslog

В интернете на этот счет оказался молчок. То ли этот функционал никто не используется, то ли он появился сравнительно недавно. На деле ларчик просто открывался достаточно добавить строчку plugins, которая указывает какие logger(ы) необходимо подключить. Таким образом итоговый ini-файл в моем случае выглядел следующим образом

[uwsgi]
  pkgdir     = /path/to/app
  socket     = 127.0.0.1:8080
  workers    = 4
  plugins    = python,logfile
  virtualenv = %(pkgdir)/env
  chdir      = %(pkgdir)/src
  env        = DJANGO_SETTINGS_MODULE=app.settings
  module     = django.core.handlers.wsgi:WSGIHandler()
  req-logger = file:/var/log/uwsgi/logger/access.log
  logger     = file:/var/log/uwsgi/logger/error.log

Важно! Директория /var/log/uwsgi/logger/ должна быть доступная для записи пользователю, от которого запускается uwsgi-приложение. По умолчанию, это пользователь www-data для debian/ubuntu.

icloud.com и spamhause.com

icloud.com и spamhause.com

Это позор, товарищи! icloud.com (сервис Apple) использует RBL/XBL в данном случае spamhause.com в качестве своих SMTP фильтров. В рассматриваемом примере они отказались принимать письмо от gmail.com. По всей видимости, школота проникает и в крупные корпорации.

Для чего нужен virbr0-nic

При использовании KVM в списке интерфейсов основной системы появляется минимум два дополнительных интерфейса virbr0 и virbr0-nic. Назначение первого говорит само за себя — виртуальный мост, который будет связывать созданные Вами виртуальные машины с основной машиной. Назначение второго является не очевидным и в первые минуты загадочным. Если выполнить команду:

# brctl show
bridge name	bridge id		STP enabled	interfaces
virbr0		8000.52540072bcb8	yes		virbr0-nic

видно, что virbr0-nic является одним из сетевых интерфейсов созданного моста virbr0. Оказывается virbr0-nic создается исключительно как workaround для следующей задачи.

Когда мы создаем любой мост, его MAC адрес назначается на основании MAC адреса первого добавленного сетевого интерфейса. Если из моста удалить все привязанные интерфейсы и добавить новый, то MAC адрес существующего моста измениться на MAC адрес нового привязанного сетевого интерфейса. А так как с виртуальными машинами происходят постоянные включения/выключения, то их виртуальные интерфейсы vnetX постоянно появляются и исчезают. Чтобы сохранить MAC адрес созданного моста virbr0 постоянным разработчики пошли на такой финт ушами и создали виртуальный интерфейс virbr0-nic, значением которого инициализируется MAC адрес моста и остается постоянным вне зависимости от создаваемых и удаляемых виртуальных машин. Убедиться в этом можно выполнением следующей команды:

# ip link
...
3: virbr0:  mtu 1500 qdisc noqueue state UP 
    link/ether 52:54:00:72:bc:b8 brd ff:ff:ff:ff:ff:ff
4: virbr0-nic:  mtu 1500 qdisc noop master virbr0 state DOWN qlen 500
    link/ether 52:54:00:72:bc:b8 brd ff:ff:ff:ff:ff:ff
...

Наглядно видно, что MAC адреса virbr0 и virbr0-nic идентичные.

Мегабайт: история ошибки

Мегабайт: история ошибки

ГигибайтФизики или математики не раз задавались вопросом: «почему Мегабайт равняется не 1 000 000 байт, как это следует из его названия?».

Своими корнями приставка Мега уходит в систему Си, в которой величина Мега равна 106. Однако в компьютерном мире Мегабайтом величают величину равную 1 048 576 байтам. Забегая вперед скажем, что это грубейшая ошибка. Правильным названием для этой величины является Mebibyte (Мебибайт).

Mebibite величина равная 220 (сокращенное обозначение MiB). Странным остается тот факт, что величина Мебибайт была стандартизирована только в 1998. Таким образом ошибочное использование термина Мегабайт, где требуется использовать Мебибайт, глубоко засело в серое вещество миллионов людей планеты. Создатели операционных систем до сих пор продолжают вводить людей в заблуждение. За нос водят пользователей абсолютно всей линейки операционных систем Microsoft.

Небольшой экскурс в историю. Ошибка своими корнями уходит в дремучие годы прошлого столетия, когда на дискетах 3½ дюйма ставили отметку о емкости в 1.44 MB, что с точки зрения системы Си соответствует величине 1 440 000. При этом емкость указанных дискет составляла на самом деле 1 474 560 байт, что соответствует величине в 1440 KiB. Объединение префиксов различных величин не допустимо, так как приводит к величинам, которые невозможно вычислить. Таким образом из величины 1440 KiB нельзя получить величину в 1.44 MB. Тем не менее это не остановило производителей и миллионными тиражами в свет вышли дискеты с емкостью 1.44 MB.

В конечном итоге появилась целая цепочка ошибочных трактований величины Мегабайт. Мегабайтом обозначают величину в 1000 x 1000 байт (корректное использование), 1024 x 1024 (некорректное использование, корректное Мебибайт), 1024 x 1000 (чудовищно некорректное использование).

Рассматриваемое заблуждение положительным образом сыграло для производителей жестких дисков. В частности они честно маркируют свои накопители. Например, 3 TB обозначает величину равную 1 000 000 000 000 байт. Однако программы, которые используются для разметки жесткого диска на логические разделы, оперируют MiB или GiB байтами (с некорректно отображаемыми аббревиатурами MB или GB). Таким образом итоговые разделы в сумме получаются меньше 3 TiB, так как 3 TiB больше 3 TB на 10244 — 10004 = 99511627776 байт, что примерно равно 93 GiB. С бо`льшими объемами накопителей разница в емкости возрастает. Это обстоятельство неминуемо в первые минуты вводит начинающих пользователей в тупиковую ситуацию. У некоторых начинаются панические атаки на производителей и продавцов накопителей в поисках недополученных Гибибайтов свободного пространства.

В последних версиях Ubuntu в оболочке разметки диска выводится корректная информация в Мегабайтах (106), таким образом для ценителей разметить жесткий диск в величинах кратных 2 (двум) задача существенно усложняется. Для решения головоломки рекоммендуем изначально разметить жесткий диск с помощью parted после чего запустить установщик и указать требуемое назначение разделов.
Установка xubuntu - разметка диска

Нетрудно заметить, что в графическом интерфейсе первый раздел имеет размер 536 Мегабайт. При этом в интерфейсе parted этот раздел имеет размер 512 MiB. Эта величина получается в ходе простой математической операции: 512 MiB = 512 * 220 / 106 bytes =~ 536 MB

preseed и GPT

preseed и GPT

Жесткий дискНе секрет, что MSDOS разметка жесткого диска уходит в небытие. Размеры дисков с каждым годом растут, а она ограничена размерами не более 2TB. Продолжительное время эксплуатируемые системы поставлялись с дисками до 2TB для системных разделов, расположенных на программном MD RAID массиве. Речь идет именно о системным разделах, для которых такие размеры более чем достаточные, так как самое объемное, что на них хранится в нашем случае, это журналы работы системы.

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

Из особенностей следует выделить момент, что для загрузки с дисков, размеченных GPT, необходимо создать дополнительный раздел со специальным типом biosgrub. Размер в 1MiB будет более чем достаточным. Для этого в директиве partman-auto/expert_recipe необходимо первым разделом указать

partman-auto/choose_recipe select multiraid

d-i partman-auto/expert_recipe string                          \
      multiraid ::                                             \
        1    1    1 free                                       \
            $gptonly{ }                                        \
            $primary{ }                                        \
            $bios_boot{ }                                      \
            method{ biosgrub }                                 \
        .  

Последующие разделы можно добавлять следующими описаниями

        1024 4096 1024 raid                                    \
            $gptonly{ }                                        \
            $primary{ }                                        \
            method{ raid }                                     \
            raidid{ 1 }                                        \
        .                                                      \
        512  4096 512  raid                                    \
            $gptonly{ }                                        \
            $primary{ }                                        \
            method{ raid }                                     \
            raidid{ 2 }                                        \   
...                     

В остальном необходимо дополнить конфигурационный файл preseed нижеследующими директивами

d-i partman-basicfilesystems/choose_label string gpt
d-i partman-basicfilesystems/default_label string gpt
d-i partman-partitioning/choose_label string gpt
d-i partman-partitioning/default_label string gpt
d-i partman/choose_label string gpt
d-i partman/default_label string gpt
partman-partitioning partman-partitioning/choose_label select gpt

На этом все. Полностью рабочий preseed файл доступен адресу http://repo.kerneltrap.ru/preseed/ubuntu/12.04/amd64/raid10.cfg. Не забываем, что для полностью автоматизированной загрузки часть конфигурационных опций необходимо задать на этапе загрузки с компакт диска или при сетевой PXE загрузке.

debugfs практическое руководство

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

Существует ряд незаменимых руководств:

Большинство указанных руководств предлагают использовать команду fdisk -lu для получения информации о таблице разделов. Указанная команда работает исключительно, если у Вас устаревшая MSDOS таблица. В случае использования GPT Вы получите сообщение:

# fdisk -lu /dev/sda

WARNING: GPT (GUID Partition Table) detected on '/dev/sda'! The util fdisk doesn't support GPT. Use GNU Parted.


Disk /dev/sda: 3000.6 GB, 3000592982016 bytes
256 heads, 63 sectors/track, 363376 cylinders, total 5860533168 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x00000000

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1               1  4294967295  2147483647+  ee  GPT
Partition 1 does not start on physical sector boundary.

Для отображения информации на дисках с GPT таблицей необходимо использовать команду

parted -s -- /dev/sda u s print

Ключ -s указывает, что необходимо запустить программу и не взаимодействовать с пользователем. «/dev/sda u s print» указывает, что необходимо отобразить на консоль информацию о таблице для диска /dev/sda в секторах. Например,

# parted -s -- /dev/sda u s print
Model: ATA ST33000651AS (scsi)
Disk /dev/sda: 5860533168s
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start      End          Size         File system  Name  Flags
 4      2048s      4095s        2048s                           bios_grub
 1      4096s      4198399s     4194304s                        raid
 2      4198400s   37752831s    33554432s                       raid
 3      37752832s  5860533134s  5822780303s                     raid

#

В качестве примера, рассмотрим ситуацию, когда сбой произошел в секторе 34066232. В этом случае команды smartctl -l selftest /dev/sda (предварительно необходимо, чтобы проверка smartctl -t long /dev/sda завершилась) и badblocks -v -b 512 /dev/sda вернут номер требуемого дефективного сектора

# smartctl -l selftest /dev/sda
smartctl 5.41 2011-06-09 r3365 [x86_64-linux-3.10.25] (local build)
Copyright (C) 2002-11 by Bruce Allen, http://smartmontools.sourceforge.net

=== START OF READ SMART DATA SECTION ===
SMART Self-test log structure revision number 1
Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
# 1  Extended offline    Completed: read failure       20%     13620         34066232

# badblocks -v -b 512 /dev/sda
...
Checking for bad blocks (read-only test): 34066232

Для наглядности таблица разделов (вынесены значения исключительно начала разделов и конца диска) и расположение дефективного сектора представлена на изображении. Расположение дефективного сектора обозначено красной стрелкой.
Расположение дефективного сектора

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

${Рассматриваемый сектор} — ${Начало раздела, которому рассматриваемый сектор принадлежит}

В нашем случае началом раздела является сектор 4198400. 34066232 — 4198400 = 29867832.

На основании файла /etc/fstab выясняем, что третий раздел является корневым и использует файловую систему EXT4.

Важно! debugfs оперируют блоками различного размера. Таким образом нам необходимо выяснить текущий размер блока, который используется на найденном разделе.

tune2fs -l /dev/md1 | grep 'Block size'
Block size:               4096

Таким образом нам предстоит еще одно вычисление. Объединяем все в одну формулу и получаем

( ${Рассматриваемый сектор} — ${Начало раздела, которому рассматриваемый сектор принадлежит} ) / (Размер блока файловой системы / 512)

Таким образом для нашего случая необходимый блок файловой системы, который содержит сектор 34066232 будет равен 3733479

( 34066232 - 4198400 ) / (4096 / 512) = 3733479

Теперь подключаемся к файловой системе для выяснения обстоятельств использования блока 3733479. По умолчанию debugfs подключает в режиме read only таким образом команда безопасна даже на примонтированной файловой системе

debugfs /dev/md1 
debugfs 1.42 (29-Nov-2011)
debugfs:  testb 3733479
Block 3733479 not in use
debugfs:  

В этом случае блок 3733479 размером 4096 байт не используется и следовательно исходный дефективный сектор 34066232 может быть безболезненно перезаписан. Важно! Команде dd или hdparm --write-sector ... --yes-i-know-what-i-am-doing необходимо указывать именно сектор 34066232! Как корректно перезаписать сектор подробно рассмотрено в руководстве.

Для случая, если избушка развернулась к Вам задом, и блок 3733479 используется файловой системой, командой icheck и ncheck можно выяснить какому файлу он принадлежит

debugfs /dev/md1 
debugfs 1.42 (29-Nov-2011)
debugfs:  testb 3733479
Block 3733479 marked in use
debugfs:  icheck 3733479
Block	Inode number
3733479	536979
debugfs:  ncheck 536979
Inode	Pathname
536979	/etc/issue
debugfs:  

В этом примере файл не является ценными и исходный дефективный сектор 34066232 может быть перезаписан командой dd

dd if=/dev/zero of=/dev/sda seek=34066232 oflag=direct count=1

Удаление большого файла с EXT3 раздела без остановки системы

Удаление большого файла или файлов на EXT3 разделе может превратиться в сущий ад на работающей системе. Проблема заключается в том, что при удалении даже не используемых больших файлов, файловый раздел с EXT3 сильно деградирует по производительности и другие дисковые операции серьезно проседают (если не сказать большего — они просто останавливаются). Детально проблема описана в документе HOW TO REMOVE BACKUPS?. Ситуация никак не меняется при использовании CFQ и принудительного выставления приоритета дисковой операции через ionice.

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

К сожалению, в некоторых мамонтах (CentOS5 и RHEL5, например) нет программы truncate. Пришлось набросать простой PERL скрипт, который каждую четверть секунды урезает требуемый файл до размера в 1GB. После чего его можно безопасно удалить.

#!/usr/bin/env perl

# lazy-shrink.pl
use strict;
use Time::HiRes qw( usleep );

use constant {
  USLEEP => 250000, # microseconds
};

my $filename = shift;

if( !defined( $filename ) ) {
  print STDERR sprintf( "Usage: %s filename", $0 ), "\n";
  exit 1;
}

my $filesize = -s $filename;

while( $filesize > 1 * 1024 * 1024 * 1024 ) {
  $filesize -= 100 * 1024 * 1024;
  truncate $filename, $filesize;
  usleep( USLEEP );
}

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

Резервное копирование MongoDB

В далекие времена довелось поработать с такими комбайнами как система резервного копирования Bacula и Amanda. Однако в системах построенных изначально на высокой доступности (HighAvailability) их применение не совсем оправдано. Причина — большая часть конфигурации хранится в GIT/SVN, каждый узел системы настраивается через puppet, chef или cfengine. Таким образом в случае выхода из строя одного сервера это никак не сказывается на работе комплекса. А систему легко восстановить путем применения написанных манифестов.

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

backup_script   /usr/bin/ssh root@postgresql-hostname "su -l postgres -c pg_dumpall | gzip" > dump.sql.gz                       postgresql-hostname/postgres/

Важно! При копировании примера директивы разделяются табуляцией.

Сложность приключилась при появлении в парке MongoDB. Дело в том, что штатная mongodump создает резервные копии всей базы в отдельной директории, таким образом простым способом аналогичный подход не применим. Существует вариант создания резервной копии через backup_script и последующий его трансфер директивной backup. Стремясь к идеальному мы не могли себе позволить столь прямолинейное решение :) На помощь, как обычно, пришла командная оболочка и на выходе родилась следующая конструкция

backup_script   /usr/bin/ssh root@mongodb-hostname 'cd /var/tmp && tmpdir=$(mktemp -d mongodump.XXXXXX) && trap "{ rm -fr $tmpdir; }" EXIT TERM INT && mongodump -o $tmpdir --oplog > /dev/null && tar czf - $tmpdir' > mongodump.tar.gz      mongodb-hostname/mongo/

Важно! При копировании примера директивы разделяются табуляцией.

Если разложить этот пример по строчкам получим

cd /var/tmp && 
tmpdir=$(mktemp -d mongodump.XXXXXX) && 
trap "{ rm -fr $tmpdir; }" EXIT TERM INT && 
mongodump -o $tmpdir --oplog > /dev/null &&
tar czf - $tmpdir

Второй строчкой мы создаем временную директорию, в которую будем в строке четыре производить сохранение в формате bson резервной копии всех баз MongoDB и oplog. Изначально была использована более сокращенная конструкция, заменяющая две первые строчки

tmpdir=$(mktemp -d /var/tmpmongodump.XXXXXX)

Минус ее заключается в том, что на последнем шаге при получении TAR архива мы на STDERR получаем сообщение

tar: Removing leading `/' from member names

Направлять STDERR в /dev/null не наш путь. Поэтому изначально мы переходим во временную директорию /var/tmp. Вывод STDOUT у mongodump завязан на /dev/null, так как в процессе работы скрипт выводит не нужную отладочную информацию. Здесь должен идти плевок в сторону разработчиков MongoDB, которые нарушают основные парадигмы unixway. Более того не предусмотрели ключ --quiet.

Самой интересной комбинацией и ради чего все это затевалось является строчка

trap "{ rm -fr $tmpdir; }" EXIT TERM INT

Которая при завершении выполнения всех процессов (в нашем случае при успешной передаче TAR архива на принимающую сторону) или при нажатии Ctrl+C произведет удаление временной директории.

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

Указанный скрипт легко адаптируется к ситуации, когда копия изначально снимается в директорию на резервном сервере (в помощь ключ --host для mongodump), архивируется и производится зачистка временных файлов. Однако это задачка для наших читателей.

PostgreSQL profiling

Профилирование хранимых процедур в PostgreSQL не является тривиальной задачей. Сложность заключается в том, что в журнал попадает обобщенное время выполнения хранимой процедуры. Таким образом, если Ваша хранимая процедура вызывает какие-то запросы или другие хранимые процедуры, в журнал попадет исключительно суммарное время выполнения.

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

SET track_functions TO pl;

После вызова желаемой функции в таблице pg_stat_user_functions будет суммарная статистика о ней.

Для более детального профилирования можно воспользоваться RAISE LOG или модулем log_functions. О использовании первого очень подробно рассказывается в руководстве PROFILING STORED PROCEDURES/FUNCTIONS, второго — Profiling PL/pgsql functions. Для второго подхода потребуется собрать дополнительный модуль для PostgreSQL. Однако у него есть и неоспоримое преимущество — он не потребует модификации исходной хранимой процедуры, что обязательно потребуется в случае использования RAISE LOG.