Klaus1: Permission denied beim PHP/MySQL Load into Table

Hallo,

ich habe ein für nicht erklärbares Problem:

Unser SAP-System überträgt per FTP Dateien auf den Webserver (direkt in das Verzeichnis /var/lib/mysql-files) Per PHP-Script sollen die übertragenen Dateien jetzt automatisch in Import-Tabellen eingelesen werden. Der dazugehörige Befehl Load into table funktioniert aber merkwürdigerweise zunehmend nicht mehr. Eine ganze Weile lang (etwa 1 Jahr) gab es gar keine Probleme, mittlerweile zeigt das Script bei jedem 2. Versuch die Fehlermeldung "Permission denied" (OS errno 13).

Ich habe in den MySQL-Einstellungen des Servers bereits secure-file-priv=" gesetzt. Und das Verzeichnis mysql-files testweise mit Rechten 777 ausgestattet.

Wenn das Problem permanent auftreten würde, könnte ich das vielleicht noch verstehen, aber bei "nur" ca. 50%? Und auch noch mit ansteigender Häufigkeit?

Dabei ist es auch unerheblich, wie lange ich bzw. Übertragung und Import warte oder zuerst SAP beendet wird oder der Browser neugestartet und neu angemeldet wird.

Hat jemand hier eine Erklärung?

