Betriebssystem-Kommandos und Dateisystemzugriffe mit Application Express

Es kommt immer wieder vor, dass man aus einer Application Express-Anwendung heraus mit dem Betriebssystem des Datenbankservers interagieren muss. Zwar gibt es das PL/SQL-Paket UTL_FILE, welches einfache Dateizugriffe lesender und schreibender Art erlaubt, es bleiben aber noch Wünsche offen:

  • Betriebssystem-Kommandos (Shell-Skripte, Batchdateien) ausführen. Zwar ist dies mit dem Paket DBMS_SCHEDULER möglich, aber an die Ausgabe des Shell-Kommandos kommt man nicht heran.
  • Verzeichnisinhalte auflisten (directory listing)
  • Dateiattribute (bspw. last-modified) auslesen
  • Root-Verzeichnisse (auf Windows) ermitteln

Diese Aufgaben lassen sich mit den Bordmitteln der Datenbank nicht ohne weiteres erledigen. So gibt es kein PL/SQL-Paket, welches ein Verzeichnislisting zurückgeben kann. Jedoch ist die Datenbank grundsätzlich dazu in der Lage: Die Java-Engine in der Datenbank kann das alles; man muss es nur für PL/SQL und Application Express verfügbar machen.

Da OracleXE keine Java-Engine enthält, läuft dieser Tipp nicht in einer OracleXE-Datenbank.

Bibliothek herunterladen und installieren

Laden Sie von plsqlexecoscomm.sourceforge.net die jüngste Version der Java- und PL/SQL Bibliothek für den Zugriff auf das Dateisystem und das Ausführen von Betriebssystem-Kommandos herunter. Es gibt zwei Alternativen zur Installation:

  1. Private installation in Parsing Schema Ihrer APEX-Anwendung:
    Lassen Sie in SQL*Plus das Skript install.sql (je nach Datenbankversion im Unterverzeichnis 10g oder 11g) laufen.
  2. Öffentliche installation in einem anderen Schema:
    Lassen Sie in SQL*Plus das Skript install.sql (je nach Datenbankversion im Unterverzeichnis 10g oder 11g) im Schema SYS oder in einem anderen Schema laufen. Die nötigen EXECUTE-Privilegien und Public Synonyms werden dann mit dem Skript grant_public.sql eingerichtet.

Die Bibliotheken bestehen aus Java-Code, welcher die eigentliche Arbeit mit den Standardmitteln der Java-Engine tut (Zugriff auf Dateien mit java.io.File und Ausführen von Betriebssystem-Kommandos mit java.lang.Runtime). Sog. PL/SQL-Wrapper-Packages machen die Funktionalität für die SQL- und PL/SQL-Welt und damit für Application Express zugänglich. Das Unterverzeichnis doc enthält die Dokumentation der PL/SQL-Pakete.

Wagen Sie nun einen ersten Test: Führen Sie (im SQL Workshop) das folgende Kommando aus, um ein Verzeichnislisting ausgeben zu lassen:

-- Linux / UNIX
select * from table(file_pkg.get_file_list(file_pkg.get_file('/home/oracle')));

