Simulator

This lesson explains how to interact with the simulator for the exercise presented in the next lesson.

We'll cover the following

This is the description of ffs.py, a simulator of FFS allocation policies. Use it to study FFS behavior under different file and directory creation scenarios.

#! /usr/bin/env python

import math
import sys
from optparse import OptionParser
import random

class file_system:
    def __init__(self, num_groups, blocks_per_group, inodes_per_group,
                 large_file_exception, spread_inodes,
                 contig_allocation_policy, spread_data_blocks, allocate_faraway,
                 show_block_addresses, do_per_file_stats, show_file_ops,
                 show_symbol_map, compute):
        self.num_groups = num_groups
        self.blocks_per_group = blocks_per_group
        self.inodes_per_group = inodes_per_group
        self.large_file_exception = large_file_exception
        self.spread_inodes = spread_inodes
        self.contig_allocation_policy = contig_allocation_policy
        self.spread_data_blocks = spread_data_blocks
        self.allocate_faraway = allocate_faraway
        self.show_block_addresses = show_block_addresses
        self.do_per_file_stats = do_per_file_stats
        self.show_file_ops = show_file_ops
        self.show_symbol_map = show_symbol_map
        self.compute = compute

        self.group_size = self.inodes_per_group + self.blocks_per_group

        self.BITMAP_FREE = '__FREE__'

        self.data_bitmap = []
        self.inode_bitmap = []
        for i in range(self.num_groups):
            self.inode_bitmap.append([])
            self.data_bitmap.append([])
            for j in range(self.blocks_per_group):
                self.data_bitmap[i].append(self.BITMAP_FREE)
            for j in range(self.inodes_per_group):
                self.inode_bitmap[i].append(self.BITMAP_FREE)

        self.init_symbols()

        self.total_data_free   = blocks_per_group * num_groups
        self.total_inodes_free = inodes_per_group * num_groups

        # make root directory
        self.inode_bitmap[0][0] = 0
        self.data_bitmap[0][0] = 0 # use one data block for ALL DIRS
        self.allocate_symbol(0, '/')

        self.total_data_free -= 1
        self.total_inodes_free -= 1

        # data for each directory, indexed by inode number
        self.dir_data = {}
        self.dir_data[0] = [('.', 0), ('..', 0)]

        # inode data: for each inode, type info
        self.inode_type = {}
        self.inode_type[0] = 'directory'

        # and which blocks comprise the file
        self.inode_blocks = {}
        self.inode_blocks[0] = [0]

        # map names to inode numbers
        self.name_to_inode_map = {}
        self.name_to_inode_map['/'] = 0
        return

    def vprint(self, msg):
        if self.show_file_ops:
            print msg,
        return

    def get_parent(self, path):
        index_of_last_slash = path.rfind('/')
        if index_of_last_slash == 0:
            return '/', path[index_of_last_slash+1:len(path)]
        return path[0:index_of_last_slash], path[index_of_last_slash+1:len(path)]

    def name_to_inode(self, path):
        if path not in self.name_to_inode_map:
            return -1
        else:
            return self.name_to_inode_map[path]

    def set_name_to_inode(self, path, inode_num):
        if path in self.name_to_inode_map:
            print 'abort: path already in mapping (internal error)'
            exit(1)
        self.name_to_inode_map[path] = inode_num
        return

    def get_free_count(self, group, bitmap):
        cnt = 0
        for b in bitmap:
            if b == self.BITMAP_FREE:
                cnt += 1
        return cnt

    def get_free_inode_count(self, group):
        return self.get_free_count(group, self.inode_bitmap[group])

    def get_free_data_count(self, group):
        return self.get_free_count(group, self.data_bitmap[group])

    def find_most_free_inodes(self, starting_point):
        free_inodes_max = 0
        target_group = -1
        for g in range(starting_point, self.num_groups):
            free_inodes_in_group = self.get_free_inode_count(g)
            if free_inodes_in_group > free_inodes_max:
                free_inodes_max = free_inodes_in_group
                target_group = g
        for g in range(0, starting_point):
            free_inodes_in_group = self.get_free_inode_count(g)
            if free_inodes_in_group > free_inodes_max:
                free_inodes_max = free_inodes_in_group
                target_group = g
        return target_group

    def find_free_inodes_in_range(self, group, how_many):
        sum_free = 0
        for g in range(group, group + how_many):
            g_curr = g % self.num_groups
            free_inodes_in_group = self.get_free_inode_count(g_curr)
            sum_free += free_inodes_in_group
        return sum_free

    def find_most_free_inodes_multiple(self, starting_point, how_many):
        free_inodes_max = 0
        target_group = -1
        for g in range(starting_point, self.num_groups, how_many):
            sum_free = self.find_free_inodes_in_range(g, how_many)
            if sum_free > free_inodes_max:
                free_inodes_max = sum_free
                target_group = g
        for g in range(0, starting_point, how_many):
            sum_free = self.find_free_inodes_in_range(g, how_many)
            if sum_free > free_inodes_max:
                free_inodes_max = sum_free
                target_group = g
        assert(target_group != -1)
        return target_group

    def find_free_inodes_near(self, target_group):
        grower = (target_group + 1) % self.num_groups
        shrinker = (target_group - 1) % self.num_groups
        for i in range(self.num_groups - 1):
            if i % 2 == 0:
                current_group = grower
                grower = (grower + 1) % self.num_groups
            else:
                current_group = shrinker
                shrinker = (shrinker - 1) % self.num_groups
            if self.get_free_inode_count(current_group) > 0:
                return current_group
        return 1

    def pick_group(self, parent, filename, type):
        if type == 'regular' and self.spread_inodes == False: 
            # FFS policy: pick based on parent
            parent_inode_number = self.name_to_inode(parent)
            target_group = parent_inode_number / self.inodes_per_group
            # ensure group has free inode... (and free data blocks?)
            num_free_inodes = self.get_free_inode_count(target_group)
            if num_free_inodes == 0:
                target_group = self.find_free_inodes_near(target_group)
            return target_group
        elif type == 'directory' or self.spread_inodes == True: 
            # find group with most free inodes
            return self.find_most_free_inodes_multiple(0, self.allocate_faraway)
        else:
            print 'abort: bad file type [%s] (internal error)' % type
            exit(1)
        return 0

    def range_free(self, group, index, needed, chunks_free):
        if needed < chunks_free:
            chunks_free = needed

        # make list of blocks to check for freedom
        index_begin = index
        index_end = index + chunks_free - 1
        if index_end >= self.blocks_per_group:
            return False

        for i in range(index_begin, index_end+1):
            if self.data_bitmap[group][i] != self.BITMAP_FREE:
                return False
        return True
        
        if self.data_bitmap[group][index] == self.BITMAP_FREE:
            return True
        return False
    
    # group is just the group where the inode is
    # size is how many are needed total
    def allocate_blocks(self, target_group, size, inode_number):
        assert(size <= self.total_data_free)
        allocated = []
        index = 0
        allocated_in_group = 0
        current_group = target_group
        chunks_free = self.contig_allocation_policy
        while True:
            if self.range_free(current_group, index, size-len(allocated), chunks_free):
                # print '  local alloc', current_group, index
                assert(self.data_bitmap[current_group][index] == self.BITMAP_FREE)
                self.data_bitmap[current_group][index] = inode_number
                allocated_in_group += 1
                allocated.append((current_group, index))
            index += 1

            if len(allocated) == size:
                # print 'done', allocated
                break

            # this moves allocation interest to next group when needed
            # i.e., when you've searched this entire group or
            #       when you've exhausted the large file exception
            if index == self.blocks_per_group or \
               (self.large_file_exception > 0 and \
                allocated_in_group == self.large_file_exception):
                allocated_in_group = 0
                index = 0
                current_group = (current_group + 1) % self.num_groups
                if current_group == target_group:
                    chunks_free = 1
        
        return allocated

    def find_free_inode(self, group):
        inode_number = -1
        for i in range(self.inodes_per_group):
            if self.inode_bitmap[group][i] == self.BITMAP_FREE:
                inode_number = i
                break
        # don't think this can ever happen but ...
        if inode_number == -1:
            self.vprint('[cannot find free inode]')
            return -1
        return inode_number

    def find_min_data_usage(self):
        min_group = -1
        min_usage = 0
        for g in range(self.num_groups):
            data_free = self.get_free_data_count(g)
            if data_free > min_usage:
                min_usage = data_free
                min_group = g
        return min_group

    def mark_inode_used(self, group, inode_index):
        self.inode_bitmap[group][inode_index] = inode_index + \
                                                (group * self.inodes_per_group)
        return

    def do_delete(self, path):
        parent, filename = self.get_parent(path)

        # now, find the file in parent directory
        parent_inode_number = self.name_to_inode(parent)
        if parent_inode_number == -1:
            self.vprint('[cannot find parent inode %s]' % parent)
            return -1

        del_index = -1
        for i in range(len(self.dir_data[parent_inode_number])):
            name, inode_number = self.dir_data[parent_inode_number][i]
            if name == filename:
                del_index = i
                break

        if del_index == -1:
            self.vprint('[cannot find %s in dir %s]' % (filename, parent))
            return -1

        if self.inode_type[inode_number] == 'directory':
            self.vprint('[cannot delete directories]')
            return -1

        self.dir_data[parent_inode_number].remove((name, inode_number))

        for b in self.inode_blocks[inode_number]:
            data_group = b / self.num_groups
            data_index = b % self.num_groups
            self.data_bitmap[data_group][data_index] = self.BITMAP_FREE

        inode_group = inode_number / self.num_groups
        inode_index = inode_number % self.num_groups
        self.inode_bitmap[inode_group][inode_index] = self.BITMAP_FREE
        self.free_symbol(inode_number)

        del self.name_to_inode_map[path] 

        self.total_inodes_free += 1
        self.total_data_free += len(self.inode_blocks[inode_number])

        self.inode_type[inode_number] = ''
        self.inode_blocks[inode_number] = []
        
        return 0

    def do_create(self, path, size, type):
        parent, filename = self.get_parent(path)

        # do global checks here
        if self.total_inodes_free == 0:
            self.vprint('[out of inodes]')
            return -1
        if self.total_data_free < size:
            self.vprint('[out of disk space]')
            return -1
        
        # check if foo already exists
        parent_inode_number = self.name_to_inode(parent)
        if parent_inode_number == -1:
            self.vprint('[failed to find parent %s]' % parent)
            return -1
        for (name, __inode_number) in self.dir_data[parent_inode_number]:
            if name == filename:
                self.vprint('[file %s already exists in dir %s]' % (filename, parent))
                return -1

        # allocate new inode: which group?
        group = self.pick_group(parent, filename, type)

        # pick inode now: it is guaranteed here that group will have a free inode
        # thanks to pick_group()...
        inode_index = self.find_free_inode(group)

        # calc global inode number 
        inode_number = inode_index + (group * self.inodes_per_group)

        # file allocation policy: could fail
        # allocated = self.allocate_blocks(group, size, inode_number)
        if self.spread_data_blocks:
            dest_block_group = self.find_min_data_usage()
            print 'target alloc', dest_block_group
            allocated = self.allocate_blocks(dest_block_group, size, inode_number)
        else:
            allocated = self.allocate_blocks(group, size, inode_number)
        if len(allocated) == 0:
            # allocation failed: no room in file system
            return -1

        # now do all the allocation work: won't fail from here on down
        self.mark_inode_used(group, inode_index)
        self.dir_data[parent_inode_number].append((filename, inode_number))

        # now, allocate some data blocks
        self.inode_type[inode_number] = type
        self.inode_blocks[inode_number] = []

        for (selected_group, index) in allocated:
            global_block_number = index + (selected_group * self.blocks_per_group)
            # print 'global allocated', global_block_number
            self.inode_blocks[inode_number].append(global_block_number)

        # record name to inode number mapping for future lookups
        self.set_name_to_inode(path, inode_number)
        self.allocate_symbol(inode_number, filename)

        # have to fill in contents of empty directory
        if type == 'directory':
            self.dir_data[inode_number] = [('.', 0), ('..', 0)]

        # global accounting
        self.total_inodes_free -= 1
        self.total_data_free -= size
        
        return 0

    def init_symbols(self):
        self.symbol_map = {}
        self.used_symbols = []
        self.available_symbols = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9','!','@','#','%','^','&','*','(',')','[',']','{','}','/','.','<','>','|']
        return

    def allocate_symbol(self, inode_number, suggested_name):
        if suggested_name not in self.used_symbols \
               and suggested_name in self.available_symbols:
            choice = suggested_name
        else:
            assert(len(self.available_symbols) > 0)
            choice = self.available_symbols[0]
        # print 'sym', suggested_name, '->', choice
        self.used_symbols.append(choice)
        self.available_symbols.remove(choice)
        self.symbol_map[inode_number] = choice
        return

    def free_symbol(self, inode_number):
        symbol = self.symbol_map[inode_number]
        del self.symbol_map[inode_number]
        self.used_symbols.remove(symbol)
        self.available_symbols.append(symbol)
        return

    def get_symbol(self, inode_number):
        return self.symbol_map[inode_number]

    def print_success_or_fail(self, rc):
        if self.show_file_ops:
            if rc == 0:
                print 'success'
            else:
                print 'failed'
        return

    def do_verify(self):
        data_used = 0
        inodes_used = 0
        for g in range(self.num_groups):
            for i in range(self.blocks_per_group):
                if self.data_bitmap[g][i] != self.BITMAP_FREE:
                    data_used += 1
            for i in range(self.inodes_per_group):
                if self.inode_bitmap[g][i] != self.BITMAP_FREE:
                    inodes_used += 1
        assert(data_used + self.total_data_free == (self.num_groups * self.blocks_per_group))
        assert(inodes_used + self.total_inodes_free == (self.num_groups * self.inodes_per_group))

        for f in self.name_to_inode_map:
            inode_number = self.name_to_inode_map[f]
            blocks = self.inode_blocks[inode_number]
            for b in blocks:
                data_group = b / self.blocks_per_group
                data_index = b % self.blocks_per_group
                assert(self.data_bitmap[data_group][data_index] == inode_number)
        return

    def create(self, path, size):
        self.vprint('op create %s [size:%d] ->' % (path, size))
        rc = self.do_create(path, size, 'regular')
        self.print_success_or_fail(rc)
        return rc

    def mkdir(self, path):
        self.vprint('op mkdir %s ->' % path)
        rc = self.do_create(path, 1, 'directory')
        self.print_success_or_fail(rc)
        return rc

    def delete(self, path):
        self.vprint('op delete %s ->' % path)
        rc = self.do_delete(path)
        self.print_success_or_fail(rc)
        return


    def read_input(self, filename):
        fd = open(filename)
        for line in fd:
            in_line = line.strip('\n')
            line_len = len(in_line)
            if line_len == 0:
                continue
            tmp = in_line.split()
            if len(tmp) == 0:
                continue
            if tmp[0] == 'file':
                assert(len(tmp) == 3)
                name, size = tmp[1], int(tmp[2])
                self.create(name, size)
            elif tmp[0] == 'dir':
                assert(len(tmp) == 2)
                name = tmp[1]
                self.mkdir(name)
            elif tmp[0] == 'delete':
                assert(len(tmp) == 2)
                name = tmp[1]
                self.delete(name)
            elif tmp[0] == 'dump':
                assert(len(tmp) == 1)
                self.dump()
            else:
                print 'command not recognized', tmp[0]
                exit(1)
            self.do_verify()
        fd.close()
        return

    def list_to_string(self, bitmap):
        out_str = ''
        for i in range(len(bitmap)):
            if i > 0 and i % 10 == 0:
                out_str += ' '
            if bitmap[i] == self.BITMAP_FREE:
                out_str += '-'
            else:
                out_str += '%s' % self.get_symbol(bitmap[i])
        return out_str

    def do_numeric_header(self, power_level, how_many):
        power = int(math.pow(10, power_level))
        counter = 0
        value = 0
        out_str = ''
        for i in range(how_many):
            if i > 0 and i % 10 == 0:
                out_str += ' '
            out_str += '%d' % (value % 10)
            counter += 1
            if counter == power:
                value += 1
                counter = 0
        print out_str,
        return

    def dump(self):
        print ''
        print 'num_groups:      ', self.num_groups
        print 'inodes_per_group:', self.inodes_per_group
        print 'blocks_per_group:', self.blocks_per_group
        print ''
        print 'free data blocks: %d (of %d)' % (self.total_data_free, 
                                                (self.num_groups * self.blocks_per_group))
        print 'free inodes:      %d (of %d)' % (self.total_inodes_free,
                                                (self.num_groups * self.inodes_per_group))
        print ''
        print 'spread inodes?   ', self.spread_inodes
        print 'spread data?     ', self.spread_data_blocks
        print 'contig alloc:    ', self.contig_allocation_policy
        print ''

        inode_power = len('%s' % self.inodes_per_group) - 1
        data_power = len('%s' % self.blocks_per_group) - 1

        if inode_power > data_power:
            max_power = inode_power
        else:
            max_power = data_power
        
        while max_power >= 0:
            print '     ',  # spacing before inode print out
            if inode_power >= max_power:
                self.do_numeric_header(max_power, self.inodes_per_group)
            else:
                out_str = ' ' * self.inodes_per_group
                print out_str,

            if data_power >= max_power:
                self.do_numeric_header(max_power, self.blocks_per_group)
            else:
                out_str = ' ' * self.inodes_per_group
                print out_str,

            print ''
            max_power -= 1

        print '\ngroup %s' % ('inodes'[0:self.inodes_per_group]),
        out_str = ''
        for i in range(self.inodes_per_group - len('inodes')):
            out_str += ' '
        print '%sdata' % out_str

        count = 0

        for i in range(self.num_groups):
            # print '  %3d inodes %s' % (i, self.list_to_string(self.inode_bitmap[i]))
            # print '        data %s' % (self.list_to_string(self.data_bitmap[i]))
            if self.compute:
                print '  %3d %s %s' % (i,
                                       self.list_to_string(self.inode_bitmap[i]),
                                       self.list_to_string(self.data_bitmap[i])),
                if self.show_block_addresses:
                    print '  [%4d-%4d]' % (count, count + self.group_size - 1)
                else:
                    print ''
            else:
                print '  %3d %s %s' % (i,
                                       '?' * self.inodes_per_group,
                                       '?' * self.blocks_per_group)
                
            count += self.group_size

        if self.show_symbol_map == False:
            print ''
            return
        
        print '\nsymbol  inode#  filename     filetype ',
        if self.do_per_file_stats:
            print 'block_addresses'
        else:
            print ''
        # sorted(student_tuples, key=lambda student: student[2])
        
        for name in sorted(self.name_to_inode_map):
            inode_number = self.name_to_inode_map[name]
            file_type = self.inode_type[inode_number]
            print '%-6s  %6d  %-11s  %9s' % \
                  (self.get_symbol(inode_number), inode_number, name, file_type),
            if self.do_per_file_stats:
                for i in self.inode_blocks[inode_number]:
                    print i,
                print ''
            else:
                print ''
        print ''
        return

    def get_dist(self, a, b):
        if a > b:
            return a - b
        else:
            return b - a

    def get_spans(self, path):
        inode_number = self.name_to_inode(path)

        inode_group = inode_number / self.inodes_per_group
        inode_index = inode_number % self.inodes_per_group
        inode_address = inode_index + inode_group * self.group_size

        # now find all data blocks
        min_address = 1 + (self.num_groups * self.group_size)
        max_address = -1

        data_blocks = self.inode_blocks[inode_number]

        for d in data_blocks:
            data_group = d / self.blocks_per_group
            data_index = d % self.blocks_per_group
            data_address = data_index + (data_group * self.group_size) + self.inodes_per_group

            if data_address > max_address: max_address = data_address
            if data_address < min_address: min_address = data_address

        return inode_number, inode_address, min_address, max_address

    def do_all_spans(self):
        current = 0
        total_dist = 0
        
        min_group = 1e6
        max_group = -1
    
        print 'span: files'
        span_results = {}
        filespan_sum = 0
        filespan_cnt = 0
        for f in self.name_to_inode_map:
            inode_number, inode_address, min_address, max_address = self.get_spans(f)
            if self.inode_type[inode_number] == 'directory':
                continue
        
            data_span = max_address - min_address
            abs_min, abs_max = min_address, max_address
            if inode_address < abs_min:
                abs_min = inode_address
            if inode_address > abs_max:
                abs_max = inode_address
            file_span = abs_max - abs_min
                
            assert(inode_number not in span_results)
            span_results[inode_number] = (inode_address, min_address, max_address)

            if options.solve:
                print '  file: %10s  filespan: %3d' % (f, file_span)
            else:
                print '  file: %10s  filespan: %s' % (f, '?')

            filespan_sum += file_span
            filespan_cnt += 1

        if filespan_cnt > 0:
            filespan_avg = '%3.2f' % (float(filespan_sum)/float(filespan_cnt))
            if options.solve:
                print '               avg  filespan: %6s' % (filespan_avg)
            else:
                print '               avg  filespan: ?'
            

        print '\nspan: directories'
        dirspan_sum = 0
        dirspan_cnt = 0
        for f in self.name_to_inode_map:
            all_addresses = []
            inode_number, inode_address, min_address, max_address = self.get_spans(f)
            if self.inode_type[inode_number] != 'directory':
                continue
            for address in [inode_address, min_address, max_address]:
                all_addresses.append(address)

            for entry_name, entry_inode_number in self.dir_data[inode_number]:
                if entry_name == '.' or entry_name == '..':
                    continue
                if self.inode_type[entry_inode_number] == 'directory':
                    continue
                for i in range(3):
                    all_addresses.append(span_results[entry_inode_number][i])
            all_sorted = sorted(all_addresses)
            # print 'dirspan', f, all_sorted[len(all_sorted)-1], all_sorted[0]
            dirspan = all_sorted[len(all_sorted)-1] - all_sorted[0]
            dirspan_sum += dirspan
            dirspan_cnt += 1
            if options.solve:
                print '  dir:  %10s  dirspan: %3d' % (f, dirspan)
            else:
                print '  dir:  %10s  dirspan: ?' % (f)

        dirspan_avg = '%3.2f' % (float(dirspan_sum)/float(dirspan_cnt))
        if options.solve:
            print '               avg  dirspan: %6s' % (dirspan_avg)
        else:
            print '               avg  dirspan: ?'


        print ''
        return

        
    

#
# 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', '--num_groups', default=10, help='number of block groups',
                  action='store', type='int', dest='num_groups')
parser.add_option('-d', '--datablocks_per_groups', default=30,
                  help='data blocks per group', action='store',
                  type='int', dest='blocks_per_group')
parser.add_option('-i', '--inodes_per_group', default=10, help='inodes per group',
                  action='store', type='int', dest='inodes_per_group')
parser.add_option('-L', '--large_file_exception', default=30,
                  help='0:off, N>0:blocks in group before spreading file to next group',
                  action='store', type='int', dest='large_file_exception')
parser.add_option('-f', '--input_file', default='/no/such/file', help='command file',
                  action='store', type='string', dest='input_file')
parser.add_option('-I', '--spread_inodes', default=False,
                  help='Instead of putting file inodes in parent dir group, \
                  spread them evenly around all groups',
                  action='store_true', dest='spread_inodes')
parser.add_option('-D', '--spread_data', default=False,
                  help='Instead of putting data near inode, \
                  spread them evenly around all groups',
                  action='store_true', dest='spread_data_blocks')
parser.add_option('-A', '--allocate_faraway', default=1,
                  help='When picking a group, examine this many groups at a time',
                  action='store', dest='allocate_faraway', type='int')
parser.add_option('-C', '--contig_allocation_policy', default=1,
                  help='number of contig free blocks needed to alloc',
                  action='store', type='int', dest='contig_allocation_policy')
