Skip to content

Commit 612b18e

Browse files
committed
Small update
1 parent a022945 commit 612b18e

1 file changed

Lines changed: 277 additions & 0 deletions

File tree

pycatfile/pycatfile.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import hashlib
3838
import inspect
3939
import tempfile
40+
import libarchive
4041
import configparser
4142
from io import open, StringIO, BytesIO
4243
from decimal import Decimal, ROUND_HALF_UP
@@ -6541,6 +6542,282 @@ def AppendFilesWithContentFromTarFile(infile, fp, extradata=[], jsondata={}, com
65416542
pass
65426543
return fp
65436544

6545+
def _is_pathlike(x):
6546+
return isinstance(x, (str, bytes, os.PathLike))
6547+
6548+
6549+
def open_archive_reader(infile):
6550+
"""
6551+
Returns a context manager that yields a libarchive reader.
6552+
6553+
Supports:
6554+
- path-like: libarchive.file_reader(path)
6555+
- file object with fileno(): libarchive.fd_reader(fd)
6556+
- bytes / bytearray / memoryview / io.BytesIO: libarchive.memory_reader(data)
6557+
(falls back to a temp file if memory_reader isn't available)
6558+
"""
6559+
# 1) Path
6560+
if _is_pathlike(infile):
6561+
return libarchive.file_reader(infile)
6562+
6563+
# 2) BytesIO or any bytes-like object
6564+
data = None
6565+
if isinstance(infile, io.BytesIO):
6566+
data = infile.getvalue()
6567+
elif isinstance(infile, (bytes, bytearray, memoryview)):
6568+
data = bytes(infile)
6569+
6570+
if data is not None:
6571+
# Preferred: in-memory reader (if available)
6572+
mem_reader = getattr(libarchive, "memory_reader", None)
6573+
if callable(mem_reader):
6574+
return mem_reader(data)
6575+
6576+
# Fallback: spill to temp file (still works everywhere)
6577+
# Note: caller doesn't need to manage cleanup; NamedTemporaryFile is a context manager,
6578+
# but libarchive.file_reader expects a filename. We'll implement a tiny CM wrapper.
6579+
class _TempFileReaderCM:
6580+
def __init__(self, blob):
6581+
self._blob = blob
6582+
self._tmp = None
6583+
self._cm = None
6584+
self._archive = None
6585+
6586+
def __enter__(self):
6587+
self._tmp = tempfile.NamedTemporaryFile(delete=False)
6588+
try:
6589+
self._tmp.write(self._blob)
6590+
self._tmp.flush()
6591+
self._tmp.close()
6592+
self._cm = libarchive.file_reader(self._tmp.name)
6593+
self._archive = self._cm.__enter__()
6594+
return self._archive
6595+
except Exception:
6596+
self.__exit__(None, None, None)
6597+
raise
6598+
6599+
def __exit__(self, exc_type, exc, tb):
6600+
try:
6601+
if self._cm is not None:
6602+
self._cm.__exit__(exc_type, exc, tb)
6603+
finally:
6604+
if self._tmp is not None:
6605+
try:
6606+
os.unlink(self._tmp.name)
6607+
except OSError:
6608+
pass
6609+
6610+
return _TempFileReaderCM(data)
6611+
6612+
# 3) File object with fileno()
6613+
if hasattr(infile, "read") and hasattr(infile, "fileno"):
6614+
return libarchive.fd_reader(infile.fileno())
6615+
6616+
raise TypeError(f"Unsupported infile type: {type(infile)!r}")
6617+
6618+
6619+
def AppendFilesWithContentFromBSDTarFileToList(infile, extradata=[], jsondata={}, contentasfile=False, compression="auto", compresswholefile=True, compressionlevel=None, compressionuselist=compressionlistalt, checksumtype=["md5", "md5", "md5"], formatspecs=__file_format_dict__, saltkey=None, verbose=False):
6620+
curinode = 0
6621+
curfid = 0
6622+
inodelist = []
6623+
inodetofile = {}
6624+
filetoinode = {}
6625+
inodetoforminode = {}
6626+
if(isinstance(infile, (list, tuple, ))):
6627+
infile = infile[0]
6628+
if(infile == "-"):
6629+
infile = MkTempFile()
6630+
shutil.copyfileobj(PY_STDIN_BUF, infile, length=__filebuff_size__)
6631+
infile.seek(0, 0)
6632+
if(not infile):
6633+
return False
6634+
infile.seek(0, 0)
6635+
elif(re.findall(__download_proto_support__, infile) and pywwwget):
6636+
infile = download_file_from_internet_file(infile)
6637+
infile.seek(0, 0)
6638+
if(not infile):
6639+
return False
6640+
infile.seek(0, 0)
6641+
elif(hasattr(infile, "read") or hasattr(infile, "write")):
6642+
try:
6643+
if(not tarfile.is_tarfile(infile)):
6644+
return False
6645+
except AttributeError:
6646+
if(not TarFileCheck(infile)):
6647+
return False
6648+
elif(not os.path.exists(infile) or not os.path.isfile(infile)):
6649+
return False
6650+
tmpoutlist = []
6651+
with open_archive_reader(infile) as archive:
6652+
for member in archive:
6653+
fencoding = "UTF-8"
6654+
fname = member.pathname
6655+
if(verbose):
6656+
VerbosePrintOut(fname)
6657+
fpremode = member.mode
6658+
ffullmode = member.mode
6659+
flinkcount = 0
6660+
fblksize = format(int(0), 'x').lower()
6661+
fblocks = format(int(0), 'x').lower()
6662+
fflags = format(int(0), 'x').lower()
6663+
ftype = 0
6664+
if(member.isreg or member.isfile):
6665+
ffullmode = member.mode | stat.S_IFREG
6666+
ftype = 0
6667+
elif(member.islnk):
6668+
ffullmode = member.mode | stat.S_IFREG
6669+
ftype = 1
6670+
elif(member.issym):
6671+
ffullmode = member.mode | stat.S_IFLNK
6672+
ftype = 2
6673+
elif(member.ischr):
6674+
ffullmode = member.mode | stat.S_IFCHR
6675+
ftype = 3
6676+
elif(member.isblk):
6677+
ffullmode = member.mode | stat.S_IFBLK
6678+
ftype = 4
6679+
elif(member.isdir):
6680+
ffullmode = member.mode | stat.S_IFDIR
6681+
ftype = 5
6682+
elif(member.isfifo):
6683+
ffullmode = member.mode | stat.S_IFIFO
6684+
ftype = 6
6685+
elif(hasattr(member, "issparse") and member.issparse):
6686+
ffullmode = member.mode | stat.S_IFREG
6687+
ftype = 12
6688+
elif(member.isdev()):
6689+
ffullmode = member.mode
6690+
ftype = 14
6691+
else:
6692+
ffullmode = member.mode | stat.S_IFREG
6693+
ftype = 0
6694+
flinkname = ""
6695+
fcurfid = format(int(curfid), 'x').lower()
6696+
fcurinode = format(int(curfid), 'x').lower()
6697+
curfid = curfid + 1
6698+
if(ftype == 2):
6699+
flinkname = member.linkname
6700+
fdev = format(int("0"), 'x').lower()
6701+
frdev = format(int(member.rdev), 'x').lower()
6702+
# Types that should be considered zero-length in the archive context:
6703+
zero_length_types = {1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 13}
6704+
# Types that have actual data to read:
6705+
data_types = {0, 7}
6706+
sparse_types = {12}
6707+
if ftype in zero_length_types:
6708+
fsize = format(int("0"), 'x').lower()
6709+
elif ftype in data_types:
6710+
fsize = format(int(member.size), 'x').lower()
6711+
else:
6712+
fsize = format(int(member.size), 'x').lower()
6713+
if(hasattr(member, "atime") and member.atime is not None):
6714+
fatime = format(int(to_ns(member.atime)), 'x').lower()
6715+
else:
6716+
fatime = format(int(to_ns(member.mtime)), 'x').lower()
6717+
fmtime = format(int(to_ns(member.mtime)), 'x').lower()
6718+
if(hasattr(member, "ctime") and member.ctime is not None):
6719+
fctime = format(int(to_ns(member.ctime)), 'x').lower()
6720+
else:
6721+
fctime = format(int(to_ns(member.mtime)), 'x').lower()
6722+
if(hasattr(member, "birthtime") and member.birthtime is not None):
6723+
fbtime = format(int(to_ns(member.birthtime)), 'x').lower()
6724+
else:
6725+
fbtime = format(int(to_ns(member.mtime)), 'x').lower()
6726+
fmode = format(int(ffullmode), 'x').lower()
6727+
fchmode = format(int(stat.S_IMODE(ffullmode)), 'x').lower()
6728+
ftypemod = format(int(stat.S_IFMT(ffullmode)), 'x').lower()
6729+
fuid = format(int(member.uid), 'x').lower()
6730+
fgid = format(int(member.gid), 'x').lower()
6731+
funame = member.uname
6732+
fgname = member.gname
6733+
flinkcount = format(int(flinkcount), 'x').lower()
6734+
fwinattributes = format(int(0), 'x').lower()
6735+
fcompression = ""
6736+
fcsize = format(int(0), 'x').lower()
6737+
fcontents = MkTempFile()
6738+
fcencoding = "UTF-8"
6739+
curcompression = "none"
6740+
if ftype in data_types:
6741+
for block in member.get_blocks():
6742+
fcontents.write(block)
6743+
typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
6744+
fcontents.seek(0, 0)
6745+
if(typechecktest is not False):
6746+
typechecktest = GetBinaryFileType(fcontents, filestart=0, closefp=False)
6747+
fcontents.seek(0, 0)
6748+
fcencoding = GetFileEncoding(fcontents, 0, False)[0]
6749+
if(typechecktest is False and not compresswholefile):
6750+
fcontents.seek(0, 2)
6751+
ucfsize = fcontents.tell()
6752+
fcontents.seek(0, 0)
6753+
if(compression == "auto"):
6754+
ilsize = len(compressionuselist)
6755+
ilmin = 0
6756+
ilcsize = []
6757+
while(ilmin < ilsize):
6758+
cfcontents = MkTempFile()
6759+
fcontents.seek(0, 0)
6760+
shutil.copyfileobj(fcontents, cfcontents, length=__filebuff_size__)
6761+
fcontents.seek(0, 0)
6762+
cfcontents.seek(0, 0)
6763+
cfcontents = CompressOpenFileAlt(
6764+
cfcontents, compressionuselist[ilmin], compressionlevel, compressionuselist, formatspecs)
6765+
if(cfcontents):
6766+
cfcontents.seek(0, 2)
6767+
ilcsize.append(cfcontents.tell())
6768+
cfcontents.close()
6769+
else:
6770+
ilcsize.append(float("inf"))
6771+
ilmin = ilmin + 1
6772+
ilcmin = ilcsize.index(min(ilcsize))
6773+
curcompression = compressionuselist[ilcmin]
6774+
fcontents.seek(0, 0)
6775+
cfcontents = MkTempFile()
6776+
shutil.copyfileobj(fcontents, cfcontents, length=__filebuff_size__)
6777+
cfcontents.seek(0, 0)
6778+
cfcontents = CompressOpenFileAlt(
6779+
cfcontents, curcompression, compressionlevel, compressionuselist, formatspecs)
6780+
cfcontents.seek(0, 2)
6781+
cfsize = cfcontents.tell()
6782+
if(ucfsize > cfsize):
6783+
fcsize = format(int(cfsize), 'x').lower()
6784+
fcompression = curcompression
6785+
fcontents.close()
6786+
fcontents = cfcontents
6787+
if(fcompression == "none"):
6788+
fcompression = ""
6789+
fcontents.seek(0, 0)
6790+
if(not contentasfile):
6791+
fcontents = fcontents.read()
6792+
ftypehex = format(ftype, 'x').lower()
6793+
tmpoutlist.append({'fheaders': [ftypehex, fencoding, fcencoding, fname, flinkname, fsize, fblksize, fblocks, fflags, fatime, fmtime, fctime, fbtime, fmode, fwinattributes, fcompression,
6794+
fcsize, fuid, funame, fgid, fgname, fcurfid, fcurinode, flinkcount, fdev, frdev, "+"+str(len(formatspecs['format_delimiter']))], 'fextradata': extradata, 'fjsoncontent': jsondata, 'fcontents': fcontents, 'fjsonchecksumtype': checksumtype[2], 'fheaderchecksumtype': checksumtype[0], 'fcontentchecksumtype': checksumtype[1]})
6795+
return tmpoutlist
6796+
6797+
def AppendFilesWithContentFromBSDTarFile(infile, fp, extradata=[], jsondata={}, compression="auto", compresswholefile=True, compressionlevel=None, compressionuselist=compressionlistalt, checksumtype=["md5", "md5", "md5", "md5", "md5"], formatspecs=__file_format_dict__, saltkey=None, verbose=False):
6798+
if(not hasattr(fp, "write")):
6799+
return False
6800+
GetDirList = AppendFilesWithContentFromBSDTarFileToList(infile, extradata, jsondata, False, compression, compresswholefile, compressionlevel, compressionuselist, [checksumtype[2], checksumtype[3], checksumtype[3]], formatspecs, saltkey, verbose)
6801+
numfiles = int(len(GetDirList))
6802+
fnumfiles = format(numfiles, 'x').lower()
6803+
AppendFileHeader(fp, numfiles, "UTF-8", [], {}, [checksumtype[0], checksumtype[1]], formatspecs, saltkey)
6804+
try:
6805+
fp.flush()
6806+
if(hasattr(os, "sync")):
6807+
os.fsync(fp.fileno())
6808+
except (io.UnsupportedOperation, AttributeError, OSError):
6809+
pass
6810+
for curfname in GetDirList:
6811+
tmpoutlist = curfname['fheaders']
6812+
AppendFileHeaderWithContent(fp, tmpoutlist, curfname['fextradata'], curfname['fjsoncontent'], curfname['fcontents'], [curfname['fheaderchecksumtype'], curfname['fcontentchecksumtype'], curfname['fjsonchecksumtype']], formatspecs, saltkey)
6813+
try:
6814+
fp.flush()
6815+
if(hasattr(os, "sync")):
6816+
os.fsync(fp.fileno())
6817+
except (io.UnsupportedOperation, AttributeError, OSError):
6818+
pass
6819+
return fp
6820+
65446821
def AppendFilesWithContentFromZipFileToList(infile, extradata=[], jsondata={}, contentasfile=False, compression="auto", compresswholefile=True, compressionlevel=None, compressionuselist=compressionlistalt, checksumtype=["md5", "md5", "md5", "md5", "md5"], formatspecs=__file_format_dict__, saltkey=None, verbose=False):
65456822
curinode = 0
65466823
curfid = 0

0 commit comments

Comments
 (0)