diff --git a/__pycache__/main.cpython-38.pyc b/__pycache__/main.cpython-38.pyc new file mode 100644 index 0000000..7fc8791 Binary files /dev/null and b/__pycache__/main.cpython-38.pyc differ diff --git a/main.py b/main.py index 04f7811..afddfba 100644 --- a/main.py +++ b/main.py @@ -23,6 +23,8 @@ from errno import ENOENT from fuse import FUSE, FuseOSError, Operations, LoggingMixIn import stat import os +import gzip +import secrets class IotaFS_BlobStore(): def __init__(self, api=None): @@ -31,7 +33,7 @@ class IotaFS_BlobStore(): else: self.api = api - def genBundles(self, data, addrIter, lenPerTx = 2187, txPerBundle = 1): + def _genBundles(self, data, addrIter, lenPerTx = 2187, txPerBundle = 1): msg = TryteString.from_bytes(data) bundles = [] nextAddr = addrIter.__next__() @@ -59,7 +61,7 @@ class IotaFS_BlobStore(): ) return bundles - def sendBundles(self, bundles): + def _sendBundles(self, bundles): bundleRets = [] for i,bundle in enumerate(bundles): print(str(int(i/len(bundles)*100))+"%") @@ -84,8 +86,8 @@ class IotaFS_BlobStore(): cipher = AES.new(sHash[:16], AES.MODE_CBC, sHash[22:][:16]) ct_bytes = cipher.encrypt(pad(data, AES.block_size)) addrIter = AddressGenerator(Seed(trSeed)).create_iterator(start = 0, step = 1) - bundles = self.genBundles(ct_bytes, addrIter) - self.sendBundles(bundles) + bundles = self._genBundles(ct_bytes, addrIter) + self._sendBundles(bundles) def uploadTxt(self, txt, secret): data = str.encode(txt) @@ -107,7 +109,7 @@ class IotaFS_BlobStore(): for tx in bundle.transactions: tryteMsg+=str(tx.signature_message_fragment) if tryteMsg == "": - return "" + return b'' tryteStr = TryteString(tryteMsg.rstrip("9")) try: ct_bytes = tryteStr.as_bytes() @@ -136,16 +138,22 @@ class IotaFS_BlobStore(): f.write(y) class IotaFS(): - def __init__(self, token): + def __init__(self, token, fileCompression=False): self.api = Iota('https://nodes.thetangle.org:443', local_pow=True) self.blobStore = IotaFS_BlobStore(self.api) #self.token = token + self.fileCompression = fileCompression self.hashState = hashlib.sha3_384() + genesis = "This is the genesis block. lol." + if self.fileCompression: + raise Exception("Compression does not work currently") + genesis += "#FILE COMPRESSION#" + self.hashState.update(genesis.encode()) self.hashState.update(token.encode()) self._fileTree = {} self.lastBlockIncomplete = False self.incompleteBlockRescanTimeout = 5 - self.chainDelimiter = "#CHAIN_DELIM#" + self.chainDelimiter = "#IOTA_FS#".encode() self.cache = {} self._fetchFileTree() @@ -155,34 +163,34 @@ class IotaFS(): self._fetchFileTree() return copy.deepcopy(self._fileTree) - async def fileTreeFetchLoop(self, interval=10): - while True: - self._fetchFileTree() - await asyncio.sleep(10) - def _fetchFileTree(self): print("[<] Fetching FileTree") - chain = "" + chain = bytes() while True: print("[<] Fetching FileTree-ChainBlock") sHash = self.hashState.digest() block = self._getBlob(sHash) - if block=="": + data = block + if self.fileCompression: + data = gzip.decompress(data) + if data==b'': print("[-] Last Block Received") break self.hashState.update(block) - chain+=block - if chain=="": - print("[.] FileTree succesfully fetched: [EMPTY FILE TREE]") + chain+=data + if chain==b'': + print("[.] FileTree succesfully fetched: [NO UPDATES]") return if chain.endswith(self.chainDelimiter): - curRing = chain.split(self.chainDelimiter)[-1] + curRing = chain.split(self.chainDelimiter)[-2] self.lastBlockIncomplete = False else: print("[-] Last Block was incomplete; refetching...") self.lastBlockIncomplete = True time.sleep(self.incompleteBlockRescanTimeout) self._fetchFileTree() + return + print("{RING}: "+str(curRing)) self._fileTree = msgpack.loads(curRing) print("[.] FileTree succesfully fetched: ") @@ -200,7 +208,7 @@ class IotaFS(): self._mergeFileTrees(value, node) else: if key in treeA and treeA[key]=="#REMOVE#" or key in treeB and treeB[key]=="#REMOVE#": - treeB[key] = "#REMOVE#" + del treeB[key] treeB[key] = value return treeB @@ -213,71 +221,117 @@ class IotaFS(): newRing = msgpack.dumps(self._fileTree)+self.chainDelimiter sHash = self.hashState.digest() - self.blobStore.uploadDataRaw(newRing.encode(), sHash) + print("{RING}: "+str(newRing)) + payload = newRing + if self.fileCompression: + payload = gzip.compress(payload) + self.blobStore.uploadDataRaw(payload, sHash) self.hashState.update(newRing) # For every link in the chain, we salt our hashState using the links data def _putBlob(self, data): - #TODO: Use secure random provider - return self.blobStore.uploadData(data, str(random.random()*999999999999).encode()) + return self.blobStore.uploadData(data, secrets.token_bytes(64)) def _getBlob(self, sHash): - return self.blobStore.getData(sHash) + data = self.blobStore.getData(sHash) + return data def _fetchFile(self, sHash): file = self._getBlob(sHash) - # file lastFetch lastAccess - self.cache[sHash] = [file, time.now(), time.now()] + blob = file + if self.fileCompression: + blob = gzip.decompress(blob) + # file lastFetch lastAccess + self.cache[sHash] = [blob, time.time(), time.time()] return self.cache[sHash] def getFile(self, sHash): + if sHash==b'': + return [b'', 0, time.time()] + print("/GET/ "+str(sHash)+" <- ") if sHash in self.cache: # TODO: maybe update if to old? - self.cache[sHash][2] = time.now() + self.cache[sHash][2] = time.time() return self.cache[sHash] else: return self._fetchFile(sHash) def putFile(self, file, path): - blablabla + print("/PUT/ "+str(file)+" -> "+path) + if file==b'': + sHash = b'' + else: + blob = file + if self.fileCompression: + blob = gzip.compress(blob) + sHash = self._putBlob(blob) + self.cache[sHash] = [file, time.time(), time.time()] + treeDelta = {} + subTree = treeDelta + for elem in path.split("/")[:-1]: + subTree[elem] = {} + subTree = subTree[elem] + subTree[path.split("/")[-1]] = sHash + self.upsertFileTree(treeDelta) + def mkdir(self, path): + treeDelta = {} + subTree = treeDelta + for elem in path.split("/"): + subTree[elem] = {} + subTree = subTree[elem] + self.upsertFileTree(treeDelta) + return 0 + + def removeFile(self, path): + treeDelta = {} + subTree = treeDelta + for elem in path.split("/")[:-1]: + subTree[elem] = {} + subTree = subTree[elem] + file = subTree[path.split("/")[-1]] + subTree[path.split("/")[-1]] = "#REMOVE#" + self.upsertFileTree(treeDelta) + return file + + def mv(self, old, new): + file = self.removeFile(old) + self.putFile(self, file, new) class IotaFS_Fuse(LoggingMixIn, Operations): - def __init__(self, token): - self.fs = IotaFS(token) - self.fileTree = self.fs.getFileTree() + def __init__(self, token, fileCompression=True): + self.fs = IotaFS(token, fileCompression=fileCompression) def getSubtree(self, path): - subTree = self.fileTree - for elem in path.split("/"): - if elem not in subTree: - return False - subTree = subTree[elem] + subTree = self.fs.getFileTree() + for elem in path[1:].split("/"): + if elem!="": + if elem not in subTree: + return False + if self.subtreeIsFile(subTree): + # we cannot traverse further, if this is a file + return False + subTree = subTree[elem] return subTree def createFileObj(self, path, fileObj): - for elem in path.split("/")[:-1]: + subTree = self.fileTree + for elem in path[1:].split("/")[:-1]: if elem not in subTree: return False subTree = subTree[elem] subTree[path.split("/")[-1]] = fileObj def subtreeIsFile(self, subtree): - return isinstance(subtree, str) + return isinstance(subtree, (bytes, bytearray)) def subtreeExists(self, subtree): return not (subtree == False) - #def chmod(self, path, mode): - # return self.sftp.chmod(path, mode) - - #def chown(self, path, uid, gid): - # return self.sftp.chown(path, uid, gid) - - #def create(self, path, mode): - # f = self.sftp.open(path, 'w') - # f.chmod(mode) - # f.close() - # return 0 + def create(self, path, mode): + print("[#] CREATE "+path) + self.fs.putFile(b'', path[1:]) + #return open(path[1:]) + return 0 #def destroy(self, path): # self.sftp.close() @@ -293,69 +347,81 @@ class IotaFS_Fuse(LoggingMixIn, Operations): st = {} # mode decides access permissions and if file object is a directory (stat.S_IFDIR), file (stat.S_IFREG) or a special file if self.subtreeIsFile(subTree): - st['st_mode'] = 777 | stat.S_IFREG + st['st_mode'] = 0o744 | stat.S_IFREG else: - st['st_mode'] = 777 | stat.S_IFDIR - st['st_ino'] = 0 - st['st_dev'] = 0 + st['st_mode'] = 0o744 | stat.S_IFDIR + #st['st_ino'] = 0 + #st['st_dev'] = 0 st['st_nlink'] = 1 st['st_uid'] = os.getuid() #file object's user id st['st_gid'] = os.getgid() #file object's group id - if fh: + if fh and False: file, path, sHash, lastFetch, lastAccess = fh st["st_size"] = len(file) st['st_atime'] = lastAccess st['st_mtime'] = lastFetch st['st_ctime'] = 0 else: - st['st_size'] = 0 # 0 Byte lol - st['st_atime'] = now #last access time in seconds - st['st_mtime'] = now #last modified time in seconds + st['st_size'] = 1024*1024*1024 # 1 Byte lol + st['st_atime'] = 0 #last access time in seconds + st['st_mtime'] = 0 #last modified time in seconds st['st_ctime'] = 0 # very old file # TODO: Actuall real value block_size = 512 st['st_blocks'] = (int) ((st['st_size'] + block_size-1) / block_size) return st - #def mkdir(self, path, mode): - # return self.sftp.mkdir(path, mode) + def mkdir(self, path, mode): + print("[#] MKDIR "+path) + self.fs.mkdir(path[1:]) + return 0 - def read(self, path2, size, offset, fh): - print("[#] WRITE "+path2) - file, path, sHash, lastFetch, lastAccess = fh + def read(self, path, size, offset, fh): + print("[#] READ "+path) + file, path2, sHash, lastFetch, lastAccess = self.openFile(path) if path!=path2: + print(path+"!="+path2) return FuseOSError(ENOENT) - return file[offset : offset+size] def readdir(self, path, fh): print("[#] READDIR "+path) - l = ['.', '..'] - for elem in self.getSubtree(path): - l.append(elem.encode('utf-8')) + subTree = self.getSubtree(path) + if self.subtreeIsFile(subTree): + # We cant list a file! + return FuseOSError(ENOENT) + pprint(subTree) + l = [".", ".."] + for elem in subTree: + l.append(elem) return l - #def rename(self, old, new): - # return self.sftp.rename(old, new) + def rename(self, old, new): + self.fs.mv(old,new) + return 0 - #def rmdir(self, path): - # return self.sftp.rmdir(path) + def rmdir(self, path): + self.fs.removeFile(path) + return 0 - def write(self, path2, data, offset, fh): - print("[#] WRITE "+path2) - file, path, sHash, lastFetch, lastAccess = fh + def write(self, path, data, offset, fh): + print("[#] WRITE "+path) + file, path2, sHash, lastFetch, lastAccess = self.openFile(path) if path!=path2: + print(path+"!="+path2) return FuseOSError(ENOENT) - raw = data.encode() - file[:offset] + raw + file[offset+len(raw):] + raw = data + file = file[:offset] + raw + file[offset+len(raw):] + + self.fs.putFile(file, path[1:]) + print("Write successfull") return len(raw) - def open(self, path, flags): - print("[#] OPEN "+path) + def openFile(self, path): subTree = self.getSubtree(path) if subTree == False: - pass + raise FuseOSError(ENOENT) else: if not self.subtreeIsFile(subTree): # cannot open a dir @@ -364,6 +430,12 @@ class IotaFS_Fuse(LoggingMixIn, Operations): file, lastFetch, lastAccess = self.fs.getFile(sHash) return (file, path, sHash, lastFetch, lastAccess) + def open(self, path, flags): + return 0 + + def release(self, path, fh): + return 0 + if __name__ == '__main__': import argparse parser = argparse.ArgumentParser()