You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, by adding a setup.py and modifying the script a little to use entry points, installing in a sever can be made much easier. Thanks a lot for the script btw, very usefull.
Modifications in script to use a function to launch the cli, change is at the end
#!/usr/bin/env python3importargparseimportioimportitertoolsimportosimportreimportstatimporttarfileimporttimeimporttracebackfromcollectionsimportnamedtuplefromtimeitimportdefault_timerastimerimportfuseprintDebug=1defoverrides(parentClass):
defoverrider(method):
assertmethod.__name__indir(parentClass)
returnmethodreturnoverriderFileInfo=namedtuple("FileInfo", "offset size mtime mode type linkname uid gid istar")
classProgressBar:
def__init__(self, maxValue):
self.maxValue=maxValueself.lastUpdateTime=time.time()
self.lastUpdateValue=0self.updateInterval=2# secondsself.creationTime=time.time()
defupdate(self, value):
if (
self.lastUpdateTimeisnotNoneand (time.time() -self.lastUpdateTime) <self.updateInterval
):
return# Use whole interval since start to estimate timeeta1=int((time.time() -self.creationTime) /value* (self.maxValue-value))
# Use only a shorter window interval to estimate time.# Accounts better for higher speeds in beginning, e.g., caused by caching effects.# However, this estimate might vary a lot while the other one stabilizes after some time!eta2=int(
(time.time() -self.lastUpdateTime)
/ (value-self.lastUpdateValue)
* (self.maxValue-value)
)
print(
"Currently at position {} of {} ({:.2f}%). ""Estimated time remaining with current rate: {} min {} s, with average rate: {} min {} s.".format(
value,
self.maxValue,
value/self.maxValue*100.0,
eta2//60,
eta2%60,
eta1//60,
eta1%60,
),
flush=True,
)
self.lastUpdateTime=time.time()
self.lastUpdateValue=valueclassIndexedTar:
""" This class reads once through the whole TAR archive and stores TAR file offsets for all contained files in an index to support fast seeking to a given file. """__slots__= (
"tarFileName",
"fileIndex",
"mountRecursively",
"cacheFolder",
"possibleIndexFilePaths",
"indexFileName",
"progressBar",
)
# these allowed backends also double as extensions for the index file to look foravailableSerializationBackends= [
"none",
"pickle",
"pickle2",
"pickle3",
"custom",
"cbor",
"msgpack",
"rapidjson",
"ujson",
"simplejson",
]
availableCompressions= ["", "lz4", "gz"] # no compressiondef__init__(
self,
pathToTar=None,
fileObject=None,
writeIndex=False,
clearIndexCache=False,
recursive=False,
serializationBackend=None,
progressBar=None,
):
self.progressBar=progressBarself.tarFileName=os.path.normpath(pathToTar)
# Stores the file hierarchy in a dictionary with keys being either# - the file and containing file metainformation# - or keys being a folder name and containing a recursively defined dictionary.self.fileIndex= {}
self.mountRecursively=recursive# will be used for storing indexes if current path is read-onlyself.cacheFolder=os.path.expanduser("~/.ratarmount")
self.possibleIndexFilePaths= [
self.tarFileName+".index",
self.cacheFolder+"/"+self.tarFileName.replace("/", "_") +".index",
]
ifnotserializationBackend:
serializationBackend="custom"ifserializationBackendnotinself.supportedIndexExtensions():
print(
"[Warning] Serialization backend '"+str(serializationBackend)
+"' not supported.",
"Defaulting to '"+serializationBackend+"'!",
)
print(
"List of supported extensions / backends:",
self.supportedIndexExtensions(),
)
serializationBackend="custom"# this is the actual index file, which will be used in the end, and by defaultself.indexFileName=self.possibleIndexFilePaths[0] +"."+serializationBackendifclearIndexCache:
forindexPathinself.possibleIndexFilePaths:
forextensioninself.supportedIndexExtensions():
indexPathWitExt=indexPath+"."+extensionifos.path.isfile(indexPathWitExt):
os.remove(indexPathWitExt)
iffileObjectisnotNone:
ifwriteIndex:
print(
"Can't write out index for file object input. Ignoring this option."
)
self.createIndex(fileObject)
else:
# first try loading the index for the given serialization backendifserializationBackendisnotNone:
forindexPathinself.possibleIndexFilePaths:
ifself.tryLoadIndex(indexPath+"."+serializationBackend):
break# try loading the index from one of the pre-configured pathsforindexPathinself.possibleIndexFilePaths:
forextensioninself.supportedIndexExtensions():
ifself.tryLoadIndex(indexPath+"."+extension):
breakifnotself.indexIsLoaded():
withopen(self.tarFileName, "rb") asfile:
self.createIndex(file)
ifwriteIndex:
forindexPathinself.possibleIndexFilePaths:
indexPath+="."+serializationBackendtry:
folder=os.path.dirname(indexPath)
ifnotos.path.exists(folder):
os.mkdir(folder)
f=open(indexPath, "wb")
f.close()
os.remove(indexPath)
self.indexFileName=indexPathbreakexceptIOError:
ifprintDebug>=2:
print("Could not create file:", indexPath)
try:
self.writeIndex(self.indexFileName)
exceptIOError:
print(
"[Info] Could not write TAR index to file. ",
"Subsequent mounts might be slow!",
)
@staticmethoddefsupportedIndexExtensions():
return [
".".join(combination).strip(".")
forcombinationinitertools.product(
IndexedTar.availableSerializationBackends,
IndexedTar.availableCompressions,
)
]
@staticmethoddefdump(toDump, file):
importmsgpackifisinstance(toDump, dict):
file.write(b"\x01") # magic code meaning "start dictionary object"forkey, valueintoDump.items():
file.write(b"\x03") # magic code meaning "serialized key value pair"IndexedTar.dump(key, file)
IndexedTar.dump(value, file)
file.write(b"\x02") # magic code meaning "close dictionary object"elifisinstance(toDump, FileInfo):
serialized=msgpack.dumps(toDump)
file.write(b"\x05") # magic code meaning "msgpack object"file.write(len(serialized).to_bytes(4, byteorder="little"))
file.write(serialized)
elifisinstance(toDump, str):
serialized=toDump.encode()
file.write(b"\x04") # magic code meaning "string object"file.write(len(serialized).to_bytes(4, byteorder="little"))
file.write(serialized)
else:
print("Ignoring unsupported type to write:", toDump)
@staticmethoddefload(file):
importmsgpackelementType=file.read(1)
ifelementType!=b"\x01": # start of dictionaryraiseException("Custom TAR index loader: invalid file format")
result= {}
dictElementType=file.read(1)
whiledictElementType:
ifdictElementType==b"\x02":
breakelifdictElementType==b"\x03":
keyType=file.read(1)
ifkeyType!=b"\x04": # key must be string objectraiseException("Custom TAR index loader: invalid file format")
size=int.from_bytes(file.read(4), byteorder="little")
key=file.read(size).decode()
valueType=file.read(1)
ifvalueType==b"\x05": # msgpack objectsize=int.from_bytes(file.read(4), byteorder="little")
serialized=file.read(size)
value=FileInfo(*msgpack.loads(serialized))
elifvalueType==b"\x01": # dict objectfile.seek(-1, io.SEEK_CUR)
value=IndexedTar.load(file)
else:
raiseException(
"Custom TAR index loader: invalid file format "+"(expected msgpack or dict but got"+str(int.from_bytes(valueType, byteorder="little"))
+")"
)
result[key] =valueelse:
raiseException(
"Custom TAR index loader: invalid file format "+"(expected end-of-dict or key-value pair but got"+str(int.from_bytes(dictElementType, byteorder="little"))
+")"
)
dictElementType=file.read(1)
returnresultdefgetFileInfo(self, path, listDir=False):
# go down file hierarchy tree along the given pathp=self.fileIndexfornameinos.path.normpath(path).split(os.sep):
ifnotname:
continueifnamenotinp:
returnNonep=p[name]
defrepackDeserializedNamedTuple(p):
ifisinstance(p, list) andlen(p) ==len(FileInfo._fields):
returnFileInfo(*p)
if (
isinstance(p, dict)
andlen(p) ==len(FileInfo._fields)
and"uid"inpandisinstance(p["uid"], int)
):
# a normal directory dict must only have dict or FileInfo values,# so if the value to the 'uid' key is an actual int,# then it is sure it is a deserialized FileInfo object and not a file named 'uid'print("P ===", p)
print("FileInfo ===", FileInfo(**p))
returnFileInfo(**p)
returnpp=repackDeserializedNamedTuple(p)
# if the directory contents are not to be printed and it is a directory,# return the "file" info of ".", which holds the directory metainformationifnotlistDirandisinstance(p, dict):
if"."inp:
p=p["."]
else:
returnFileInfo(
offset=0, # not necessary for directory anywayssize=1, # might be misleading / non-conformmtime=0,
mode=0o555|stat.S_IFDIR,
type=tarfile.DIRTYPE,
linkname="",
uid=0,
gid=0,
istar=False,
)
returnrepackDeserializedNamedTuple(p)
defisDir(self, path):
returnisinstance(self.getFileInfo(path, listDir=True), dict)
defexists(self, path):
path=os.path.normpath(path)
returnself.isDir(path) orisinstance(self.getFileInfo(path), FileInfo)
defsetFileInfo(self, path, fileInfo):
""" path: the full path to the file with leading slash (/) for which to set the file info """assertisinstance(fileInfo, FileInfo)
pathHierarchy=os.path.normpath(path).split(os.sep)
ifnotpathHierarchy:
return# go down file hierarchy tree along the given pathp=self.fileIndexfornameinpathHierarchy[:-1]:
ifnotname:
continueassertisinstance(p, dict)
p=p.setdefault(name, {})
# create a new key in the dictionary of the parent folderp.update({pathHierarchy[-1]: fileInfo})
defsetDirInfo(self, path, dirInfo, dirContents={}):
""" path: the full path to the file with leading slash (/) for which to set the folder info """assertisinstance(dirInfo, FileInfo)
assertisinstance(dirContents, dict)
pathHierarchy=os.path.normpath(path).strip(os.sep).split(os.sep)
ifnotpathHierarchy:
return# go down file hierarchy tree along the given pathp=self.fileIndexfornameinpathHierarchy[:-1]:
ifnotname:
continueassertisinstance(p, dict)
p=p.setdefault(name, {})
# create a new key in the dictionary of the parent folderp.update({pathHierarchy[-1]: dirContents})
p[pathHierarchy[-1]].update({".": dirInfo})
defcreateIndex(self, fileObject):
ifprintDebug>=1:
print(
"Creating offset dictionary for",
"<file object>"ifself.tarFileNameisNoneelseself.tarFileName,
"...",
)
t0=timer()
self.fileIndex= {}
try:
loadedTarFile=tarfile.open(fileobj=fileObject, mode="r:")
excepttarfile.ReadErrorasexception:
print(
"Archive can't be opened! This might happen for compressed TAR archives, ""which currently is not supported."
)
raiseexceptionifself.progressBarisNoneandos.path.isfile(self.tarFileName):
self.progressBar=ProgressBar(os.stat(self.tarFileName).st_size)
fortarInfoinloadedTarFile:
ifself.progressBarisnotNone:
self.progressBar.update(tarInfo.offset_data)
mode=tarInfo.modeiftarInfo.isdir():
mode |= stat.S_IFDIRiftarInfo.isfile():
mode |= stat.S_IFREGiftarInfo.issym():
mode |= stat.S_IFLNKiftarInfo.ischr():
mode |= stat.S_IFCHRiftarInfo.isfifo():
mode |= stat.S_IFIFOfileInfo=FileInfo(
offset=tarInfo.offset_data,
size=tarInfo.size,
mtime=tarInfo.mtime,
mode=mode,
type=tarInfo.type,
linkname=tarInfo.linkname,
uid=tarInfo.uid,
gid=tarInfo.gid,
istar=False,
)
# open contained tars for recursive mountingindexedTar=Noneif (
self.mountRecursivelyandtarInfo.isfile()
andtarInfo.name.endswith(".tar")
):
oldPos=fileObject.tell()
ifoldPos!=tarInfo.offset_data:
fileObject.seek(tarInfo.offset_data)
indexedTar=IndexedTar(
tarInfo.name,
fileObject=fileObject,
writeIndex=False,
progressBar=self.progressBar,
)
# might be especially necessary if the .tar is not actually a tar!fileObject.seek(fileObject.tell())
# Add a leading '/' as a convention where '/' represents the TAR root folder# Partly, done because fusepy specifies paths in a mounted directory like thispath=os.path.normpath("/"+tarInfo.name)
# test whether the TAR file could be loaded and if so "mount" it recursivelyifindexedTarisnotNoneandindexedTar.indexIsLoaded():
# actually apply the recursive tar mountingextractedName=re.sub(r"\.tar$", "", path)
ifnotself.exists(extractedName):
path=extractedNamemountMode= (fileInfo.mode&0o777) |stat.S_IFDIRifmountMode&stat.S_IRUSR!=0:
mountMode |= stat.S_IXUSRifmountMode&stat.S_IRGRP!=0:
mountMode |= stat.S_IXGRPifmountMode&stat.S_IROTH!=0:
mountMode |= stat.S_IXOTHfileInfo=fileInfo._replace(mode=mountMode, istar=True)
ifself.exists(path):
print(
"[Warning]",
path,
"already exists in database and will be overwritten!",
)
# merge fileIndex from recursively loaded TAR into our Indexesself.setDirInfo(path, fileInfo, indexedTar.fileIndex)
elifpath!="/":
# just a warning and check for the path already existingifself.exists(path):
fileInfo=self.getFileInfo(path, listDir=False)
iffileInfo.istar:
# move recursively mounted TAR directory to original .tar name if there is a name-clash,# e.g., when foo/ also exists in the TAR but foo.tar would be mounted to foo/.# In this case, move that mount to foo.tar/self.setFileInfo(
path+".tar",
fileInfo,
self.getFileInfo(path, listDir=True),
)
else:
print(
"[Warning]",
path,
"already exists in database and will be overwritten!",
)
# simply store the file or directory information from current TAR itemiftarInfo.isdir():
self.setDirInfo(path, fileInfo, {})
else:
self.setFileInfo(path, fileInfo)
t1=timer()
ifprintDebug>=1:
print(
"Creating offset dictionary for",
"<file object>"ifself.tarFileNameisNoneelseself.tarFileName,
"took {:.2f}s".format(t1-t0),
)
defserializationBackendFromFileName(self, fileName):
splitName=fileName.split(".")
if (
len(splitName) >2and".".join(splitName[-2:]) inself.supportedIndexExtensions()
):
return".".join(splitName[-2:])
ifsplitName[-1] inself.supportedIndexExtensions():
returnsplitName[-1]
returnNonedefindexIsLoaded(self):
returnbool(self.fileIndex)
defwriteIndex(self, outFileName):
""" outFileName: Full file name with backend extension. Depending on the extension the serialization is chosen. """serializationBackend=self.serializationBackendFromFileName(outFileName)
ifprintDebug>=1:
print(
"Writing out TAR index using",
serializationBackend,
"to",
outFileName,
"...",
)
t0=timer()
fileMode="wt"if"json"inserializationBackendelse"wb"ifserializationBackend.endswith(".lz4"):
importlz4.framewrapperOpen=lambdax: lz4.frame.open(x, fileMode)
elifserializationBackend.endswith(".gz"):
importgzipwrapperOpen=lambdax: gzip.open(x, fileMode)
else:
wrapperOpen=lambdax: open(x, fileMode)
serializationBackend=serializationBackend.split(".")[0]
# libraries tested but not working:# - marshal: can't serialize namedtuples# - hickle: for some reason, creates files almost 64x larger and slower than pickle!?# - yaml: almost a 10 times slower and more memory usage and deserializes everything including ints to stringifserializationBackend=="none":
print(
"Won't write out index file because backend 'none' was chosen. ""Subsequent mounts might be slow!"
)
returnwithwrapperOpen(outFileName) asoutFile:
ifserializationBackend=="pickle2":
importpicklepickle.dump(self.fileIndex, outFile)
pickle.dump(self.fileIndex, outFile, protocol=2)
# default serialization because it has the fewest dependencies and because it was legacy defaultelif (
serializationBackend=="pickle3"orserializationBackend=="pickle"orserializationBackendisNone
):
importpicklepickle.dump(self.fileIndex, outFile)
pickle.dump(
self.fileIndex, outFile, protocol=3
) # 3 is default protocolelifserializationBackend=="simplejson":
importsimplejsonsimplejson.dump(self.fileIndex, outFile, namedtuple_as_object=True)
elifserializationBackend=="custom":
IndexedTar.dump(self.fileIndex, outFile)
elifserializationBackendin ["msgpack", "cbor", "rapidjson", "ujson"]:
importimportlibmodule=importlib.import_module(serializationBackend)
getattr(module, "dump")(self.fileIndex, outFile)
else:
print(
"Tried to save index with unsupported extension backend:",
serializationBackend,
"!",
)
t1=timer()
ifprintDebug>=1:
print(
"Writing out TAR index to",
outFileName,
"took {:.2f}s".format(t1-t0),
"and is sized",
os.stat(outFileName).st_size,
"B",
)
defloadIndex(self, indexFileName):
ifprintDebug>=1:
print("Loading offset dictionary from", indexFileName, "...")
t0=timer()
serializationBackend=self.serializationBackendFromFileName(indexFileName)
fileMode="rt"if"json"inserializationBackendelse"rb"ifserializationBackend.endswith(".lz4"):
importlz4.framewrapperOpen=lambdax: lz4.frame.open(x, fileMode)
elifserializationBackend.endswith(".gz"):
importgzipwrapperOpen=lambdax: gzip.open(x, fileMode)
else:
wrapperOpen=lambdax: open(x, fileMode)
serializationBackend=serializationBackend.split(".")[0]
withwrapperOpen(indexFileName) asindexFile:
ifserializationBackendin ("pickle2", "pickle3", "pickle"):
importpickleself.fileIndex=pickle.load(indexFile)
elifserializationBackend=="custom":
self.fileIndex=IndexedTar.load(indexFile)
elifserializationBackend=="msgpack":
importmsgpackself.fileIndex=msgpack.load(indexFile, raw=False)
elifserializationBackend=="simplejson":
importsimplejsonself.fileIndex=simplejson.load(indexFile, namedtuple_as_object=True)
elifserializationBackendin ["cbor", "rapidjson", "ujson"]:
importimportlibmodule=importlib.import_module(serializationBackend)
self.fileIndex=getattr(module, "load")(indexFile)
else:
print(
"Tried to load index path with unsupported serializationBackend:",
serializationBackend,
"!",
)
returnifprintDebug>=2:
defcountDictEntries(d):
n=0forvalueind.values():
n+=countDictEntries(value) ifisinstance(value, dict) else1returnnprint("Files:", countDictEntries(self.fileIndex))
t1=timer()
ifprintDebug>=1:
print(
"Loading offset dictionary from",
indexFileName,
"took {:.2f}s".format(t1-t0),
)
deftryLoadIndex(self, indexFileName):
"""calls loadIndex if index is not loaded already and provides extensive error handling"""ifself.indexIsLoaded():
returnTrueifnotos.path.isfile(indexFileName):
returnFalseifos.path.getsize(indexFileName) ==0:
try:
os.remove(indexFileName)
exceptOSError:
print(
"[Warning] Failed to remove empty old cached index file:",
indexFileName,
)
returnFalsetry:
self.loadIndex(indexFileName)
exceptException:
self.fileIndex=Nonetraceback.print_exc()
print("[Warning] Could not load file '"+indexFileName)
print(
"[Info] Some likely reasons for not being able to load the index file:"
)
print("[Info] - Some dependencies are missing. Please isntall them with:")
print("[Info] pip3 --user -r requirements.txt")
print("[Info] - The file has incorrect read permissions")
print("[Info] - The file got corrupted because of:")
print(
"[Info] - The program exited while it was still writing the index because of:"
)
print("[Info] - the user sent SIGINT to force the program to quit")
print("[Info] - an internal error occured while writing the index")
print("[Info] - the disk filled up while writing the index")
print("[Info] - Rare lowlevel corruptions caused by hardware failure")
print(
"[Info] This might force a time-costly index recreation, so if it happens often and ""mounting is slow, try to find out why loading fails repeatedly, ""e.g., by opening an issue on the public github page."
)
try:
os.remove(indexFileName)
exceptOSError:
print(
"[Warning] Failed to remove corrupted old cached index file:",
indexFileName,
)
returnself.indexIsLoaded()
classTarMount(fuse.Operations):
""" This class implements the fusepy interface in order to create a mounted file system view to a TAR archive. This class can and is relatively thin as it only has to create and manage an IndexedTar object and query it for directory or file contents. It also adds a layer over the file permissions as all files must be read-only even if the TAR reader reports the file as originally writable because no TAR write support is planned. """def__init__(
self,
pathToMount,
clearIndexCache=False,
recursive=False,
serializationBackend=None,
prefix="",
):
self.tarFileName=pathToMountself.tarFile=open(self.tarFileName, "rb")
self.indexedTar=IndexedTar(
self.tarFileName,
writeIndex=True,
clearIndexCache=clearIndexCache,
recursive=recursive,
serializationBackend=serializationBackend,
)
ifprefixandnotself.indexedTar.isDir(prefix):
prefix=""ifprefixandnotprefix.endswith("/"):
prefix+="/"self.prefix=prefix# make the mount point read only and executable if readable, i.e., allow directory listing# @todo In some cases, I even 2(!) '.' directories listed with ls -la!# But without this, the mount directory is owned by roottarStats=os.stat(self.tarFileName)
# clear higher bits like S_IFREG and set the directory bit insteadmountMode= (tarStats.st_mode&0o777) |stat.S_IFDIRifmountMode&stat.S_IRUSR!=0:
mountMode |= stat.S_IXUSRifmountMode&stat.S_IRGRP!=0:
mountMode |= stat.S_IXGRPifmountMode&stat.S_IROTH!=0:
mountMode |= stat.S_IXOTHself.indexedTar.fileIndex[self.prefix+"."] =FileInfo(
offset=0,
size=tarStats.st_size,
mtime=tarStats.st_mtime,
mode=mountMode,
type=tarfile.DIRTYPE,
linkname="",
uid=tarStats.st_uid,
gid=tarStats.st_gid,
istar=True,
)
ifprintDebug>=3:
print("Loaded File Index:", self.indexedTar.fileIndex)
@overrides(fuse.Operations)defgetattr(self, path, fh=None):
ifprintDebug>=2:
print("[getattr( path =", path, ", fh =", fh, ")] Enter")
fileInfo=self.indexedTar.getFileInfo(self.prefix+path, listDir=False)
ifnotisinstance(fileInfo, FileInfo):
ifprintDebug>=2:
print("Could not find path:", path)
raisefuse.FuseOSError(fuse.errno.EROFS)
# dictionary keys: https://pubs.opengroup.org/onlinepubs/007904875/basedefs/sys/stat.h.htmlstatDict=dict(
("st_"+key, getattr(fileInfo, key))
forkeyin ("size", "mtime", "mode", "uid", "gid")
)
# signal that everything was mounted read-onlystatDict["st_mode"] &= ~(stat.S_IWUSR|stat.S_IWGRP|stat.S_IWOTH)
statDict["st_mtime"] =int(statDict["st_mtime"])
statDict["st_nlink"] =2ifprintDebug>=2:
print("[getattr( path =", path, ", fh =", fh, ")] return:", statDict)
returnstatDict@overrides(fuse.Operations)defreaddir(self, path, fh):
ifprintDebug>=2:
print(
"[readdir( path =",
path,
", fh =",
fh,
")] return:",
self.indexedTar.getFileInfo(self.prefix+path, listDir=True).keys(),
)
# we only need to return these special directories. FUSE automatically expands these and will not ask# for paths like /../foo/./../bar, so we don't need to worry about cleaning such pathsyield"."yield".."forkeyinself.indexedTar.getFileInfo(self.prefix+path, listDir=True).keys():
yieldkey@overrides(fuse.Operations)defreadlink(self, path):
ifprintDebug>=2:
print("[readlink( path =", path, ")]")
fileInfo=self.indexedTar.getFileInfo(self.prefix+path)
ifnotisinstance(fileInfo, FileInfo):
raisefuse.FuseOSError(fuse.errno.EROFS)
pathname=fileInfo.linknameifpathname.startswith("/"):
returnos.path.relpath(
pathname, "/"
) # @todo Not exactly sure what to return herereturnpathname@overrides(fuse.Operations)defread(self, path, length, offset, fh):
ifprintDebug>=2:
print(
"[read( path =",
path,
", length =",
length,
", offset =",
offset,
",fh =",
fh,
")] path:",
path,
)
fileInfo=self.indexedTar.getFileInfo(self.prefix+path)
ifnotisinstance(fileInfo, FileInfo):
raisefuse.FuseOSError(fuse.errno.EROFS)
self.tarFile.seek(fileInfo.offset+offset, os.SEEK_SET)
returnself.tarFile.read(length)
defcli():
globalprintDebugparser=argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="""\ If no mount path is specified, then the tar will be mounted to a folder of the same name but without a file extension. TAR files contained inside the tar and even TARs in TARs in TARs will be mounted recursively at folders of the same name barred the file extension '.tar'. In order to reduce the mounting time, the created index for random access to files inside the tar will be saved to <path to tar>.index.<backend>[.<compression]. If it can't be saved there, it will be saved in ~/.ratarmount/<path to tar: '/' -> '_'>.index.<backend>[.<compression]. """,
)
parser.add_argument(
"-f",
"--foreground",
action="store_true",
default=False,
help="keeps the python program in foreground so it can print debug""output when the mounted path is accessed.",
)
parser.add_argument(
"-d",
"--debug",
type=int,
default=1,
help="sets the debugging level. Higher means more output. Currently 3 is the highest",
)
parser.add_argument(
"-c",
"--recreate-index",
action="store_true",
default=False,
help="if specified, pre-existing .index files will be deleted and newly created",
)
parser.add_argument(
"-r",
"--recursive",
action="store_true",
default=False,
help="mount TAR archives inside the mounted TAR recursively. Note that this only has an effect when creating an index. If an index already exists, then this option will be effectively ignored. Recreate the index if you want change the recursive mounting policy anyways.",
)
parser.add_argument(
"-s",
"--serialization-backend",
type=str,
default="custom",
help="specify which library to use for writing out the TAR index. Supported keywords: ("+",".join(IndexedTar.availableSerializationBackends)
+")[.("+",".join(IndexedTar.availableCompressions).strip(",")
+")]",
)
parser.add_argument(
"-p",
"--prefix",
type=str,
default="",
help="The specified path to the folder inside the TAR will be mounted to root. ""This can be useful when the archive as created with absolute paths. ""E.g., for an archive created with `tar -P cf /var/log/apt/history.log`, ""-p /var/log/apt/ can be specified so that the mount target directory "">directly< contains history.log.",
)
parser.add_argument(
"tarfilepath",
metavar="tar-file-path",
type=argparse.FileType("r"),
nargs=1,
help="the path to the TAR archive to be mounted",
)
parser.add_argument(
"mountpath",
metavar="mount-path",
nargs="?",
help="the path to a folder to mount the TAR contents into",
)
args=parser.parse_args()
tarToMount=os.path.abspath(args.tarfilepath[0].name)
try:
tarfile.open(tarToMount, mode="r:")
excepttarfile.ReadError:
print(
"Archive",
tarToMount,
"can't be opened!",
"This might happen for compressed TAR archives, which currently is not supported.",
)
exit(1)
mountPath=args.mountpathifmountPathisNone:
mountPath=os.path.splitext(tarToMount)[0]
mountPathWasCreated=Falseifnotos.path.exists(mountPath):
os.mkdir(mountPath)
printDebug=args.debugfuseOperationsObject=TarMount(
pathToMount=tarToMount,
clearIndexCache=args.recreate_index,
recursive=args.recursive,
serializationBackend=args.serialization_backend,
prefix=args.prefix,
)
fuse.FUSE(
operations=fuseOperationsObject,
mountpoint=mountPath,
foreground=args.foreground,
)
ifmountPathWasCreatedandargs.foreground:
os.rmdir(mountPath)
if__name__=="__main__":
cli()
The text was updated successfully, but these errors were encountered:
I saw that you added allow_other in one of your commits. I added a --fuse option where a comma-separated list of FUSE options can be forwarded through fusepy to libfuse, so you would use --fuse allow_other.
Hi, by adding a setup.py and modifying the script a little to use entry points, installing in a sever can be made much easier. Thanks a lot for the script btw, very usefull.
posible setup.py file
Modifications in script to use a function to launch the cli, change is at the end
The text was updated successfully, but these errors were encountered: