def filetype(self, filename, parent_type = ''): ''' Determine filetype for the given file.''' if self.__types: OUT.debug('Returning file type', 7) return self.__types.filetype(filename, parent_type)
def remove(self, entry): ''' Decide whether to delete something - and then go ahead and do so Just like portage, we only remove files that have not changed from when we installed them. If the timestamp or checksum is different, we leave the file in place. Inputs entry - file/dir/sym to remove ''' OUT.debug('Trying to remove file', 6) # okay, deal with the file | directory | symlink removeable = self.__content.get_canremove(entry) if not removeable: # Remove directory or file. # Report if we are only pretending if self.__p: OUT.info(' pretending to remove: ' + entry) # try to remove the entry try: entry_type = self.__content.etype(entry) if self.__content.etype(entry) == 'dir': # its a directory -> rmdir if not self.__p: os.rmdir(entry) else: # its a file -> unlink if not self.__p: os.unlink(entry) except: # Report if there is a problem OUT.notice('!!! ' + self.__content.epath(entry)) return if self.__v and not self.__p: # Report successful deletion OUT.notice('<<< ' + entry_type + ' ' * (5 - len(entry_type)) + self.__content.epath(entry)) self.__content.delete(entry) return True else: OUT.notice(removeable) return False
def read(self): ''' Read the contents of the dot config file.''' dotconfig = self.__dot_config() OUT.debug('Checking for dotconfig ', 6) if not self.has_dotconfig(): raise Exception('Cannot read file ' + dotconfig) tokens = shlex.shlex(open(dotconfig)) while True: a = tokens.get_token() b = tokens.get_token() c = tokens.get_token() OUT.debug('Reading token', 8) if (a in self.__tokens and b == '=' and c): if c[0] == '"': c = c[1:] if c[-1] == '"': c = c[:-1] self.__data[a] = c else: break
def read(self, config_owned = 'config-files', server_owned = 'server-owned-files', virtual_files = 'virtual', default_dirs = 'default-owned'): ''' Initialize the type cache. ''' import WebappConfig.filetype server_files = [] config_files = [] if os.access(self.appdir() + '/' + config_owned, os.R_OK): flist = open(self.appdir() + '/' + config_owned) config_files = flist.readlines() OUT.debug('Identified config-protected files.', 7) flist.close() if os.access(self.appdir() + '/' + server_owned, os.R_OK): flist = open(self.appdir() + '/' + server_owned) server_files = flist.readlines() OUT.debug('Identified server-owned files.', 7) flist.close() self.__types = WebappConfig.filetype.FileType(config_files, server_files, virtual_files, default_dirs)
def listunused(self, db): ''' Outputs a list of what has not been installed so far ''' packages = self.list_locations() if not packages: OUT.die('No packages found!') keys = sorted(packages) OUT.debug('Check for unused web applications', 7) for i in keys: db.set_category(packages[i][0]) db.set_package (packages[i][1]) db.set_version (packages[i][2]) if not db.has_installs(): if packages[i][0]: OUT.notice(packages[i][0] + '/' + packages[i][1] + '-' + packages[i][2]) else: OUT.notice(packages[i][1] + '-' + packages[i][2])
def read(self, config_owned='config-files', server_owned='server-owned-files', virtual_files='virtual', default_dirs='default-owned'): ''' Initialize the type cache. ''' import WebappConfig.filetype server_files = [] config_files = [] if os.access(self.appdir() + '/' + config_owned, os.R_OK): flist = open(self.appdir() + '/' + config_owned) config_files = flist.readlines() OUT.debug('Identified config-protected files.', 7) flist.close() if os.access(self.appdir() + '/' + server_owned, os.R_OK): flist = open(self.appdir() + '/' + server_owned) server_files = flist.readlines() OUT.debug('Identified server-owned files.', 7) flist.close() self.__types = WebappConfig.filetype.FileType(config_files, server_files, virtual_files, default_dirs)
def packageavail(self): ''' Check to see whether the given package has been installed or not. These checks are carried out by using wrapper.py to facilitate distribution independant handling of the task. Outputs: 0 - on success 1 - package not found 2 - no package to find 3 - package isn't webapp-config compatible ' ''' OUT.debug('Verifying package ' + self.package_name(), 6) # package_installed() does not handle "/PN" correctly package = self.pn if self.category: package = self.category + '/' + self.pn # not using self.package_name() here as we don't need pvr return 1 # unfortunately, just because a package has been installed, it # doesn't mean that the package itself is webapp-compatible # # we need to check that the package has an entry in the # application repository if not self.appdb(): return 3 else: return 0
def listunused(self, db): ''' Outputs a list of what has not been installed so far ''' packages = self.list_locations() if not packages: OUT.die('No packages found!') keys = sorted(packages) OUT.debug('Check for unused web applications', 7) for i in keys: db.set_category(packages[i][0]) db.set_package(packages[i][1]) db.set_version(packages[i][2]) if not db.has_installs(): if packages[i][0]: OUT.notice(packages[i][0] + '/' + packages[i][1] + '-' + packages[i][2]) else: OUT.notice(packages[i][1] + '-' + packages[i][2])
def dirtype(self, directory): ''' Determine filetype for the given directory.''' if self.__types: OUT.debug('Returning directory type', 7) return self.__types.dirtype(directory)
def filetype(self, filename): ''' Determine filetype for the given file.''' if self.__types: OUT.debug('Returning file type', 7) return self.__types.filetype(filename)
def dirtype(self, directory, parent_type = ''): ''' Determine filetype for the given directory.''' if self.__types: OUT.debug('Returning directory type', 7) return self.__types.dirtype(directory, parent_type)
def show_postinst(self, server = None): ''' Display any post-installation instructions, if there are any. ''' OUT.debug('Running show_postinst', 6) self.show_post(filename = 'postinst-en.txt', ptype = 'install', server = server)
def show_postupgrade(self, server = None): ''' Display any post-upgrade instructions, if there are any. ''' OUT.debug('Running show_postupgrade', 6) self.show_post(filename = 'postupgrade-en.txt', ptype = 'upgrade', server = server)
def show_postupgrade(self, server=None): ''' Display any post-upgrade instructions, if there are any. ''' OUT.debug('Running show_postupgrade', 6) self.show_post(filename='postupgrade-en.txt', ptype='upgrade', server=server)
def show_postinst(self, server=None): ''' Display any post-installation instructions, if there are any. ''' OUT.debug('Running show_postinst', 6) self.show_post(filename='postinst-en.txt', ptype='install', server=server)
def clean(self): self.file_behind_flag = False OUT.debug('Basic server clean', 7) self.file_behind_flag |= self.__del.remove_files() self.file_behind_flag |= self.__del.remove_dirs() OUT.info('Any files or directories listed above must be removed b' 'y hand') # okay, let's finish off # # we don't need the contents file anymore self.file_behind_flag |= not self.__content.kill() # right - we need to run the hook scripts now # if they fail, we don't actually care # run the hooks self.__ebuild.run_hooks('clean', self) # do we need the dotconfig file? # # if the .webapp file is the only one in the dir, we believe # that we can remove it self.__dotconfig.kill() # is the installation directory empty? if not os.listdir(self.__destd) and os.path.isdir(self.__destd): if not self.__p: os.rmdir(self.__destd) else: OUT.notice('--- ' + self.__destd) # update the list of installs self.__db.remove(self.__destd) # Remove the selinux module if self.__selinux is not None: self.__selinux.remove_module() # did we leave anything behind? if self.file_behind_flag: OUT.warn('Remove whatever is listed above by hand')
def remove_files(self): ''' It is time to remove the files that we installed originally. ''' OUT.debug('Trying to remove files', 6) success = [self.remove(i) for i in self.__content.get_files()] # Tell the caller if anything was left behind return all(success)
def packagename(self): ''' Retrieve the package name from the values specified in the dot config file''' OUT.debug('Trying to retrieve package name', 6) if 'WEB_PN' in list(self.__data.keys()) and 'WEB_PVR' in list(self.__data.keys()): if 'WEB_CATEGORY' in list(self.__data.keys()): return self.__data['WEB_CATEGORY'] + '/' + \ self.__data['WEB_PN'] + '-' + self.__data['WEB_PVR'] else: return self.__data['WEB_PN'] + '-' + self.__data['WEB_PVR'] return ''
def has_dotconfig(self): ''' Return True if the install location already has a dotconfig file.''' dotconfig = self.__dot_config() OUT.debug('Verifying path', 7) if not os.path.isfile(dotconfig): return False if not os.access(dotconfig, os.R_OK): return False return True
def packageavail(self): ''' Check to see whether the given package has been installed or not. These checks are carried out by using wrapper.py to facilitate distribution independant handling of the task. Outputs: 0 - on success 1 - package not found 2 - no package to find 3 - package isn't webapp-config compatible ' >>> import os.path >>> here = os.path.dirname(os.path.realpath(__file__)) Does not exist: >>> a = WebappSource(root = here + '/tests/testfiles/share-webapps', ... category='www-apps',package='nothere', version='1',pm='portage') >>> a.packageavail() 1 Incompatible cannot be tested since that would require a oackage (including version number) that is installed on all systems. ''' OUT.debug('Verifying package ' + self.package_name(), 6) # package_installed() does not handle "/PN" correctly package = self.pn if self.category: package = self.category + '/' + self.pn # not using self.package_name() here as we don't need pvr if not wrapper.package_installed(package, self.pm): return 1 # unfortunately, just because a package has been installed, it # doesn't mean that the package itself is webapp-compatible # # we need to check that the package has an entry in the # application repository if not self.appdb(): return 3 else: return 0
def clean(self): self.file_behind_flag = False OUT.debug('Basic server clean', 7) self.file_behind_flag |= self.__del.remove_files() self.file_behind_flag |= self.__del.remove_dirs() OUT.info('Any files or directories listed above must be removed b' 'y hand') # okay, let's finish off # # we don't need the contents file anymore self.file_behind_flag |= not self.__content.kill() # right - we need to run the hook scripts now # if they fail, we don't actually care # run the hooks self.__ebuild.run_hooks('clean', self) # do we need the dotconfig file? # # if the .webapp file is the only one in the dir, we believe # that we can remove it self.__dotconfig.kill() # is the installation directory empty? if not os.listdir(self.__destd) and os.path.isdir(self.__destd): if not self.__p: os.rmdir(self.__destd) else: OUT.notice('--- ' + self.__destd) # update the list of installs self.__db.remove(self.__destd) # did we leave anything behind? if self.file_behind_flag: OUT.warn('Remove whatever is listed above by hand')
def add(self, installdir, user, group): ''' Add a record to the list of virtual installs. installdir - the installation directory ''' if not installdir: OUT.die('The installation directory must be specified!') if not str(user): OUT.die('Please specify a valid user!') if not str(group): OUT.die('Please specify a valid group!') OUT.debug('Adding install record', 6) dbpath = self.appdb() if not dbpath: OUT.die('No package specified!') if not self.__p and not os.path.isdir(os.path.dirname(dbpath)): os.makedirs(os.path.dirname(dbpath), self.__dir_perm(0o755)) fd = None if not self.__p: fd = os.open(dbpath, os.O_WRONLY | os.O_APPEND | os.O_CREAT, self.__file_perm(0o600)) entry = str(int(time.time())) + ' ' + str(user) + ' ' + str(group)\ + ' ' + installdir + '\n' OUT.debug('New record', 7) if not self.__p: os.write(fd, (entry).encode('utf-8')) os.close(fd) else: OUT.info('Pretended to append installation ' + installdir) OUT.info('Entry:\n' + entry)
def list_locations(self): ''' List all available db files.''' OUT.debug('Retrieving hierarchy locations', 6) dbpath = self.appdb() if dbpath and os.path.isfile(dbpath): return {dbpath: [self.category, self.pn, self.pvr]} if dbpath and not os.path.isfile(dbpath): OUT.debug( 'Package "' + self.package_name() + '" not listed in the hierarchy (file "' + dbpath + ' is missing)!', 8) return {} locations = {} packages = [] if self.pn: packages.append(os.path.join(self.root, self.pn)) if self.category: packages.append(os.path.join(self.root, self.category, self.pn)) else: packages.extend( os.path.join(self.root, m) for m in os.listdir(self.root)) for i in packages: if os.path.isdir(i): packages.extend(os.path.join(i, m) for m in os.listdir(i)) for i in packages: OUT.debug('Checking package', 8) if os.path.isdir(i): OUT.debug('Checking version', 8) versions = os.listdir(i) for j in versions: appdir = os.path.join(i, j) location = os.path.join(appdir, self.dbfile) if (os.path.isdir(appdir) and os.path.isfile(location)): pn = os.path.basename(i) cat = os.path.basename(os.path.split(i)[0]) if cat == "webapps": cat = "" locations[location] = [cat, pn, j] return locations
def get_protectedname(self, destination, filename): ''' Woh. Somewhere, we've decided that we're trying to overwrite a file that we really want to save. So, we need a new name for the file that we want to install - which is where we come in. NOTE: The filename that we produce is compatible with Gentoo's etc-update tool. This is deliberate. Inputs: destination - the directory that the file is being installed into filename - the original name of the file ''' my_file = os.path.basename(filename) my_filedir = destination + '/' + os.path.dirname(filename) # find the highest numbered protected file that already # exists, and increment it by one entries = os.listdir(my_filedir) OUT.debug('Identifying possible file number', 7) numbers = [] prefix = self.protect_prefix rep = re.compile(prefix.replace('.','\.') + '(\d{4})_') for i in entries: rem = rep.match(i) if rem: numbers.append(int(rem.group(1))) if numbers: max_n = max(numbers) + 1 else: max_n = 0 return my_filedir + '/%s%.4d_%s' % (prefix, max_n, my_file)
def list_locations(self): ''' List all available db files.''' OUT.debug('Retrieving hierarchy locations', 6) dbpath = self.appdb() if dbpath and os.path.isfile(dbpath): return {dbpath : [ self.category, self.pn, self.pvr]} if dbpath and not os.path.isfile(dbpath): OUT.debug('Package "' + self.package_name() + '" not listed in the hierarchy (file "' + dbpath + ' is missing)!', 8) return {} locations = {} packages = [] if self.pn: packages.append(os.path.join(self.root, self.pn)) if self.category: packages.append(os.path.join(self.root, self.category, self.pn)) else: packages.extend(os.path.join(self.root, m) for m in os.listdir(self.root)) for i in packages: if os.path.isdir(i): packages.extend(os.path.join(i,m) for m in os.listdir(i)) for i in packages: OUT.debug('Checking package', 8) if os.path.isdir(i): OUT.debug('Checking version', 8) versions = os.listdir(i) for j in versions: appdir = os.path.join(i,j) location = os.path.join( appdir, self.dbfile) if (os.path.isdir(appdir) and os.path.isfile(location)): pn = os.path.basename(i) cat = os.path.basename(os.path.split(i)[0]) if cat == "webapps": cat = "" locations[location] = [ cat, pn, j ] return locations
def kill(self): ''' Remove the dot config file.''' empty = self.is_empty() OUT.debug('Trying to removing .webapp file', 7) if not empty: if not self.__p: try: os.unlink(self.__dot_config()) except: OUT.warn('Failed to remove ' + self.__dot_config() + '!') return False else: OUT.info('Would have removed ' + self.__dot_config()) return True else: OUT.notice('--- ' + empty) return False
def get_protectedname(self, destination, filename): ''' Woh. Somewhere, we've decided that we're trying to overwrite a file that we really want to save. So, we need a new name for the file that we want to install - which is where we come in. NOTE: The filename that we produce is compatible with Gentoo's etc-update tool. This is deliberate. Inputs: destination - the directory that the file is being installed into filename - the original name of the file ''' my_file = os.path.basename(filename) my_filedir = destination + '/' + os.path.dirname(filename) # find the highest numbered protected file that already # exists, and increment it by one entries = os.listdir(my_filedir) OUT.debug('Identifying possible file number', 7) numbers = [] prefix = self.protect_prefix rep = re.compile(prefix.replace('.', '\.') + '(\d{4})_') for i in entries: rem = rep.match(i) if rem: numbers.append(int(rem.group(1))) if numbers: max_n = max(numbers) + 1 else: max_n = 0 return my_filedir + '/%s%.4d_%s' % (prefix, max_n, my_file)
def __init__(self, config_owned, server_owned, server_owned_r, virtual_files = 'virtual', default_dirs = 'default-owned'): ''' Populates the cache with the file types as provided by the ebuild. ''' self.__cache = {} # Validity of entries are checked by the command line parser self.__virtual_files = virtual_files self.__default_dirs = default_dirs # populate cache for i in config_owned: OUT.debug('Adding config-owned file', 8) self.__cache[self.__fix(i)] = 'config-owned' for i in server_owned: if self.__fix(i) in self.__cache.keys(): OUT.debug('Adding config-server-owned file', 8) self.__cache[self.__fix(i)] = 'config-server-owned' else: OUT.debug('Adding server-owned file', 8) self.__cache[self.__fix(i)] = 'server-owned' for i in server_owned_r: if self.__fix(i) in self.__cache.keys(): OUT.die('{} is a the same time recursively server-owned and {}: This case is not supported.'.format(self.__fix(i), self.__cache[self.__fix(i)])) else : OUT.debug('Adding recursively server-owned file', 8) self.__cache[self.__fix(i).strip()] = 'server-owned-dir'
def run_hooks(self, type, server): ''' Run the hook scripts - if there are any ''' if self.config.pretend(): return sandbox = Sandbox(self.config) # save list of environment variables to set env_map = self.run_vars(server) if os.path.isdir(self.__hooksd): for x in os.listdir(self.__hooksd): if (os.path.isfile(self.__hooksd + '/' + x) and os.access(self.__hooksd + '/' + x, os.X_OK)): OUT.debug('Running hook script', 7) sandbox.spawn(self.__hooksd + '/' + x + ' ' + type, env_map)
def show_post(self, filename, ptype, server=None): ''' Display one of the post files. ''' post_file = self.__sourced + '/' + filename OUT.debug('Check for instruction file', 7) if not os.path.isfile(post_file): return self.run_vars(server) post_instructions = open(post_file).readlines() OUT.debug('Read post instructions', 7) post = [ '', '=================================================================', 'POST-' + ptype.upper() + ' INSTRUCTIONS', '=================================================================', '' ] for i in post_instructions: i = i.replace('"', '\\"') post.append(os.popen('echo -n "' + i + '"\n').read()[:-1]) post = post + [ '', '=================================================================', '' ] for i in post: OUT.notice(i)
def read(self, config_owned = 'config-files', server_owned = 'server-owned-files', server_owned_r = 'server-owned-dirs', virtual_files = 'virtual', default_dirs = 'default-owned'): ''' Initialize the type cache. >>> import os.path >>> here = os.path.dirname(os.path.realpath(__file__)) >>> a = WebappSource(root=here + '/tests/testfiles/share-webapps', ... category='', package='horde', version='3.0.5') >>> a.read() >>> a.filetype('test1') 'config-owned' >>> a.filetype('test2') 'server-owned' ''' import WebappConfig.filetype self.server_files = [] self.server_dirs = [] config_files = [] if os.access(self.appdir() + '/' + config_owned, os.R_OK): flist = open(self.appdir() + '/' + config_owned) config_files = flist.readlines() OUT.debug('Identified config-protected files.', 7) flist.close() if os.access(self.appdir() + '/' + server_owned, os.R_OK): flist = open(self.appdir() + '/' + server_owned) self.server_files = flist.readlines() OUT.debug('Identified server-owned files.', 7) flist.close() if os.access(self.appdir() + '/' + server_owned_r, os.R_OK): flist = open(self.appdir() + '/' + server_owned_r) self.server_dirs = flist.readlines() OUT.debug('Identified server-owned directories.', 7) flist.close() self.__types = WebappConfig.filetype.FileType(config_files, self.server_files, self.server_dirs, virtual_files, default_dirs)
def show_post(self, filename, ptype, server = None): ''' Display one of the post files. ''' post_file = self.__sourced + '/' + filename OUT.debug('Check for instruction file', 7) if not os.path.isfile(post_file): return self.run_vars(server) post_instructions = open(post_file).readlines() OUT.debug('Read post instructions', 7) post = [ '', '=================================================================', 'POST-' + ptype.upper() + ' INSTRUCTIONS', '=================================================================', ''] for i in post_instructions: i = i.replace('"', '\\"') post.append(os.popen('echo -n "' + i + '"\n').read()[:-1]) post = post + [ '', '=================================================================', ''] for i in post: OUT.notice(i)
def mkdir(self, directory, current_type): ''' Create a directory with the correct ownership and permissions. directory - name of the directory ''' src_dir = self.__sourced + '/' + directory dst_dir = self.__destd + '/' + directory OUT.debug('Creating directory', 6) # some special cases # # these should be triggered only if we are trying to install # a webapp into a directory that already has files and dirs # inside it if os.path.exists(dst_dir) and not os.path.isdir(dst_dir): # something already exists with the same name # # in theory, this should automatically remove symlinked # directories OUT.warn(' ' + dst_dir + ' already exists, but is not a di' 'rectory - removing') if not self.__p: os.unlink(dst_dir) dirtype = self.__ws.dirtype(src_dir, current_type) OUT.debug('Checked directory type', 8) (user, group, perm) = self.__perm['dir'][dirtype] dsttype = 'dir' if not os.path.isdir(dst_dir): OUT.debug('Creating directory', 8) if not self.__p: os.makedirs(dst_dir, perm(0o755)) os.chown(dst_dir, user, group) self.__content.add(dsttype, dirtype, self.__destd, directory, directory, self.__relative) return dirtype
def mkdir(self, directory): ''' Create a directory with the correct ownership and permissions. directory - name of the directory ''' src_dir = self.__sourced + '/' + directory dst_dir = self.__destd + '/' + directory OUT.debug('Creating directory', 6) # some special cases # # these should be triggered only if we are trying to install # a webapp into a directory that already has files and dirs # inside it if os.path.exists(dst_dir) and not os.path.isdir(dst_dir): # something already exists with the same name # # in theory, this should automatically remove symlinked # directories OUT.warn(' ' + dst_dir + ' already exists, but is not a di' 'rectory - removing') if not self.__p: os.unlink(dst_dir) dirtype = self.__ws.dirtype(src_dir) OUT.debug('Checked directory type', 8) (user, group, perm) = self.__perm['dir'][dirtype] dsttype = 'dir' if not os.path.isdir(dst_dir): OUT.debug('Creating directory', 8) if not self.__p: os.makedirs(dst_dir, perm(0o755)) os.chown(dst_dir, user, group) self.__content.add(dsttype, dirtype, self.__destd, directory, directory, self.__relative)
def mkdirs(self, directory = '', current_type = ''): ''' Create a set of directories Inputs directory - the directory within the source hierarchy ''' sd = self.__sourced + '/' + directory real_dir = re.compile('/+').sub('/', self.__ws.appdir() + '/' + self.__sourced + '/' + directory) OUT.debug('Creating directories', 6) if not self.__ws.source_exists(sd): OUT.warn(self.__ws.package_name() + ' does not install any files from ' + real_dir + '; skipping') return OUT.info(' Installing from ' + real_dir) for i in self.__ws.get_source_directories(sd): OUT.debug('Handling directory', 7) # create directory first next_type = self.mkdir(directory + '/' + i, current_type) # then recurse into the directory self.mkdirs(directory + '/' + i, next_type) for i in self.__ws.get_source_files(sd): OUT.debug('Handling file', 7) # handle the file self.mkfile(directory + '/' + i, current_type)
def mkdirs(self, directory=''): ''' Create a set of directories Inputs directory - the directory within the source hierarchy ''' sd = self.__sourced + '/' + directory real_dir = re.compile('/+').sub( '/', self.__ws.appdir() + '/' + self.__sourced + '/' + directory) OUT.debug('Creating directories', 6) if not self.__ws.source_exists(sd): OUT.warn(self.__ws.package_name() + ' does not install any files from ' + real_dir + '; skipping') return OUT.info(' Installing from ' + real_dir) for i in self.__ws.get_source_directories(sd): OUT.debug('Handling directory', 7) # create directory first self.mkdir(directory + '/' + i) # then recurse into the directory self.mkdirs(directory + '/' + i) for i in self.__ws.get_source_files(sd): OUT.debug('Handling file', 7) # handle the file self.mkfile(directory + '/' + i)
def __init__(self, config_owned, server_owned, virtual_files = 'virtual', default_dirs = 'default-owned'): ''' Populates the cache with the file types as provided by the ebuild. ''' self.__cache = {} # Validity of entries are checked by the command line parser self.__virtual_files = virtual_files self.__default_dirs = default_dirs # populate cache for i in config_owned: OUT.debug('Adding config-owned file', 8) self.__cache[self.__fix(i)] = 'config-owned' for i in server_owned: if self.__fix(i) in list(self.__cache.keys()): OUT.debug('Adding config-server-owned file', 8) self.__cache[self.__fix(i)] = 'config-server-owned' else: OUT.debug('Adding server-owned file', 8) self.__cache[self.__fix(i)] = 'server-owned'
def remove(self, installdir): ''' Remove a record from the list of virtual installs. installdir - the installation directory ''' if not installdir: OUT.die('The installation directory must be specified!') dbpath = self.appdb() if not dbpath: OUT.die('No package specified!') if not os.access(dbpath, os.R_OK): OUT.warn('Unable to read the install database ' + dbpath) return # Read db file fdb = open(dbpath) entries = fdb.readlines() fdb.close() newentries = [] found = False for i in entries: j = i.strip().split(' ') if j: if len(j) != 4: # Remove invalid entry OUT.warn('Invalid line "' + i.strip() + '" remo' 'ved from the database file!') elif j[3] != installdir: OUT.debug('Keeping entry', 7) # Keep valid entry newentries.append(i.strip()) elif j[3] == installdir: # Remove entry, indicate found found = True if not found: OUT.warn('Installation at "' + installdir + '" could not be ' 'found in the database file. Check the entries in "' + dbpath + '"!') if not self.__p: installs = open(dbpath, 'w') installs.write('\n'.join(newentries) + '\n') installs.close() if not self.has_installs(): os.unlink(dbpath) else: OUT.info('Pretended to remove installation ' + installdir) OUT.info('Final DB content:\n' + '\n'.join(newentries) + '\n')
def install(self, upgrade = False): self.config_protected_dirs = [] OUT.debug('Basic server install', 7) # The root of the virtual install location needs to exist if not os.path.isdir(self.__destd) and not self.__p: OUT.debug('Directory missing', 7) dir = self.__destd dirs = [] while dir != '/': dirs.insert(0, dir) dir = os.path.dirname(dir) a = self.__perm['dir']['install-owned'][2]('0755') OUT.debug('Strange') # Create the directories for i in dirs: if not os.path.isdir(i): os.mkdir(i) os.chmod(i, self.__perm['dir']['install-owned'][2]('0755')) os.chown(i, self.__perm['dir']['install-owned'][0], self.__perm['dir']['install-owned'][1]) if self.__v: OUT.info(' Creating installation directory: ' + i) # Create the handler for installing self.__flags['relative'] = True wa = WebappAdd(self.__sourced, self.__destd, self.__perm, self.__handler, self.__flags) self.__add = wa OUT.info('Installing ' + self.__ws.package_name() + '...') # we need to create the directories to place our files in # and we need to copy in the files OUT.info(' Creating required directories', 1) OUT.info(' Linking in required files', 1) OUT.info(' This can take several minutes for larger apps', 1) self.__add.mkdirs() self.config_protected_dirs += self.__add.config_protected_dirs # Create the second handler for installing the root files self.__flags['relative'] = False wa = WebappAdd(self.__hostroot, self.__vhostroot, self.__perm, self.__handler, self.__flags) self.__add = wa self.__add.mkdirs() self.config_protected_dirs += self.__add.config_protected_dirs OUT.info(' Files and directories installed', 1) self.__dotconfig.write(self.__ws.category, self.__ws.pn, self.__ws.pvr, self.__flags['host'], self.__flags['orig'], str(self.__perm['file']['config-owned'][0]) + ':' + str(self.__perm['file']['config-owned'][1]),) self.__db.add(self.__destd, self.__perm['file']['config-owned'][0], self.__perm['file']['config-owned'][1]) # run the hooks self.__ebuild.run_hooks('install', self) # show the post-installation instructions if not upgrade: self.__ebuild.show_postinst(self) else: self.__ebuild.show_postupgrade(self) # to finish, we need to tell the user if they need to run # etc-update or not if self.config_protected_dirs: # work out whether this directory is part of the # CONFIG_PROTECT list or not self.__protect.how_to_update(self.config_protected_dirs) self.__content.write() # and we're done OUT.info('Install completed - success', 1)
def add(self, dsttype, ctype, destination, path, real_path, relative=True): ''' Add an entry to the contents file. Just like Portage, when we install an app, we create a contents file to say what we installed and when. We use this contents file to help us safely remove & upgrade apps. CONTENTS file format: <what> <rel> <type> <filename> <timestamp> <sum> [<optional>] where <what> is one of dir|sym|file|hardlink <rel> is 1 for relative filenames, 0 for absolute filenames <type> is one of server-owned|default-owned|config-owned|virtual <timestamp> is the timestamp when the file was installed <sum> is the md5sum of the file (this is 0 for directories and symlinks) <filename> is the actual name of the file we have installed <optional> is additional data that depends upon <what> NOTE: Filenames used to be on the end of the line. This made the old bash version more complicated, and prone to failure. So I have moved the filename into the middle of the line. -- Stuart Portage uses absolute names for its files, dirs, and symlinks. We do not. In theory, you can move a directory containing a web-based app, and a) the app itself will not break, and b) webapp-config will still work on that directory for upgrades and cleans. Position-independence *is* a design constraint that all future changes to this script need to honour. Inputs: dsttype - type to add (one of dir|sym|file|hardlink) ctype - internal webapp-config type - (server-owned | config-owned | virtual) destination - install dir (normally $G_INSTALLDIR) path - filename inside 'destination' real_path - for config-protected files realpath =! path (and this is important for md5) relative - 1 for storing a relative filename, 0 otherwise ''' OUT.debug('Adding entry to content dictionary', 6) # Build the full path that we use as index in the contents list while path[0] == '/': path = path[1:] while destination[-1] == '/': destination = destination[:-1] entry = destination + '/' + path # special case - we don't add entries for '.' if os.path.basename(entry) == '.': return if (not self.__p and not os.path.islink(entry) and (not os.path.exists(entry) or not os.access(entry, os.R_OK))): OUT.warn('Cannot access file ' + entry + ' to add it as' ' installation content. This should not happen!') return allowed_types = { 'file': ['file', self.file_md5, self.file_null], 'hardlink': ['file', self.file_md5, self.file_null], 'dir': ['dir', self.file_zero, self.file_null], 'sym': ['sym', self.file_zero, self.file_link], } if not dsttype in list(allowed_types.keys()): OUT.die('Oops, webapp-config bug. "dsttype" is ' + dsttype) # Generate handler for file attributes a = allowed_types[dsttype] # For absolute entries the path must match the entry if not relative: path = entry OUT.debug('Adding entry', 7) # report if pretending if self.__p: OUT.info(' pretending to add: ' + ' '.join( [dsttype, str(int(relative)), ctype, '"' + path + '"'])) else: # Only the path is enclosed in quotes, NOT the link targets self.__content[entry] = [ a[0], str(int(relative)), ctype, '"' + path + '"', self.file_time(entry), a[1](real_path), a[2](entry) ] if self.__v: msg = path if msg[0] == "/": msg = self.__root + msg msg = self.__re.sub('/', msg) OUT.notice('>>> ' + a[0] + ' ' * (4 - len(a[0])) + ' (' \ + ctype + ') ' + msg)
def get_canremove(self, entry): ''' Determines if an entry can be removed. Returns a string if the entry may not be removed. The string will describe the reason why the entry should not be removed. In case the entry can be removed nothing will be returned. ''' OUT.debug('Checking if the file can be removed', 6) # Path not found. # Cannot remove -> return False if not os.path.exists(entry) and not os.path.islink(entry): OUT.debug('Did not find the file.', 7) return '!found ' + self.epath(entry) entry_type = self.etype(entry) if entry_type == 'sym': # Should be a link but is not. if not os.path.islink(entry): return '!sym ' + self.epath(entry) # Expected link location does not match with # current target. # this results in leaving broken symlinks behind # if self.file_link(entry) != self.etarget(entry): # return '!target ' + self.epath(entry) if entry_type == 'file': # Expected file, path is not a file if not os.path.isfile(entry): return '!file ' + self.epath(entry) # This is config protected. Do not remove! #if self.eowner(entry)[0:6] == 'config': # return '!cfgpro ' + self.epath(entry) # Modification time does not match. Refuse to remove. if self.file_time(entry) != self.etime(entry): return '!time ' + self.epath(entry) # Content has different hash. Do not remove. if self.file_md5(entry) != self.emd5(entry): return '!sum ' + self.epath(entry) if entry_type == 'dir': # Expected directory, path is not a directory. if not os.path.isdir(entry): return '!dir ' + self.epath(entry) # the rules are simple # # if the directory is empty, it can go # if the directory is not empty, it cannot go # get a directory listing entry_list = os.listdir(entry) # Support for ignoring file. Currently only needed # to enable doctests in the subversion repository if self.ignore: entry_list = [i for i in entry_list if not i in self.ignore] OUT.debug('Remaining files', 7) # Directory not empty # Cannot remove -> return False if entry_list: return '!empty ' + self.epath(entry)
def run_vars(self, server = None): ''' This function exports the necessary variables to the shell environment so that they are accessible within the shell scripts and/or files provided by the ebuild. ''' v_root = self.get_config('vhost_root') v_cgi = self.get_config('g_cgibindir') v_conf = self.get_config('vhost_config_dir') v_err = v_root + '/' + self.get_config('my_errorsbase') v_icon = v_root + '/' + self.get_config('my_iconsbase') g_inst = self.get_config('g_installdir') g_htd = self.get_config('g_htdocsdir') g_orig = self.get_config('g_orig_installdir') vsu = None vsg = None if server: vsu = pwd.getpwuid(server.vhost_server_uid)[0] vsg = grp.getgrgid(server.vhost_server_gid)[0] OUT.debug('Exporting variables', 7) export_map = {'MY_HOSTROOTDIR' : None, 'MY_HTDOCSDIR' : None, 'MY_CGIBINDIR' : None, 'MY_INSTALLDIR' : g_inst, 'MY_ICONSDIR' : None, 'MY_SERVERCONFIGDIR' : None, 'MY_ERRORSDIR' : None, 'MY_SQLSCRIPTSDIR' : None, 'VHOST_ROOT' : None, 'VHOST_HTDOCSDIR' : g_htd, 'VHOST_CGIBINDIR' : v_cgi, 'VHOST_CONFDIR' : v_conf, 'VHOST_ERRORSDIR' : v_err, 'VHOST_ICONSDIR' : v_icon, 'VHOST_HOSTNAME' : None, 'VHOST_SERVER' : None, 'VHOST_APPDIR' : g_orig, 'VHOST_CONFIG_UID' : None, 'VHOST_CONFIG_GID' : None, 'VHOST_SERVER_UID' : vsu, 'VHOST_SERVER_GID' : vsg, 'VHOST_DEFAULT_UID' : None, 'VHOST_DEFAULT_GID' : None, 'VHOST_PERMS_SERVEROWNED_DIR' : None, 'VHOST_PERMS_SERVEROWNED_FILE' : None, 'VHOST_PERMS_CONFIGOWNED_DIR' : None, 'VHOST_PERMS_CONFIGOWNED_FILE' : None, 'VHOST_PERMS_DEFAULTOWNED_DIR' : None, 'VHOST_PERMS_VIRTUALOWNED_FILE': None, 'VHOST_PERMS_INSTALLDIR' : None, 'ROOT' : self.__root, 'PN' : None, 'PVR': None} result = {} for i in list(export_map.keys()): value = export_map[i] if not value: value = self.get_config(i.lower()) os.putenv(i, str(value)) result[i] = str(value) return result
def read(self): ''' Reads the contents database. ''' dbpath = self.appdb() if not dbpath or not os.access(dbpath, os.R_OK): OUT.die('Content file ' + dbpath + ' is missing or not accessibl' 'e!') content = open(dbpath).readlines() for i in content: i = i.strip() rfn = re.compile('"(.*)"') rfs = rfn.search(i) if not rfs: ok = False else: fn = rfs.group(1) i = rfn.sub('', i) line_split = i.split(' ') line_split[3] = fn OUT.debug('Adding content line', 10) ok = True if len(line_split) < 6: ok = False OUT.warn('Content file ' + dbpath + ' has an invalid line' ':\n' + i + '\nNot enough entries.') if ok and not line_split[0] in ['file', 'sym', 'dir']: ok = False OUT.warn('Content file ' + dbpath + ' has an invalid line' ':\n' + i + '\nInvalid file type: ' + line_split[0]) if ok and not line_split[1] in ['0', '1']: ok = False OUT.warn('Content file ' + dbpath + ' has an invalid line' ':\n' + i + '\nInvalid relative flag: ' + line_split[1]) if ok and not line_split[2] in [ 'virtual', 'server-owned', 'config-owned', 'default-owned', 'config-server-owned', # Still need that in case an # application was installed # with w-c-1.11 'root-owned' ]: ok = False OUT.warn('Content file ' + dbpath + ' has an invalid line' ':\n' + i + '\nInvalid owner: ' + line_split[2]) if ok and line_split[0] == 'sym' and len(line_split) == 6: OUT.warn('Content file ' + dbpath + ' has an invalid line' ':\n' + i + '\nMissing link target! ') if len(line_split) == 6: line_split.append('') # I think this could happen if the link target contains # spaces # -- wrobel if len(line_split) > 7: line_split = line_split[0:6] \ + [' '.join(line_split[6:])] if ok: if line_split[1] == '0': self.__content[line_split[3]] = line_split else: self.__content[self.__installdir + '/' + line_split[3]] = line_split else: OUT.warn('Invalid line in content file (' + i + '). Ignor' 'ing!')
def run_vars(self, server=None): ''' This function exports the necessary variables to the shell environment so that they are accessible within the shell scripts and/or files provided by the ebuild. ''' v_root = self.get_config('vhost_root') v_cgi = self.get_config('g_cgibindir') v_conf = self.get_config('vhost_config_dir') v_err = v_root + '/' + self.get_config('my_errorsbase') v_icon = v_root + '/' + self.get_config('my_iconsbase') g_inst = self.get_config('g_installdir') g_htd = self.get_config('g_htdocsdir') g_orig = self.get_config('g_orig_installdir') vsu = None vsg = None if server: vsu = pwd.getpwuid(server.vhost_server_uid)[0] vsg = grp.getgrgid(server.vhost_server_gid)[0] OUT.debug('Exporting variables', 7) export_map = { 'MY_HOSTROOTDIR': None, 'MY_HTDOCSDIR': None, 'MY_CGIBINDIR': None, 'MY_INSTALLDIR': g_inst, 'MY_ICONSDIR': None, 'MY_SERVERCONFIGDIR': None, 'MY_ERRORSDIR': None, 'MY_SQLSCRIPTSDIR': None, 'VHOST_ROOT': None, 'VHOST_HTDOCSDIR': g_htd, 'VHOST_CGIBINDIR': v_cgi, 'VHOST_CONFDIR': v_conf, 'VHOST_ERRORSDIR': v_err, 'VHOST_ICONSDIR': v_icon, 'VHOST_HOSTNAME': None, 'VHOST_SERVER': None, 'VHOST_APPDIR': g_orig, 'VHOST_CONFIG_UID': None, 'VHOST_CONFIG_GID': None, 'VHOST_SERVER_UID': vsu, 'VHOST_SERVER_GID': vsg, 'VHOST_DEFAULT_UID': None, 'VHOST_DEFAULT_GID': None, 'VHOST_PERMS_SERVEROWNED_DIR': None, 'VHOST_PERMS_SERVEROWNED_FILE': None, 'VHOST_PERMS_CONFIGOWNED_DIR': None, 'VHOST_PERMS_CONFIGOWNED_FILE': None, 'VHOST_PERMS_DEFAULTOWNED_DIR': None, 'VHOST_PERMS_VIRTUALOWNED_FILE': None, 'VHOST_PERMS_INSTALLDIR': None, 'ROOT': self.__root, 'PN': None, 'PVR': None } result = {} for i in list(export_map.keys()): value = export_map[i] if not value: value = self.get_config(i.lower()) os.putenv(i, str(value)) result[i] = str(value) return result
def get_canremove(self, entry): ''' Determines if an entry can be removed. Returns a string if the entry may not be removed. The string will describe the reason why the entry should not be removed. In case the entry can be removed nothing will be returned. >>> import os.path >>> here = os.path.dirname(os.path.realpath(__file__)) Trying to remove the contents: >>> a = Contents(here + '/tests/testfiles/contents/app/', ... package = 'test', version = '1.0') >>> a.read() >>> a.ignore += ['.svn'] >>> for i in a.get_directories(): ... a.get_canremove(i) '!dir test7' '!empty dir1' >>> for i in a.get_files(): ... a.get_canremove(i) '!time test2' '!time test4' '!found test6' '!sym dir3' '!file dir4' # Disabled #'!target test3' ''' OUT.debug('Checking if the file can be removed', 6) # Path not found. # Cannot remove -> return False if not os.path.exists(entry) and not os.path.islink(entry): OUT.debug('Did not find the file.', 7) return '!found ' + self.epath(entry) entry_type = self.etype(entry) if entry_type == 'sym': # Should be a link but is not. if not os.path.islink(entry): return '!sym ' + self.epath(entry) # Expected link location does not match with # current target. # this results in leaving broken symlinks behind # if self.file_link(entry) != self.etarget(entry): # return '!target ' + self.epath(entry) if entry_type == 'file': # Expected file, path is not a file if not os.path.isfile(entry): return '!file ' + self.epath(entry) # This is config protected. Do not remove! #if self.eowner(entry)[0:6] == 'config': # return '!cfgpro ' + self.epath(entry) # Modification time does not match. Refuse to remove. if self.file_time(entry) != self.etime(entry): return '!time ' + self.epath(entry) # Content has different hash. Do not remove. if self.file_md5(entry) != self.emd5(entry): return '!sum ' + self.epath(entry) if entry_type == 'dir': # Expected directory, path is not a directory. if not os.path.isdir(entry): return '!dir ' + self.epath(entry) # the rules are simple # # if the directory is empty, it can go # if the directory is not empty, it cannot go # get a directory listing entry_list = os.listdir(entry) # Support for ignoring file. Currently only needed # to enable doctests in the subversion repository if self.ignore: entry_list = [i for i in entry_list if not i in self.ignore] OUT.debug('Remaining files', 7) # Directory not empty # Cannot remove -> return False if entry_list: return '!empty ' + self.epath(entry)
def get_protectedname(self, destination, filename): ''' Woh. Somewhere, we've decided that we're trying to overwrite a file that we really want to save. So, we need a new name for the file that we want to install - which is where we come in. NOTE: The filename that we produce is compatible with Gentoo's etc-update tool. This is deliberate. Inputs: destination - the directory that the file is being installed into filename - the original name of the file Let's test the code on some examples: >>> import os.path >>> here = os.path.dirname(os.path.realpath(__file__)) >>> a = Protection('','horde','3.0.5','portage') >>> a.get_protectedname(here + '/tests/testfiles/protect/empty', ... 'test')#doctest: +ELLIPSIS '.../tests/testfiles/protect/empty//._cfg0000_test' >>> a.get_protectedname(here + '/tests/testfiles/protect/simple', ... 'test')#doctest: +ELLIPSIS '.../tests/testfiles/protect/simple//._cfg0001_test' >>> a.get_protectedname(here + '/tests/testfiles/protect/complex', ... 'test')#doctest: +ELLIPSIS '.../tests/testfiles/protect/complex//._cfg0801_test' ''' my_file = os.path.basename(filename) my_filedir = destination + '/' + os.path.dirname(filename) # find the highest numbered protected file that already # exists, and increment it by one entries = os.listdir(my_filedir) OUT.debug('Identifying possible file number', 7) numbers = [] prefix = self.protect_prefix rep = re.compile(prefix.replace('.','\.') + '(\d{4})_') for i in entries: rem = rep.match(i) if rem: numbers.append(int(rem.group(1))) if numbers: max_n = max(numbers) + 1 else: max_n = 0 return my_filedir + '/%s%.4d_%s' % (prefix, max_n, my_file)
def install(self, upgrade=False): self.config_protected_dirs = [] OUT.debug('Basic server install', 7) # The root of the virtual install location needs to exist if not os.path.isdir(self.__destd) and not self.__p: OUT.debug('Directory missing', 7) dir = self.__destd dirs = [] while dir != EPREFIX + '/': dirs.insert(0, dir) dir = os.path.dirname(dir) a = self.__perm['dir']['install-owned'][2]('0755') OUT.debug('Strange') # Create the directories for i in dirs: if not os.path.isdir(i): os.mkdir(i) os.chmod(i, self.__perm['dir']['install-owned'][2]('0755')) os.chown(i, self.__perm['dir']['install-owned'][0], self.__perm['dir']['install-owned'][1]) if self.__v: OUT.info(' Creating installation directory: ' + i) # Create the handler for installing self.__flags['relative'] = True wa = WebappAdd(self.__sourced, self.__destd, self.__perm, self.__handler, self.__flags) self.__add = wa OUT.info('Installing ' + self.__ws.package_name() + '...') # we need to create the directories to place our files in # and we need to copy in the files OUT.info(' Creating required directories', 1) OUT.info(' Linking in required files', 1) OUT.info(' This can take several minutes for larger apps', 1) self.__add.mkdirs() self.config_protected_dirs += self.__add.config_protected_dirs # Create the second handler for installing the root files self.__flags['relative'] = False wa = WebappAdd(self.__hostroot, self.__vhostroot, self.__perm, self.__handler, self.__flags) self.__add = wa self.__add.mkdirs() self.config_protected_dirs += self.__add.config_protected_dirs OUT.info(' Files and directories installed', 1) self.__dotconfig.write( self.__ws.category, self.__ws.pn, self.__ws.pvr, self.__flags['host'], self.__flags['orig'], str(self.__perm['file']['config-owned'][0]) + ':' + str(self.__perm['file']['config-owned'][1]), ) self.__db.add(self.__destd, self.__perm['file']['config-owned'][0], self.__perm['file']['config-owned'][1]) # run the hooks self.__ebuild.run_hooks('install', self) # show the post-installation instructions if not upgrade: self.__ebuild.show_postinst(self) else: self.__ebuild.show_postupgrade(self) # to finish, we need to tell the user if they need to run # etc-update or not if self.config_protected_dirs: # work out whether this directory is part of the # CONFIG_PROTECT list or not self.__protect.how_to_update(self.config_protected_dirs) self.__content.write() # and we're done OUT.info('Install completed - success', 1)
def mkfile(self, filename): ''' This is what we are all about. No more games - lets take a file from the master image of the web-based app, and make it available inside the install directory. filename - name of the file ''' OUT.debug('Creating file', 6) dst_name = self.__destd + '/' + filename file_type = self.__ws.filetype(self.__sourced + '/' + filename) OUT.debug('File type determined', 7) # are we overwriting an existing file? OUT.debug('Check for existing file', 7) if os.path.exists(dst_name): OUT.debug('File in the way!', 7) my_canremove = True # o-oh - we're going to be overwriting something that already # exists # If we are upgrading, check if the file can be removed if self.__u: my_canremove = self.__remove.remove(self.__destd, filename) # Config protected file definitely cannot be removed elif file_type[0:6] == 'config': my_canremove = False if not my_canremove: # not able to remove the file # or # file is config-protected dst_name = self.__protect.get_protectedname( self.__destd, filename) OUT.notice('^o^ hiding ' + filename) self.config_protected_dirs.append(self.__destd + '/' + os.path.dirname(filename)) OUT.debug('Hiding config protected file', 7) else: # it's a file we do not know about - so get rid # of it anyway # # this behaviour here *is* by popular request # personally, I'm not comfortable with it -- Stuart if not self.__p: if os.path.isdir(dst_name): os.rmdir(dst_name) else: os.unlink(dst_name) else: OUT.info(' would have removed "' + dst_name + '" s' 'ince it is in the way for the current instal' 'l. It should not be present in that location' '!') # if we get here, we can get on with the business of making # the file available (user, group, perm) = self.__perm['file'][file_type] my_contenttype = '' src_name = self.__ws.appdir() + '/' + self.__sourced + '/' + filename # Fix the paths src_name = re.compile('/+').sub('/', src_name) dst_name = re.compile('/+').sub('/', dst_name) OUT.debug('Creating File', 7) # this is our default file type # # we link in (soft and hard links are supported) # if we're allowed to # # some applications (/me points at PHP scripts) # won't run if symlinked in. # so we now support copying files in too # # default behaviour is to hard link (if we can), and # to copy if we cannot # # if the user wants symlinks, then the user has to # use the new '--soft' option if file_type == 'virtual' or os.path.islink(src_name): if self.__link_type == 'soft': try: OUT.debug('Trying to softlink', 8) if not self.__p: if self.__v: print("\n>>> SOFTLINKING FILE: ") print(">>> Source: " + src_name + "\n>>> Destination: " + dst_name + "\n") os.symlink(src_name, dst_name) my_contenttype = 'sym' except Exception as e: if self.__v: OUT.warn('Failed to softlink (' + str(e) + ')') elif self.__link_type == 'copy': try: OUT.debug('Trying to copy files directly', 8) if not self.__p: if self.__v: print("\n>>> COPYING FILE: ") print(">>> Source: " + src_name + "\n>>> Destination: " + dst_name + "\n") shutil.copy(src_name, dst_name) my_contenttype = 'file' except Exception as e: if self.__v: OUT.warn('Failed to copy (' + str(e) + ')') elif os.path.islink(src_name): try: OUT.debug('Trying to copy symlink', 8) if not self.__p: if self.__v: print("\n>>> SYMLINK COPY: ") print(">>> Source: " + src_name + "\n>>> Destination: " + dst_name + "\n") os.symlink(os.readlink(src_name), dst_name) my_contenttype = 'sym' except Exception as e: if self.__v: OUT.warn('Failed copy symlink (' + str(e) + ')') else: try: OUT.debug('Trying to hardlink', 8) if not self.__p: if self.__v: print("\n>>> HARDLINKING FILE: ") print(">>> Source: " + src_name + "\n>>> Destination: " + dst_name + "\n") os.link(src_name, dst_name) my_contenttype = 'file' except Exception as e: if self.__v: OUT.warn('Failed to hardlink (' + str(e) + ')') if not my_contenttype: if not self.__p: if self.__v: print("\n>>> COPYING FILE: ") print(">>> Source: " + src_name + "\n>>> Destination: " + dst_name + "\n") shutil.copy(src_name, dst_name) my_contenttype = 'file' if not self.__p and not os.path.islink(src_name): old_perm = os.stat(src_name)[stat.ST_MODE] & 511 os.chown(dst_name, user, group) os.chmod(dst_name, perm(old_perm)) self.__content.add(my_contenttype, file_type, self.__destd, filename, dst_name, self.__relative)