def how_to_update(self, dirs): ''' Instruct the user how to update the application. ''' my_command = self.update_command directories = [] for i in dirs: present = False if directories: for j in directories: if (i == j[:len(i)] or j == i[:len(j)]): present = True break if not present: directories.append(i) my_command_list = '' for i in directories: if not self.dirisconfigprotected(i): my_command_list += 'CONFIG_PROTECT="' + i + '" ' + my_command + '\n' if not my_command_list: my_command_list = my_command OUT.warn( 'One or more files have been config protected\nTo comple' 'te your install, you need to run the following command(s):\n\n' + my_command_list)
def write(self): ''' Write the contents file. ''' dbpath = self.appdb() if not dbpath: OUT.die('No package specified!') self.check_installdir() values = [' '.join(i) for i in list(self.__content.values())] if not self.__p: try: fd = os.open(self.appdb(), os.O_WRONLY | os.O_CREAT, self.__perm(0o600)) os.write(fd, ('\n'.join(values)).encode('utf-8')) os.close(fd) except Exception as e: OUT.warn('Failed to write content file ' + dbpath + '!\n' + 'Error was: ' + str(e)) else: OUT.info('Would have written content file ' + dbpath + '!')
def how_to_update(self, dirs): ''' Instruct the user how to update the application. ''' my_command = self.update_command directories = [] for i in dirs: present = False if directories: for j in directories: if (i == j[:len(i)] or j == i[:len(j)]): present = True break if not present: directories.append(i) my_command_list = '' for i in directories: if not self.dirisconfigprotected(i): my_command_list += 'CONFIG_PROTECT="' + i + '" ' + my_command + '\n' if not my_command_list: my_command_list = my_command OUT.warn('One or more files have been config protected\nTo comple' 'te your install, you need to run the following command(s):\n\n' + my_command_list)
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 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 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 kill(self): ''' Remove the contents file.''' if not self.__p: try: dbpath = self.appdb() self.check_installdir() os.unlink(dbpath) self.__content = {} return True except: OUT.warn('Failed to remove ' + self.appdb() + '!') return False else: OUT.info('Would have removed ' + self.appdb()) return True
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 dirtype(self, directory, parent_type = ''): ''' Inputs: directory - the directory that we need a decision about parent_type - the type of the parent directory returns one of these: server-owned - dir needs to be owned by the webserver user config-owned - dir needs to be owned by the config user config-server-owned - Both the previous cases at the same time server-owned-dir - Directory that contains file/dirs to be owned by the webserver user default-owned - we need a local copy, owned by root NOTE: Use get_filetype(filename) for files NOTE: the user can use --default-dirs on the command-line to change what type default directories are really reported as ''' # remove any whitespace and trailing / directory = self.__fix(directory) # check the cache if directory in self.__cache.keys(): # Check if parent type is recursive if parent_type == 'server-owned-dir': new_type = self.__cache[directory] if new_type == 'config-owned': OUT.die('This version does not support config dirs') if new_type == server-owned: OUT.warn('Configuration error: {} is marked server-owned two times'.format(filename)) return 'server-owned-dir' return self.__cache[directory] # Check if parent type is recursive if parent_type == 'server-owned-dir': return 'server-owned-dir' # unspecified directories are default-owned return self.__default_dirs
def filetype(self, filename, parent_type = ''): ''' Inputs: filename - the file that we need a decision about parent_type - the type of the parent directory returns one of these: server-owned - file needs to be owned by the webserver user (and needs to be a local copy) config-owned - file needs to be owned by the config user (and needs to be a local copy) config-server-owned - Both the previous cases at the same time virtual - we do not need a local copy of the file NOTE: Use get_dirtype(directory) for directories NOTE: the user can use --virtual-files on the command-line to change what type virtual files are really reported as ''' # remove any whitespace and trailing / filename = self.__fix(filename) # check the cache if filename in self.__cache.keys(): # Check if parent type is recursive if parent_type == 'server-owned-dir': new_type = self.__cache[filename] if new_type == 'config-owned': return 'config-server-owned' if new_type == 'server-owned': OUT.warn('Configuration error: {} is marked server-owned twice'.format(filename)) return 'server-owned' return self.__cache[filename] # Check if parent type is recursive if parent_type == 'server-owned-dir': return 'server-owned' # unspecified file (and thus virtual) return self.__virtual_files
def package_installed(full_name, pm): ''' This function identifies installed packages. The Portage part is stolen from gentoolkit. We are not using gentoolkit directly as it doesn't seem to support ${ROOT} ''' if pm == "portage": try: import portage except ImportError as e: OUT.die("Portage libraries not found, quitting:\n%s" % e) try: t = portage.db[portage.root]["vartree"].dbapi.match(full_name) # catch the "ambiguous package" Exception except ValueError as e: if isinstance(e[0], list): t = [] for cp in e[0]: t += portage.db[portage.root]["vartree"].dbapi.match(cp) else: raise ValueError(e) return t elif pm == "paludis": cmd="cave print-best-version '%s'" % (full_name) fi, fo, fe = os.popen3(cmd) fi.close() result_lines = fo.readlines() error_lines = fe.readlines() fo.close() fe.close() if error_lines: for i in error_lines: OUT.warn(i) return ' '.join(result_lines) else: OUT.die("Unknown package manager: " + pm)
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 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 write(self): ''' Write the contents file. A short test: >>> import os.path >>> here = os.path.dirname(os.path.realpath(__file__)) >>> a = Contents(here + '/tests/testfiles/contents/', ... package = 'test', version = '1.0', ... pretend = True) >>> a.read() >>> OUT.color_off() >>> a.write() #doctest: +ELLIPSIS * Would have written content file .../tests/testfiles/contents//.webapp-test-1.0! ''' dbpath = self.appdb() if not dbpath: OUT.die('No package specified!') self.check_installdir() values = [' '.join(i) for i in self.__content.values()] if not self.__p: try: fd = os.open(self.appdb(), os.O_WRONLY | os.O_CREAT, self.__perm(0o600)) os.write(fd, ('\n'.join(values)).encode('utf-8')) os.close(fd) except Exception as e: OUT.warn('Failed to write content file ' + dbpath + '!\n' + 'Error was: ' + str(e)) else: OUT.info('Would have written content file ' + dbpath + '!')
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 prune_database(self, action): ''' Prunes the installs files to ensure no webapp is incorrectly listed as installed. ''' loc = self.read_db() if not loc and self.__v: OUT.die('No virtual installs found!') files = self.list_locations() keys = sorted(loc) if action != 'clean': OUT.warn( 'This is a list of all outdated entries that would be removed: ' ) for j in keys: for i in loc[j]: appdir = i[3].strip() # We check to see if the webapp is installed. if not os.path.exists(appdir + '/.webapp-' + j): if self.__v: OUT.warn('No .webapp file found in dir: ') OUT.warn(appdir) OUT.warn('Assuming webapp is no longer installed.') OUT.warn('Pruning entry from database.') if action == 'clean': for installs in list(files.keys()): contents = open(installs).readlines() new_entries = '' for entry in contents: # Grab all the other entries but the one that # isn't installed. if not re.search('.* ' + appdir + '\\n', entry): new_entries += entry f = open(installs, 'w') f.write(new_entries) f.close() else: OUT.warn(appdir)
def read(self): ''' Reads the contents database. Some content files have been provided for test purposes: >>> import os.path >>> here = os.path.dirname(os.path.realpath(__file__)) This one should succeed: >>> a = Contents(here + '/tests/testfiles/contents/', ... package = 'test', version = '1.0') >>> a.read() >>> a.db_print() file 1 virtual util/icon_browser.php 1124612216 9ffb2ca9ccd2db656b97cd26a1b06010 file 1 config-owned inc/prefs.php 1124612215 ffae752dba7092cd2d1553d04a0f0045 file 1 virtual lib/prefs.php 1124612215 ffae752dba7092cd2d1553d04a0f0045 file 1 virtual signup.php 1124612220 dc838bc375b3d02dafc414f8e71a2aec file 1 server-owned data.php 1117009618 0 sym 1 virtual test 1124612220 dc838bc375b3d02dafc414f8e71a2aec /I link / to a very / strange location dir 1 default-owned util 1117009618 0 dir 1 config-owned inc 1117009618 0 dir 1 default-owned lib 1117009618 0 dir 0 default-owned /var/www/localhost/cgi-bin 1124577741 0 dir 0 default-owned /var/www/localhost/error 1124577740 0 dir 0 default-owned /var/www/localhost/icons 1124577741 0 >>> a.get_directories() #doctest: +ELLIPSIS ['.../contents//util', '.../contents//inc', '.../contents//lib', '/var/www/localhost/cgi-bin', '/var/www/localhost/error', '/var/www/localhost/icons'] This is a corrupted file that checks all fail safes: >>> OUT.color_off() >>> a = Contents(here + '/tests/testfiles/contents/', ... package = 'test', version = '1.1') >>> a.read() #doctest: +ELLIPSIS * Invalid line in content file (dir 1 default-owned). Ignoring! * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line: * dir 1 nobody-owned 1117009618 0 * Invalid owner: nobody-owned * Invalid line in content file (dir 1 nobody-owned 1117009618 0). Ignoring! * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line: * garbage 1 virtual 1124612215 ffae752dba7092cd2d1553d04a0f0045 * Invalid file type: garbage * Invalid line in content file (garbage 1 virtual 1124612215 ffae752dba7092cd2d1553d04a0f0045). Ignoring! * Invalid line in content file (file 1 virtual). Ignoring! * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line: * file 1 virtual * Not enough entries. * Invalid line in content file (file 1 virtual ). Ignoring! * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line: * file 31 config-owned 1124612215 ffae752dba7092cd2d1553d04a0f0045 * Invalid relative flag: 31 * Invalid line in content file (file 31 config-owned 1124612215 ffae752dba7092cd2d1553d04a0f0045). Ignoring! ''' 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', 'server-owned-dir', '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 remove_module(self): OUT.info('Removing SELinux modules') for policy in self.policy_types: if subprocess.call(['semodule', '-s', policy, '-r', self.policy_name]): OUT.warn('Unable to remove {} SELinux module for {} @ {}'.format(policy, self.package_name, self.vhost_hostname))
def how_to_update(self, dirs): ''' Instruct the user how to update the application. >>> OUT.color_off() >>> a = Protection('','horde','3.0.5','portage') >>> a.how_to_update(['/my/strange/htdocs/where/i/installed/x']) * One or more files have been config protected * To complete your install, you need to run the following command(s): * * CONFIG_PROTECT="/my/strange/htdocs/where/i/installed/x" etc-update * >>> a.how_to_update(['/a/','/c/']) * One or more files have been config protected * To complete your install, you need to run the following command(s): * * CONFIG_PROTECT="/a/" etc-update * CONFIG_PROTECT="/c/" etc-update * >>> a.how_to_update(['/a//test3','/a//test3/abc', '/c/']) * One or more files have been config protected * To complete your install, you need to run the following command(s): * * CONFIG_PROTECT="/a//test3" etc-update * CONFIG_PROTECT="/c/" etc-update * Add a virtual config protected directory: >>> a.config_protect += ' /my/strange/htdocs/' >>> a.how_to_update(['/my/strange/htdocs/where/i/installed/x']) * One or more files have been config protected * To complete your install, you need to run the following command(s): * * etc-update ''' my_command = self.update_command directories = [] for i in dirs: present = False if directories: for j in directories: if (i == j[:len(i)] or j == i[:len(j)]): present = True break if not present: directories.append(i) my_command_list = '' for i in directories: if not self.dirisconfigprotected(i): my_command_list += 'CONFIG_PROTECT="' + i + '" ' + my_command + '\n' if not my_command_list: my_command_list = my_command OUT.warn('One or more files have been config protected\nTo comple' 'te your install, you need to run the following command(s):\n\n' + my_command_list)
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)
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.color_off() import os.path here = os.path.dirname(os.path.realpath(__file__)) One for pretending: a = Contents(here + '/tests/testfiles/contents/app/', ... package = 'test', version = '1.0', ... pretend = True) And this one is for real: b = Contents(here + '/tests/testfiles/contents/app/', ... package = 'test', version = '1.0') Pretend to add a file: a.add('file', 'config-owned', ... destination = here + '/tests/testfiles/contents/app/', ... path = '/test1', relative = True) * pretending to add: file 1 config-owned "test1" Lets not pretend this time: b.add('file', 'config-owned', ... destination = here + '/tests/testfiles/contents/app/', ... path = '/test1', relative = True) b.entry(here + '/tests/testfiles/contents/app/test1') #doctest: +ELLIPSIS 'file 1 config-owned "test1" ... d8e8fca2dc0f896fd7cb4cb0031ba249 ' Lets produce an error with a file that does not exist: b.add('file', 'config-owned', ... destination = here + '/tests/testfiles/contents/app/', ... path = '/nothere', relative = True) #doctest: +ELLIPSIS * Cannot access file .../tests/testfiles/contents/app/nothere to add it as installation content. This should not happen! Other file types: b.add('hardlink', 'config-owned', ... destination = here + '/tests/testfiles/contents/app/', ... path = '/test2', relative = True) b.entry(here + '/tests/testfiles/contents/app/test2') #doctest: +ELLIPSIS 'file 1 config-owned "test2" ... d8e8fca2dc0f896fd7cb4cb0031ba249 ' b.add('dir', 'default-owned', ... destination = here + '/tests/testfiles/contents/app/', ... path = '/dir1', relative = True) b.entry(here + '/tests/testfiles/contents/app/dir1') #doctest: +ELLIPSIS 'dir 1 default-owned "dir1" ... 0 ' b.add('dir', 'default-owned', destination = here + '/tests/testfiles/contents/app', ... path = '/dir1', ... relative = False) b.entry(here + '/tests/testfiles/contents/app/dir1') #doctest: +ELLIPSIS 'dir 0 default-owned ".../tests/testfiles/contents/app/dir1" ... 0 ' Q: Is the full link to the target what we want? A: Yes, since the link will still be ok even if we move the directory. b.add('sym', 'virtual', ... destination = here + '/tests/testfiles/contents/app/', ... path = '/test3', relative = True) b.entry(here + '/tests/testfiles/contents/app/test3') #doctest: +ELLIPSIS 'sym 1 virtual "test3" ... 0 .../tests/testfiles/contents/app/test1' b.db_print() #doctest: +ELLIPSIS file 1 config-owned "test1" ... d8e8fca2dc0f896fd7cb4cb0031ba249 file 1 config-owned "test2" ... d8e8fca2dc0f896fd7cb4cb0031ba249 sym 1 virtual "test3" ... 0 .../tests/testfiles/contents/app/test1 dir 0 default-owned ".../tests/testfiles/contents/app/dir1" ... 0 ''' 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 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 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 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 prune_database(self, action): ''' Prunes the installs files to ensure no webapp is incorrectly listed as installed. ''' loc = self.read_db() if not loc and self.__v: OUT.die('No virtual installs found!') files = self.list_locations() keys = sorted(loc) if action != 'clean': OUT.warn('This is a list of all outdated entries that would be removed: ') for j in keys: for i in loc[j]: appdir = i[3].strip() # We check to see if the webapp is installed. if not os.path.exists(appdir+'/.webapp-'+j): if self.__v: OUT.warn('No .webapp file found in dir: ') OUT.warn(appdir) OUT.warn('Assuming webapp is no longer installed.') OUT.warn('Pruning entry from database.') if action == 'clean': for installs in files.keys(): contents = open(installs).readlines() new_entries = '' for entry in contents: # Grab all the other entries but the one that # isn't installed. if not re.search('.* ' + appdir +'\\n', entry): new_entries += entry f = open(installs, 'w') f.write(new_entries) f.close() else: OUT.warn(appdir)
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 install(self, upgrade = False): self.config_protected_dirs = [] OUT.debug('Basic server install', 7) # Create the selinux module if self.__selinux is not None: self.__selinux.create_module(self.__ws.pvr, self.__vhostroot, self.__ws.server_files, self.__ws.server_dirs) # 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() # Warn the user about needed relabelling OUT.warn('You probably need to relabel the new installation, using for' 'example "restorecon -R ' + self.__destd + '"') # and we're done OUT.info('Install completed - success', 1)
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 mkfile(self, filename, current_type): ''' 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, current_type) 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) return file_type