parser.add_option('-T', '--show_spans', help='show file and directory spans',
                  default=False, action='store_true', dest='show_spans')
parser.add_option('-M', '--show_symbol_map', help='show symbol map',
                  default=False, action='store_true', dest='show_symbol_map')
parser.add_option('-B', '--show_block_addresses',
                  help='show block addresses alongside groups',
                  action='store_true', default=False, dest='show_block_addresses')
parser.add_option('-S', '--do_per_file_stats',
                  help='print out detailed inode stats',
                  action='store_true', default=False, dest='do_per_file_stats')
parser.add_option('-v', '--show_file_ops',
                  help='print out detailed per-op success/failure',
                  action='store_true', default=False, dest='show_file_ops')
parser.add_option('-c', '--compute', help='compute answers for me', action='store_true',
                  default=False, dest='solve')

(options, args) = parser.parse_args()

random.seed(options.seed)

fs = file_system(num_groups=options.num_groups,
                 blocks_per_group=options.blocks_per_group,
                 inodes_per_group=options.inodes_per_group,
                 large_file_exception=options.large_file_exception,
                 spread_inodes=options.spread_inodes,
                 contig_allocation_policy=options.contig_allocation_policy,
                 spread_data_blocks=options.spread_data_blocks,
                 allocate_faraway=options.allocate_faraway,
                 show_block_addresses=options.show_block_addresses,
                 do_per_file_stats=options.do_per_file_stats,
                 show_file_ops=options.show_file_ops,
                 show_symbol_map=options.show_symbol_map,
                 compute=options.solve)