LG Klaus

  1. Hello,

    die SQL-Datenfiles gehören i. d. R. nur dem SQL-Server und sollten dementsprechende Rechte bekommen. Welches Datenbankformat wird benutzt (MyISAM, InnoDB, ...)?

    Wie heißt der FTP-User? Ist der z. B. Mitglied einer Import-Group, in der dann auch der Datenbankserver-User Mitglied sein sollte?

    Da kommt man meistens ohne eine Zeichnung nicht mehr aus für die Planung.

    Glück Auf
    Tom vom Berg

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
    1. Hallo,

      das DB-Format ist durchgängig InnoDB. Der FTP-User heisst 'salden' und ist kein Mitglied einer Gruppe.

      Wenn es denn niemals funktionieren würde, würde ich Gruppen, Rechte etc. auch als Ursache vermuten, aber es funktioniert(e) ja eine ganze Weile ohne einen einzigen Aussetzer und erst nach ca. 1 Jahr immer häufiger. Rechte unterliegen doch üblicherweise keiner Zerfallsrate oder werden nach einer gewissen Zeit schlecht 😉

      LG

      Klaus

      1. Hello,

        Du hast da mindestens drei Beteiligte, deren Rechte im Filesystem unter einen Hut gebracht werden müssen:

        • Datenbankserver
        • FTP-User
        • PHP-User (ggf. mittels Webserver)

        Und dann der Benutzer, den PHP wissen muss, um den Datenbankserver benutzen zu können. Der bildet sich aber nicht in den Filesystem-Rechten ab, sondern ist in der DB gespeichert.

        Wird das PHP-Script per Konsole/Cronjob oder via Webserver aufgerufen?
        Wenn ja, welcher Webserver, welche Version auf welchr Plattform, welche PHP-Version?

        Glück Auf
        Tom vom Berg

        --
        Es gibt nichts Gutes, außer man tut es!
        Das Leben selbst ist der Sinn.
        1. Der DB-Server läuft als user 'mysql', der FTP-User als 'salden', der Webserver als 'wwwserver' und PHP nutzt für den Zugriff auf die DB 'dbuser'.

          Und welcher dieser User würfelt jetzt, ob er den Zugriff mal wieder zulässt oder doch mal nicht?

          Ich hatte ja Timeouts bzw. Dateisperren im Verdacht, konnte das aber bisher nicht reproduzieren. 5 Mal hintereinander exakt den selben Prozess, 4 Mal funktioniert es nicht, beim 5. Mal klappts plötzlich.

          Was mir noch aufgefallen ist: Nach dem Import werden die Dateien normalerweise in ein Unterverzeichnis Archiv geschoben (PHP rename Befehl). Wenn der Fehler auftritt, sind die Dateien danach komplett verschwunden, also auch nicht im Archiv-Ordner gelandet.

          LG Klaus

          1. Hello,

            nun mal Butter bei die Fische:

            Wenn da tatsächlich die Fehlermeldung Permisdion denied [OS errno 13] kommt, dann handelt es sich mit 99%iger Wahrscheinlichkeit (Wette) um ein Python-Sctipt und nicht um PHP. Der Fehler tritt immer dann auf, wenn ein zu erstellendes Verzeichnis nicht erstellt werden darf, weil im übergeordneten keine Schreibrechte bestehen für den User, oder aber das Verzeichnis schon vorhanden ist.

            Zeig uns doch bitte mal den Quelltext des Scriptes.

            Glück Auf
            Tom vom Berg

            --
            Es gibt nichts Gutes, außer man tut es!
            Das Leben selbst ist der Sinn.
            1. Also die Wette mit Python wäre verloren 😉

              Es ist ein reines PHP, das den Import durchführen möchte und die Fehlermeldung ausgibt:

              Hier ein Code-Schnipsel:

              ini_set('display_errors', 1);
              ini_set('display_startup_errors', 1);
              error_reporting(E_ALL);
              
              $referer = $_SERVER['HTTP_REFERER'];
              $client_ip = $_SERVER['REMOTE_ADDR'];
              if (($referer == "https://xxxxx.de/main.php") AND (substr($client_ip,0,3)=="10.")) {
              	$verbindung = mysqli_connect($server,$login,$pass,$dbname);
              
              	echo "Start<br/>";
              
              	$importdateien = "";
              	$zeitstempel = "";
              	foreach (glob("/var/lib/mysql-files/t_company_*.csv") as $file) {			// alle Dateien, die mit t_company anfangen und als Endung .csv haben
              		// Zeitstempel extrahieren
              		$zeitstempel = substr($file,31,-4);
              		$importdateien .= "t_company_".$zeitstempel.".csv,t_position_".$zeitstempel.".csv,t_kopf_".$zeitstempel.".csv,";
              
              		echo "Import-Tabellen leeren...<br/>";
              
              		$abfrage = "truncate table imp_company";
              		$erg = mysqli_query($verbindung,$abfrage);
              	  
              		echo "Daten mit Zeitstempel $zeitstempel werden importiert...<br/>";
              
              		// importieren
              		$abfrage = "LOAD DATA INFILE '/var/lib/mysql-files/t_company_".$zeitstempel.".csv' INTO TABLE imp_company CHARACTER SET UTF8 FIELDS TERMINATED BY ';'";
              		$erg = mysqli_query($verbindung,$abfrage);
              		if (!$erg) {
              			echo "Import-Error: ".mysqli_error($verbindung);
              		}
              
              
              		// hier werden die Daten dann verarbeitet und danach ins Archiv geschoben
              
              
              		rename("/var/lib/mysql-files/t_company_".$zeitstempel.".csv", "/var/lib/mysql-files/archiv/t_company_".$zeitstempel.".csv");
              
              
              
              

              Die ausgegebene Fehlermeldung sieht dann so aus:

              Import-Tabellen leeren...
              Daten mit Zeitstempel 20191120125208 werden importiert...
              Import-Error: File '/var/lib/mysql-files/t_company_20191120125208.csv' not found (OS errno 13 - Permission denied)
              

              LG Klaus

              1. Hello,

                machen wir es mal langsam:

                wenn ein mysql_error > 0 auftritt, kannst Du in der Schleife schon mal mit continue zum Ende der Schleife springen.

                Außerdem solltest du den Filename nur einmal bestimmen und nicht für jede Verwendung neu zusammenbauen. Dann kannst Du ihn im Fehlerfall auch mit ausgeben, bzw loggen lassen.

                Bevor Du das SQL-Statement aufrufst, bzw. schon, bevor Du es zusammenbaust, könntest Du mit file_exists() und mit is_readable() auch prüfen, ob der Pfad gültig und lesbar ist. Genaugenommen ist das aber nur Kosmetik und es besteht da dann immer noch eine TOCTTOU-Lücke, aber Du wertest ja noch den SQL-Fehler aus. Das reicht im Prinzip auch. Das TOCTTOU-Problem baust Du dir schon mig Glob() auf. Wenn Du die damit erstellte Liste erst später abarbeitest, kann die im Dateisystem schon wieder ungültig sein. Kommt darauf an, ob parallele Prozesse ablaufen. Das isg aber in einem Netzwerk üblich.

                Den Fehler musst Du also genau an der Stelle feststellen, an der Du das auch schon tust. Du musst nur auch darauf reagieren.

                Bau also die Directorystruktur erstmal so um, dass Du nicht mitten im Dateinamen rumfummeln musst, sondern dass es ganz klare Verzeichnisse für Quelle und Ziel gibt, in denen die Dateien sich dann befinden.

                Und lass Dir den nicht gefundenen Dateinamen mit ausgeben.

                Du könntest auch die Glob-Liste am Anfang ausgeben. Das kostet Dich ein müdes Runzeln und verschafft Dir Übersicht.

                Glück Auf
                Tom vom Berg

                --
                Es gibt nichts Gutes, außer man tut es!
                Das Leben selbst ist der Sinn.
                1. Hallo,

                  wenn ein mysql_error > 0 auftritt, kannst Du in der Schleife schon mal mit continue zum Ende der Schleife springen.

                  Das stimmt wohl, das ist noch keine korrekte Fehlerbehandlung

                  Außerdem solltest du den Filename nur einmal bestimmen und nicht für jede Verwendung neu zusammenbauen. Dann kannst Du ihn im Fehlerfall auch mit ausgeben, bzw loggen lassen.

                  Den Dateinamen bestimme ich mehrmals, da es mehrere Exporte aus SAP geben kann/wird, die mit unterschiedlichen Zeitstempeln erzeugt wurden.

                  Bevor Du das SQL-Statement aufrufst, bzw. schon, bevor Du es zusammenbaust, könntest Du mit file_exists() und mit is_readable() auch prüfen, ob der Pfad gültig und lesbar ist.

                  Wenn keine Dateien lesbar wären, würde glob() ein leeres Ergebis bringen. Da aber Dateien gefunden werden, aus denen auch der Zeitstempel extrahiert werden können (durch das Echo "Daten mit dem Zeitstempel..." bewiesen), ist der Lesezugriff durch PHP schonmal gewährleistet

                  Das TOCTTOU-Problem baust Du dir schon mig Glob() auf. Wenn Du die damit erstellte Liste erst später abarbeitest, kann die im Dateisystem schon wieder ungültig sein. Kommt darauf an, ob parallele Prozesse ablaufen. Das isg aber in einem Netzwerk üblich.

                  Da nur eine einzige Person berechtigt ist den Import durchzuführen und die Verarbeitung direkt innerhalb der Glob-Schleife stattfindet, sollte es keine störenden parallelen Prozesse geben.

                  Bau also die Directorystruktur erstmal so um, dass Du nicht mitten im Dateinamen rumfummeln musst, sondern dass es ganz klare Verzeichnisse für Quelle und Ziel gibt, in denen die Dateien sich dann befinden.

                  Der Dateinamen hat doch nichts mit der Directorystruktur zu tun. Die Dateinamen werden durch den SAP-Export vorgegeben und haben immer dieselbe Struktur und Länge: t_company_20191120125208.csv

                  Und lass Dir den nicht gefundenen Dateinamen mit ausgeben.

                  Der "nicht gefundene" Dateiname wird ja in der Fehlermeldung ausgegeben.

                  Import-Error: File '/var/lib/mysql-files/t_company_20191120125208.csv' not found (OS errno 13 - Permission denied)
                  

                  Du könntest auch die Glob-Liste am Anfang ausgeben. Das kostet Dich ein müdes Runzeln und verschafft Dir Übersicht.

                  Im Grunde geben ich die Glob_Liste ja aus. Siehe

                  echo "Daten mit Zeitstempel $zeitstempel werden importiert...<br/>";
                  

                  Vielleicht hätte ich erwähnen sollen, ich habe den Script-Schnipsel reduziert, da nicht nur eine Datei (t_company_xxxx.csv), sondern noch 2 weitere automatisch vom SAP erzeugt und von mir eingelesen werden. Da der Fehler aber bei allen 3, wenn er denn auftritt, identisch ist, habe ich das Script hier dahingehend verkürzt.

                  Könnte es vielleicht sein, dass der Glob() die Dateien anpackt und vielleicht einen Ticken zu lange festhält, sodass die Datei für den direkt im Anschluss ausgeführte "Load Data"-Befehl noch nicht wieder freigegeben ist? Das würde für mich erklären, dass es mal funktioniert und mal nicht. Ich habe hierfür mal ein sleep(3)eingebaut und werde testen lassen, sobald wieder Export-Dateien vom SAP zur Verfügung gestellt werden.

                  LG Klaus

                  1. Hello,

                    es könnte sein, dass sich der DBServer die Dateien in seinem Verzeichnis von Zeit zu Zeit anguckt, ob er etwas damit anfangen kann. Es wurde Dir ja schon mehrfach geschrieben, dass in dieses Verzeichnis nichts anderes hineingehört!

                    Lege ein separates Verzeisvhnis für data/uploaded/ und für data/achive/ an außerhalb des DB-Verzeichnisses.

                    Wenn der Fehler dann nicht weg ist, bin ich mit meinen Ferndiagnosefähigkeiten am Ende. Und bau das continue o. ä. für den Fehlerfall ein.

                    Glück Auf
                    Tom vom Berg

                    --
                    Es gibt nichts Gutes, außer man tut es!
                    Das Leben selbst ist der Sinn.
                    1. Hallo,

                      es könnte sein, dass sich der DBServer die Dateien in seinem Verzeichnis von Zeit zu Zeit anguckt, ob er etwas damit anfangen kann. Es wurde Dir ja schon mehrfach geschrieben, dass in dieses Verzeichnis nichts anderes hineingehört!

                      Meinem Verständnis nach, gehören Import-Dateien sogar genau da hin, genau dafür soll das Verzeichnis sein und im Standard ist die Variable secure-file-priv extra so gesetzt, dass ein Import aus anderen Verzeichnissen nicht erlaubt ist.

                      Siehe: https://dev.mysql.com/doc/mysql-installation-excerpt/5.7/en/data-directory-initialization.html

                      Insbesondere Punkt 2.

                      LG Klaus

                      1. Laut Handbuch geht

                        LOAD DATA INFILE '/tmp/test.txt' …;
                        

                        sehr wohl. Bauchschmerzen bereitet mir das Umbenennen der Dateien durch den Webserver-Benutzer, unter den PHP wohl (Es gibt genug Methoden das zu ändern) auch läuft.

                        Ich denke, Du hast, was das Einlesen betrifft, ein schnödes Rechte-Problem.

                        1. Laut Handbuch geht

                          LOAD DATA INFILE '/tmp/test.txt' …;
                          

                          sehr wohl.

                          Das hätte wohl auch funktionieren können, ich wollte aber Import-Dateien und archivierte Dateien zusammenhalten und laut Handbuch soll ja mysql-files genau für Imports verwendet werden.

                          Bauchschmerzen bereitet mir das Umbenennen der Dateien durch den Webserver-Benutzer, unter den PHP wohl (Es gibt genug Methoden das zu ändern) auch läuft. Ich denke, Du hast, was das Einlesen betrifft, ein schnödes Rechte-Problem.

                          Da ich dem Ordner die Rechte 777 gegeben habe, sollte es meinem Verständnis nach kein Rechte-Problem, sonder eher ein Sicherheitsproblem sein. Richtig, Verzeichnisrechte sind nicht gleich Leserechte, aber Rechte gelten meines Wissens nach 100%ig und nicht mal und mal nicht. Insofern halte ich ein grundsätzliches Rechteproblem hier für ausgeschlossen, denn es funktioniert ja oft (wenn auch immer öfter nicht). Aber wenn es auch nur einziges Mal funktioniert, kann es kein Rechte-Problem mehr sein, oder?

                          LG Klaus

                          1. Da ich dem Ordner die Rechte 777 gegeben habe, sollte es meinem Verständnis nach kein Rechte-Problem, sonder eher ein Sicherheitsproblem sein. Richtig, Verzeichnisrechte sind nicht gleich Leserechte, aber Rechte gelten meines Wissens nach 100%ig und nicht mal und mal nicht.

                            Solange du immer nur dem Ordner Rechte verpasst und uns die Rechte der Dateien nicht zeigst, deren Import fehlschlägt, gehe ich davon aus, dass der OS-Error 13 auf ein Problem mit den Rechten hinweist.

                            Aber wenn es auch nur einziges Mal funktioniert, kann es kein Rechte-Problem mehr sein, oder?

                            Niemand hier sieht, wie die Dateien in das Verzeichnis gelangen. Möglicherweise geschieht das durch unterschiedliche Skripte und bei einem oder mehreren davon werden die Rechte an den Dateien falsch gesetzt, durch ein oder mehrere andere richtig.

                            Ein schnödes:

                            ls -ld /var/lib/mysql-files; ls -l /var/lib/mysql-files
                            

                            klärt das.

                            1. Da ich dem Ordner die Rechte 777 gegeben habe

                              NeverEver!

                              Für die komplexe Rechtevergabe, auch mit Access-Control-Lists, lese in diesem Machwerk.

                              1. Hallo Raketenwissenschaftler,

                                Für die komplexe Rechtevergabe, auch mit Access-Control-Lists, lese in diesem Machwerk.

                                Würde die Anpassung und Veröffentlichung des Machwerks im Wiki oder Blog gegen die Lizenzbestimmungen verstoßen („ dürfen Sie es nicht unter eine Lizenz stellen, welche die Empfänger schlechter stellt als diese Lizenz“)?

                                Bis demnächst
                                Matthias

                                --
                                Du kannst das Projekt SELFHTML unterstützen,
                                indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
                                1. Hm.

                                  1. Streng formale Auslegung:

                                  Im Machwerk wird nur verlangt, dass die Namensnennung („Hinweise auf den Autor“) "nicht entfernt" wird. Dieser muss also bei Zitaten nicht genannt werden.

                                  Die CC-BY-SA 3.0 (de) des Wikis verlangt hingegen eine explizite Namensnennung. Demnach wäre es - rein formal - ein Vertoß gegen den Passus „Sie … dürfen Sie es nicht unter eine Lizenz stellen, welche die Empfänger schlechter stellt als diese Lizenz“, wenn das Machwerk oder Teile daraus unter der CC-BY-SA 3.0 (de) verbreitet würden. Aber § 3 der Lizenz des Machwerks verlangt nach einer Würdigung:

                                  2. Blick auf den „erkennbaren Wille des Erklärenden“:

                                  Der Autor des Machwerks gewährt in dessen §3 nämlich gerade explizit das Recht, eine eigene Lizenz zu verwenden, worunter die CC-BY-SA 3.0 (de) fallen könnte. Naturgemäß unterscheidet sich eine andere Lizenz aber stets vom Original. Das „nicht schlechter stellt als diese Lizenz“ ist also auszulegen, wobei auf den „erkennbaren Wille des Erklärenden“ abzustellen ist: Hier kommt also der Kern der Willenserklärung zum Tragen: Beide Lizenzen ermöglichen sehr weitgehend die vollständige oder teilweise Weitergabe sowie die Bearbeitung und wollen lediglich unterbinden, dass sich der Weiterverbreiter mit fremden Federn schmückt. Auf die Feinheiten der Namensnennung, welche die Lizenz "CC-BY-SA 3.0 (de)" umfassender regelt, kommt es dem Autor des „Machwerks“ hingegen ganz offensichtlich nicht an.

                                  3. Summa Summarum:

                                  Die Lizenzen sind „kerngleich“. Leichte Abweichungen sind durch das Erlauben eigener Lizenzen gestattet, die strengeren Regeln zur Namensnennung aus CC-BY-SA 3.0 (de) führen allenfalls nur zu einer unbeachtlichen Schlechterstellung der späteren Empfänger - welche sich übrigens ohnehin auf die originale Lizenz berufen könnten. Die Eingangsfrage „Würde die Anpassung und Veröffentlichung des Machwerks im Wiki oder Blog gegen die Lizenzbestimmungen verstoßen“ ist demnach ganz klar mit „Nein“ zu beantworten.

                            2. Hallo,

                              Ein schnödes:

                              ls -ld /var/lib/mysql-files; ls -l /var/lib/mysql-files
                              

                              klärt das.

                              ls -ld /var/lib/mysql-files zeigt:

                              drwxrwxrwx 1 mysql mysql 176 Nov 22 11:10 /var/lib/mysql-files
                              

                              ls -l /var/lib/mysql-files zeigt:

                              drwxrwxrwx 1 root root 39988 Nov 22 11:06 archiv
                              -rwxrwxrwx 1 salden users 98 Nov 22 11:10 t_company_20191122101248.csv
                              -rwxrwxrwx 1 salden users 98 Nov 22 11:10 t_kopf_20191122101248.csv
                              -rwxrwxrwx 1 salden users 98 Nov 22 11:10 t_position_20191122101248.csv
                              

                              Durch das 777 auf den Ordnern und allen Dateien sollten die Rechte doch für wirklich jeden gegeben sein. Nachwievor verstehe ich nicht, warum etwas 3 Mal hintereinander fehlschlägt und beim 4. Mal funktioniert. Ohne irgendeine Änderung am Prozess oder an den Dateien (bis darauf, dass vom SAP jedesmal ein neuer Zeitstempel erzeugt wird und sich damit der Dateiname ändert)

                              LG Klaus

                              1. Hallo Klaus!

                                Zu meiner Erinnerung noch mal die Fehlermeldung:

                                Import-Error: File '/var/lib/mysql-files/t_company_20191120125208.csv' not found (OS errno 13 - Permission denied)
                                

                                Ok. Die Rechte sind zwar viel zu weitgegehend gesetzt - das dürfte ein ernstes Sicherheitsproblem sein - aber gerade nicht zu der Fehlermeldung führen.

                                Kommen wir zu einem der nächsten Punkte der Liste:

                                LOAD DATA INFILE '...'
                                

                                hat noch eine weitere Geschmacksrichtung. Also

                                LOAD DATA INFILE '...'
                                

                                lädt eine Datei vom Dateisystem des Servers, Und verlangt, dass der Benutzer das "File-Privileg" hat.

                                LOAD DATA LOCAL INFILE '...'
                                

                                lädt eine Datei vom Dateisystem des Client, kopiert diese auf den Server. Verlangt NICHT, dass der Benutzer das "File-Privileg" hat.

                                Das "Geht, geht nicht" kann also auch daraus resultieren, dass 'LOCAL' mal gesetzt ist, mal nicht.

                                Wichtiges aus dem Handbuch:

                                "Non-LOCAL load operations read text files located on the server. For security reasons, such operations require that you have the FILE privilege. See Section 6.2.2, “Privileges Provided by MySQL”. Also, non-LOCAL load operations are subject to the secure_file_priv system variable setting. If the variable value is a nonempty directory name, the file to be loaded must be located in that directory. If the variable value is empty (which is insecure), the file need only be readable by the server.

                                Using LOCAL is a bit slower than letting the server access the files directly, because the file contents must be sent over the connection by the client to the server. On the other hand, you do not need the FILE privilege to load local files."

                                • Mit LOCAL wird übrigens immer eine Kopie auf dem Server erzeugt, was dann problematisch wird, wenn diese sehr groß sind und der freie Platz nicht mehr ausreicht. df -h /tmp (aus dem Server) könnte hilfreich sein, dieses zu erkennen.

                                ToDo:

                                • Zu prüfen ist also, wie (und bei welchem Host/IP!) die Anmeldung erfolgt und ob immer der selbe Benutzer von demselben Host angemeldet wird (Falls der Benutzer bei lokaler oder Netzwerkanmeldung unterschiedliche Rechte bezüglich des "File-Privileg" hat).
                                • Zu prüfen ist also auch, ob mal LOAD DATA INFILE und alternierend LOAD DATA LOCAL INFILE benutzt wird.
                                • Man irrt oft bezüglich der Einstellungen. Ein SHOW GLOBAL VARIABLES LIKE 'secure_file_priv' zeigt, was wirklich läuft. Vergleiche das mit dem Pfad. Steht da wirklich '/var/lib/mysql-files/'? (Und "Nein, das wäre nicht falsch, Oracle sieht das als Default-Wert durchaus so vor.")
                                • Falls LOAD DATA LOCAL INFILE benutzt wird, wäre die Variable load_infile zu prüfen: SHOW GLOBAL VARIABLES LIKE 'local_infile' und entweder neu zu setzen (ist dynamisch) oder voreinzustellen.
                                1. Hallo,

                                  LOAD DATA LOCAL INFILE '...'
                                  

                                  lädt eine Datei vom Dateisystem des Client, kopiert diese auf den Server. Verlangt NICHT, dass der Benutzer das "File-Privileg" hat.

                                  Ich habe jetzt extra nochmals geprüft, aber es wird nie "load data local infile" verwendet. Und das File-Privileg sollte ja, wie Du auch schon geschrieben hast, "viel zu weitgehend" gesetzt. Das macht man schonmal, wenn man ein Rechte-Problem ausschließen möchte.

                                  Die Variable secure_file_priv war ursprünglich auf /var/lib/mysql-files gesetzt (so wie es durch die Standardinstallation mit zypper in mysql gesetzt wird), habe ich aber auch schon testweise auf secure_file_priv="" gesetzt.

                                  ToDo:

                                  • Zu prüfen ist also, wie (und bei welchem Host/IP!) die Anmeldung erfolgt und ob immer der selbe Benutzer von demselben Host angemeldet wird (Falls der Benutzer bei lokaler oder Netzwerkanmeldung unterschiedliche Rechte bezüglich des "File-Privileg" hat).

                                  Das SAP benutzt immer den einzigen FTP-Zugang, um die Daten zu übertragen. Der Anwender benutzt immer die Weboberfläche (PHP-Script, um den Import zu starten). Es ist immer derselbe Anwender (oder ich).

                                  • Zu prüfen ist also auch, ob mal LOAD DATA INFILE und alternierend LOAD DATA LOCAL INFILE benutzt wird.

                                  Es wird immer nur LOAD DATA INFILE verwendet.

                                  • Man irrt oft bezüglich der Einstellungen. Ein SHOW GLOBAL VARIABLES LIKE 'secure_file_priv'

                                  zeigt: ""

                                  LG Klaus

                                2. Was mir noch aufgefallen ist: (PHP rename Befehl). Wenn der Fehler auftritt, sind die Dateien danach komplett verschwunden, also auch nicht im Archiv-Ordner gelandet.

                                  Da wäre zu klären, ob die Dateien wirklich vorhanden waren.

                                  Ergänze bitte mal folgendes:

                                  $abfrage = "LOAD DATA INFILE '"
                                             . mysqli_real_escape_string( $verbindung, $file )
                                             . "' INTO TABLE imp_company CHARACTER SET UTF8 FIELDS TERMINATED BY ';'";
                                  

                                  (Ich kann nicht erkennen, warum Du den Dateiname fehlerträchtig neu zusammenbaust, statt $file zu nehmen. mysqli_real_escape_string() fehlt.)

                                      if ( ! $erg ) {
                                        echo 'Import-Error: " . mysqli_error( $verbindung );
                                        echo '<pre>';
                                        echo "Datei: '$file'" . PHP_EOL;
                                        echo "Abfrage $abfrage "  . PHP_EOL;
                                        $shell = 'ls -l ' . escape_shell_arg( $file );
                                        echo `$shell` . PHP_EOL;
                                        echo '</pre>';
                                      }
                                  

                                  (Wenn es nicht geht will man ja wissen, warum...)

                                  Du baust:

                                  $importdateien .= "t_company_".$zeitstempel.".csv,t_position_".$zeitstempel.".csv,t_kopf_".$zeitstempel.".csv,";
                                  

                                  Aber ich sehe nicht, wo Du das verwendest. Zusammen mit (1.) führt mich das zu der Vermutung, dass das gezeigte Skript nicht alles wesentliche, insbesondere den eigentlichen Fehler, nicht enthält. Womöglich hast Du im lobenswerten Bemühen, es auf auf das Wesentliche zu reduzieren, übertrieben.

                                  Das Error-Handling beim Verbindungsaufbau ist nicht vorhanden oder nicht sichtbar.

                                  1.       $shell = 'ls -l ' . escape_shell_arg( $file );
                                          echo `$shell` . PHP_EOL;
                                          echo '</pre>';
                                        }
                                    

                                    Verbessert:

                                          $shell = 'ls -l ' . escape_shell_arg( $file ) . ' 2>&1';;
                                          echo `$shell` . PHP_EOL;
                                          echo '</pre>';
                                    

                                    Duuch die Umleitung (2>&1) der Fehlerkonsole bekommst Du in PHP eine Fehlermeldung angezeigt, wenn die Datei nicht existert, statt einer leeren Zeile.

                                  2. Unter 2. muss es vollständig lauten:

                                    
                                        if ( ! $erg ) {
                                          echo 'Import-Error: ' . mysqli_error( $verbindung );
                                          echo '<pre>';
                                          echo "Datei: '$file'" . PHP_EOL;
                                          echo "Abfrage: $abfrage "  . PHP_EOL;
                                          $shell = 'ls -l ' . escape_shell_arg( $file ) . ' 2>&1';
                                          echo `$shell` . PHP_EOL;
                                          echo '</pre>';
                                        }
                                    

                                    Dann stimmen auch die Farben.

                                  3. Hallo,

                                    Da wäre zu klären, ob die Dateien wirklich vorhanden waren.

                                    Ich kann sicherstellen, dass die Dateien immer vorhanden sind, da ich während meiner Fehleranalyse immer jeden einzelnen Schritt überpüfe, d.h. bevor ich den Import starte, habe ich bereits manuell geprüft ob die Dateien vorhanden sind.

                                    (Ich kann nicht erkennen, warum Du den Dateiname fehlerträchtig neu zusammenbaust, statt $file zu nehmen. mysqli_real_escape_string() fehlt.)

                                    Das liegt daran, dass mit jedem Export aus SAP nicht nur eine sondern 3 Dateien erstellt werden, die zusammengehören und zusammen import werden müssen.

                                    Bsp.:

                                    t_company_1.csv
                                    t_kopf_1.csv
                                    t_position_1.csv
                                    t_company_2.csv
                                    t_kopf_2.csv
                                    t_position_2.csv
                                    

                                    Ich hole mir also den Zeitspempel von jeder t_company_x.csv und weiß damit, wie die korrespondierenden t_kopf und t_position heißen.

                                        if ( ! $erg ) {
                                          echo 'Import-Error: " . mysqli_error( $verbindung );
                                          echo '<pre>';
                                          echo "Datei: '$file'" . PHP_EOL;
                                          echo "Abfrage $abfrage "  . PHP_EOL;
                                          $shell = 'ls -l ' . escape_shell_arg( $file );
                                          echo `$shell` . PHP_EOL;
                                          echo '</pre>';
                                        }
                                    

                                    (Wenn es nicht geht will man ja wissen, warum...)

                                    echo mysqli_error($verbgindung);
                                    

                                    zeigt die Fehlermeldung und auch den Dateinamen. Einzig die Abfrage könnte ich zusätzlich noch ausgeben lassen.

                                    Du baust:

                                    $importdateien .= "t_company_".$zeitstempel.".csv,t_position_".$zeitstempel.".csv,t_kopf_".$zeitstempel.".csv,";
                                    

                                    Die Verwendung habe ich tatsächlich weggekürzt. Wird ausschließlich hier verwendet:

                                    	// Protokolleintrag erstellen
                                    	$wann = date("Y-m-d, H:i:s");
                                    	$abfrage = "insert into importprotokoll (wer,wann,was) values ('$pnr','$wann','$importdateien')";
                                    	$erg = mysqli_query($verbindung,$abfrage);
                                    

                                    Das Error-Handling beim Verbindungsaufbau ist nicht vorhanden oder nicht sichtbar.

                                    Habe ich auch rausgekürzt, da ich den Aufbau der DB-Verbindung (und des Errorhandlings) für nicht relevant hielt.

                                    Was ich noch immer nicht verstehe: Ein Computer würfelt nicht. Er läuft immer einen vorgegebenen Pfad ab. Ohne Ausnahme. Das SAP exportiert die Dateien immer auf dieselbe Art, immer im selben Format. Die Dateien sind nach jedem Export da. Ohne erkennbaren Unterschied. Weder beim Namen, noch bei den Rechten, noch bei Struktur oder Inhalt. Das Script versucht unverändert die Dateien zu importieren. Bei 5 Versuchen gibt es immer dieselbe Fehlermeldung, beim 6. Versuch klappt es. Manchmal klappt es direkt (immer seltener), manchmal beim 2. Versuch, manchmal erst beim 8. Eine Regel habe ich bisher nicht erkennen können. Getestet habe ich auch: Die Zeit zwischen Export und Import, SAP abmelden, Webseite anmelden, Browser neustarten vor Import.

                                    LG Klaus

                                    1. Hallo,

                                      Ich hole mir also den Zeitspempel von jeder t_company_x.csv und weiß damit, wie die korrespondierenden t_kopf und t_position heißen.

                                      Weiß du das sicher? Zeitstempel neigen dazu, sich mindestens sekündlich zu ändern.

                                      Gruß
                                      Kalk

                                      1. Hallo,

                                        Weiß du das sicher? Zeitstempel neigen dazu, sich mindestens sekündlich zu ändern.

                                        Das weiß ich sicher, da die Transaktion im SAP zum Exportieren der Daten nur exklusiv gestartet werden kann und von SAP ein Zeitstempel erzeugt wird, mit dem dann die drei Dateien übertragen werden.

                                        LG Klaus

                                    2. Bei 5 Versuchen gibt es immer dieselbe Fehlermeldung, beim 6. Versuch klappt es.

                                      Ich weiß ja nicht, wie die Daten auf den Rechner kommen - aber kann es sein, dass die Datei(en) zu dem Zeitpunkt des Scheiterns noch nicht fertig / erzeugt oder übertragen und deswegen exclusiv gelockt sind? Auch sowas kann nämlich dazu führen, dass die Datei dann existiert, passende Rechte gesetzt sind, der lesende Zugriff aber doch verweigert wird. Stichwort: flock.

                                      • Ein lsof /pfad/zur/datei könnte Aufklärung verschaffen.

                                      Ausgaben sähen dann etwa so aus:

                                      COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
                                      php     5099   foo    3rW  REG    8,5        6 25165834 /tmp/test
                                      
                                      1. Hallo,

                                        Bei 5 Versuchen gibt es immer dieselbe Fehlermeldung, beim 6. Versuch klappt es.

                                        Ich weiß ja nicht, wie die Daten auf den Rechner kommen - aber kann es sein, dass die Datei(en) zu dem Zeitpunkt des Scheiterns noch nicht fertig / erzeugt oder übertragen und deswegen exclusiv gelockt sind? Auch sowas kann nämlich dazu führen, dass die Datei dann existiert, passende Rechte gesetzt sind, der lesende Zugriff aber doch verweigert wird. Stichwort: flock.

                                        Die Dateien werden per einfachen FTP vom SAP auf den Server übertragen.

                                        Ein flock war auch meine erste Vermutung. Das würde erklären, warum es mal geht und mal nicht. Daher hatte ich das versucht zu reproduzieren:

                                        • Eine genügend lange Zeit (ca. 2 Minuten) warten zwischen Export vom SAP und Import vom Script.
                                        • SAP beendet (um eventuell offen gehaltene Dateien durch FTP auszuschließen)
                                        • Browser erst nach Export gestartet, auf Seite angemeldet und Import gestartet
                                        • Die Dateien über WinSCP zum Lesen geöffnet, kein Problem
                                        • Zwischen glob() und load into table ein Wait für 3 Sekunden eingebaut.

                                        Die Zeit-Komponente meine ich so ausschließen zu können.

                                        lsof hat bisher keine Auffälligkeiten gezeigt, allerdings haben bisher auch (mal wieder) alle Versuche beim ersten Mal funktioniert.

                                        LG Klaus

                                        • Ein lsof /pfad/zur/datei könnte Aufklärung verschaffen.

                                        Ausgaben sähen dann etwa so aus:

                                        COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
                                        php     5099   foo    3rW  REG    8,5        6 25165834 /tmp/test
                                        
                  2. Wenn keine Dateien lesbar wären, würde glob() ein leeres Ergebis bringen. Da aber Dateien gefunden werden, aus denen auch der Zeitstempel extrahiert werden können (durch das Echo "Daten mit dem Zeitstempel..." bewiesen), ist der Lesezugriff durch PHP schonmal gewährleistet

                    Nein. Dir hat noch niemand die Sache mit den Unix-Rechtem richtig erklärt. Ich nehm statt glob() mal ls.

                    Es sei das Verzeichnis "ver", Rechte wie folgt:

                    user~ ls -ld ver
                    
                    drwxr-xr-x 2 user group 4096 Nov 21 09:47 ver
                    

                    Darin die Dateien "keine", "lesen", "lesen+schreiben", "lesen+execute". Inhalt ist jeweils gelesen\n Wir machen damit mal "ls -l":

                    user$ ls -l ver
                    
                    -rw-r----- 1 user group 8 Nov 21 09:50 keine
                    -rw-r--r-- 1 user group 8 Nov 21 09:48 lesen
                    -rwxr-xr-x 1 user group 8 Nov 21 09:49 lesen+execute
                    -rw-rw-rw- 1 user group 8 Nov 21 09:49 lesen+schreiben
                    

                    Und sehen jetzt mal nach, ob der www-data das Verzeichnis und dann die Dateien lesen kann

                    Zuerst werden wir mal 'www-data', was eine kleine Vorbereitung erfordert, die Ihr zu Hause bitte nicht nachmacht:

                    user$ su
                    root# usermod -s www-data /bin/bash
                    root# su www-data
                    

                    Und dann:

                    www-data$ ls -l
                    
                    -rw-r----- 1 user group 8 Nov 21 09:50 keine
                    -rw-r--r-- 1 user group 8 Nov 21 09:48 lesen
                    -rwxr-xr-x 1 user group 8 Nov 21 09:49 lesen+execute
                    -rw-rw-rw- 1 user group 8 Nov 21 09:49 lesen+schreiben
                    
                    www-data$ for file in `ls`; do echo "${file}:"; cat $file; done
                    keine:
                    cat: keine: Keine Berechtigung
                    lesen:
                    gelesen
                    lesen+execute:
                    gelesen
                    lesen+schreiben:
                    gelesen
                    

                    Fazit:

                    1. Für glob() (ls benutzt, wie das glob() von PHP, glob aus der C-Lib) muss man Leserechte am Verzeichnis haben.
                    2. Um den Dateiinhalt lesen zu können muss man Leserechte an der Datei haben. Beides hat niteinander nichts zu tun.

                    Wer meine obige Warnung überging darf Folgendes nicht vergessen:

                    www-data$ exit
                    root# usermod -s /usr/sbin/nologin www-data
                    exit
                    
                    1. Hallo,

                      Zuerst werden wir mal 'www-data', was eine kleine Vorbereitung erfordert, die Ihr zu Hause bitte nicht nachmacht:

                      user$ su
                      root# usermod -s www-data /bin/bash
                      root# su www-data
                      

                      Als kleine Anmerkung:
                      Mit su -s /bin/bash www-data erreicht man das gleiche, aber man muss nichts an den Benutzereinstellungen verändern und vor allem muss man nicht daran denken, dass man es auch wieder rückgängig machen muss.

                      Gruß
                      Patrick

                      1. Ja. Danke. Auch ich sollte häufiger das Manual (man $Programm) oder wenigstens $Programm --help benutzen.

              2. /var/lib/mysql-files/

                Oha. Wenn darin ein via Webserver aufgerufenes Skript herumschreibt mag das nicht jeder Admin mögen und deshalb durchaus Probleme geben.

                Es gibt, je nach Linux und Installation durchaus noch ein paar 'Zugriffsverhinderer' mehr: appamor, SELinux

                Eventuell findet sich was in den passenden Logfiles.