#! /usr/bin/env python # # lfs.py # # A simple simulator to emulate the behavior of LFS. # # Make lots of simplifying assumptions including things like: # - all entities take up exactly one block # - no segments or buffering of writes in memory # - many other things # from __future__ import print_function import math import sys from optparse import OptionParser import random import copy # to make Python2 and Python3 act the same -- how dumb def random_seed(seed): try: random.seed(seed, version=1) except: random.seed(seed) return # fixed addr ADDR_CHECKPOINT_BLOCK = 0 # These are not (yet) things you can change NUM_IMAP_PTRS_IN_CR = 16 NUM_INODES_PER_IMAP_CHUNK = 16 NUM_INODE_PTRS = 8 NUM_INODES = NUM_IMAP_PTRS_IN_CR * NUM_INODES_PER_IMAP_CHUNK # block types BLOCK_TYPE_CHECKPOINT = 'type_cp' BLOCK_TYPE_DATA_DIRECTORY = 'type_data_dir' BLOCK_TYPE_DATA_BLOCK = 'type_data' BLOCK_TYPE_INODE = 'type_inode' BLOCK_TYPE_IMAP = 'type_imap' # inode types INODE_DIRECTORY = 'dir' INODE_REGULAR = 'reg' # root inode is "well known" per Unix conventions ROOT_INODE = 0 # policies ALLOCATE_SEQUENTIAL = 1 ALLOCATE_RANDOM = 2 # # Heart of simulation is found here # class LFS: def __init__(self, use_disk_cr=False, no_force_checkpoints=False, inode_policy=ALLOCATE_SEQUENTIAL, solve=False): # whether to read checkpoint region and imap pieces from disk (if True) # or instead just to use "in-memory" inode map instead self.use_disk_cr = use_disk_cr # to force an update of the checkpoint region after each write self.no_force_checkpoints = no_force_checkpoints # inode allocation policy assert(inode_policy == ALLOCATE_SEQUENTIAL or inode_policy == ALLOCATE_RANDOM) self.inode_policy = inode_policy # whether to show "answers" to things or not self.solve = solve # dump assistance self.dump_last = 1 # ALL blocks are in the "disk" self.disk = [] # checkpoint region (first block) self.cr = [3,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1] assert(len(self.cr) == NUM_IMAP_PTRS_IN_CR) # create first checkpoint region self.log({'block_type':BLOCK_TYPE_CHECKPOINT, 'entries': self.cr}) assert(len(self.disk) == 1) # init root dir data self.log(self.make_new_dirblock(ROOT_INODE, ROOT_INODE)) assert(len(self.disk) == 2) # root inode root_inode = self.make_inode(itype=INODE_DIRECTORY, size=1, refs=2) root_inode['pointers'][0] = 1 root_inode_address = self.log(root_inode) assert(len(self.disk) == 3) # init in memory imap self.inode_map = {} for i in range(NUM_INODES): self.inode_map[i] = -1 self.inode_map[ROOT_INODE] = root_inode_address # imap piece self.log(self.make_imap_chunk(ROOT_INODE)) assert(len(self.disk) == 4) # error code: tracking self.error_clear() return def make_data_block(self, data): return {'block_type':BLOCK_TYPE_DATA_BLOCK, 'contents':data} def make_inode(self, itype, size, refs): return {'block_type':BLOCK_TYPE_INODE, 'type':itype, 'size':size, 'refs':refs, 'pointers':[-1,-1,-1,-1,-1,-1,-1,-1]} def make_new_dirblock(self, parent_inum, current_inum): dirblock = self.make_empty_dirblock() dirblock['entries'][0] = ('.', current_inum) dirblock['entries'][1] = ('..', parent_inum) return dirblock def make_empty_dirblock(self): return {'block_type':BLOCK_TYPE_DATA_DIRECTORY, 'entries': [('-',-1), ('-',-1), ('-',-1), ('-',-1), ('-',-1), ('-',-1), ('-',-1), ('-',-1)]} def make_imap_chunk(self, cnum): imap_chunk = {} imap_chunk['block_type'] = BLOCK_TYPE_IMAP imap_chunk['entries'] = list() start = cnum * NUM_INODES_PER_IMAP_CHUNK for i in range(start, start + NUM_INODES_PER_IMAP_CHUNK): imap_chunk['entries'].append(self.inode_map[i]) return imap_chunk def make_random_blocks(self, num): contents = [] for i in range(num): L = chr(ord('a') + int(random.random() * 26)) contents.append(str(16 * ('%s%d' % (L, i)))) return contents def inum_to_chunk(self, inum): return int(inum / NUM_INODES_PER_IMAP_CHUNK) def determine_liveness(self): # first, assume all are dead self.live = {} for i in range(len(self.disk)): self.live[i] = False # checkpoint region self.live[0] = True # now mark latest pieces of imap as live for ptr in self.cr: if ptr == -1: continue self.live[ptr] = True # go through imap, find live inodes and their addresses # latest inodes are all live, by def inodes = [] for i in range(len(self.inode_map)): if self.inode_map[i] == -1: continue self.live[self.inode_map[i]] = True inodes.append(i) # go through live inodes and find blocks each points to for i in inodes: inode = self.disk[self.inode_map[i]] for ptr in inode['pointers']: self.live[ptr] = True return def error_log(self, s): self.error_list.append(s) return def error_clear(self): self.error_list = [] return def error_dump(self): for i in self.error_list: print(' %s' % i) return def dump_partial(self, show_liveness, show_checkpoint): if show_checkpoint or not self.no_force_checkpoints: self.__dump(0, 1, show_liveness) if not self.no_force_checkpoints: print('...') self.__dump(self.dump_last, len(self.disk), show_liveness) self.dump_last = len(self.disk) return def dump(self, show_liveness): self.__dump(0, len(self.disk), show_liveness) return def __dump(self, start, end, show_liveness): self.determine_liveness() for i in range(start, end): # print ADDRESS on disk b = self.disk[i] block_type = b['block_type'] print('[ %3d ]' % i, end='') # print LIVENESS if show_liveness or self.solve: if self.live[i]: print(' live', end=' ') else: print(' ', end=' ') else: print(' ? ', end=' ') if block_type == BLOCK_TYPE_CHECKPOINT: print('checkpoint:', end=' ') for e in b['entries']: if e != -1: print(e, end=' ') else: print('--', end=' ') print('') elif block_type == BLOCK_TYPE_DATA_DIRECTORY: for e in b['entries']: if e[1] != -1: print('[%s,%s]' % (str(e[0]), str(e[1])), end=' ') else: print('--', end=' ') print('') elif block_type == BLOCK_TYPE_DATA_BLOCK: print (b['contents']) elif block_type == BLOCK_TYPE_INODE: print('type:'+b['type'], 'size:'+str(b['size']), 'refs:'+str(b['refs']), 'ptrs:', end=' ') for p in b['pointers']: if p != -1: print('%s' % p, end=' ') else: print('--', end=' ') print('') elif block_type == BLOCK_TYPE_IMAP: print('chunk(imap):', end=' ') for e in b['entries']: if e != -1: print(e, end=' ') else: print('--', end=' ') print('') else: print('error: unknown block_type', block_type) exit(1) return def log(self, block): new_address = len(self.disk) self.disk.append(copy.deepcopy(block)) return new_address def allocate_inode(self): if self.inode_policy == ALLOCATE_SEQUENTIAL: for i in range(len(self.inode_map)): if self.inode_map[i] == -1: # ugh: temporary holder until real on-disk location filled in self.inode_map[i] = 1 return i elif self.inode_policy == ALLOCATE_RANDOM: # inefficiently ensure that space exists # better done with counter of alloc/free but this is ok for now space_exists = False imap_len = len(self.inode_map) for i in range(imap_len): if self.inode_map[i] == -1: space_exists = True break if not space_exists: return -1 while True: index = int(random.random() * imap_len) if self.inode_map[index] == -1: self.inode_map[index] = 1 return index # no free inode found return -1 def free_inode(self, inum): assert(self.inode_map[inum] != -1) self.inode_map[inum] = -1 return def remap(self, inode_number, inode_address): self.inode_map[inode_number] = inode_address return def dump_inode_map(self): for i in range(len(self.inode_map)): if self.inode_map[i] != -1: print(' ', i, '->', self.inode_map[i]) print('') return def cr_sync(self): # only place in code where an OVERWRITE occurs self.disk[ADDR_CHECKPOINT_BLOCK] = copy.deepcopy({'block_type':BLOCK_TYPE_CHECKPOINT, 'entries': self.cr}) return 0 def get_inode_from_inumber(self, inode_number): imap_entry_index = int(inode_number / NUM_INODES_PER_IMAP_CHUNK) imap_entry_offset = inode_number % NUM_INODES_PER_IMAP_CHUNK if self.use_disk_cr: # this is the disk path checkpoint_block = self.disk[ADDR_CHECKPOINT_BLOCK] assert(checkpoint_block['block_type'] == BLOCK_TYPE_CHECKPOINT) imap_block_address = checkpoint_block['entries'][imap_entry_index] imap_block = self.disk[imap_block_address] assert(imap_block['block_type'] == BLOCK_TYPE_IMAP) inode_address = imap_block['entries'][imap_entry_offset] else: # this is the just-use-the-mem-inode_map path inode_address = self.inode_map[inode_number] assert(inode_address != -1) inode = self.disk[inode_address] assert(inode['block_type'] == BLOCK_TYPE_INODE) return inode def __lookup(self, parent_inode_number, name): parent_inode = self.get_inode_from_inumber(parent_inode_number) assert(parent_inode['type'] == INODE_DIRECTORY) for address in parent_inode['pointers']: if address == -1: continue directory_block = self.disk[address] assert(directory_block['block_type'] == BLOCK_TYPE_DATA_DIRECTORY) for entry_name, entry_inode_number in directory_block['entries']: if entry_name == name: return (entry_inode_number, parent_inode) return (-1, parent_inode) def __walk_path(self, path): split_path = path.split('/') if split_path[0] != '': self.error_log('path malformed: must start with /') return -1, '', -1, '' inode_number = -1 parent_inode_number = ROOT_INODE # root inode number is well known for i in range(1, len(split_path) - 1): inode_number, inode = self.__lookup(parent_inode_number, split_path[i]) if inode_number == -1: self.error_log('directory %s not found' % split_path[i]) return -1, '', -1, '' if inode['type'] != INODE_DIRECTORY: self.error_log('invalid element of path [%s] (not a dir)' % split_path[i]) return -1, '', -1, '' parent_inode_number = inode_number file_name = split_path[len(split_path) - 1] inode_number, parent_inode = self.__lookup(parent_inode_number, file_name) return inode_number, file_name, parent_inode_number, parent_inode def update_imap(self, inum_list): chunk_list = list() for inum in inum_list: cnum = self.inum_to_chunk(inum) if cnum not in chunk_list: chunk_list.append(cnum) self.log(self.make_imap_chunk(cnum)) self.cr[cnum] = len(self.disk) - 1 return def __read_dirblock(self, inode, index): return self.disk[inode['pointers'][index]] # return (inode_index, dirblock_index) def __find_matching_dir_slot(self, name, inode): for inode_index in range(inode['size']): directory_block = self.__read_dirblock(inode, inode_index) assert(directory_block['block_type'] == BLOCK_TYPE_DATA_DIRECTORY) for slot_index in range(len(directory_block['entries'])): entry_name, entry_inode_number = directory_block['entries'][slot_index] if entry_name == name: return inode_index, slot_index return -1, -1 def __add_dir_entry(self, parent_inode, file_name, inode_number): # this will be the directory block to contain the new name->inum mapping inode_index, dirblock_index = self.__find_matching_dir_slot('-', parent_inode) if inode_index != -1: # there is room in existing block: make copy, update it, and log it index_to_update = inode_index parent_size = parent_inode['size'] new_directory_block = copy.deepcopy(self.__read_dirblock(parent_inode, inode_index)) new_directory_block['entries'][dirblock_index] = (file_name, inode_number) else: # no room in existing directory block: allocate new one IF there is room in inode to point to it if parent_inode['size'] != NUM_INODE_PTRS: index_to_update = parent_inode['size'] parent_size = index_to_update + 1 new_directory_block = self.make_empty_dirblock() new_directory_block['entries'][0] = (file_name, inode_number) else: return -1, -1, {} return index_to_update, parent_size, new_directory_block # create (file OR dir) def __file_create(self, path, is_file): inode_number, file_name, parent_inode_number, parent_inode = self.__walk_path(path) if inode_number != -1: # self.error_log('create failed: file %s already exists' % path) self.error_log('create failed: file already exists') return -1 if parent_inode_number == -1: self.error_log('create failed: walkpath returned error [%s]' % path) return -1 # finally, allocate inode number for new file/dir new_inode_number = self.allocate_inode() if new_inode_number == -1: self.error_log('create failed: no more inodes available') return -1 # this will be the directory block to contain the new name->inum mapping index_to_update, parent_size, new_directory_block = self.__add_dir_entry(parent_inode, file_name, new_inode_number) if index_to_update == -1: self.error_log('error: directory is full (path %s)' % path) self.free_inode(new_inode_number); return -1 # log directory data block (either new version of old OR new one entirely) new_directory_block_address = self.log(new_directory_block) # now have to make new version of directory inode # update size (if needed), inc refs if this is a dir, point to new dir block addr new_parent_inode = copy.deepcopy(parent_inode) new_parent_inode['size'] = parent_size if not is_file: new_parent_inode['refs'] += 1 new_parent_inode['pointers'][index_to_update] = new_directory_block_address # if directory, must create empty dir block if not is_file: self.log(self.make_new_dirblock(parent_inode_number, new_inode_number)) new_dirblock_address = len(self.disk) - 1 # and the new inode itself if is_file: # create empty file by default new_inode = self.make_inode(itype=INODE_REGULAR, size=0, refs=1) else: # create directory inode and point it to the one dirblock it owns new_inode = self.make_inode(itype=INODE_DIRECTORY, size=1, refs=2) new_inode['pointers'][0] = new_dirblock_address # # ADD updated parent inode, file/dir inode TO LOG # new_parent_inode_address = self.log(new_parent_inode) new_inode_address = self.log(new_inode) # and new imap entries for both parent and new inode self.remap(parent_inode_number, new_parent_inode_address) self.remap(new_inode_number, new_inode_address) # finally, create new chunk of imap self.update_imap([parent_inode_number, new_inode_number]) # SYNC checkpoint region if not self.no_force_checkpoints: self.cr_sync() return 0 # file_create() def file_create(self, path): self.error_clear() return self.__file_create(path, True) # dir_create() def dir_create(self, path): self.error_clear() return self.__file_create(path, False) # link() def file_link(self, srcpath, dstpath): self.error_clear() src_inode_number, src_file_name, src_parent_inode_number, src_parent_inode = self.__walk_path(srcpath) if src_inode_number == -1: self.error_log('link failed, src [%s] not found' % srcpath) return -1 src_inode = self.get_inode_from_inumber(src_inode_number) if src_inode['type'] != INODE_REGULAR: self.error_log('link failed: cannot link to non-regular file [%s]' % srcpath) return -1 dst_inode_number, dst_file_name, dst_parent_inode_number, dst_parent_inode = self.__walk_path(dstpath) if dst_inode_number != -1: self.error_log('link failed, dst [%s] exists' % dstpath) return -1 # this will be the directory block to contain the new name->inum mapping dst_index_to_update, dst_parent_size, new_directory_block = self.__add_dir_entry(dst_parent_inode, dst_file_name, src_inode_number) if dst_index_to_update == -1: self.error_log('error: directory is full [path %s]' % dstpath) return -1 # log directory data block (either new version of old OR new one entirely) new_directory_block_address = self.log(new_directory_block) # now have to make new version of directory inode # update size (if needed), inc refs if this is a dir, point to new dir block addr new_dst_parent_inode = copy.deepcopy(dst_parent_inode) new_dst_parent_inode['size'] = dst_parent_size new_dst_parent_inode['pointers'][dst_index_to_update] = new_directory_block_address # ADD updated parent inode TO LOG new_dst_parent_inode_address = self.log(new_dst_parent_inode) # inode must change too: to reflect NEW refs count new_src_inode = copy.deepcopy(src_inode) new_src_inode['refs'] += 1 new_src_inode_address = self.log(new_src_inode) # and new imap entries for both parent and new inode self.remap(dst_parent_inode_number, new_dst_parent_inode_address) self.remap(src_inode_number, new_src_inode_address) # finally, create new chunk of imap self.update_imap([dst_parent_inode_number]) # SYNC checkpoint region if not self.no_force_checkpoints: self.cr_sync() return 0 def file_write(self, path, offset, num_blks): self.error_clear() # just make up contents of data blocks - up to the max spec'd by write # note: may not write all of these, because of running out of room in inode... contents = self.make_random_blocks(num_blks) inode_number, file_name, parent_inode_number, parent_inode = self.__walk_path(path) if inode_number == -1: self.error_log('write failed: file not found [path %s]' % path) return -1 inode = self.get_inode_from_inumber(inode_number) if inode['type'] != INODE_REGULAR: self.error_log('write failed: cannot write to non-regular file %s' % path) return -1 if offset < 0 or offset >= NUM_INODE_PTRS: self.error_log('write failed: bad offset %d' % offset) return -1 # create potential write list -- up to max file size current_log_ptr = len(self.disk) current_offset = offset potential_writes = [] while current_offset < NUM_INODE_PTRS and current_offset < offset + len(contents): potential_writes.append((current_offset, current_log_ptr)) current_offset += 1 current_log_ptr += 1 # write data block(s) for i in range(len(potential_writes)): self.log(self.make_data_block(contents[i])) # write new version of inode, with updated size new_inode = copy.deepcopy(inode) new_inode['size'] = max(current_offset, inode['size']) for new_offset, new_addr in potential_writes: new_inode['pointers'][new_offset] = new_addr new_inode_address = self.log(new_inode) # write new chunk of imap self.remap(inode_number, new_inode_address) self.log(self.make_imap_chunk(self.inum_to_chunk(inode_number))) self.cr[self.inum_to_chunk(inode_number)] = len(self.disk) - 1 # write checkpoint region if not self.no_force_checkpoints: self.cr_sync() # return size of write (total # written, not desired, may be less than asked for) return current_offset - offset def file_delete(self, path): self.error_clear() inode_number, file_name, parent_inode_number, parent_inode = self.__walk_path(path) if inode_number == -1: self.error_log('delete failed: file not found [%s]' % path) return -1 inode = self.get_inode_from_inumber(inode_number) if inode['type'] != INODE_REGULAR: self.error_log('delete failed: cannot delete non-regular file [%s]' % path) return -1 # have to check: is the file actually down to its last ref? if inode['refs'] == 1: self.free_inode(inode_number) # now, find entry in DIRECTORY DATA BLOCK and zero it inode_index, dirblock_index = self.__find_matching_dir_slot(file_name, parent_inode) assert(inode_index != -1) new_directory_block = copy.deepcopy(self.__read_dirblock(parent_inode, inode_index)) new_directory_block['entries'][dirblock_index] = ('-', -1) # this leads to DIRECTORY DATA, DIR INODE, (and hence IMAP_CHUNK, CR_SYNC) writes dir_addr = self.log(new_directory_block) new_parent_inode = copy.deepcopy(parent_inode) new_parent_inode['pointers'][inode_index] = dir_addr new_parent_inode_addr = self.log(new_parent_inode) self.remap(parent_inode_number, new_parent_inode_addr) # if this ISNT the last link, decrease ref count and output new version if inode['refs'] > 1: new_inode = copy.deepcopy(inode) new_inode['refs'] -= 1 new_inode_addr = self.log(new_inode) self.remap(inode_number, new_inode_addr) # create new chunk of imap self.update_imap([inode_number, parent_inode_number]) # and sync if need be if not self.no_force_checkpoints: self.cr_sync() return 0 def sync(self): self.error_clear() return self.cr_sync() # # HELPERs for main # def pick_random(a_list): if len(a_list) == 0: return '' index = int(random.random() * len(a_list)) return a_list[index] def make_random_file_name(parent_dir): L1 = chr(ord('a') + int(random.random() * 26)) L2 = chr(ord('a') + int(random.random() * 26)) N1 = str(int(random.random() * 10)) if parent_dir == '/': return '/' + L1 + L2 + N1 return parent_dir + '/' + L1 + L2 + N1 # # must be in format: cXX,wXX,etc # where first letter is command and XX is percent (from 0-100) # def process_percentages(percentages): tmp = percentages.split(',') csum = 0 for p in tmp: cmd = p[0] value = int(p[1:]) if value < 0: print('percentages must be positive or zero') exit(1) csum += int(value) if csum != 100: print('percentages do not add to 100') exit(1) p_array = {} cmd_list = ['c', 'w', 'd', 'r', 'l', 's'] for c in cmd_list: p_array[c] = (0, 0) csum = 0 for p in tmp: cmd = p[0] if cmd not in cmd_list: print('bad command', cmd) exit(1) value = int(p[1:]) p_array[cmd] = (csum, csum + value) csum += value for i in p_array: p_array[i] = (p_array[i][0] / 100.0, p_array[i][1] / 100.0) return p_array def make_command_list(num_commands, percent): command_list = '' existing_files = [] existing_dirs = ['/'] while num_commands > 0: chances = random.random() command = '' if chances >= percents['c'][0] and chances < percents['c'][1]: pdir = pick_random(existing_dirs) if pdir == '': continue nfile = make_random_file_name(pdir) command = 'c,%s' % nfile existing_files.append(nfile) elif chances >= percents['w'][0] and chances < percents['w'][1]: pfile = pick_random(existing_files) if pfile == '': continue woff = int(random.random() * 8) wlen = int(random.random() * 8) command = 'w,%s,%d,%d' % (pfile, woff, wlen) elif chances >= percents['d'][0] and chances < percents['d'][1]: pdir = pick_random(existing_dirs) if pdir == '': continue ndir = make_random_file_name(pdir) command = 'd,%s' % ndir existing_dirs.append(ndir) elif chances >= percents['r'][0] and chances < percents['r'][1]: if len(existing_files) == 0: continue index = int(random.random() * len(existing_files)) command = 'r,%s' % existing_files[index] del existing_files[index] elif chances >= percents['l'][0] and chances < percents['l'][1]: if len(existing_files) == 0: continue index = int(random.random() * len(existing_files)) pdir = pick_random(existing_dirs) if pdir == '': continue nfile = make_random_file_name(pdir) command = 'l,%s,%s' % (existing_files[index], nfile) existing_files.append(nfile) elif chances >= percents['s'][0] and chances < percents['s'][1]: command = 's' else: print('abort: internal error with percent operations') exit(1) if command_list == '': command_list = command else: command_list += ':' + command num_commands -= 1 return command_list # # MAIN program # parser = OptionParser() parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed') parser.add_option('-N', '--no_force', help='Do not force checkpoint writes after updates', default=False, action='store_true', dest='no_force_checkpoints') parser.add_option('-F', '--no_final', help='Do not show the final state of the file system', default=False, action='store_true', dest='no_final') parser.add_option('-D', '--use_disk_cr', help='use disk (maybe old) version of checkpoint region', default=False, action='store_true', dest='use_disk_cr') parser.add_option('-c', '--compute', help='compute answers for me', action='store_true', default=False, dest='solve') parser.add_option('-o', '--show_operations', help='print out operations as they occur', action='store_true', default=False, dest='show_operations') parser.add_option('-i', '--show_intermediate', help='print out state changes as they occur', action='store_true', default=False, dest='show_intermediate') parser.add_option('-e', '--show_return_codes', help='show error/return codes', action='store_true', default=False, dest='show_return_codes') parser.add_option('-v', '--show_live_paths', help='show live paths', action='store_true', default=False, dest='show_live_paths') parser.add_option('-n', '--num_commands', help='generate N random commands', action='store', default=3, dest='num_commands') parser.add_option('-p', '--percentages', help='percent chance of: createfile,writefile,createdir,rmfile,linkfile,sync (example is c30,w30,d10,r20,l10,s0)', action='store', default='c30,w30,d10,r20,l10,s0', dest='percentages') parser.add_option('-a', '--allocation_policy', help='inode allocation policy: "r" for "random" or "s" for "sequential"', action='store', default='s', dest='inode_policy') parser.add_option('-L', '--command_list', default = '', action='store', type='str', dest='command_list', help='command list in format: "cmd1,arg1,...,argN:cmd2,arg1,...,argN:... where cmds are: c:createfile, d:createdir, r:delete, w:write, l:link, s:sync format: c,filepath d,dirpath r,filepath w,filepath,offset,numblks l,srcpath,dstpath s') (options, args) = parser.parse_args() random.seed(options.seed) command_list = options.command_list num_commands = int(options.num_commands) percents = process_percentages(options.percentages) if options.inode_policy == 's': inode_policy = ALLOCATE_SEQUENTIAL elif options.inode_policy == 'r': inode_policy = ALLOCATE_RANDOM else: print('bad policy', options.inode_policy) exit(1) # where most of the work is done L = LFS(use_disk_cr=options.use_disk_cr, no_force_checkpoints=options.no_force_checkpoints, inode_policy=inode_policy, solve=options.solve) # what to show print_operation = options.show_operations print_intermediate = options.show_intermediate # generate some random commands if command_list == '': if num_commands < 0: print('num_commands must be greater than zero', num_commands) exit(1) command_list = make_command_list(num_commands, percents) print('') print('INITIAL file system contents:') L.dump(True) L.dump_last = 4 # ugly ... but needed to make intermediate dumps correct print('') # # this variant allows control over each command # files_that_exist = [] dirs_that_exist = [] if command_list != '': commands = command_list.split(':') for i in range(len(commands)): command_and_args = commands[i].split(',') if command_and_args[0] == 'c': assert(len(command_and_args) == 2) if print_operation: print('create file', command_and_args[1], end=' ') rc = L.file_create(command_and_args[1]) if rc == 0: files_that_exist.append(command_and_args[1]) elif command_and_args[0] == 'd': assert(len(command_and_args) == 2) if print_operation: print('create dir ', command_and_args[1], end=' ') rc = L.dir_create(command_and_args[1]) if rc == 0: dirs_that_exist.append(command_and_args[1]) elif command_and_args[0] == 'r': assert(len(command_and_args) == 2) if print_operation: print('delete file', command_and_args[1], end=' ') rc = L.file_delete(command_and_args[1]) if rc == 0: if command_and_args[1] in files_that_exist: files_that_exist.remove(command_and_args[1]) else: print('warning: cannot find file', command_and_args[1]) elif command_and_args[0] == 'l': assert(len(command_and_args) == 3) if print_operation: print('link file ', command_and_args[1], command_and_args[2], end=' ') rc = L.file_link(command_and_args[1], command_and_args[2]) if rc == 0: files_that_exist.append(command_and_args[2]) elif command_and_args[0] == 'w': assert(len(command_and_args) == 4) if print_operation: print('write file %s offset=%d size=%d' % (command_and_args[1], int(command_and_args[2]), int(command_and_args[3])), end=' ') rc = L.file_write(command_and_args[1], int(command_and_args[2]), int(command_and_args[3])) elif command_and_args[0] == 's': if print_operation: print('sync', end=' ') rc = L.sync() else: print('command not understood so skipping [%s]' % command_and_args[0]) if not print_operation: print('command?', end=' ') if print_intermediate: print('') print('') if command_and_args[0] == 's': L.dump_partial(False, True) else: L.dump_partial(False, False) print('') if options.show_return_codes: print('->', rc) L.error_dump() else: print('') #if not print_intermediate: # print('\nChanges to log, checkpoint region?') # print('') if not options.no_final: print('') print('FINAL file system contents:') L.dump(False) print('') if options.show_live_paths: print('Live directories: ', dirs_that_exist) print('Live files: ', files_that_exist) print('') else: print('')