fs.read_input(options.input_file)

fs.dump()

if options.show_spans:
    fs.do_all_spans()
Simulator

The tool is invoked by specifying a command file with the -f flag, which consists of a series of file create, file delete, and directory create operations.

For example, run:

prompt> ./ffs.py -f in.example1 -c

to see the output from the first example in the chapter on how FFS based allocation works.

The file in.example1 consists of the following commands:

dir /a
dir /b
file /a/c 2
file /a/d 2
file /a/e 2
file /b/f 2

This tells the simulator to create two directories (/a and /b) and four files (/a/c, /a/d, /a/e, and /b/f). The root directory is created by default.

The output of the simulator is the location of the inodes and data blocks of all extant files and directories. For example, from the run above, we would end up seeing (with the -c flag on, to show you the results):

prompt> ./ffs.py -f in.example1 -c

num_groups:       10
inodes_per_group: 10
blocks_per_group: 30

free data blocks: 289 (of 300)
free inodes:      93 (of 100)

spread inodes?    False
spread data?      False
contig alloc:     1

      0000000000 0000000000 1111111111 2222222222
      0123456789 0123456789 0123456789 0123456789

group inodes     data
    0 /--------- /--------- ---------- ----------
    1 acde------ accddee--- ---------- ----------
    2 bf-------- bff------- ---------- ----------
    3 ---------- ---------- ---------- ----------
    4 ---------- ---------- ---------- ----------
    5 ---------- ---------- ---------- ----------
    6 ---------- ---------- ---------- ----------
    7 ---------- ---------- ---------- ----------
    8 ---------- ---------- ---------- ----------
    9 ---------- ---------- ---------- ----------

prompt>

This first part of the output shows us various parameters of the simulation, from the number of FFS cylinder groups that are created to some policy details. But the main part of the output is the actual allocation map:

      0000000000 0000000000 1111111111 2222222222
      0123456789 0123456789 0123456789 0123456789

group inodes     data
    0 /--------- /--------- ---------- ----------
    1 acde------ accddee--- ---------- ----------
    2 bf-------- bff------- ---------- ----------
    3 ---------- ---------- ---------- ----------
    4 ---------- ---------- ---------- ----------
    5 ---------- ---------- ---------- ----------
    6 ---------- ---------- ---------- ----------
    7 ---------- ---------- ---------- ----------
    8 ---------- ---------- ---------- ----------
    9 ---------- ---------- ---------- ----------

For this instantiation, we have created a file system with 10 groups, each with 10 inodes and 30 data blocks. Each group just shows the inodes and data blocks, and how they are allocated. If they are free, a - is shown; otherwise, a different symbol is shown per file.

If you want to see a mapping of the symbols to file names, you should use the -M flag:

prompt> ./ffs.py -f in.example1 -c -M

You’ll then see a table at the bottom of the output shows the meanings of each symbol:

symbol  inode#  filename     filetype
/            0  /            directory
a           10  /a           directory
c           11  /a/c           regular
d           12  /a/d           regular
e           13  /a/e           regular
b           20  /b           directory
f           21  /b/f           regular

