--- a/src/man/pkg.5 Wed Jun 25 14:24:57 2014 -0700
+++ b/src/man/pkg.5 Fri Jun 27 14:29:07 2014 -0700
@@ -1,6 +1,6 @@
'\" te
.\" Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
-.TH pkg 5 "10 Apr 2014" "SunOS 5.12" "Standards, Environments, and Macros"
+.TH pkg 5 "28 May 2014" "SunOS 5.12" "Standards, Environments, and Macros"
.SH NAME
pkg \- Image Packaging System
.SH DESCRIPTION
@@ -145,22 +145,22 @@
.RS 4n
Specifies when and how files are preserved during package operations.
.sp
-When a package is initially installed, if a file delivered by the package has a \fBpreserve\fR attribute defined with any value and the file already exists in the image, the existing file is stored in \fB/var/pkg/lost+found\fR and the packaged file is installed.
+When a package is initially installed, if a file delivered by the package has a \fBpreserve\fR attribute defined with any value except \fBabandon\fR and the file already exists in the image, the existing file is stored in \fB/var/pkg/lost+found\fR and the packaged file is installed.
.sp
When a package is initially installed, if a file delivered by the package has a \fBpreserve\fR attribute defined and the file does not already exist in the image, whether that file is installed depends on the value of the \fBpreserve\fR attribute:
.RS +4
.TP
.ie t \(bu
.el o
-If the value of \fBpreserve\fR is \fBlegacy\fR, the packaged file is not installed.
+If the value of \fBpreserve\fR is \fBlegacy\fR or \fBabandon\fR, the packaged file is not installed.
.RE
.RS +4
.TP
.ie t \(bu
.el o
-If the value of \fBpreserve\fR is not \fBlegacy\fR, the packaged file is installed.
+If the value of \fBpreserve\fR is not \fBlegacy\fR or \fBabandon\fR, the packaged file is installed.
.RE
-When a package is downgraded, if a file delivered by the downgraded version of the package has a \fBpreserve\fR attribute defined with any value and all of the following conditions are true, the file that currently exists in the image is renamed with the extension \fB\&.update\fR, and the file from the downgraded package is installed:
+When a package is downgraded, if a file delivered by the downgraded version of the package has a \fBpreserve\fR attribute defined with any value except \fBabandon\fR and all of the following conditions are true, the file that currently exists in the image is renamed with the extension \fB\&.update\fR, and the file from the downgraded package is installed.
.RS +4
.TP
.ie t \(bu
@@ -188,6 +188,12 @@
.TP
.ie t \(bu
.el o
+If the file delivered by the upgraded version of the package has a \fBpreserve\fR value of \fBabandon\fR in the upgraded package, the new file will not be installed and the existing file will not be modified.
+.RE
+.RS +4
+.TP
+.ie t \(bu
+.el o
If the file does not exist in the image, the new file is installed.
.RE
.RS +4
@@ -238,6 +244,8 @@
.el o
If the file delivered by the upgraded version of the package exists in the image and has a \fBpreserve\fR value of \fBlegacy\fR in both the upgraded package and the currently installed version of the package, the permissions and timestamp (if present) are reset on the existing file.
.RE
+.sp
+When a package is uninstalled, if a \fBfile\fR action delivered by the currently installed version of the package has a \fBpreserve\fR value of \fBabandon\fR and the file exists in the image, the file will not be removed.
.RE
.sp
--- a/src/modules/actions/file.py Wed Jun 25 14:24:57 2014 -0700
+++ b/src/modules/actions/file.py Fri Jun 27 14:29:07 2014 -0700
@@ -118,8 +118,10 @@
self.makedirs(os.path.dirname(final_path),
mode=misc.PKG_DIR_MODE,
fmri=pkgplan.destination_fmri)
- elif not orig and not pkgplan.origin_fmri and \
- "preserve" in self.attrs and os.path.isfile(final_path):
+ elif (not orig and not pkgplan.origin_fmri and
+ "preserve" in self.attrs and
+ self.attrs["preserve"] != "abandon" and
+ os.path.isfile(final_path)):
# Unpackaged editable file is already present during
# initial install; salvage it before continuing.
pkgplan.salvage(final_path)
@@ -161,6 +163,8 @@
old_path = final_path + ".old"
elif pres_type == "renamenew":
final_path = final_path + ".new"
+ elif pres_type == "abandon":
+ return
# If it is a directory (and not empty) then we should
# salvage the contents.
@@ -292,6 +296,9 @@
the preserve attribute is not present, that the hashes
and other attributes of the file match."""
+ if self.attrs.get("preserve") == "abandon":
+ return [], [], []
+
path = self.get_installed_path(img.get_root())
lstat, errors, warnings, info, abort = \
@@ -453,7 +460,8 @@
Returns False if it is, but no preservation is necessary.
Returns True for the normal preservation form. Returns one of
the strings 'renameold', 'renameold.update', 'renamenew',
- or 'legacy' for each of the respective forms of preservation.
+ 'legacy', or 'abandon' for each of the respective forms of
+ preservation.
"""
# If the logic in this function ever changes, all callers will
@@ -471,6 +479,9 @@
# attribute, this will need to be updated.
return
+ if pres_type == "abandon":
+ return pres_type
+
final_path = self.get_installed_path(pkgplan.image.get_root())
# 'legacy' preservation is very different than other forms of
@@ -617,7 +628,7 @@
return True
pres_type = self._check_preserve(orig, pkgplan)
- if pres_type != None and pres_type != True:
+ if pres_type not in (None, True, "abandon"):
# Preserved files only need data if they're being
# changed (e.g. "renameold", etc.).
return True
@@ -640,6 +651,9 @@
# likely overlaid and is moving).
return
+ if self.attrs.get("preserve") == "abandon":
+ return
+
try:
# Make file writable so it can be deleted.
os.chmod(path, stat.S_IWRITE|stat.S_IREAD)
--- a/src/modules/client/imageplan.py Wed Jun 25 14:24:57 2014 -0700
+++ b/src/modules/client/imageplan.py Fri Jun 27 14:29:07 2014 -0700
@@ -2577,6 +2577,10 @@
# Ignore erroneously tagged files.
continue
+ if src.attrs.get("preserve") == "abandon":
+ # preserve=abandon files are never removed.
+ continue
+
entry = [src.attrs["path"]]
save_file = src.attrs.get("save_file")
if save_file:
@@ -2631,7 +2635,10 @@
# We can't rely on _check_preserve for this case
# as there's no existing on-disk file at the
# destination path yet.
- if dest.attrs.get("preserve") != "legacy":
+ dpres_type = dest.attrs.get("preserve")
+ if (dpres_type != "legacy" and
+ dpres_type != "abandon"):
+ # 'abandon' actions are never delivered;
# 'legacy' actions are only delivered if
# we're updating something already
# installed or moving an existing file.
@@ -2669,6 +2676,10 @@
else:
mpath = tpath
+ if pres_type == "abandon":
+ # newly-tagged preserve=abandon files never
+ # delivered.
+ continue
if pres_type == "renameold":
moved.append([mpath, tpath + ".old"])
installed.append(entry)
--- a/src/tests/cli/t_pkg_install.py Wed Jun 25 14:24:57 2014 -0700
+++ b/src/tests/cli/t_pkg_install.py Fri Jun 27 14:29:07 2014 -0700
@@ -2461,6 +2461,21 @@
close
"""
+ presabandon = """
+ open [email protected]
+ add file tmp/preserve1 path=testme mode=0444 owner=root group=root preserve=true
+ close
+ open [email protected]
+ add file tmp/preserve1 path=testme mode=0644 owner=root group=root preserve=abandon
+ close
+ open [email protected]
+ add file tmp/preserve3 path=testme mode=0444 owner=root group=root preserve=abandon
+ close
+ open [email protected]
+ add file tmp/preserve3 path=testme mode=0644 owner=root group=root preserve=true
+ close
+ """
+
renpreserve = """
open [email protected]
add file tmp/preserve1 path=foo1 mode=0644 owner=root group=root preserve=true
@@ -3464,6 +3479,184 @@
self.file_contains("testme.legacy", "preserve1")
self.file_contains("newme", "preserve2")
+ def test_file_preserve_abandon(self):
+ """Verify that preserve=abandon works as expected."""
+
+ install_cmd = "install"
+ self.pkgsend_bulk(self.rurl, self.presabandon)
+ self.image_create(self.rurl)
+
+ # Ensure directory is empty before testing.
+ api_inst = self.get_img_api_obj()
+ img_inst = api_inst.img
+ sroot = os.path.join(img_inst.imgdir, "lost+found")
+ shutil.rmtree(sroot)
+
+ # Verify that unpackaged files will not be salvaged on initial
+ # install if a package being installed delivers the same file
+ # and that the new file will not be installed.
+ self.file_append("testme", "unpackaged")
+ self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
+ self._assertEditables()
+ self.file_contains("testme", "unpackaged")
+ self.assert_(not os.path.exists(os.path.join(sroot, "testme")))
+ self.file_remove("testme")
+ self.pkg("uninstall presabandon")
+
+ # Verify that an initial install of an action with
+ # preserve=abandon will not install the payload of the action.
+ self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
+ self._assertEditables()
+ self.file_doesnt_exist("testme")
+ self.pkg("uninstall presabandon")
+
+ # If an action delivered by the upgraded version of the package
+ # has a preserve=abandon, the new file will not be installed and
+ # the existing file will not be modified.
+
+ # First with no content change ...
+ self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
+ self._assertEditables(
+ installed=['testme'],
+ )
+ self.pkg("update --parsable=0 presabandon@2")
+ self._assertEditables()
+ self.file_contains("testme", "preserve1")
+ # The currently installed version of the package has a preserve
+ # value of abandon, so the file will not be removed.
+ self.pkg("uninstall --parsable=0 presabandon")
+ self._assertEditables()
+ self.file_exists("testme")
+
+ # If an action delivered by the downgraded version of the package
+ # has a preserve=abandon, the new file will not be installed and
+ # the existing file will not be modified.
+ self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
+ self._assertEditables(
+ installed=['testme'],
+ )
+ self.file_contains("testme", "preserve3")
+ self.pkg("verify presabandon")
+ self.pkg("update --parsable=0 presabandon@3")
+ self._assertEditables()
+ self.file_contains("testme", "preserve3")
+ self.pkg("verify presabandon")
+ self.pkg("uninstall --parsable=0 presabandon")
+ self.file_remove("testme")
+
+ # ... and again with content change.
+ self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
+ self.pkg("%s --parsable=0 presabandon@3" % install_cmd)
+ self._assertEditables()
+ self.file_contains("testme", "preserve1")
+ self.pkg("uninstall --parsable=0 presabandon")
+
+ self.pkg("install --parsable=0 presabandon@4")
+ self._assertEditables(
+ installed=['testme'],
+ )
+ self.file_contains("testme", "preserve3")
+ self.pkg("update --parsable=0 presabandon@2")
+ self._assertEditables()
+ self.file_contains("testme", "preserve3")
+ self.pkg("verify presabandon")
+ self.pkg("uninstall --parsable=0 presabandon")
+ self.file_remove("testme")
+
+ # Modify the file locally and upgrade to a version where the
+ # file has a preserve=abandon attribute and the content changes.
+ self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
+ self.file_append("testme", "junk")
+ self.pkg("%s --parsable=0 presabandon@3" % install_cmd)
+ self._assertEditables()
+ self.file_contains("testme", "preserve1")
+ self.file_contains("testme", "junk")
+ self.file_doesnt_exist("testme.old")
+ self.file_doesnt_exist("testme.new")
+ self.pkg("uninstall --parsable=0 presabandon")
+ self.file_remove("testme")
+
+ # Modify the file locally and downgrade to a version where the
+ # file has a preserve=abandon attribute and the content changes.
+ self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
+ self.file_append("testme", "junk")
+ self.file_contains("testme", "preserve3")
+ self.pkg("update --parsable=0 presabandon@2")
+ self._assertEditables()
+ self.file_contains("testme", "preserve3")
+ self.file_contains("testme", "junk")
+ self.file_doesnt_exist("testme.old")
+ self.file_doesnt_exist("testme.new")
+ self.file_doesnt_exist("testme.update")
+ self.pkg("verify presabandon")
+ self.pkg("uninstall --parsable=0 presabandon")
+ self.file_remove("testme")
+
+ # Modify the file locally and upgrade to a version where the
+ # file has a preserve=abandon attribute and just the mode changes.
+ self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
+ self.file_append("testme", "junk")
+ self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
+ self._assertEditables()
+ self.file_contains("testme", "preserve1")
+ self.file_contains("testme", "junk")
+ self.file_doesnt_exist("testme.old")
+ self.file_doesnt_exist("testme.new")
+ self.pkg("verify presabandon")
+ self.pkg("uninstall --parsable=0 presabandon")
+ self.file_remove("testme")
+
+ # Modify the file locally and downgrade to a version where the
+ # file has a preserve=abandon attribute and just the mode changes.
+ self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
+ self.file_append("testme", "junk")
+ self.pkg("update --parsable=0 presabandon@3")
+ self._assertEditables()
+ self.file_contains("testme", "preserve3")
+ self.file_contains("testme", "junk")
+ self.file_doesnt_exist("testme.old")
+ self.file_doesnt_exist("testme.new")
+ self.file_doesnt_exist("testme.update")
+ self.pkg("verify presabandon")
+ self.pkg("uninstall --parsable=0 presabandon")
+ self.file_remove("testme")
+
+ # Remove the file locally and update the package where the
+ # file has a preserve=abandon attribute; this will not replace
+ # the missing file.
+ self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
+ self.file_remove("testme")
+ self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
+ self._assertEditables()
+ self.file_doesnt_exist("testme")
+ self.pkg("uninstall --parsable=0 presabandon")
+
+ # Remove the file locally and downgrade the package where the
+ # file has a preserve=abandon attribute; this will not replace
+ # the missing file.
+ self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
+ self.file_remove("testme")
+ self.pkg("update --parsable=0 presabandon@3")
+ self._assertEditables()
+
+ # Verify that a package with a missing file that is marked with
+ # the preserve=abandon won't cause uninstall failure.
+ self.file_doesnt_exist("testme")
+ self.pkg("uninstall --parsable=0 presabandon")
+
+ # Verify that if the file for an action marked with
+ # preserve=abandon is removed that the package still
+ # verifies.
+ self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
+ self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
+ self.file_remove("testme")
+ self.pkg("verify -v presabandon")
+
+ # Verify that a file removed for an action marked with
+ # preserve=abandon can be reverted.
+ self.pkg("revert testme")
+ self.file_contains("testme", "preserve1")
+
def test_directory_salvage(self):
"""Make sure basic directory salvage works as expected"""