-- Windows
select * from table(file_pkg.get_file_list(file_pkg.get_file('C:\')));

Das Ergebnis dürfte in etwa so aussehen:

FEHLER in Zeile 1:
ORA-29532: Java-Aufruf durch nicht abgefangene Java-Exception beendet:
java.security.AccessControlException: the Permission (java.io.FilePermission
/mnt/hgfs/D read) has not been granted to [APEX Parsing-Schema]. The PL/SQL to grant this is
dbms_java.grant_permission( '[APEX Parsing-Schema]', 'SYS:java.io.FilePermission',
'/home/oracle', 'read' )
Der Zugriff auf das Dateisystem erfordert spezielle Privilegien

Abbildung 1: Der Zugriff auf das Dateisystem erfordert spezielle Privilegien

Rechtevergabe

Der Zugriff auf Dateien oder Betriebssystem-Kommandos ist in der Java-Engine der Datenbank durch einen Security Manager geschützt. Um auf eine Datei zuzugreifen oder ein Betriebssystem-Kommando auszuführen, benötigen Sie neben den EXECUTE-Privilegien am PL/SQL-Package auch Privilegien an der entsprechenden Betriebssystem-Ressource. Der folgende Aufruf (welcher auch in obiger Fehlermeldung direkt enthalten ist) muss als DBA ausgeführt werden und gibt dem Parsing Schema lesenden Zugriff das das entsprechnde Verzeichnis.

begin
  dbms_java.grant_permission( 
    grantee =>           '[APEX Parsing-Schema]', 
    permission_type =>   'SYS:java.io.FilePermission',
    permission_name =>   '/home/oracle', 
    permission_action => 'read' 
  );
end;

Mit DBMS_JAVA.GRANT_PERMISSION wird ein Recht auf eine Betriebssystem-Ressource vergeben; mit DBMS_JAVA.REVOKE_PERMISSION wieder entzogen. Zusätzlich sind mit DBMS_JAVA.RESTRICT_PERMISSION auch "negative" Rechte möglich. Das folgende Beispiel gibt alle Dateien bis auf die Datei sehrgeheim.txt im Ordner /home/oracle für lesende Zugriffe frei. Spielen Sie diese zusätzlich zu obigem Kommando ein, denn der Zugriff muss sowohl für den Ordner selbst (/home/oracle), als auch für die Dateien darin (/home/oracle/*) freigegeben werden.

begin
  dbms_java.grant_permission( 
    grantee =>           '[APEX Parsing-Schema]', 
    permission_type =>   'SYS:java.io.FilePermission',
    permission_name =>   '/home/oracle/*', 
    permission_action => 'read' 
  );
end;
/

begin
  dbms_java.restrict_permission( 
    grantee =>           '[APEX Parsing-Schema]', 
    permission_type =>   'SYS:java.io.FilePermission',
    permission_name =>   '/home/oracle/sehrgeheim.txt', 
    permission_action => 'read' 
  );
end;
/

Der Stern (*) gibt alle Dateien eines Ordners, nicht aber die Unterordner frei. Wenn Sie rekursiv alle Unterordner freigeben möchten, verwenden Sie das Minuszeichen (-). Wenn Sie /- oder D:\- angeben, ist demnach das ganze Dateisystem von der Freigabe betroffen.

Neben Leseprivilegien sind auch Schreibrechte möglich:

begin
  dbms_java.grant_permission( 
    grantee =>           '[APEX Parsing Schema]', 
    permission_type =>   'SYS:java.io.FilePermission',
    permission_name =>   '/home/oracle/schreibbar.txt', 
    permission_action => 'read, write' 
  );
end;
/

Seien Sie sehr restriktiv bei Vergabe dieser Privilegien. Der Zugriff erfolgt mit den Rechten des Betriebssystem-Users, unter dem die Oracle-Prozesse laufen. Geben Sie nur die tatsächlich benötigten Dateien und Ordner mit den tatsächlich benötigten Privilegien frei.

Dateien und Ordner in Application Express

Damit können Sie nun eine Application Express-Seite mit einem Bericht auf ein Verzeichnis aufbauen. Das Ergebnis sollte dann in etwa wie in Abbildung 2 aussehen.

Dateisystem-Listing in Application Express

Abbildung 2: Dateisystem-Listing in Application Express

Der nächste Schritt könnte nun sein, die Inhalte einer Datei auszulesen. Dazu bietet der Datentyp FILE_TYPE die Methoden GET_CONTENT_AS_CLOB und GET_CONTENT_AS_BLOB an. Navigieren Sie zum SQL Workshop und spielen Sie dort folgenden PL/SQL-Code ein.

create or replace procedure get_file_content(p_file in varchar2) is
  v_file file_type;
  v_blob blob;
begin
  v_file := file_pkg.get_file(p_file);
  if v_file.is_dir = 'N' and v_file.file_exists='Y' then 
    owa_util.mime_header(null, false);
    htp.p('Content-Length: '||v_file.file_size);
    owa_util.http_header_close;
    v_blob := v_file.get_content_as_blob();
    wpg_docload.download_file(v_blob);
  else 
    htp.p('Cannot read contents of file '||v_file.file_name);
  end if;
end;
/

grant execute on get_file_content to public
/ 

Navigieren Sie dann zu den Berichtseigenschaften und machen Sie aus der Spalte FILE_NAME einen Link wie in Abbildung 3.

#OWNER#.GET_FILE_CONTENT?P_FILE=#FILE_PATH#
Spalte FILE_NAME als Link darstellen

Abbildung 3: Spalte FILE_NAME als Link darstellen

Bei Klick auf den Dateinamen wird nun der Inhalt der Datei angezeigt; bei Verzeichnissen erhalten Sie eine Fehlermeldung. Natürlich könnte man die Anwendung nun so erweitern, dass man bei Klick auf ein Verzeichnis (entsprechende Privilegien vorausgesetzt) in dieses wechselt.

Das Ergebnis: Anzeigen von Dateiinhalten

Abbildung 4: Das Ergebnis: Anzeigen von Dateiinhalten

Mit den Methoden FILE_TYPE.WRITE_TO_FILE und FILE_TYPE.APPEND_TO_FILE können Sie auch in Dateien schreiben. Der folgende Aufruf schreibt einen Text in die Datei /home/oracle/schreibbar.txt (entsprechende Privilegien vorausgesetzt).

declare
  v_new_length number;
begin
  v_new_length := 
    file_pkg.get_file('/home/oracle/schreibbar.txt').write_to_file('Dies ist ein Text');
end;
/

Exkurs: Bilder und Dokumente in die Datenbank laden

Das FILE_PKG und der FILE_TYPE können nun auch sehr nützlich sein, wenn Sie einen ganzen Ordner mit Dateien (bspw. Bilder oder Dokumente) in eine Datenbanktabelle als BLOB laden möchten - dies funktioniert dann mit nur einem einzigen Aufruf:

create table bilder_tab (
  filename varchar2(200),
  content  blob
) 
/

insert into bilder_tab (
  select e.file_name, e.get_content_as_blob() 
  from table(file_pkg.get_file_list(file_pkg.get_file('/pfad/zum/ordner/mit/den/dateien'))) e
  where e.is_dir = 'N'
);

Betriebssystem-Kommandos ausfühen

Neben dem einfachen Auslesen und Schreiben von Dateien ist auch das Ausführen von Betriebssystem-Kommandos möglich - dazu dient das PL/SQL-Paket OS_COMMAND. Und dieses wollen wir sogleich in die Application Express-Anwendung einbinden. Fügen Sie Ihrer Application Express-Anwendung eine neue Region (evtl. auf einer neuen Seite) mit einem einfachen Textfeld ( P1_COMMAND) und einem Textbereich (P1_OUTPUT) hinzu. Vergessen Sie die Schaltfläche zum Absenden nicht.

Anwendungsseite zum Ausführen von Betriebssystem-Kommandos

Abbildung 5: Anwendungsseite zum Ausführen von Betriebssystem-Kommandos

Erstellen Sie einen neuen onSubmit-Prozeß (wird beim Weiterleiten der Seite ausgeführt). Nennen Sie ihn Kommando ausführen und hinterlegen Sie folgenden PL/SQL-Code.

begin
  -- Arbeitsverzeichnis setzen
  OS_COMMAND.SET_WORKING_DIR(
    FILE_PKG.GET_FILE('/home/oracle')
  );
  -- Kommando ausführen
  :P1_OUTPUT := OS_COMMAND.EXEC_CLOB(:P1_COMMAND);
end;

Wenn Sie die Seite nun ausprobieren, werden Sie wiederum eine Fehlermeldung erhalten, denn zum Ausführen (execute) eines Kommandos benötigen Sie besondere Rechte. Mit dem folgenden Kommando vergeben Sie (als DBA) das Recht, das Kommando /bin/ls (und nur dieses) auszuführen.

Auf Windows-Systemene können Sie das entsprechende Kommando dir nicht direkt aufrufen; es muss "indirekt" über cmd.exe ausgeführt werden. Auf Windows müssen Sie demnach mit dem Kommando "cmd.exe /c dir" arbeiten. Auch Batchdateien müssen auf diese Weise aufgerufen werden: "cmd.exe /c mybatch.bat".

begin
  dbms_java.grant_permission( 
    grantee =>           '[APEX Parsing-Schema]', 
    permission_type =>   'SYS:java.io.FilePermission',
    permission_name =>   '/bin/ls', 
    permission_action => 'execute' 
  );
end;

Um die Ausgabe des Kommandos auslesen und zurückgeben zu können, werden zusätzlich Rechte auf STDIN und STDOUT benötigt. Diese werden wie folgt vergeben.

begin
  dbms_java.grant_permission(
    grantee =>           '[APEX Parsing-Schema]',
    permission_type =>   'SYS:java.lang.RuntimePermission',
    permission_name =>   'readFileDescriptor',
    permission_action => null
  );

  dbms_java.grant_permission(
    grantee =>           '[APEX Parsing-Schema]',
    permission_type =>   'SYS:java.lang.RuntimePermission',
    permission_name =>   'writeFileDescriptor',
    permission_action => null
  );
end;

Starten Sie die Seite anschließend nochmals und führen Sie das Kommando /bin/ls erneut auf. Das Ergebnis sollte wie in Abbildung 6 aussehen.

Ausgeführtes Betriebssystem-Kommando /bin/ls

Abbildung 6: Ausgeführtes Betriebssystem-Kommando /bin/ls

Natürlich ist das auch mit anderen Kommandos denkbar - Abbildung 7 zeigt den Aufruf des Kommandos /bin/ps.

Ausgeführtes Betriebssystem-Kommando /bin/ps

Abbildung 7: Ausgeführtes Betriebssystem-Kommando /bin/ps

Zu den Paketen FILE_PKG, FILE_TYPE und OS_COMMAND finden Sie beim Download und auch online eine umfangreiche Dokumentation. So unterstützt OS_COMMAND auch das Setzen von Umgebungsvariablen und die Rückgabe des Return Codes eines ausgeführten Betriebssystem-Prozesses.

Achten Sie besonders auf eine strikte Vergabe der Privilegien. Die Interaktion mit dem Betriebsystem erfolgt als der Nutzer, unter dem die Prozesse der Oracle-Datenbank laufen. Vergeben Sie die Privilegien also stets feingranular und nur so weit, wie sie wirklich benötigt werden.

Zurück zur Community-Seite