странное поведение запятой в ночь преступления

Nov 11, 2024 20:32

Продолжение и окончание истории об испорченных данных, начало см.
https://avva.livejournal.com/3705558.html

Испорченные данные возвращаются!

В комментариях к прошлой записи меня не раз спрашивали, почему я просто не сделал rsync между двумя иерархиями файлов. На это у меня есть сразу три ответа, два из них пустяшные (надо найти и установить rsync под Windows; мне было любопытно разобраться, где и что испорчено), а третий важный: я не знал только, а лишь подозревал, что копия данных в Америке - чистый неиспорченный экземпляр. Анализ файлов должен был эту версию подтвердить.

Когда я писал свою запись, программа, сравнивающая отличающиеся файлы из Америки и в Израиле, еще не закончила работу, но все указывало на то, что так и есть. Через пять часов после того, как я запостил, я посмотрел на логи и увидел, что все видимо сложнее. Так в итоге и оказалось.

Вернемся к началу и проведем учет того, что у нас есть. А есть у нас три массива данных, в идеале идентичных, назовем их O, A и B. O - это данные экспериментов, которые записывались на встроенный диск компьютера во время самих экспериментов. A и B - это подключаемые внешние SSD-диски. Размер диска O всего 1 терабайт, а данных генерировалось сотни гигабайт в день, поэтому во время исследования мы периодически сбрасывали данные с O на A и B, а потом стирали на O. Несколько раз мы торопились и копировали только на A или только на B, а потом отдельно копировали между ними - но это было реже. В каком порядке в точности что копировалось откуда и куда, сейчас уже никто не помнит. В конце всей этой вакханалии на O почти ничего не осталось, а на A и B были идентичные - по замыслу - копии всех данных. A остался в Америке, а B уехал в Израиль. И все было бы хорошо, если бы не плохой бит в памяти компьютера. (Да, в следующий раз мы все сделаем по-другому и лучше).

Итак, я скопировал все файлы A, у которых контрольная сумма отличается от B, в одно место с B, и запустил программу, которая находит все отличия. Программа написала мне лог-файл из 350 тысяч строк такого типа:

filename: offset 139709: 51 != 55, diff. -4

Для каждого различия написано, в каком файле и на каком смещении, приведен байт из Α и байт из B, и разница между ними.
Когда я писал запись, в этом файле, который продолжал создаваться, были только строки с разницей -4, что подтверждало следующую теорию (мне также казалось логичным, что порча была внесена во время одной сессии массивного копирования именно на B):

ТЕОРИЯ 1. A - это чистые исходные данные , а порча в B всегда состоит в том, что третий бит ставится в 1.

Но когда сравнение всех файлов закончилось, оказалось, что есть также много строк в другую сторону:

filename: offset 14467: 56 != 52, diff. 4

Это опровергает ТЕОРИЮ 1.

У нас есть файл всех изменений, который можно легко изучать с помощью текстового поиска (я настоятельно рекомендую программу ripgrep). Давайте разбудим нашего внутреннего Шерлока Холмса, усядемся поудобнее в мягком кресле, раскурим трубку и подумаем: что там могло случиться?

ТЕОРИЯ 2. A - это все еще чистые исходные данные, но порча в B иногда ставила бит в 1, иногда в 0.

Изменения в бинарных файлах не могут легко помочь подтвердить или опровергнуть эту теорию. Текстовые CSV файлы - другое дело. В них встречаются только символы: 0-9, английский алфавит (только первая строка заголовка, нерелевантна, слишком редка, чтобы попасть под порчу), запятая, точка (десятичная), перенос строки (коды 13 и 10 - перенос строки под Windows это два символа, а эти файлы писала программа в C++ под Windows).

Быстро находим много строк такого типа:

filename.csv: offset 1234: 14 != 10, diff. 4

Это опровергает ТЕОРИЮ 2: эта порча создалась изменением переноса строки 10 в 14, но в файле A как раз записано 14, значит, он испорченный. К сожалению, чистого экземпляра всех данных у нас просто нет. Значит ли это, что все потеряно?

Предположим, что между A и B есть разница: которую можно схематически обозначить так: xXxxxxxx и xxxxxxXx. Есть +4 в одном байте и -4 в другом, в разных файлах или даже в одном. Казалось бы, это значит, что бит менялся случайным образом. Но есть и другая возможость: что в исходном O было xxxxxxxx, и бит менялся в сторону увеличения дважды, при записи O->A и O->B, в разных местах.

ТЕОРИЯ 3. A и B оба испорчены, но во время копирования 3-й бит менялся произвольным, случайным образом.

ТЕОРИЯ 4. A и B оба испорчены, но во время копирования 3-й бит всегда ставился в 1.

Если верна ТЕОРИЯ 3, то у нас нет шансов восстановить чистые данные. Кое-где, как в различии 14 != 10, мы знаем, что там было 10, но если это 51 != 55 (цифры "3" и "7"), мы никак не можем узнать, какая цифра правильная. И в бинарных файлах нет шансов узнать.

Если же верна ТЕОРИЯ 4, то мы можем восстановить чистые данные почти целиком: надо просто исправить все байты в B, которые на 4 больше байтов в A, а те, которые на 4 меньше, оставить как есть - они как раз правильные. Крайне маловероятно, что где-то есть порча, попавшая на тот же байт в A и B в разное время копирования. Возможно, мы упустим небольшое количество испорченных данных, которые были скопированы с O на A, а потом без изменений с A на B, но тут уж ничего не поделаешь - они вообще не в списке наших измененных файлов.

Можно ли понять, какая из теорий верна? Можно. Если верна ТЕОРИЯ 3, то должно быть свидетельство именно падения бита из 1 в 0. Различие типа 51 != 55, где оба байта законны в CSV, не может таким считаться. Различие типа 10 != 14 доказывает как раз обратное: что было возрастание бита из 0 в 1, потому что 14 в исходном файле не было. Какой символ имеет выставленный 3-й бит, но если его сбросить, получается символ, который не бывает в CSV?

Это запятая. ASCII-код 44, если сбросить третий бит, выходит 40 - код левой скобки "(", которая в CSV не встречается. Запятых в CSV-файлах много, больше чем переносов строки. Примеров "10 != 14" в моем файле десятки. Примеров "40 != 44" ни одного. Вывод: ТЕОРИЯ 3 неверна, и все-таки бит всегда выставлялся в 1. Верна ТЕОРИЯ 4, и мы можем восстановить побитые файлы. Скрипт, который это делает, работает прямо сейчас.

(честности ради должен добавить: изначально я думал, что одна только запятая доказывает мои рассуждения, и мне нравилась эта драматичность. Оформляя эту запись, я осознал, что и точка, и carriage return (код 13, часть переноса строки) тоже дают свидетельства того же самого. Уже не так драматично, но истина дороже)

программирование

Previous post Next post
Up