Here, you can see the root directory is represented by the symbol /, the file /a by the symbol a, and so forth.

Looking at the output, you can thus see a number of interesting things:

  • The root inode is in the first slot of the Group 0’s piece of the inode table
  • The root data block is found in the first allocated data block (Group 0)
  • Directory /a was placed in Group 1, directory /b in Group 2
  • The files (inodes and data) for each regular file are found in the same group as their parent inodes (as per FFS)

All options

The rest of the options let you play around with FFS and some minor variants. They are:

prompt> ./ffs.py -h
Usage: ffs.py [options]

Options:
  -h, --help            show this help message and exit
  -s SEED, --seed=SEED  the random seed
  -n NUM_GROUPS, --num_groups=NUM_GROUPS
                        number of block groups
  -d BLOCKS_PER_GROUP, --datablocks_per_groups=BLOCKS_PER_GROUP
                        data blocks per group
  -i INODES_PER_GROUP, --inodes_per_group=INODES_PER_GROUP
                        inodes per group
  -L LARGE_FILE_EXCEPTION, --large_file_exception=LARGE_FILE_EXCEPTION
                        0:off, N>0:blocks in group before spreading file to
                        next group
  -f INPUT_FILE, --input_file=INPUT_FILE
                        command file
  -I, --spread_inodes   Instead of putting file inodes in parent dir group,
                        spread them evenly around all groups
  -D, --spread_data     Instead of putting data near inode,
                        spread them evenly around all groups
  -A ALLOCATE_FARAWAY, --allocate_faraway=ALLOCATE_FARAWAY
                        When picking a group, examine this many groups at a
                        time
  -C CONTIG_ALLOCATION_POLICY,
  --contig_allocation_policy=CONTIG_ALLOCATION_POLICY
                        number of contig free blocks needed to alloc
  -T, --show_spans      show file and directory spans
  -M, --show_symbol_map
                        show symbol map
  -B, --show_block_addresses
                        show block addresses alongside groups
  -S, --do_per_file_stats
                        print out detailed inode stats
  -v, --show_file_ops   print out detailed per-op success/failure
  -c, --compute         compute answers for me

We’ll explore more of these options in the exercise.

Get hands-on with 1400+ tech skills courses.