mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2024-12-28 12:07:39 +06:00
Remove AlfCrypto libraries and perform everything in Python
The old AlfCrypto DLL, SO and DYLIB files are ancient, I don't have the systems to recompile them all, they cause issues on ARM Macs, and I doubt with all the Python improvements over the last years that they have a significant performance advantage. And even if that's the case, nobody is importing hundreds of DRM books at the same time so it shouldn't hurt if some decryptions might take a bit longer.
This commit is contained in:
parent
9276d77f63
commit
410e086d08
@ -187,15 +187,12 @@ class DeDRM(FileTypePlugin):
|
|||||||
os.mkdir(self.alfdir)
|
os.mkdir(self.alfdir)
|
||||||
# only continue if we've never run this version of the plugin before
|
# only continue if we've never run this version of the plugin before
|
||||||
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
||||||
if not os.path.exists(self.verdir):
|
if not os.path.exists(self.verdir) and not iswindows and not isosx:
|
||||||
if iswindows:
|
|
||||||
names = ["alfcrypto.dll","alfcrypto64.dll"]
|
names = ["kindlekey.py","adobekey.py","ignoblekeyNookStudy.py"]
|
||||||
elif isosx:
|
|
||||||
names = ["libalfcrypto.dylib"]
|
|
||||||
else:
|
|
||||||
names = ["libalfcrypto32.so","libalfcrypto64.so","kindlekey.py","adobekey.py","subasyncio.py"]
|
|
||||||
lib_dict = self.load_resources(names)
|
lib_dict = self.load_resources(names)
|
||||||
print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print("{0} v{1}: Copying needed Python scripts from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
|
|
||||||
for entry, data in lib_dict.items():
|
for entry, data in lib_dict.items():
|
||||||
file_path = os.path.join(self.alfdir, entry)
|
file_path = os.path.join(self.alfdir, entry)
|
||||||
|
Binary file not shown.
@ -8,260 +8,90 @@
|
|||||||
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
||||||
# pbkdf2.py This code may be freely used and modified for any purpose.
|
# pbkdf2.py This code may be freely used and modified for any purpose.
|
||||||
|
|
||||||
import sys, os
|
|
||||||
import hmac
|
import hmac
|
||||||
from struct import pack
|
from struct import pack
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import aescbc
|
||||||
|
|
||||||
# interface to needed routines libalfcrypto
|
class Pukall_Cipher(object):
|
||||||
def _load_libalfcrypto():
|
def __init__(self):
|
||||||
import ctypes
|
self.key = None
|
||||||
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
|
|
||||||
|
|
||||||
pointer_size = ctypes.sizeof(ctypes.c_voidp)
|
def PC1(self, key, src, decryption=True):
|
||||||
name_of_lib = None
|
sum1 = 0;
|
||||||
if sys.platform.startswith('darwin'):
|
sum2 = 0;
|
||||||
name_of_lib = 'libalfcrypto.dylib'
|
keyXorVal = 0;
|
||||||
elif sys.platform.startswith('win'):
|
if len(key)!=16:
|
||||||
if pointer_size == 4:
|
raise Exception("PC1: Bad key length")
|
||||||
name_of_lib = 'alfcrypto.dll'
|
wkey = []
|
||||||
else:
|
for i in range(8):
|
||||||
name_of_lib = 'alfcrypto64.dll'
|
wkey.append(key[i*2]<<8 | key[i*2+1])
|
||||||
else:
|
dst = bytearray(len(src))
|
||||||
if pointer_size == 4:
|
for i in range(len(src)):
|
||||||
name_of_lib = 'libalfcrypto32.so'
|
temp1 = 0;
|
||||||
else:
|
byteXorVal = 0;
|
||||||
name_of_lib = 'libalfcrypto64.so'
|
for j in range(8):
|
||||||
|
temp1 ^= wkey[j]
|
||||||
# hard code to local location for libalfcrypto
|
sum2 = (sum2+j)*20021 + sum1
|
||||||
libalfcrypto = os.path.join(sys.path[0],name_of_lib)
|
sum1 = (temp1*346)&0xFFFF
|
||||||
if not os.path.isfile(libalfcrypto):
|
sum2 = (sum2+sum1)&0xFFFF
|
||||||
libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib)
|
temp1 = (temp1*20021+1)&0xFFFF
|
||||||
if not os.path.isfile(libalfcrypto):
|
byteXorVal ^= temp1 ^ sum2
|
||||||
libalfcrypto = os.path.join('.',name_of_lib)
|
curByte = src[i]
|
||||||
if not os.path.isfile(libalfcrypto):
|
if not decryption:
|
||||||
raise Exception('libalfcrypto not found at %s' % libalfcrypto)
|
keyXorVal = curByte * 257;
|
||||||
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
libalfcrypto = CDLL(libalfcrypto)
|
|
||||||
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libalfcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
# aes cbc decryption
|
|
||||||
#
|
|
||||||
# struct aes_key_st {
|
|
||||||
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
|
|
||||||
# int rounds;
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# typedef struct aes_key_st AES_KEY;
|
|
||||||
#
|
|
||||||
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
|
|
||||||
# const unsigned long length, const AES_KEY *key,
|
|
||||||
# unsigned char *ivec, const int enc);
|
|
||||||
|
|
||||||
AES_MAXNR = 14
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
|
|
||||||
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Pukall 1 Cipher
|
|
||||||
# unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
|
|
||||||
# unsigned char *dest, unsigned int len, int decryption);
|
|
||||||
|
|
||||||
PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
|
|
||||||
|
|
||||||
# Topaz Encryption
|
|
||||||
# typedef struct _TpzCtx {
|
|
||||||
# unsigned int v[2];
|
|
||||||
# } TpzCtx;
|
|
||||||
#
|
|
||||||
# void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
|
|
||||||
# void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
|
|
||||||
|
|
||||||
class TPZ_CTX(Structure):
|
|
||||||
_fields_ = [('v', c_long * 2)]
|
|
||||||
|
|
||||||
TPZ_CTX_p = POINTER(TPZ_CTX)
|
|
||||||
topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
|
|
||||||
topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
|
|
||||||
|
|
||||||
|
|
||||||
class AES_CBC(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._blocksize = 0
|
|
||||||
self._keyctx = None
|
|
||||||
self._iv = 0
|
|
||||||
|
|
||||||
def set_decrypt_key(self, userkey, iv):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise Exception('AES CBC improper key used')
|
|
||||||
return
|
|
||||||
keyctx = self._keyctx = AES_KEY()
|
|
||||||
self._iv = iv
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
|
||||||
if rv < 0:
|
|
||||||
raise Exception('Failed to initialize AES CBC key')
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
mutable_iv = create_string_buffer(self._iv, len(self._iv))
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise Exception('AES CBC decryption failed')
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
class Pukall_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.key = None
|
|
||||||
|
|
||||||
def PC1(self, key, src, decryption=True):
|
|
||||||
self.key = key
|
|
||||||
out = create_string_buffer(len(src))
|
|
||||||
de = 0
|
|
||||||
if decryption:
|
if decryption:
|
||||||
de = 1
|
keyXorVal = curByte * 257;
|
||||||
rv = PC1(key, len(key), src, out, len(src), de)
|
for j in range(8):
|
||||||
return out.raw
|
wkey[j] ^= keyXorVal;
|
||||||
|
dst[i] = curByte
|
||||||
|
return bytes(dst)
|
||||||
|
|
||||||
class Topaz_Cipher(object):
|
class Topaz_Cipher(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._ctx = None
|
self._ctx = None
|
||||||
|
|
||||||
def ctx_init(self, key):
|
def ctx_init(self, key):
|
||||||
tpz_ctx = self._ctx = TPZ_CTX()
|
ctx1 = 0x0CAFFE19E
|
||||||
topazCryptoInit(tpz_ctx, key, len(key))
|
if isinstance(key, str):
|
||||||
return tpz_ctx
|
key = key.encode('latin-1')
|
||||||
|
for keyByte in key:
|
||||||
|
ctx2 = ctx1
|
||||||
|
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
|
||||||
|
self._ctx = [ctx1, ctx2]
|
||||||
|
return [ctx1,ctx2]
|
||||||
|
|
||||||
def decrypt(self, data, ctx=None):
|
def decrypt(self, data, ctx=None):
|
||||||
if ctx == None:
|
if ctx == None:
|
||||||
ctx = self._ctx
|
ctx = self._ctx
|
||||||
out = create_string_buffer(len(data))
|
ctx1 = ctx[0]
|
||||||
topazCryptoDecrypt(ctx, data, out, len(data))
|
ctx2 = ctx[1]
|
||||||
return out.raw
|
plainText = ""
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode('latin-1')
|
||||||
|
for dataByte in data:
|
||||||
|
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
|
||||||
|
ctx2 = ctx1
|
||||||
|
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
|
||||||
|
plainText += chr(m)
|
||||||
|
return plainText
|
||||||
|
|
||||||
print("Using Library AlfCrypto DLL/DYLIB/SO")
|
class AES_CBC(object):
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
def __init__(self):
|
||||||
|
self._key = None
|
||||||
|
self._iv = None
|
||||||
|
self.aes = None
|
||||||
|
|
||||||
|
def set_decrypt_key(self, userkey, iv):
|
||||||
|
self._key = userkey
|
||||||
|
self._iv = iv
|
||||||
|
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
|
||||||
|
|
||||||
def _load_python_alfcrypto():
|
def decrypt(self, data):
|
||||||
|
iv = self._iv
|
||||||
import aescbc
|
cleartext = self.aes.decrypt(iv + data)
|
||||||
|
return cleartext
|
||||||
class Pukall_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.key = None
|
|
||||||
|
|
||||||
def PC1(self, key, src, decryption=True):
|
|
||||||
sum1 = 0;
|
|
||||||
sum2 = 0;
|
|
||||||
keyXorVal = 0;
|
|
||||||
if len(key)!=16:
|
|
||||||
raise Exception('Pukall_Cipher: Bad key length.')
|
|
||||||
wkey = []
|
|
||||||
for i in range(8):
|
|
||||||
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
|
||||||
dst = ""
|
|
||||||
for i in range(len(src)):
|
|
||||||
temp1 = 0;
|
|
||||||
byteXorVal = 0;
|
|
||||||
for j in range(8):
|
|
||||||
temp1 ^= wkey[j]
|
|
||||||
sum2 = (sum2+j)*20021 + sum1
|
|
||||||
sum1 = (temp1*346)&0xFFFF
|
|
||||||
sum2 = (sum2+sum1)&0xFFFF
|
|
||||||
temp1 = (temp1*20021+1)&0xFFFF
|
|
||||||
byteXorVal ^= temp1 ^ sum2
|
|
||||||
curByte = ord(src[i])
|
|
||||||
if not decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
|
||||||
if decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
for j in range(8):
|
|
||||||
wkey[j] ^= keyXorVal;
|
|
||||||
dst+=chr(curByte)
|
|
||||||
return dst
|
|
||||||
|
|
||||||
class Topaz_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._ctx = None
|
|
||||||
|
|
||||||
def ctx_init(self, key):
|
|
||||||
ctx1 = 0x0CAFFE19E
|
|
||||||
if isinstance(key, str):
|
|
||||||
key = key.encode('latin-1')
|
|
||||||
for keyByte in key:
|
|
||||||
ctx2 = ctx1
|
|
||||||
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
|
|
||||||
self._ctx = [ctx1, ctx2]
|
|
||||||
return [ctx1,ctx2]
|
|
||||||
|
|
||||||
def decrypt(self, data, ctx=None):
|
|
||||||
if ctx == None:
|
|
||||||
ctx = self._ctx
|
|
||||||
ctx1 = ctx[0]
|
|
||||||
ctx2 = ctx[1]
|
|
||||||
plainText = ""
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = data.encode('latin-1')
|
|
||||||
for dataByte in data:
|
|
||||||
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
|
|
||||||
ctx2 = ctx1
|
|
||||||
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
|
|
||||||
plainText += chr(m)
|
|
||||||
return plainText
|
|
||||||
|
|
||||||
class AES_CBC(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._key = None
|
|
||||||
self._iv = None
|
|
||||||
self.aes = None
|
|
||||||
|
|
||||||
def set_decrypt_key(self, userkey, iv):
|
|
||||||
self._key = userkey
|
|
||||||
self._iv = iv
|
|
||||||
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
iv = self._iv
|
|
||||||
cleartext = self.aes.decrypt(iv + data)
|
|
||||||
return cleartext
|
|
||||||
|
|
||||||
print("Using Library AlfCrypto Python")
|
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
AES_CBC = Pukall_Cipher = Topaz_Cipher = None
|
|
||||||
cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
|
|
||||||
for loader in cryptolist:
|
|
||||||
try:
|
|
||||||
AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, Exception):
|
|
||||||
pass
|
|
||||||
return AES_CBC, Pukall_Cipher, Topaz_Cipher
|
|
||||||
|
|
||||||
AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
|
|
||||||
|
|
||||||
|
|
||||||
class KeyIVGen(object):
|
class KeyIVGen(object):
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -80,10 +80,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import binascii
|
import binascii
|
||||||
try:
|
from alfcrypto import Pukall_Cipher
|
||||||
from alfcrypto import Pukall_Cipher
|
|
||||||
except:
|
|
||||||
print("AlfCrypto not found. Using python PC1 implementation.")
|
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from utilities import SafeUnbuffered
|
||||||
|
|
||||||
@ -140,41 +137,8 @@ def PC1(key, src, decryption=True):
|
|||||||
# if we can get it from alfcrypto, use that
|
# if we can get it from alfcrypto, use that
|
||||||
try:
|
try:
|
||||||
return Pukall_Cipher().PC1(key,src,decryption)
|
return Pukall_Cipher().PC1(key,src,decryption)
|
||||||
except NameError:
|
except:
|
||||||
pass
|
raise
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# use slow python version, since Pukall_Cipher didn't load
|
|
||||||
sum1 = 0;
|
|
||||||
sum2 = 0;
|
|
||||||
keyXorVal = 0;
|
|
||||||
if len(key)!=16:
|
|
||||||
DrmException ("PC1: Bad key length")
|
|
||||||
wkey = []
|
|
||||||
for i in range(8):
|
|
||||||
wkey.append(key[i*2]<<8 | key[i*2+1])
|
|
||||||
dst = bytearray(len(src))
|
|
||||||
for i in range(len(src)):
|
|
||||||
temp1 = 0;
|
|
||||||
byteXorVal = 0;
|
|
||||||
for j in range(8):
|
|
||||||
temp1 ^= wkey[j]
|
|
||||||
sum2 = (sum2+j)*20021 + sum1
|
|
||||||
sum1 = (temp1*346)&0xFFFF
|
|
||||||
sum2 = (sum2+sum1)&0xFFFF
|
|
||||||
temp1 = (temp1*20021+1)&0xFFFF
|
|
||||||
byteXorVal ^= temp1 ^ sum2
|
|
||||||
curByte = src[i]
|
|
||||||
if not decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
|
||||||
if decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
for j in range(8):
|
|
||||||
wkey[j] ^= keyXorVal;
|
|
||||||
dst[i] = curByte
|
|
||||||
return bytes(dst)
|
|
||||||
|
|
||||||
# accepts unicode returns unicode
|
# accepts unicode returns unicode
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
@ -232,12 +196,7 @@ class MobiBook:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile):
|
||||||
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2022 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||||
|
|
||||||
try:
|
|
||||||
from alfcrypto import Pukall_Cipher
|
|
||||||
except:
|
|
||||||
print("AlfCrypto not found. Using python PC1 implementation.")
|
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = open(infile, 'rb').read()
|
self.data_file = open(infile, 'rb').read()
|
||||||
|
@ -1,148 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
import signal
|
|
||||||
import threading
|
|
||||||
import subprocess
|
|
||||||
from subprocess import Popen, PIPE, STDOUT
|
|
||||||
|
|
||||||
# **heavily** chopped up and modfied version of asyncproc.py
|
|
||||||
# to make it actually work on Windows as well as Mac/Linux
|
|
||||||
# For the original see:
|
|
||||||
# "http://www.lysator.liu.se/~bellman/download/"
|
|
||||||
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
|
||||||
# available under GPL version 3 or Later
|
|
||||||
|
|
||||||
# create an asynchronous subprocess whose output can be collected in
|
|
||||||
# a non-blocking manner
|
|
||||||
|
|
||||||
# What a mess! Have to use threads just to get non-blocking io
|
|
||||||
# in a cross-platform manner
|
|
||||||
|
|
||||||
# luckily all thread use is hidden within this class
|
|
||||||
|
|
||||||
class Process(object):
|
|
||||||
def __init__(self, *params, **kwparams):
|
|
||||||
if len(params) <= 3:
|
|
||||||
kwparams.setdefault('stdin', subprocess.PIPE)
|
|
||||||
if len(params) <= 4:
|
|
||||||
kwparams.setdefault('stdout', subprocess.PIPE)
|
|
||||||
if len(params) <= 5:
|
|
||||||
kwparams.setdefault('stderr', subprocess.PIPE)
|
|
||||||
self.__pending_input = []
|
|
||||||
self.__collected_outdata = []
|
|
||||||
self.__collected_errdata = []
|
|
||||||
self.__exitstatus = None
|
|
||||||
self.__lock = threading.Lock()
|
|
||||||
self.__inputsem = threading.Semaphore(0)
|
|
||||||
self.__quit = False
|
|
||||||
|
|
||||||
self.__process = subprocess.Popen(*params, **kwparams)
|
|
||||||
|
|
||||||
if self.__process.stdin:
|
|
||||||
self.__stdin_thread = threading.Thread(
|
|
||||||
name="stdin-thread",
|
|
||||||
target=self.__feeder, args=(self.__pending_input,
|
|
||||||
self.__process.stdin))
|
|
||||||
self.__stdin_thread.setDaemon(True)
|
|
||||||
self.__stdin_thread.start()
|
|
||||||
|
|
||||||
if self.__process.stdout:
|
|
||||||
self.__stdout_thread = threading.Thread(
|
|
||||||
name="stdout-thread",
|
|
||||||
target=self.__reader, args=(self.__collected_outdata,
|
|
||||||
self.__process.stdout))
|
|
||||||
self.__stdout_thread.setDaemon(True)
|
|
||||||
self.__stdout_thread.start()
|
|
||||||
|
|
||||||
if self.__process.stderr:
|
|
||||||
self.__stderr_thread = threading.Thread(
|
|
||||||
name="stderr-thread",
|
|
||||||
target=self.__reader, args=(self.__collected_errdata,
|
|
||||||
self.__process.stderr))
|
|
||||||
self.__stderr_thread.setDaemon(True)
|
|
||||||
self.__stderr_thread.start()
|
|
||||||
|
|
||||||
def pid(self):
|
|
||||||
return self.__process.pid
|
|
||||||
|
|
||||||
def kill(self, signal):
|
|
||||||
self.__process.send_signal(signal)
|
|
||||||
|
|
||||||
# check on subprocess (pass in 'nowait') to act like poll
|
|
||||||
def wait(self, flag):
|
|
||||||
if flag.lower() == 'nowait':
|
|
||||||
rc = self.__process.poll()
|
|
||||||
else:
|
|
||||||
rc = self.__process.wait()
|
|
||||||
if rc != None:
|
|
||||||
if self.__process.stdin:
|
|
||||||
self.closeinput()
|
|
||||||
if self.__process.stdout:
|
|
||||||
self.__stdout_thread.join()
|
|
||||||
if self.__process.stderr:
|
|
||||||
self.__stderr_thread.join()
|
|
||||||
return self.__process.returncode
|
|
||||||
|
|
||||||
def terminate(self):
|
|
||||||
if self.__process.stdin:
|
|
||||||
self.closeinput()
|
|
||||||
self.__process.terminate()
|
|
||||||
|
|
||||||
# thread gets data from subprocess stdout
|
|
||||||
def __reader(self, collector, source):
|
|
||||||
while True:
|
|
||||||
data = os.read(source.fileno(), 65536)
|
|
||||||
self.__lock.acquire()
|
|
||||||
collector.append(data)
|
|
||||||
self.__lock.release()
|
|
||||||
if data == "":
|
|
||||||
source.close()
|
|
||||||
break
|
|
||||||
return
|
|
||||||
|
|
||||||
# thread feeds data to subprocess stdin
|
|
||||||
def __feeder(self, pending, drain):
|
|
||||||
while True:
|
|
||||||
self.__inputsem.acquire()
|
|
||||||
self.__lock.acquire()
|
|
||||||
if not pending and self.__quit:
|
|
||||||
drain.close()
|
|
||||||
self.__lock.release()
|
|
||||||
break
|
|
||||||
data = pending.pop(0)
|
|
||||||
self.__lock.release()
|
|
||||||
drain.write(data)
|
|
||||||
|
|
||||||
# non-blocking read of data from subprocess stdout
|
|
||||||
def read(self):
|
|
||||||
self.__lock.acquire()
|
|
||||||
outdata = "".join(self.__collected_outdata)
|
|
||||||
del self.__collected_outdata[:]
|
|
||||||
self.__lock.release()
|
|
||||||
return outdata
|
|
||||||
|
|
||||||
# non-blocking read of data from subprocess stderr
|
|
||||||
def readerr(self):
|
|
||||||
self.__lock.acquire()
|
|
||||||
errdata = "".join(self.__collected_errdata)
|
|
||||||
del self.__collected_errdata[:]
|
|
||||||
self.__lock.release()
|
|
||||||
return errdata
|
|
||||||
|
|
||||||
# non-blocking write to stdin of subprocess
|
|
||||||
def write(self, data):
|
|
||||||
if self.__process.stdin is None:
|
|
||||||
raise ValueError("Writing to process with stdin not a pipe")
|
|
||||||
self.__lock.acquire()
|
|
||||||
self.__pending_input.append(data)
|
|
||||||
self.__inputsem.release()
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
# close stdinput of subprocess
|
|
||||||
def closeinput(self):
|
|
||||||
self.__lock.acquire()
|
|
||||||
self.__quit = True
|
|
||||||
self.__inputsem.release()
|
|
||||||
self.__lock.release()
|
|
Loading…
Reference in New Issue
Block a user