def fsobj_checkpath(self, pkgplan, final_path): """Verifies that the specified path doesn't contain one or more symlinks relative to the image root. Raises an ActionExecutionError exception if path check fails.""" valid_dirs = pkgplan.image.imageplan.valid_directories parent_path = os.path.dirname(final_path) if parent_path in valid_dirs: return real_parent_path = os.path.realpath(parent_path) if parent_path == real_parent_path: valid_dirs.add(parent_path) return fmri = pkgplan.destination_fmri # Now test each component of the parent path until one is found # to be a link. When found, that's the parent that has been # redirected to some other location. idx = 0 while idx < len(parent_path) and parent_path[idx] == os.path.sep: idx += 1 tmp = parent_path[idx - 1:] if pkgplan.image.root != os.path.sep: img_root = pkgplan.image.root.rstrip(os.path.sep) else: img_root = pkgplan.image.root while 1: if tmp == img_root: # No parent directories up to the root were # found to be links, so assume this is ok. valid_dirs.add(parent_path) return if os.path.islink(tmp): # We've found the parent that changed locations. break # Drop the final component. tmp = os.path.split(tmp)[0] parent_dir = tmp parent_target = os.path.realpath(parent_dir) err_txt = _( "Cannot install '%(final_path)s'; parent directory " "%(parent_dir)s is a link to %(parent_target)s. To " "continue, move the directory to its original location and " "try again.") % locals() raise apx.ActionExecutionError(self, details=err_txt, fmri=fmri)
def install(self, pkgplan, orig, retry=False): """client-side method that adds the group use gid from disk if different""" if not have_cfgfiles: # the group action is ignored if cfgfiles is not available return template = self.extract(["groupname", "gid"]) gr = GroupFile(pkgplan.image) cur_attrs = gr.getvalue(template) # check for (wrong) pre-existing definition # if so, rewrite entry using existing defs but new group entry # (XXX this doesn't chown any files on-disk) # else, nothing to do if cur_attrs: if (cur_attrs["gid"] != self.attrs["gid"]): template = cur_attrs template["gid"] = self.attrs["gid"] else: return # XXX needs modification if more attrs are used gr.setvalue(template) try: gr.writefile() except EnvironmentError as e: if e.errno != errno.ENOENT: raise # If we're in the postinstall phase and the # files *still* aren't there, bail gracefully. if retry: txt = _("Group cannot be installed " "without group database files " "present.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) img = pkgplan.image img._groups.add(self) if "gid" in self.attrs: img._groupsbyname[self.attrs["groupname"]] = \ int(self.attrs["gid"])
def install(self, pkgplan, orig, retry=False): """client-side method that adds the user... update any attrs that changed from orig unless the on-disk stuff was changed""" if not have_cfgfiles: # The user action is ignored if cfgfiles is not # available. return username = self.attrs["username"] try: pw, gr, ftp, cur_attrs = \ self.readstate(pkgplan.image, username, lock=True) self.attrs["gid"] = str( pkgplan.image.get_group_by_name(self.attrs["group"])) orig_attrs = {} default_attrs = pw.getdefaultvalues() if orig: # Grab default values from files, extend by # specifics from original manifest for # comparisons sake. orig_attrs.update(default_attrs) orig_attrs["group-list"] = [] orig_attrs["ftpuser"] = "******" orig_attrs.update(orig.attrs) else: # If we're installing a user for the first time, # we want to override whatever value might be # represented by the presence or absence of the # user in the ftpusers file. Remove the value # from the representation of the file so that # the new value takes precedence in the merge. del cur_attrs["ftpuser"] # add default values to new attrs if not present for attr in default_attrs: if attr not in self.attrs: self.attrs[attr] = default_attrs[attr] self.attrs["group-list"] = self.attrlist("group-list") final_attrs = self.merge(orig_attrs, cur_attrs) pw.setvalue(final_attrs) if "group-list" in final_attrs: gr.setgroups(username, final_attrs["group-list"]) ftp.setuser(username, final_attrs.get("ftpuser", "true") == "true") pw.writefile() gr.writefile() ftp.writefile() except EnvironmentError as e: if e.errno != errno.ENOENT: raise # If we're in the postinstall phase and the files # *still* aren't there, bail gracefully. if retry: txt = _("User cannot be installed without user " "database files present.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) img = pkgplan.image img._users.add(self) if "uid" in self.attrs: img._usersbyname[self.attrs["username"]] = \ int(self.attrs["uid"]) raise pkg.actions.ActionRetry(self) except KeyError as e: # cannot find group self.validate() # should raise error if no group in action txt = _("{group} is an unknown or invalid group").format( group=self.attrs.get("group", "None")) raise apx.ActionExecutionError(self, details=txt, fmri=pkgplan.destination_fmri) finally: if "pw" in locals(): pw.unlock()
def install(self, pkgplan, orig, retry=False): """client-side method that adds the group use gid from disk if different""" if not have_cfgfiles: # the group action is ignored if cfgfiles is not # available. return template = self.extract(["groupname", "gid"]) root = pkgplan.image.get_root() try: pw = PasswordFile(root, lock=True) except EnvironmentError as e: if e.errno != errno.ENOENT: raise pw = None gr = GroupFile(pkgplan.image) cur_attrs = gr.getvalue(template) # check for (wrong) pre-existing definition # if so, rewrite entry using existing defs but new group entry # (XXX this doesn't chown any files on-disk) # else, nothing to do if cur_attrs: if "gid" not in self.attrs: self.attrs["gid"] = cur_attrs["gid"] elif self.attrs["gid"] != cur_attrs["gid"]: cur_gid = cur_attrs["gid"] template = cur_attrs; template["gid"] = self.attrs["gid"] # Update the user database with the new gid # as well in case group is someone's primary # group. usernames = pkgplan.image.get_usernames_by_gid( cur_gid) try: for username in usernames: user_entry = pw.getuser( username) user_entry["gid"] = self.attrs[ "gid"] pw.setvalue(user_entry) except Exception as e: if pw: pw.unlock() txt = _("Group cannot be installed. " "Updating related user entries " "failed.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) # Deal with other columns in the group row. # # pkg has no support for the legacy password field # in the group table and thus requires it to be # empty for any pkg delivered group. So there is # explicitly no support for updating # template["password"] since we require it to be empty. # # If the admin has assigned any users to a group that is # delivered as an action we preserve that list without # attempting to validate it in any way. if cur_attrs["user-list"]: template["user-list"] = cur_attrs["user-list"] gr.setvalue(template) try: gr.writefile() if pw: pw.writefile() except EnvironmentError as e: if e.errno != errno.ENOENT: raise # If we're in the postinstall phase and the # files *still* aren't there, bail gracefully. if retry: txt = _("Group cannot be installed " "without group database files " "present.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) img = pkgplan.image img._groups.add(self) if "gid" in self.attrs: img._groupsbyname[self.attrs["groupname"]] = \ int(self.attrs["gid"]) raise pkg.actions.ActionRetry(self) finally: if pw: pw.unlock()
def install(self, pkgplan, orig, retry=False): """client-side method that adds the group use gid from disk if different""" if not have_cfgfiles: # the group action is ignored if cfgfiles is not # available. return template = self.extract(["groupname", "gid"]) root = pkgplan.image.get_root() try: pw = PasswordFile(root, lock=True) except EnvironmentError as e: if e.errno != errno.ENOENT: raise pw = None gr = GroupFile(pkgplan.image) cur_attrs = gr.getvalue(template) # check for (wrong) pre-existing definition # if so, rewrite entry using existing defs but new group entry # (XXX this doesn't chown any files on-disk) # else, nothing to do if cur_attrs: if (cur_attrs["gid"] == self.attrs["gid"]): if pw: pw.unlock() return cur_gid = cur_attrs["gid"] template = cur_attrs template["gid"] = self.attrs["gid"] # Update the user database with the new gid # as well. try: usernames = pkgplan.image.get_usernames_by_gid(cur_gid) for username in usernames: user_entry = pw.getuser(username) user_entry["gid"] = self.attrs["gid"] pw.setvalue(user_entry) except Exception as e: if pw: pw.unlock() txt = _("Group cannot be installed. " "Updating related user entries " "failed.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) # XXX needs modification if more attrs are used gr.setvalue(template) try: gr.writefile() if pw: pw.writefile() except EnvironmentError as e: if e.errno != errno.ENOENT: raise # If we're in the postinstall phase and the # files *still* aren't there, bail gracefully. if retry: txt = _("Group cannot be installed " "without group database files " "present.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) img = pkgplan.image img._groups.add(self) if "gid" in self.attrs: img._groupsbyname[self.attrs["groupname"]] = \ int(self.attrs["gid"]) raise pkg.actions.ActionRetry(self) finally: if pw: pw.unlock()
def install(self, pkgplan, orig, retry=False): """client-side method that adds the user... update any attrs that changed from orig unless the on-disk stuff was changed""" if not have_cfgfiles: # The user action is ignored if cfgfiles is not # available. return username = self.attrs["username"] if "PKG_ACCOUNTS_DEBUG" in os.environ: sys.stderr.write("[D] Processing user " + username + "\n") try: pw, gr, ftp, cur_attrs = \ self.readstate(pkgplan.image, username, lock=True) # Check if the UID is already occupied if "uid" in self.attrs: cur_attrs_uid = pw.getusersbyid(self.attrs["uid"]) if cur_attrs_uid: # ...if the UID definition(s) existed on-disk # note there may be several aliases to same UID if "PKG_ACCOUNTS_DEBUG" in os.environ: sys.stderr.write("[D] User(s) with UID " + self.attrs["uid"] + ": " + str(cur_attrs_uid) + "\n") may_proceed = False existing_names = [] for cur_attrs_iter in cur_attrs_uid: if cur_attrs_iter["username"] == self.attrs[ "username"]: # At least one existing alias matches the user # we would create at the UID we require may_proceed = True # Note: unlike group.py we do not reset cur_attrs # entry here as the one we want to manage/update: # readstate() did a diligent job of finding it by # name and adding correlated data from elsewhere. else: existing_names.append(cur_attrs_iter["username"]) if not may_proceed: # ...on-disk user name(s) all differ from # what we want to make now # TODO: want a 3-way check vs. orig(?) whether # the earlier package version we are upgrading # from delivered that other name, then we can # safely just rename it... or add an alias?.. # Currently we safely bail and ask a human to # handle this "properly". if pw: pw.unlock() txt = _("User named '%s' cannot be installed. " "Requested UID number '%s' is already " "occupied by %s. " "Please evacuate that user to another ID " "first (including FS object ownership), or " "rename (or alias) the account to the new " "packaged name if applicable." % (self.attrs["username"], str( self.attrs["uid"]), existing_names)) if "PKG_ACCOUNTS_COLLISION" in os.environ and os.environ[ "PKG_ACCOUNTS_COLLISION"] == "permit": sys.stderr.write( "WARNING (relaxed because PKG_ACCOUNTS_COLLISION==permit): " + txt + "\n") else: raise apx.ActionExecutionError( self, details=txt, fmri=pkgplan.destination_fmri) if "PKG_ACCOUNTS_DEBUG" in os.environ: sys.stderr.write( "[D] UID " + self.attrs["uid"] + " is available or occupied by expected username\n") self.attrs["gid"] = str( pkgplan.image.get_group_by_name(self.attrs["group"])) orig_attrs = {} default_attrs = pw.getdefaultvalues() if orig: # Grab default values from files, extend by # specifics from original manifest for # comparisons sake. orig_attrs.update(default_attrs) orig_attrs["group-list"] = [] orig_attrs["ftpuser"] = "******" orig_attrs.update(orig.attrs) else: # If we're installing a user for the first time, # we want to override whatever value might be # represented by the presence or absence of the # user in the ftpusers file. Remove the value # from the representation of the file so that # the new value takes precedence in the merge. del cur_attrs["ftpuser"] # add default values to new attrs if not present for attr in default_attrs: if attr not in self.attrs: self.attrs[attr] = default_attrs[attr] self.attrs["group-list"] = self.attrlist("group-list") final_attrs = self.merge(orig_attrs, cur_attrs) pw.setvalue(final_attrs) if "group-list" in final_attrs: gr.setgroups(username, final_attrs["group-list"]) ftp.setuser(username, final_attrs.get("ftpuser", "true") == "true") pw.writefile() gr.writefile() ftp.writefile() except EnvironmentError as e: if e.errno != errno.ENOENT: raise # If we're in the postinstall phase and the files # *still* aren't there, bail gracefully. if retry: txt = _("User cannot be installed without user " "database files present.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) img = pkgplan.image img._users.add(self) if "uid" in self.attrs: img._usersbyname[self.attrs["username"]] = \ int(self.attrs["uid"]) raise pkg.actions.ActionRetry(self) except KeyError as e: # cannot find group self.validate() # should raise error if no group in action txt = _("{group} is an unknown or invalid group").format( group=self.attrs.get("group", "None")) raise apx.ActionExecutionError(self, details=txt, fmri=pkgplan.destination_fmri) finally: if "pw" in locals(): pw.unlock()
def remove_fsobj(self, pkgplan, path): """Shared logic for removing file and link objects.""" # Necessary since removal logic is reused by install. fmri = pkgplan.destination_fmri if not fmri: fmri = pkgplan.origin_fmri try: portable.remove(path) except EnvironmentError, e: if e.errno == errno.ENOENT: # Already gone; don't care. return elif e.errno == errno.EBUSY and os.path.ismount(path): # User has replaced item with mountpoint, or a # package has been poorly implemented. err_txt = _("Unable to remove %s; it is in use " "as a mountpoint. To continue, please " "unmount the filesystem at the target " "location and try again.") % path raise apx.ActionExecutionError(self, details=err_txt, error=e, fmri=fmri) elif e.errno == errno.EBUSY: # os.path.ismount() is broken for lofs # filesystems, so give a more generic # error. err_txt = _("Unable to remove %s; it is in " "use by the system, another process, or " "as a mountpoint.") % path raise apx.ActionExecutionError(self, details=err_txt, error=e, fmri=fmri) elif e.errno == errno.EPERM and \ not stat.S_ISDIR(os.lstat(path).st_mode): # Was expecting a directory in this failure # case, it is not, so raise the error. raise elif e.errno in (errno.EACCES, errno.EROFS): # Raise these permissions exceptions as-is. raise elif e.errno != errno.EPERM: # An unexpected error. raise apx.ActionExecutionError(self, error=e, fmri=fmri) # Attempting to remove a directory as performed above # gives EPERM. First, try to remove the directory, # if it isn't empty, salvage it. try: os.rmdir(path) except OSError, e: if e.errno in (errno.EPERM, errno.EACCES): # Raise permissions exceptions as-is. raise elif e.errno not in (errno.EEXIST, errno.ENOTEMPTY): # An unexpected error. raise apx.ActionExecutionError(self, error=e, fmri=fmri) pkgplan.image.salvage(path)
fs = os.stat(os.path.join(*pathlist[:i])) for i, e in g: p = os.path.join(*pathlist[:i]) try: os.mkdir(p, fs.st_mode) except OSError, e: if e.ernno != errno.ENOTDIR: raise err_txt = _("Unable to create %(path)s; a " "parent directory %(p)s has been replaced " "with a file or link. Please restore the " "parent directory and try again.") % \ locals() raise apx.ActionExecutionError(self, details=err_txt, error=e, fmri=kw.get("fmri", None)) os.chmod(p, fs.st_mode) try: portable.chown(p, fs.st_uid, fs.st_gid) except OSError, e: if e.errno != errno.EPERM: raise # Create the leaf with any requested permissions, substituting # missing perms with the parent's perms. mode = kw.get("mode", fs.st_mode) uid = kw.get("uid", fs.st_uid) gid = kw.get("gid", fs.st_gid) os.mkdir(path, mode)
def install(self, pkgplan, orig, retry=False): """client-side method that adds the group use gid from disk if different""" if not have_cfgfiles: # the group action is ignored if cfgfiles is not # available. return template = self.extract(["groupname", "gid"]) if "PKG_ACCOUNTS_DEBUG" in os.environ: sys.stderr.write("[D] Processing group " + self.attrs["groupname"] + "\n") root = pkgplan.image.get_root() try: pw = PasswordFile(root, lock=True) except EnvironmentError as e: if e.errno != errno.ENOENT: raise pw = None gr = GroupFile(pkgplan.image) # Read group attrs from on-disk data, if any cur_attrs = gr.getvalue(template) # Check if the gid number is occupied if "gid" in self.attrs: # ...if the pkg action required a gid number cur_attrs_gid = gr.getgroupsbyid(self.attrs["gid"]) if cur_attrs_gid: # ...if the GID definition(s) existed on-disk # note there may be several aliases to same GID if "PKG_ACCOUNTS_DEBUG" in os.environ: sys.stderr.write("[D] Group(s) with GID " + self.attrs["gid"] + ": " + str(cur_attrs_gid) + "\n") may_proceed = False existing_names = [] for cur_attrs_iter in cur_attrs_gid: if cur_attrs_iter["groupname"] == self.attrs["groupname"]: # At least one existing alias matches the group # we would create at the GID we require may_proceed = True # Of all possible aliases on this GID, this name # entry is the one we want to manage/update cur_attrs = cur_attrs_iter else: existing_names.append(cur_attrs_iter["groupname"]) if not may_proceed: # ...on-disk group name(s) all differ from # what we want to make now # TODO: want a 3-way check vs. orig(?) whether # the earlier package version we are upgrading # from delivered that other name, then we can # safely just rename it... or add an alias?.. # Currently we safely bail and ask a human to # handle this "properly". if pw: pw.unlock() txt = _("Group named '%s' cannot be installed. " "Requested GID number '%s' is already " "occupied by %s. " "Please evacuate that group to another ID " "first (including FS object ownership), or " "rename (or alias) the account to the new " "packaged name if applicable." % (self.attrs["groupname"], str( self.attrs["gid"]), existing_names)) if "PKG_ACCOUNTS_COLLISION" in os.environ and os.environ[ "PKG_ACCOUNTS_COLLISION"] == "permit": sys.stderr.write( "WARNING (relaxed because PKG_ACCOUNTS_COLLISION==permit): " + txt + "\n") else: raise apx.ActionExecutionError( self, details=txt, fmri=pkgplan.destination_fmri) if "PKG_ACCOUNTS_DEBUG" in os.environ: sys.stderr.write( "[D] GID " + self.attrs["gid"] + " is available or occupied by expected groupname\n") # check for (maybe wrong) pre-existing definition # if so, rewrite entry using existing defs but new group entry # (XXX this doesn't chown any files on-disk) # else, nothing to do if cur_attrs: # ...if the definition existed on-disk if "gid" not in self.attrs: # ...if the pkg action did not require a gid number self.attrs["gid"] = cur_attrs["gid"] elif self.attrs["gid"] != cur_attrs["gid"]: # ...if the pkg action required a gid number and # it differs from on-disk for same group name cur_gid = cur_attrs["gid"] template = cur_attrs template["gid"] = self.attrs["gid"] # Update the user database with the new gid # as well in case group is someone's primary # group. usernames = pkgplan.image.get_usernames_by_gid(cur_gid) try: for username in usernames: user_entry = pw.getuser(username) user_entry["gid"] = self.attrs["gid"] pw.setvalue(user_entry) except Exception as e: if pw: pw.unlock() txt = _("Group cannot be installed. " "Updating related user entries " "failed.") raise apx.ActionExecutionError( self, error=e, details=txt, fmri=pkgplan.destination_fmri) # Deal with other columns in the group row. # # pkg has no support for the legacy password field # in the group table and thus requires it to be # empty for any pkg delivered group. So there is # explicitly no support for updating # template["password"] since we require it to be empty. # # If the admin has assigned any users to a group that is # delivered as an action we preserve that list without # attempting to validate it in any way. if cur_attrs["user-list"]: template["user-list"] = cur_attrs["user-list"] gr.setvalue(template) try: gr.writefile() if pw: pw.writefile() except EnvironmentError as e: if e.errno != errno.ENOENT: raise # If we're in the postinstall phase and the # files *still* aren't there, bail gracefully. if retry: txt = _("Group cannot be installed " "without group database files " "present.") raise apx.ActionExecutionError(self, error=e, details=txt, fmri=pkgplan.destination_fmri) img = pkgplan.image img._groups.add(self) if "gid" in self.attrs: img._groupsbyname[self.attrs["groupname"]] = \ int(self.attrs["gid"]) raise pkg.actions.ActionRetry(self) finally: if pw: pw.unlock()
def install(self, pkgplan, orig): """Client-side method that installs a directory.""" mode = None try: mode = int(self.attrs.get("mode", None), 8) except (TypeError, ValueError): # Mode isn't valid, so let validate raise a more # informative error. self.validate(fmri=pkgplan.destination_fmri) omode = oowner = ogroup = None owner, group = self.get_fsobj_uid_gid(pkgplan, pkgplan.destination_fmri) if orig: try: omode = int(orig.attrs.get("mode", None), 8) except (TypeError, ValueError): # Mode isn't valid, so let validate raise a more # informative error. orig.validate(fmri=pkgplan.origin_fmri) oowner, ogroup = orig.get_fsobj_uid_gid(pkgplan, pkgplan.origin_fmri) path = os.path.normpath(os.path.sep.join(( pkgplan.image.get_root(), self.attrs["path"]))) # Don't allow installation through symlinks. self.fsobj_checkpath(pkgplan, path) # XXX Hack! (See below comment.) if not portable.is_admin(): mode |= stat.S_IWUSR if not orig: try: self.makedirs(path, mode=mode, fmri=pkgplan.destination_fmri) except OSError, e: if e.filename != path: # makedirs failed for some component # of the path. raise fs = os.lstat(path) fs_mode = stat.S_IFMT(fs.st_mode) if e.errno == errno.EROFS: # Treat EROFS like EEXIST if both are # applicable, since we'll end up with # EROFS instead. if stat.S_ISDIR(fs_mode): return raise elif e.errno != errno.EEXIST: raise if stat.S_ISLNK(fs_mode): # User has replaced directory with a # link, or a package has been poorly # implemented. It isn't safe to # simply re-create the directory as # that won't restore the files that # are supposed to be contained within. err_txt = _("Unable to create " "directory %s; it has been " "replaced with a link. To " "continue, please remove the " "link or restore the directory " "to its original location and " "try again.") % path raise apx.ActionExecutionError( self, details=err_txt, error=e, fmri=pkgplan.destination_fmri) elif stat.S_ISREG(fs_mode): # User has replaced directory with a # file, or a package has been poorly # implemented. Salvage what's there, # and drive on. pkgplan.image.salvage(path) os.mkdir(path, mode) elif stat.S_ISDIR(fs_mode): # The directory already exists, but # ensure that the mode matches what's # expected. os.chmod(path, mode)