Python3 - Popen und seine Geheimnisse ;)
-
Eine der letzten Baustellen in meinem Tool Restic UI war die Funktion zum Mounten eines Restic Backups.
Das gröĂte Problem dabei war, das dieser Prozess nicht beendet wird sonder offen bleibt. Das kennt ihr, wenn ihr das schon mal ĂŒber die Konsole gemacht habt, dann erscheint das folgende
reading repository password from stdin repository 6859cd64 opened successfully, password is correct Now serving the repository at /home/frank/m5 When finished, quit with Ctrl-c or umount the mountpoint.
Jetzt kann man in meinem Tool das Passwort mittlerweile abspeichern oder wahlweise auch jedes mal eingeben. Aber, was mache ich wenn das Passwort falsch eingegeben wurde? Ich habe tagelang versucht, die Fehler abzufangen, das geht aber meiner Meinung nach nicht, da der Prozess am Laufen ist. Was machen?
Ich habe so ziemlich jede Seite im Netz zum Thema Popen durch Nichts davon brachten den gewĂŒnschten Erfolg.
Ok, fangen wir von vorne an, was sagt die Dokumentation von Restic?
EXIT STATUS =========== Exit status is 0 if the command was successful, and non-zero if there was any error.
Gut, wenn ich den richtigen Fehlercode nicht abfangen kann, dann kann ich evt. ermitteln ob der Prozess lĂ€uft oder einen Fehler ausgibt. Das wĂŒrde mir zu mindestens helfen, den User zu informieren.
Bei den hunderten von Webseiten, hatte ich irgendwo was ĂŒber p.poll() aufgeschnappt. Das steht in der Python Dokumentation.
Popen.poll()
Check if child process has terminated. Set and return returncode attribute. Otherwise, returns None.
Das sollte fĂŒr meinen Zweck ausreichend sein. Erster Versuch
# no passwd in backup_data? Ok, we ask for one if len(backup_data[row].password) == 0: pwd = get_password(self) ## pwd ('1234', True) if pwd[1] is False: return False try: cmd = ['restic', '-r', backup_data[row].repository, 'mount', mount_path] # We create the object, here with stdin and open the asynchronous subprocess p1 = subprocess.Popen(cmd,stdin=subprocess.PIPE,stderr=subprocess.PIPE) p1.stdin.write(pwd[0].encode())) p1.stdin.close() except Exception: pass finally: try: if p1.poll() == 1: raise Exception("The Restic mount command was not successful. Probable cause: wrong password or no key found") else: msg_box(self, "Successful mount of the Repository!") except Exception as e: msg_box_error(self, repr(e))
Ich hoffe man versteht was ich hier mache.
- Wir holen uns das Passort ĂŒber eine Usereingabe
- Wir bauen den Restic Befehl cmd
- Wir starten den subprocess.Popen
- Wir senden das Passwort auf stdin
- Wir schlieĂen stdin
- In finally machen wir den p1.poll() und werten diesen aus.
Bei den vielen, vielen, super vielen Test war mir aufgefallen, das das klappte.
p1.poll() gab einmal None aus und einmal eine 1
None war ein laufender Prozess, also erfolgreich gemountet.
Eine 1 war ein Fehler, hier falsche Passwort Eingabe.Nach einigen weiteren Tests, ging es auf einmal nicht mehr. Ich wusste aber ganz genau, das es ging, ich konnte das in der Konsole noch lesen. Also meine Kontrollausgaben. Was war passiert. Eine halbe Stunde GrĂŒbeln und ich kam zu der Ăberzeugung, das muss ein Zeitproblem sein. Ok, also eine Bremse einbauen. Im finally vor dem try kommt das rein.
print("POLL", p1.poll()) import time time.sleep(1.0)
Kleine Kontrollausgabe, wir importieren das Modul time (ja, gehört nicht an diese Stelle, ich weiĂ...) und dann lassen wir den Prozess 1 Sekunde pausieren. Wir geben hier jetzt dem subprocess die Gelegenheit, das falsche Passwort zu erkennen und den richtigen Exit Status zu setzen. Und Bingo es ging so wie ich es mir vorgestellt hatte.
Wieder extrem viel gelernt.
Hinweis
Der Code oben ist nur ein Ausschnitt, ich hoffe das er nachvollziehbar ist. Wenn nicht einfach hier drunter kommentieren, es gibt das dann in lang
FĂŒr Profis, die evt. hier mal drĂŒber stolpern. Ich nehme jede Verbesserung gerne auf und probiere es auch aus.
WĂŒnsche mir eigentlich immer noch, das ich die Ausgabe
Fatal: wrong password or no key found
vernĂŒnftig abfangen und weiterverarbeiten kann. So wie mit
output = process.stdout
bei einem subprocess.run.