Если то или иное действие может быть выполнено в базе данных, я это использую.
... суть иcпользуемого мною подхода заключается в том, что все, что только возможно, я стараюсь выполнять в базе данных.
Том Кайт "Oracle для профессионалов"
PL/SQL-хакер всю свою сознательную жизнь программировал на PL/SQL, и старался избегать использования других языков программирования. Сегодня заказчик "подкинул" ему задачу, для решения которой никак не получалось ограничиться только PL/SQL-процедурой.
Нужно было по окончании обработки данных в БД, выполнить командную утилиту на сервере, на котором работает база. Эта командная утилита для каждой платформы была своя, и предварительно ее нужно было скопировать на сервер.
Хорошо, - выполнить консольную утилиту на сервере из PL/SQL не проблема: можно использовать например External table preprocessing, но ведь предварительно придется вручную скопировать туда эти утилиту, да еще и не забыть передать их заказчику.
Да еще наверняка студент-админ, по ошибке, скопирует на сервер файл не для нужной платформы !
Нельзя ли как нибудь скопировать бинарный файл утилиты с клиентской машины прямо из PL/SQL-процедуры, и потом выполнить его?
- Стоп!
- Откуда она его скопирует?
- Ведь PL/SQL-процедура (или скрипт SQL+) это всего лишь текст, откуда он скопирует бинарный файл ?
Задача выглядела неразрешимой ...
Но PL/SQL-хакер не привык отступать. Как часто его раздражали программисты, пишущие код на новомодных Java и C#, которые "рожали" тысячи строк кода, когда как на PL/SQL это можно реализовать парой десятков строк.
Вопрос выглядел безумным: может ли исходный текст PL/SQL-процедуры нести в себе бинарный файл ?
Ничего не приходило в голову.
Привычным движением руки PL/SQL-хакер взял с полки нетленную книгу Стива Ферстайна "Oracle PL/SQL. Для профессионалов".
Нет - ничего не получалось.
Подошел конец рабочего дня. Поставив на закачку очередной GI PSU Patch 11.2.0.2.3, PL/SQL-хакер засобирался домой. "Патчи у оракла становятся все жирнее и жирнее", - подумал он, залочив компьютер.
Cтоя в метро он начал перелиcтывать распечатку документации, вчитываясь в описания системных пакетов.
Неожиданно внимание PL/SQL-хакера привлекла девушка в красном платье. Платье плотно облегало ее тело, подчеркивая достоинства фигуры. Взгляд PL/SQL-хакера машинально переводился с документации на эту девушку и обратно.
Мысленно поругав себя за то что отвлекся, PL/SQL-хакер повернулся к девушке спиной, и снова принялся читать документацию.
И тут его осенило: он дошел до описания пакета UTL_ENCODE
Этот пакет позволял декодировать BASE64-строки в бинарный эквивалент.
Поэтому прямо в PL/SQL-коде можно получить из BASE64-строки соответствующий поток байтов и получившийся BLOB уже копировать на файловую систему.
PL/SQL-хакеру не терпелось быстрее добраться до дома и проверить эту идею. Дома его ждал недавно собранный мощный компьютер на основе процессора с ядром Intel Sandy Bridge, 16Гб оперативной памяти и SSD-диском.
Первым шагом нужно было написать утилиту, которая переведет бинарный файл в BASE64-строку, и далее вставить эту строку в PL/SQL_процедуру в виде varchar2-константы.
Придя домой и наскоро поужинав, PL/SQL-хакер принялся за работу.
После часа работы и банки пива такая утилитка была написана:
C:\Work\Projects\plslsq_hacker>plsql_base64.exe girlInRed.jpg girlInRed.sql
Base64 for PL/SQL converter: Release 1.0.0.0.0 - Production on 16.07.2011 12:56:50
Utility for generation sql-file for binary files
Copyright (c) 2011, PL/SQL-hacker. All rights reserved.
Usage: plsql_base64.exe <input filename> <output filename> [PL/SQL Variable name]
Examples:
plsql_base64.exe logo.gif logo.sql
Read file "girlInRed.jpg" ...
Done.
Program finished.
На выходе эта утилита генерировала текстовый файл с константой в виде BASE64-строки соответствующей бинарному файлу:
v_xGirlInRed := 'UEVSRk9STUVSICJEaWdpdGFsIEVtb3Rpb25zIg0KVElUTEUgIjEyMiINCkZJTEUgIkZvbmFyZXZf
........
NjoyODo0OQ0K';
Код PL/SQL-функции конвертации BASE64-строки в BLOB получился тривиальным:
function getBlob(v_pBase64Str in out nocopy varchar2) return blob is
v_xRes blob;
v_xRaw raw(32000);
begin
dbms_lob.createtemporary(v_xRes, true);
v_xRaw := utl_raw.cast_to_raw(v_pBase64Str);
v_xRaw := utl_encode.base64_decode(v_xRaw);
dbms_lob.write(lob_loc => v_xRes,
amount => dbms_lob.getlength(v_xRaw),
offset => 1,
buffer => v_xRaw);
return v_xRes;
end;
"Теперь нужно написать функцию сохранения BLOB-а в файл" - подумал PL/SQL-хакер, дожевывая засохшую воблу и запивая ее противным теплым пивом. Такая функция уже была когда-то им написана для другого заказчика, осталось просто вставить ее исходник в итоговый скрипт (для записи в файл использовался пакет UTL_FILE):
procedure saveBlobToFile(v_pBlob in blob, v_pDirectoryName in varchar2, v_pFileName in varchar2) is v_xFile utl_file.file_type; v_xWrittenSofar pls_integer := 0; v_xChunkSize constant pls_integer := 4096; v_xBuf raw(4096); v_xBytesToWrite pls_integer; v_xLobLen pls_integer; begin v_xLobLen := dbms_lob.getlength(v_pBlob); v_xFile := utl_file.fopen(v_pDirectoryName, v_pFileName, 'WB'); while (v_xWrittenSofar + v_xChunkSize < v_xLobLen) loop v_xBytesToWrite := v_xChunkSize; dbms_lob.read(v_pBlob,v_xBytesToWrite,v_xWrittenSofar+1,v_xBuf); utl_file.put_raw(v_xFile,v_xBuf); v_xWrittenSofar := v_xWrittenSofar + v_xChunkSize; end loop; v_xBytesToWrite := v_xLobLen - v_xWrittenSofar; dbms_lob.read(v_pBlob,v_xBytesToWrite,v_xWrittenSofar+1,v_xBuf); utl_file.put_raw(v_xFile,v_xBuf); utl_file.fclose(v_xFile); end;
Получившийся в итоге скрипт поражал воображение своими размерами, поскольку включал в себя текстовые константы бинарных файлов в виде BASE64-строк.
Поэтому было решено оптимизировать его размер с помощью сжатия: BASE64-строка получается из бинарного файла предварительно упакованного утилитой gzip. Далее в PL/SQL коде, после получения соответствующего BLOB-а, производится его распаковка встроенным пакетом UTL_COMPRESS, и только затем полученный таким образом BLOB сохраняется в файл:
... ... ...
-- Extract BASE64-string to blob
v_xCompressedBlob := getBlob(v_xBase64Str);
-- Uncompress blob ...
v_xBlob := utl_compress.lz_uncompress(v_xCompressedBlob);
-- Save blob to file ...
saveBlobToFile(v_xBlob,:v_gExecutionDir, 'bin_exec.exe');
... ... ...
Для того, чтобы не засорять library cache лишним кодом, в компилируемый исходник включается только одна BASE64-константа - для нужной платформы. Для этого используется условная компиляция, символ который вычисляется предварительно "на лету" в отдельном анонимном блоке и выставляется в нативном динамическом SQL c помощью DDL-команды alter session ccflags=.
Удовлетворенно нажав Ctrl-S в текстовом редакторе, PL/SQL-хакер отправил итоговый скрипт заказчику и лег спать. В сне ему приснилась девушка из метро, в этот раз она просто шла по улице. Она смотрела на PL/SQL-хакера и улыбалась. На ней была одета футболка, спереди которой характерным шрифтом графического SQL+ была напечатана надпись "PL/SQL procedure successfully completed."Рабочий скрипт, проделывающий эти манипуляции, любезно предоставлен PL/SQL-хакером и его можно посмотреть здесь. Консольная программа заменена на программу, которая просто выдает на экран "Hello World &имя_платформы". Для уменьшения размера скрипта поддерживаются только платформы Win32, Win_x64, Solaris x64, Linux_x86 и Linux_x64.
PL/SQL в очередной раз доказал свою мощь и эффективность!
Каким образом решена проблема установки выполняемого бита из PL/SQL для сохраненного файла на Unix-платформе ? Для решения этой проблемы пришлось применить Java для вызова chmod x+, - здесь, к сожалению, только средствами PL/SQL не удалось обойтись.На очереди у PL/SQL-хакера уже была другая интересная задача - обфускация кода PL/SQL. Об этом он обещал рассказать потом...
Ой как всё сложно задумано...
ОтветитьУдалитьУбиться апстену можно с таким "хакерством"...
BASE64-содержимое файла можно просто сохранить в виде многострочного комментария в теле процедуры, а потом циклом прочитать собственный source из user_source.
Это гораздо сложнее - нужно парсить комментарии!
ОтветитьУдалитьЗачем их парсить?
ОтветитьУдалитьПодготовьте набор файлов под каждую платформу, прилагаемый к инсталляционному скрипту, с содержимым типа:
create procedure install_file_src as
begin
/*
H4sICBGwMU4CAHRlc3QuZXhlAMwaa1gUVXRmd4CR1ma1JdGstqKCMoPoIRG1gdBGrwXUFHtZsCGZ
...
vnxTMSNk6tzE+l+/q4/w/3AaAFoAAA==
*/
null;
end;
/
Потом условной компиляцией "$if $$isWin64 $then" (а еще лучше использовать результат dbms_utility.port_string) присвойте переменной нужное имя файла скрипта, создающего base64-содержимое файла, запустите нужный скрипт по @@&
Затем в скрипте:
begin
for i in (
select text
from user_source
where name='INSTALL_FILE_SRC'
and type='PROCEDURE'
and line >= 4
order by line)
loop
if i.text like '%=' then
exit;
end if;
... тут обрабатываем очередной base64-кусок
end loop;
end;
/
Этот путь как минимум даст унификацию, гибкость и возможность создавать на стороне сервера файлы, упакованный размер которых более 32к*3/4 байт...