вторник, 13 января 2015 г.

Проклятие текущего каталога

Это перевод The curse of the current directory. Автор: Реймонд Чен.

Текущий каталог - это и благо и проклятие. Это благо, потому что экономит вам набор текста (путём использования относительных путей). Это проклятие по всем остальным причинам.

Корнем многих зол является то, что в семейство операционных систем Windows NT держит открытым описатель текущего каталога каждого процесса (упреждающий комментарий от Yuhong Bao: семейство систем Windows 95, напротив, не держало описатель открытым, что порождало кучу других проблем, что, впрочем, никак не связано с темой этого поста).

Первое следствие: вы не можете удалить каталог, если этот каталог является текущим для какого-то запущенного процесса. Я неоднократно видел, как это вгоняет людей в ступор:
Я пытаюсь удалить каталог X, но при попытке его удалить, я получаю сообщение об ошибке: "Процесс не может получить доступ к файлу, поскольку он открыт в другой программе"... После долгих часов, я нашёл виновника: оказывается, каталог X держится программой someapp.exe. Какого чёрта ей вообще надо в моём каталоге? Как мне удалить каталог?
Конкретное приложение someapp.exe не имеет значения, оно каждый раз разное. Когда такое происходит, пользователи винят someapp.exe.

Но почти во всех случаях, someapp.exe - просто невинная жертва текущего каталога.

Во-первых, рассмотрим отдельно случай, когда someapp.exe - это explorer.exe. Почему текущий каталог Проводника может быть равен этому каталогу?

Ну, быть может потому, что это ещё одно проклятие текущего каталога, а именно: текущий каталог - один на весь процесс, это глобальная настройка. Если расширение Оболочки решило вызвать SetCurrentDirectory, то этим она поменяла текущий каталог для всего Проводника. И если это расширение не побеспокоилось о том, чтобы вызвать SetCurrentDirectory второй раз для возврата предыдущего текущего каталога, то теперь текущий каталог застрял на этом каталоге.

Заметьте, что даже если расширение оболочки попытается сделать всё правильно, то это может не сработать:
GetCurrentDirectory(Old) // возвращает C:\Previous
SetCurrentDirectory(New) // меняет на C:\Victim
.. что-то делаем ..
SetCurrentDirectory(Old) // меняем на C:\Previous - неудачно?!
Второй вызов SetCurrentDirectory может завершиться неудачей, если, к примеру, каталог C:\Previous был удалён или переименован, пока расширение оболочки делало свою работу. Расширение Оболочки не может вернуть текущий каталог, так что оно оставляет C:\Victim текущим каталогом, и теперь вы не можете удалить C:\Victim, потому что он стал текущим каталогом Проводника.

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

Заметьте, что если сделать текущий каталог настройкой потока (перевод поста), а не процесса, то это также не решит проблему (полностью), потому что текущий каталог потока (если бы такая вещь существовала бы) всё равно держал бы открытым описатель текущего каталога. Но если бы текущий каталог был бы настройкой потока, то, по крайней мере, если бы этот поток был ассоциирован с окном, то вы могли бы закрыть окно и, таким образом, завершить поток - что, в свою очередь, освободило бы открытый каталог. Ну если только вы не вызвали Terminate­Thread - в последнем случае описатель текущего каталога просто утёк бы, и ваша "попытка" отпустить каталог только что прогарантировала, что этого никогда не случится (заметка для технологических ипохондриков: весь этот параграф был гипотетическим и поэтому совершенно не годится для решения вашей проблемы).

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

Бонус: Привет, люди. "История ещё не закончена". Воздержитесь от спекуляций в комментариях.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.