Программизм
 
Программизм
На главную | Графомания | Программизм | Книги | Всячина | Скачать | Ъ?  

Копирование файлов

Математики делают то, что можно так, как нужно.
Программисты делают то, что нужно так, как можно.

Программистский фольклор.

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

copy source.txt \\bigserver\store\dest.txt

Всё это действительно так, но задача осложняется двумя дополнительными условиями: во-первых, процедуру копирования нужно вызвать из программы на VisualBasic, а во-вторых, у пользователя нет прав на запись в каталог \\bigserver\store\

Решение задачи достаточно очевидно — надо войти в систему под именем пользователя, имеющего права на запись в указанный каталог, и выполнить необходимую операцию. И вот тут-то и начинаются поиски и метания.

Достаточно быстро в MSDN находятся функции LogonUser() и ImpersonateLoggedOnUser(). Правда, в Windows 95/98/ME эти функции не поддерживаются, но, как читатель уже успел заметить, я эти оболочки за операционные системы не считаю и по поводу отсутствия в них чего-либо совершенно не расстраиваюсь. Тем же, кто захочет решить поставленную задачу для этих недосистем, пожелаю удачи и посоветую почитать форум на RSDN.

Но, как выясняется, и в Windows NT не всё так безоблачно. Оказывается, LogonUser() требует привилегии SE_TCB_NAME, а привилегия эта настолько серьёзна, что дешевле дать пользователю права на запись в каталог. Почему столь невинная, в общем-то, операция требует столь серьёзных прав, абсолютно непонятно. Видать, не поняли этого и в Microsoft, поэтому в Windows XP этой функцией можно смело пользоваться. В Windows же NT и 2000 единственный метод — это создать сервис, позволяющий запускать процессы от имени другого пользователя, и запустить его от имени LocalSystem, то есть системы, у которой есть требуемая привилегия. Реализацию этого сервиса под NT оставим энтузиастам, а в Windows 2000 уже есть сервис RunAs, и если поставить в свойствах ярлыка «запускать от имени другого пользователя», то Проводник (Explorer) воспользуется именно им.

Для того, чтобы сервисом RunAs мог воспользоваться простой программист, в Win32 включена функция CreateProcessWithLogonW() (она реализована только в Unicode). Итак, казалось бы, всё просто — пишется программа, которая запускает сама себя от имени другого пользователя. Проверить, что именно нужно делать, можно либо по дополнительному параметру командной строки, либо воспользовавшись функцией GetUserName(). Имейте в виду, что переменная среды USER сама по себе не устанавливается — её должен задать родительский процесс, а это в данном случае совершенно излишне.

Запускаем и... ничего не работает. А всё потому, что пользователь, имеющий права на запись в один каталог, совсем не обязан иметь права на чтение из другого. В общем-то, эта проблема решается просто — копирование происходит в два этапа. Надо только создать сетевой ресурс, права на запись в который будут у всех.

После этой переделки всё вроде бы работает, но если вдруг что-то не получится, мы никак об этом не узнаем. Почему? А потому, что функция shell в VisualBasic не возвращает код завершения процесса.

Вернуть в VisualBasic код ошибки просто — надо всего лишь создать DLL, экспортирующую нужную функцию. Правда, тут тоже есть тонкость: экспортируемая функция должна соответствовать соглашению __stdcall, а компилятор MS Visual C/C++ 6.0 по-особенному обращается с именами таких функций. Так, например, если у нас объявлена функция

extern "C" __declspec(dllexport) DWORD __stdcall FCopy(LPSTR src, LPSTR dest);

то экспортируемый символ будет называться _FCopy@8. Поэтому экспортировать символы надо через .def-файл. Делается это просто — в проект включается файл export.def примерно такого содержания:

LIBRARY FOREIGNCOPY
EXPORTS
FCopy @1

Теперь предстоит решить новую проблему — как создать новый процесс. В Win32, в отличие от POSIX, создать новый процесс можно только указав исполняемый модуль. Visual C позволяет скомпилировать исполняемый (exe) модуль, который экспортирует символ, но вот VisualBasic работать с таким модулем напрочь отказывается — программа выполняет недопустимую операцию, встретив в экспортируемой функции первый исполняемый оператор. Казалось бы, открывается прямая дорога к разделению программы на два модуля, но, как говорил герой известного фильма, «это не наши методы».

В Windows NT есть замечательная утилита — RunDll32. Она позволяет вызвать функцию в любой DLL, передав в качестве параметра имя библиотеки и имя функции. Единственное условие — функция должна иметь определённый прототип. Написать такую функцию несложно, а для того, чтобы вернуть из функции типа void значение в вызывающий процесс, закончить её надо не банальным return, а вызовом функции ExitProcess().

Полученная DLL занимает около 40 K. Размер, конечно, невелик, но библиотека такого размера задевает эстетические чувства, поэтому её стоит подвергнуть процедуре уменьшения. И разумеется, наша библиотека не была бы нашей, если бы здесь всё прошло без сучка и задоринки.

Во-первых, intrinsic functions генерируются только для мультибайтных кодировок (список функций можно найти в MSDN). Поэтому Unicode-функции (в частности, wcscat()) придётся реализовать самому. Впрочем, не так это и сложно.

Во-вторых, для того, чтобы правильно сформировать командную строку для порождаемого процесса, нам придётся вызывать функцию GetModuleFileName(). А если передать ей в качестве первого параметра NULL, она возвращает не имя библиотеки, а имя главного модуля программы. Следовательно, нам надо создать функцию DllMain() и при загрузке DLL сохранить в глобальной переменной её HANDLE. Беда в том, что DllMain() вызывается из CRT, а поскольку мы приказываем линкеру игнорировать стандартные библиотеки, никакой CRT нет и в помине. Ключ noentry, рекомендуемый процедурой сокращения, вообще убирает точку входа в DLL, а нам надо явно указать точкой входа DllMain().

Ну, вот и всё. Теперь можно спокойно копировать файлы.

Готовую программу здесь не привожу — оставляю читателю самому насладиться написанием кода. Впрочем, если кому-то она понадобится — пишите, обсудим.

10.03.2004

Поиск
См. также

Пользователи всех клонов *nix давно уже могут писать командные файлы на любом языке. Для этого надо лишь указать в первой строке имя интерпретатора... »»»

Именно им призван помочь описанный здесь скрипт, сохраняющий письма с бесплатных ящиков на локальный диск.... »»»

архив с драйвером занимает около 1.4 M. Те файлы, которые нам необходимы, в неархивированном виде занимают около 900 K... »»»

Рекомендую

e.g.Orius’
Игорь Иртеньев
Вячеслав Шевченко

Copyright notice

ъ) Все материалы, размещённые на странице, являются неотъемлемой собственностью автора с вытекающими отсюда правами, как ©, так и (ъ). Некоммерческое их распространение всячески приветствуется, разумеется, при условии сохранения ссылки на оригинал. Что касается коммерческого использования — пишите письма, договориться можно всегда.

Удивительное рядом

lj userhardsign
Закладки Карта Королёва

Пишите письма

Счётчики

XPEHOMETP™ Рейтинг@Mail.ru