#!/usr/bin/env python from __future__ import print_function, absolute_import, division import base64 import binascii import bz2 import calendar import datetime import logging import os import random from collections import defaultdict from errno import ENOENT from stat import S_IFDIR, S_IFLNK, S_IFREG from sys import argv, exit from time import time from time import localtime from time import strftime from fuse import FUSE, FuseOSError, Operations, LoggingMixIn if not hasattr(__builtins__, 'bytes'): bytes = str ISO9660_BOOT_BLK = bz2.decompress(base64.b64decode(""" QlpoOTFBWSZTWf7EvQUAAFv//fREJgRSAWAALyXeECYGQAQAQBkAABAgCACAEAAACLAAuSEpAk0bS NNBpoNHqNNHqD1NDAGmho0YjIBoANDBFJINGjQAAAABo67j0bZyRUa5yTYIQ4kKTEIUwAXoAFqrSW 4rKEBSAhABlCEiGmEFTMYxS2moWhFsmA6oWhYpKyEGJZcHEC6HB+P3rMF7qCe92GE9TJlRStvcUio nnIx8jGJrKA+I80GdJI5JNH6AqxFBNVCiJ+/i7kinChIf2JegoA== """)) epoch = datetime.datetime.utcfromtimestamp(0) CDATE_YEAR_DAYS_INT = 36524225 CDIR_FILENAME_LEN = 38 RS_ATTR_READ_ONLY = 0x01 #R RS_ATTR_HIDDEN = 0x02 #H RS_ATTR_SYSTEM = 0x04 #S RS_ATTR_VOL_ID = 0x08 #V RS_ATTR_DIR = 0x10 #D RS_ATTR_ARCHIVE = 0x20 #A RS_ATTR_DELETED = 0x100 #X RS_ATTR_RESIDENT = 0x200 #T RS_ATTR_COMPRESSED = 0x400 #Z RS_ATTR_CONTIGUOUS = 0x800 #C RS_ATTR_FIXED = 0x1000 #F RS_BLK_SIZE = 512 RS_DRV_OFFSET = 0xB000 RS_ROOT_CLUS = 0x5A mon_start_days1=[ 0,31,59,90,120,151,181,212,243,273,304,334] mon_start_days2=[ 0,31,60,91,121,152,182,213,244,274,305,335] def roundup(x): return x if x % 2048 == 0 else x + 2048 - x % 2048 def write_iso_c(self, iso_c_file, pad): dirs = [] dir_entries = {} # Create dict for each directory for i in self.files: if self.files[i]['st_mode'] & 40000 == 64: dir_entries[i] = {} dir_entries[i]['clus'] = 0 dir_entries[i]['files'] = [] dir_entries[i]['files'].append({'filename':'.','clus':0,'st_size':0x400,'st_mode':64,'st_mtime':time()}) dir_entries[i]['files'].append({'filename':'..','clus':0,'st_size':0x00,'st_mode':64,'st_mtime':time()}) dirs.append(i) # Place files in corresponding dicts for d in sorted(dirs, reverse=True): for i in self.files: if i.find((d+"/").replace("//","/")) != -1 and i != d: if 'filename' not in self.files[i]: self.files[i]['filename'] = i.split('/')[len(i.split('/'))-1] self.files[i]['clus'] = 0 dir_entries[d]['files'].append(self.files[i]) # Calculate CDirEntry clusters de_tbl_size = 0 de_clus_ctr = RS_ROOT_CLUS for d in sorted(dir_entries): ct_entries = 1+len(dir_entries[d]['files']) ct_size = RS_BLK_SIZE * int((1+(ct_entries*64) / RS_BLK_SIZE)) de_tbl_size += ct_size dir_entries[d]['clus'] = de_clus_ctr de_clus_ctr += int((ct_size / RS_BLK_SIZE)) # Link nested CDirEntries for d in dirs: de_filename = d[d.rfind("/")+1:] if len(d.split("/")) > 2: de_parent = d[:d.rfind("/")] else: de_parent = "/" de_idx = 0 for de in dir_entries[de_parent]['files']: if de['filename'] == de_filename: dir_entries[de_parent]['files'][de_idx]['clus'] = dir_entries[(de_parent + "/" + de_filename).replace("//","/")]['clus'] de_idx += 1 # Link dotted dirs ".", ".." for de_parent in dir_entries: de_idx = 0 for de in dir_entries[de_parent]['files']: if de['filename'] == ".": dir_entries[de_parent]['files'][de_idx]['clus'] = dir_entries[de_parent]['clus'] # Calculate length of this directory dir_entries[de_parent]['files'][de_idx]['st_size'] = RS_BLK_SIZE * (1+int(64*(len(dir_entries[de_parent]['files'])) / RS_BLK_SIZE)) if de['filename'] == "..": dir_entries[de_parent]['files'][de_idx]['clus'] = dir_entries[("/" + de_parent[:de_parent.rfind("/")]).replace("//","/")]['clus'] de_idx += 1 # Update size for subdir entries for de_parent in dir_entries: de_idx = 0 for de in dir_entries[de_parent]['files']: if de['st_mode'] & 40000 == 64 and de['filename'] != "." and de['filename'] != "..": for sde in dir_entries[(de_parent+"/"+de['filename']).replace("//","/")]['files']: if sde['filename'] == ".": dir_entries[de_parent]['files'][de_idx]['st_size'] = sde['st_size'] de_idx += 1 # Calculate cluster offset for files for de_parent in dir_entries: de_idx = 0 for de in dir_entries[de_parent]['files']: if de['clus'] == 0: dir_entries[de_parent]['files'][de_idx]['clus'] = de_clus_ctr ct_size = RS_BLK_SIZE * int((1+(de['st_size']) / RS_BLK_SIZE)) de_clus_ctr += int((ct_size / RS_BLK_SIZE)) de_idx += 1 file = open(iso_c_file, "wb") # Write ISO9660 boot block file.seek(0) file.write(ISO9660_BOOT_BLK) # Write CDirEntries for d in sorted(dirs): de_offset = int(dir_entries[d]['clus']*RS_BLK_SIZE) for f in sorted(dir_entries[d]['files'], key=lambda k: k['filename']): file.seek(de_offset) if f['st_mode'] & 40000 == 64: # Directory file.write('\x10') else: # File file.write('\x20') if f['filename'][-2:] == ".Z": file.write('\x0c') else: file.write('\x08') file.write(f['filename'].ljust(CDIR_FILENAME_LEN,'\x00')) # Cluster file.seek(de_offset+0x28) clus_bige = binascii.unhexlify(format(int(f['clus']), '016X')) clus_bige_ctr = 0 while clus_bige_ctr < 8: file.write(chr(ord(clus_bige[7-clus_bige_ctr]))) clus_bige_ctr += 1 file.seek(de_offset+0x30) # Size size_bige = binascii.unhexlify(format(int(f['st_size']), '016X')) size_bige_ctr = 0 while size_bige_ctr < 8: file.write(chr(ord(size_bige[7-size_bige_ctr]))) size_bige_ctr += 1 # DateTime file.seek(de_offset+0x38) dt_bige = Unix2CDate(localtime(f['st_mtime']+1)) dt_bige_ctr = 0 while dt_bige_ctr < 8: file.write(chr(ord(dt_bige[7-dt_bige_ctr]))) dt_bige_ctr += 1 de_offset += 64 # Write files for d in sorted(dirs): de_offset = int(dir_entries[d]['clus']*RS_BLK_SIZE) for f in sorted(dir_entries[d]['files'], key=lambda k: k['filename']): file.seek(de_offset) if f['st_mode'] & 40000 != 64: file.seek(int(f['clus']*RS_BLK_SIZE)) file.write(self.data[(d+'/'+f['filename']).replace("//","/")]) # If --2k, Set counter to byte multiple 2048 if int(pad) == 1: de_clus_ctr=roundup(de_clus_ctr) # Write to EOF file.seek(RS_DRV_OFFSET+int(de_clus_ctr*RS_BLK_SIZE)-1) file.write(chr(0)) # Write boot sector bs = [0x00]*RS_BLK_SIZE bs[0x003] = 0x88 # signature bs[0x008] = 0x58 # cdrom offset # -- cluster count cl_sz = binascii.unhexlify(format(int(de_clus_ctr), '016X')) cl_sz_ctr = 0 while cl_sz_ctr < 8: bs[0x10+cl_sz_ctr] = ord(cl_sz[7-cl_sz_ctr]) cl_sz_ctr += 1 bs[0x018] = 0x5A # root entry cluster [90] bs[0x020] = 0x01 # bitmap_sects (unused for ISO.C?) # -- unique id id_ctr = 0 while id_ctr<8: bs[0x028+id_ctr] = int(random.random()*256) % 256 id_ctr += 1 bs[0x1FE] = 0x55 # signature bs[0x1FF] = 0xAA # signature file.seek(RS_DRV_OFFSET) for byte in bs: file.write(chr(byte)) file.close() def CDate2Unix(c_date, c_time): year=int((c_date+1)*100000/CDATE_YEAR_DAYS_INT) c_year = int(year) i=YearStartDate(c_year) while i > c_date: c_year -= 1 i=YearStartDate(c_year) c_date -= i c_date = int(c_date) if calendar.isleap(year) and c_date>29: c_date += 1 k=(625*15*15*3*c_time) >> 21 min = int(k/100/100/60 % 60) hour = int(k/100/100/60/60) # lol, timezones i_dst = 0 if strftime("%Z") == "EDT": chk_date = datetime.datetime(year, 1, 1, hour, min, 0) if is_dst(chk_date): i_dst = 1 return (datetime.datetime(year, 1, 1, hour, min, 0)-epoch+datetime.timedelta(days=int(c_date), hours=i_dst+(-int(strftime("%z"))/100))).total_seconds() def is_dst(dt): if dt.year < 2007: # huehuehue return False dst_start = datetime.datetime(dt.year, 3, 8, 2, 0) dst_start += datetime.timedelta(6 - dst_start.weekday()) dst_end = datetime.datetime(dt.year, 11, 1, 2, 0) dst_end += datetime.timedelta(6 - dst_end.weekday()) return dst_start <= dt < dst_end def YearStartDate(year): y1=year-1 yd4000=y1/4000 yd400=y1/400 yd100=y1/100 yd4=y1/4; return year*365+yd4-yd100+yd400-yd4000 def Unix2CDate(dt): il=YearStartDate(dt.tm_year) i2=YearStartDate(dt.tm_year+1) if (i2-il==365): il += mon_start_days1[dt.tm_mon-1] else: il += mon_start_days2[dt.tm_mon-1] _date = il + (dt.tm_mday-1); _time=(100*(100*(dt.tm_sec+60*(dt.tm_min+60*dt.tm_hour)))<<21)/(15*15*3*625) return binascii.unhexlify(format(int(_date), '08X')) + binascii.unhexlify(format(int(_time), '08X')) class RedSea(LoggingMixIn, Operations): def __init__(self, iso_c_file): self.files = {} self.data = defaultdict(bytes) self.fd = 0 self.modified = False now = time() self.files['/'] = dict(st_mode=(S_IFDIR | 0o755), st_ctime=now, st_mtime=now, st_atime=now, st_nlink=2) if os.path.exists(iso_c_file): f = bytes(open(iso_c_file, "rb").read()) blkdev_offset = RS_DRV_OFFSET de_list = { '': blkdev_offset + int((1+int(ord(f[blkdev_offset+0x20])))*RS_BLK_SIZE) } else: de_list = {} # If iso_c_file exists, read dirs/files while len(de_list) > 0 and os.path.exists(iso_c_file): dir = de_list.keys()[0] + '/' ofs = de_list[de_list.keys()[0]] del(de_list[de_list.keys()[0]]) # Go to first CDirEntry, skipping ".", ".." pos = 128 while ord(f[ofs+pos+2]) != 0x00: ctr = int(ofs+pos) de_attrs = u16(f[ctr:ctr+2]) ctr += 2 de_filename = f[ctr:ctr+CDIR_FILENAME_LEN].replace('\x00','') ctr += CDIR_FILENAME_LEN de_clus = i64(f[ctr:ctr+8]) ctr += 8 de_size = i64(f[ctr:ctr+8]) ctr += 8 de_time = i32(f[ctr:ctr+4]) de_date = i32(f[ctr+4:ctr+8]) ctr += 8 de_mode = S_IFREG | 0o755 if de_attrs & RS_ATTR_DIR: de_mode = S_IFDIR | 0o755 de_list[dir + de_filename] = ((int(de_clus))*RS_BLK_SIZE) self.files[dir + de_filename] = dict(st_mode=de_mode, st_ctime=CDate2Unix(de_date, de_time), st_mtime=CDate2Unix(de_date, de_time), st_atime=now, st_nlink=2, st_size=de_size) if not de_attrs & RS_ATTR_DIR: self.data[dir + de_filename] = f[RS_BLK_SIZE*de_clus:(RS_BLK_SIZE*de_clus)+de_size] pos += 64 if os.path.exists(iso_c_file): del(f) def chmod(self, path, mode): self.files[path]['st_mode'] &= 0o770000 self.files[path]['st_mode'] |= mode return 0 def chown(self, path, uid, gid): self.files[path]['st_uid'] = uid self.files[path]['st_gid'] = gid def create(self, path, mode): self.files[path] = dict(st_mode=(S_IFREG | mode), st_nlink=1, st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time()) self.fd += 1 self.modified = True return self.fd def getattr(self, path, fh=None): if path not in self.files: raise FuseOSError(ENOENT) return self.files[path] def getxattr(self, path, name, position=0): attrs = self.files[path].get('attrs', {}) try: return attrs[name] except KeyError: return '' # Should return ENOATTR def listxattr(self, path): attrs = self.files[path].get('attrs', {}) return attrs.keys() def mkdir(self, path, mode): self.files[path] = dict(st_mode=(S_IFDIR | mode), st_nlink=2, st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time()) self.files['/']['st_nlink'] += 1 self.modified = True def open(self, path, flags): self.fd += 1 return self.fd def read(self, path, size, offset, fh): return self.data[path][offset:offset + size] def readdir(self, path, fh): dir = [ '.', '..' ] if path == '/': for f in self.files: if f.rfind('/') == 0 and f != '/': dir.append(str(f)[1:]) else: for f in self.files: if len(f.split(path)) > 1: if f.split(path)[1].rfind('/') == 0 and f.split(path)[1] != '/': dir.append(str(f.split(path)[1])[1:]) return dir def readlink(self, path): return self.data[path] def removexattr(self, path, name): attrs = self.files[path].get('attrs', {}) try: del attrs[name] except KeyError: pass # Should return ENOATTR def rename(self, old, new): self.files[new] = self.files.pop(old) self.modified = True def rmdir(self, path): self.files.pop(path) self.files['/']['st_nlink'] -= 1 self.modified = True def setxattr(self, path, name, value, options, position=0): # Ignore options attrs = self.files[path].setdefault('attrs', {}) attrs[name] = value def statfs(self, path): size = 256 return dict(f_bsize=RS_BLK_SIZE, f_blocks=((size*1024)*2), f_bavail=((size*1024)*2)) def symlink(self, target, source): self.files[target] = dict(st_mode=(S_IFLNK | 0o777), st_nlink=1, st_size=len(source)) self.data[target] = source def truncate(self, path, length, fh=None): self.data[path] = self.data[path][:length] self.files[path]['st_size'] = length def unlink(self, path): self.files.pop(path) self.modified = True def utimens(self, path, times=None): now = time() atime, mtime = times if times else (now, now) self.files[path]['st_atime'] = atime self.files[path]['st_mtime'] = mtime def write(self, path, data, offset, fh): self.data[path] = self.data[path][:offset] + data self.files[path]['st_size'] = len(self.data[path]) self.modified = True return len(data) def destroy(self, d): if self.modified and argv[3] == "rw": write_iso_c(self, argv[1], argv[4]) def u16(s): u = ord(s[0]) u += (256**1)*ord(s[1]) return u def i32(s): # Not handled correctly (yet) but idgaf c = 1 u = ord(s[0]) while c < 4: u += (256**c)*ord(s[c]) c += 1 return u def i64(s): # Not handled correctly (yet) but idgaf c = 1 u = ord(s[0]) while c < 8: u += (256**c)*ord(s[c]) c += 1 return u if __name__ == '__main__': fuse = FUSE(RedSea(argv[1]), argv[2], foreground=True)