From fbd2782b387bebbcd7678d12fa8d893223197610 Mon Sep 17 00:00:00 2001 From: desaster Date: Fri, 5 Apr 2013 12:13:00 +0000 Subject: [PATCH] Add interactive fs.pickle editing utility by: Donovan Hubbard Douglas Hubbard git-svn-id: https://kippo.googlecode.com/svn/trunk@240 951d7100-d841-11de-b865-b3884708a8e2 --- utils/fsctl.py | 522 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100755 utils/fsctl.py diff --git a/utils/fsctl.py b/utils/fsctl.py new file mode 100755 index 0000000..1d20d0d --- /dev/null +++ b/utils/fsctl.py @@ -0,0 +1,522 @@ +#!/usr/bin/python + +############################################################### +# This program creates a command line interpreter used to edit +# kippo file system pickle files. +# +# It is intended to mimic a basic bash shell and supports relative +# file references. +# +# This isn't meant to build a brand new filesystem. Instead it +# should be used to edit existing filesystems such as the default +# /opt/kippo/fs.pickle. +# +# Donovan Hubbard +# Douglas Hubbard +# March 2013 +# +############################################################### + +import os, pickle, sys, locale, time, cmd +from stat import * + +A_NAME, A_TYPE, A_UID, A_GID, A_SIZE, A_MODE, \ + A_CTIME, A_CONTENTS, A_TARGET, A_REALFILE = range(0, 10) +T_LINK, T_DIR, T_FILE, T_BLK, T_CHR, T_SOCK, T_FIFO = range(0, 7) + +def getpath(fs, path): + cwd = fs + for part in path.split('/'): + if not len(part): + continue + ok = False + for c in cwd[A_CONTENTS]: + if c[A_NAME] == part: + cwd = c + ok = True + break + if not ok: + raise Exception('File not found') + return cwd + +def exists(fs, path): + try: + getpath(fs, path) + return True + except Exception, e: + if str(e) == 'File not found': + return False + else: + raise Exception(e) + +def is_directory(fs,path): + "Returns whether or not the file at 'path' is a directory" + file = getpath(fs,path) + if file[A_TYPE] == T_DIR: + return True + else: + return False + +def resolve_reference(pwd, relativeReference): + '''Used to resolve a current working directory and a relative + reference into an absolute file reference.''' + + tempPath = os.path.join(pwd, relativeReference) + absoluteReference = os.path.normpath(tempPath) + + return absoluteReference + +class fseditCmd(cmd.Cmd): + + def __init__(self, pickle_file_path): + cmd.Cmd.__init__(self) + + if not os.path.isfile(pickle_file_path): + print "File %s does not exist." % pickle_file_path + sys.exit(1) + + try: + pickle_file = open(pickle_file_path, 'rb') + except IOError as e: + print "Unable to open file %s" % pickle_file_path + sys.exit(1) + + try: + self.fs = pickle.load(pickle_file) + except: + print ("Unable to load file '%s'. " + \ + "Are you sure it is a valid pickle file?") % \ + (pickle_file_path,) + sys.exit(1) + + self.pickle_file_path=pickle_file_path + + #get the name of the file so we can display it as the prompt + path_parts = pickle_file_path.split('/') + self.fs_name = path_parts[-1] + + self.update_pwd("/") + + self.intro = "\nKippo file system interactive editor\n" + \ + "Donovan Hubbard, Douglas Hubbard, March 2013\n" + \ + "Type 'help' for help\n" + + def save_pickle(self): + '''saves the current file system to the pickle''' + try: + pickle.dump(self.fs, file(self.pickle_file_path, 'wb')) + except: + print ("Unable to save pickle file '%s'. " + \ + "Are you sure you have write access?") % \ + (self.pickle_file_path,) + sys.exit(1) + + def do_exit(self, args): + '''Exits the file system editor''' + return True + + def do_EOF(self, args): + '''The escape character ctrl+d exits the session''' + #exiting from the do_EOF method does not create a newline automaticaly + #so we add it manually + print + return True + + def do_ls(self, args): + '''Prints the contents of a directory. + Prints the current directory if no arguments are specified''' + + if not len(args): + path = self.pwd + else: + path = resolve_reference(self.pwd,args) + + if exists(self.fs, path) == False: + print "ls: cannot access %s: No such file or directory" % (path,) + return + + if is_directory(self.fs, path) == False: + print "ls: %s is not a directory" % (path,) + return + + cwd = getpath(self.fs, path) + + for file in cwd[A_CONTENTS]: + if file[A_TYPE] == T_DIR: + print file[A_NAME] + '/' + else: + print file[A_NAME] + + def update_pwd(self, directory): + self.pwd = directory + self.prompt = self.fs_name + ":" + self.pwd + "$ " + + def do_cd(self, args): + '''Changes the current directory.\nUsage: cd ''' + + #count the number of arguments + # 1 or more arguments: changes the directory to the first arg + # and ignores the rest + # 0 arguments: changes to '/' + arguments = args.split() + + if not len(arguments): + self.update_pwd("/") + else: + relative_dir = arguments[0] + target_dir = resolve_reference(self.pwd, relative_dir) + + if exists(self.fs, target_dir) == False: + print "cd: %s: No such file or directory" % target_dir + elif is_directory(self.fs, target_dir): + self.update_pwd(target_dir) + else: + print "cd: %s: Not a directory" % target_dir + + def do_pwd(self, args): + '''Prints the current working directory''' + print self.pwd + + def do_mkdir(self, args): + "Add a new directory in the target directory. + Handles relative or absolute file paths. \n + Usage: mkdir " + + arg_list=args.split() + if len(arg_list) != 1: + print "usage: mkdir " + else: + self.mkfile(arg_list, T_DIR) + + def do_touch(self, args): + "Add a new file in the target directory. + Handles relative or absolute file paths. \n + Usage: touch []" + + arg_list=args.split() + + if len(arg_list) < 1: + print 'Usage: touch ()' + else: + self.mkfile(arg_list, T_FILE) + + def mkfile(self, args, file_type): + '''args must be a list of arguments''' + cwd = self.fs + path = resolve_reference(self.pwd, args[0]) + pathList = path.split('/') + parentdir = '/'.join(pathList[:-1]) + fileName = pathList[len(pathList) - 1] + + if not exists(self.fs, parentdir): + print ('Parent directory %s doesn\'t exist! ' + + 'Please create it first.') % \ + (parentdir,) + return + + if exists(self.fs, path): + print 'Error: %s already exists!' % (path,) + return + + cwd = getpath(self.fs, parentdir) + + #get uid, gid, mode from parent + uid = cwd[A_UID] + gid = cwd[A_GID] + mode = cwd[A_MODE] + + #create default file/directory size if none is specified + if len(args) == 1: + size = 4096 + else: + size = args[1] + + #set the last update timestamp to now + ctime = time.time() + + cwd[A_CONTENTS].append( + [fileName, file_type, uid, gid, size, mode, ctime, [], None, None]) + + self.save_pickle() + + print "Added '%s'" % path + + def do_rm(self, arguments): + '''Remove an object from the filesystem. + Will not remove a directory unless the -r switch is invoked.\n + Usage: rm [-r] ''' + + args = arguments.split() + + if len(args) < 1 or len(args) > 2: + print 'Usage: rm [-r] ' + return + + if len(args) == 2 and args[0] != "-r": + print 'Usage: rm [-r] ' + return + + if len(args) == 1: + target_path = resolve_reference(self.pwd, args[0]) + else: + target_path = resolve_reference(self.pwd, args[1]) + + if exists(self.fs, target_path) == False: + print "File \'%s\' doesn\'t exist" % (target_path,) + return + + if target_path == "/": + print "rm: cannot delete root directory '/'" + return + + target_object = getpath(self.fs, target_path) + + if target_object[A_TYPE]==T_DIR and args[0] != "-r": + print "rm: cannot remove '%s': Is a directory" % (target_path,) + return + + parent_path = '/'.join(target_path.split('/')[:-1]) + parent_object = getpath(self.fs, parent_path) + + parent_object[A_CONTENTS].remove(target_object) + + self.save_pickle() + + print "Deleted %s" % target_path + + def do_rmdir(self, arguments): + '''Remove a file object. Like the unix command, + this can only delete empty directories. + Use rm -r to recursively delete full directories.\n + Usage: rmdir ''' + args = arguments.split() + + if len(args) != 1: + print 'Usage: rmdir ' + return + + target_path = resolve_reference(self.pwd, args[0]) + + if exists(self.fs, target_path) == False: + print "File \'%s\' doesn\'t exist" % (target_path,) + return + + target_object = getpath(self.fs, target_path) + + if target_object[A_TYPE] != T_DIR: + print "rmdir: failed to remove '%s': Not a directory" % \ + (target_path,) + return + + #The unix rmdir command does not delete directories if they are not + #empty + if len(target_object[A_CONTENTS]) != 0: + print "rmdir: failed to remove '%s': Directory not empty" % \ + (target_path,) + return + + parent_path = '/'.join(target_path.split('/')[:-1]) + parent_object = getpath(self.fs, parent_path) + + parent_object[A_CONTENTS].remove(target_object) + + self.save_pickle() + + if self.pwd == target_path: + self.do_cd("..") + + print "Deleted %s" % target_path + + def do_mv(self, arguments): + '''Moves a file/directory from one directory to another.\n + Usage: mv ''' + args = arguments.split() + if len(args) != 2: + print 'Usage: mv ' + return + src = resolve_reference(self.pwd, args[0]) + dst = resolve_reference(self.pwd, args[1]) + + if src == "/": + print "mv: cannot move the root directory '/'" + return + + src = src.strip('/') + dst = dst.strip('/') + + if not exists(self.fs, src): + print "Source file \'%s\' does not exist!" % src + return + + #Get the parent directory of the source file + #srcparent = '/'.join(src.split('/')[:-1]) + srcparent = "/".join(src.split('/')[:-1]) + + #Get the object for source + srcl = getpath(self.fs, src) + + #Get the object for the source's parent + srcparentl = getpath(self.fs, srcparent) + + #if the specified filepath is a directory, maintain the current name + if exists(self.fs, dst) and is_directory(self.fs, dst): + dstparent = dst + dstname = srcl[A_NAME] + else: + dstparent = '/'.join(dst.split('/')[:-1]) + dstname = dst.split('/')[-1] + + if exists(self.fs, dstparent + '/' + dstname): + print "A file already exists at "+dst+"!" + return + + if not exists(self.fs, dstparent): + print 'Destination directory \'%s\' doesn\'t exist!' % dst + return + + if src == self.pwd: + self.do_cd("..") + + dstparentl = getpath(self.fs, dstparent) + copy = srcl[:] + copy[A_NAME] = dstname + dstparentl[A_CONTENTS].append(copy) + srcparentl[A_CONTENTS].remove(srcl) + + self.save_pickle() + + print 'File moved from /%s to /%s' % (src, dst) + + def do_cp(self, arguments): + '''Copies a file/directory from one directory to another.\n + Usage: cp ''' + args = arguments.split() + if len(args) != 2: + print 'Usage: cp ' + return + + #src, dst = args[0], args[1] + + src = resolve_reference(self.pwd, args[0]) + dst = resolve_reference(self.pwd, args[1]) + + src = src.strip('/') + dst = dst.strip('/') + + if not exists(self.fs, src): + print "Source file '%s' does not exist!" % (src,) + return + + #Get the parent directory of the source file + srcparent = '/'.join(src.split('/')[:-1]) + + #Get the object for source + srcl = getpath(self.fs, src) + + #Get the ojbect for the source's parent + srcparentl = getpath(self.fs, srcparent) + + #if the specified filepath is a directory, maintain the current name + if exists(self.fs, dst) and is_directory(self.fs, dst): + dstparent = dst + dstname = srcl[A_NAME] + else: + dstparent = '/'.join(dst.split('/')[:-1]) + dstname = dst.split('/')[-1] + + if exists(self.fs, dstparent + '/' + dstname): + print 'A file already exists at %s/%s!' % (dstparent, dstname) + return + + if not exists(self.fs, dstparent): + print 'Destination directory %s doesn\'t exist!' % (dstparent,) + return + + dstparentl = getpath(self.fs, dstparent) + copy = srcl[:] + copy[A_NAME] = dstname + dstparentl[A_CONTENTS].append(copy) + + self.save_pickle() + + print 'File copied from /%s to /%s/%s' % (src, dstparent, dstname) + + def do_file(self, args): + '''Identifies file types.\nUsage: file ''' + arg_list = args.split() + + if len(arg_list) != 1: + print "Incorrect number of arguments.\nUsage: file " + return + + target_path = resolve_reference(self.pwd, arg_list[0]) + + if not exists(self.fs, target_path): + print "File '%s' doesn't exist." % target_path + return + + target_object = getpath(self.fs, target_path) + + file_type = target_object[A_TYPE] + + if file_type == T_FILE: + msg = "normal file object" + elif file_type == T_DIR: + msg = "directory" + elif file_type == T_LINK: + msg = "link" + elif file_type == T_BLK: + msg = "block file" + elif file_type == T_CHR: + msg = "character special" + elif file_type == T_SOCK: + msg = "socket" + elif file_type == T_FIFO: + msg = "named pipe" + else: + msg = "unrecognized file" + + print target_path+" is a "+msg + + def do_clear(self, args): + '''Clears the screen''' + os.system('clear') + + def emptyline(self): + '''By default the cmd object will repeat the last command + if a blank line is entered. Since this is different than + bash behavior, overriding this method will stop it.''' + pass + + def help_help(self): + print "Type help to get more information." + + def help_about(self): + print "Kippo stores information about its file systems in a " + \ + "series of nested lists. Once the lists are made, they are " + \ + "stored in a pickle file on the hard drive. Every time kippo " + \ + "gets a new client, it reads from the pickle file and loads " + \ + "the fake filesystem into memory. By default this file " + \ + "is /opt/kippo/fs.pickle. Originally the script " + \ + "/opt/kippo/createfs.py was used to copy the filesystem " + \ + "of the existing computer. However, it quite difficult to " + \ + "edit the pickle file by hand.\n\nThis script strives to be " + \ + "a bash-like interface that allows users to modify " + \ + "existing fs pickle files. It supports many of the " + \ + "common bash commands and even handles relative file " + \ + "paths. Keep in mind that you need to restart the " + \ + "kippo process in order for the new file system to be " + \ + "reloaded into memory.\n\nDonovan Hubbard, Douglas Hubbard, " + \ + "March 2013\nVersion 1.0" + +if __name__ == '__main__': + if len(sys.argv) != 2: + print "Usage: %s " % os.path.basename(sys.argv[0],) + sys.exit(1) + + pickle_file_name = sys.argv[1].strip() + print pickle_file_name + + fseditCmd(pickle_file_name).cmdloop() + +# vim: set sw=4 et: