From 194f5181c44f97b8663b9677e8187e576aec85a6 Mon Sep 17 00:00:00 2001 From: Jordan Hagan Date: Mon, 2 Apr 2012 15:23:07 +1200 Subject: [PATCH 1/3] Initial prototype for preserving permissions on files in fpm (dir source and deb target). --- lib/fpm/command.rb | 5 +++++ lib/fpm/package.rb | 3 ++- lib/fpm/package/deb.rb | 22 ++++++++++++++++++++-- lib/fpm/package/dir.rb | 19 ++++++++++++++++++- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/lib/fpm/command.rb b/lib/fpm/command.rb index 215629a530..732d63e2c0 100644 --- a/lib/fpm/command.rb +++ b/lib/fpm/command.rb @@ -145,6 +145,11 @@ class FPM::Command < Clamp::Command File.expand_path(val) # Get the full path to the script end # --before-remove + option "--dir-user", "USER", "(dir source only) the user that will own the " \ + "files in the created package (root by default) or - to preserve current ownership" + option "--dir-group", "GROUP", "(dir source only) the group that will own " \ + "the files in the created package (root by default) or - to preserve current ownership" + parameter "[ARGS] ...", "Inputs to the source package type. For the 'dir' type, this is the files" \ " and directories you want to include in the package. For others, like " \ diff --git a/lib/fpm/package.rb b/lib/fpm/package.rb index af004b8c36..d3e43754a1 100644 --- a/lib/fpm/package.rb +++ b/lib/fpm/package.rb @@ -155,6 +155,7 @@ def initialize @dependencies = [] @scripts = {} @config_files = [] + @file_metadata = {} staging_path build_path @@ -178,7 +179,7 @@ def convert(klass) :@architecture, :@attributes, :@category, :@config_files, :@conflicts, :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer, :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version, - :@config_files, :@staging_path + :@config_files, :@staging_path, :@file_metadata ] ivars.each do |ivar| #@logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar), diff --git a/lib/fpm/package/deb.rb b/lib/fpm/package/deb.rb index 8d09a008f6..6aa7b5e076 100644 --- a/lib/fpm/package/deb.rb +++ b/lib/fpm/package/deb.rb @@ -197,8 +197,7 @@ def output(output_path) write_control_tarball # Tar up the staging_path and call it 'data.tar.gz' - datatar = build_path("data.tar.gz") - safesystem(tar_cmd, "-C", staging_path, "-zcf", datatar, ".") + datatar = build_data_tarball # pack up the .deb, which is just an 'ar' archive with 3 files # the 'debian-binary' file has to be first @@ -210,6 +209,25 @@ def output(output_path) @logger.log("Created deb package", :path => output_path) end # def output + def build_data_tarball + # Get a path to store the tarball at. + datatar = build_path("data.tar") + + # Create an empty tar archive + safesystem(tar_cmd, '-cf', datatar, '--files-from', '/dev/null') + + @file_metadata.each do |file, metadata| + # get a mode we can use with tar + mode = "%o" % (metadata['mode'] & 0777) + safesystem(tar_cmd, "-C", staging_path, '--owner', metadata['owner'], '--group', metadata['group'], '--mode', mode, '-rf', datatar, file) + end + + # This adds .gz to the end of the tar archive + safesystem('gzip', datatar) + + return "#{datatar}.gz" + end # def build_data_tarball + def default_output if iteration "#{name}_#{version}-#{iteration}_#{architecture}.#{type}" diff --git a/lib/fpm/package/dir.rb b/lib/fpm/package/dir.rb index 4023665bfb..b93ec46000 100644 --- a/lib/fpm/package/dir.rb +++ b/lib/fpm/package/dir.rb @@ -3,6 +3,7 @@ require "fileutils" require "find" require "socket" +require "etc" # A directory package. # @@ -75,12 +76,28 @@ def clone(source, destination) # Copy all files from 'path' into staging_path Find.find(source) do |file| + @logger.log("File", :file => file) next if source == file && File.directory?(file) # ignore the directory itself target = File.join(destination, file) copy(file, target) + record_metadata(file) end + + @logger.log("Metadata", :metadata => @file_metadata) end # def clone + private + # Records the metadata of the file for use later in the packaging process. + def record_metadata(file) + stats = File.stat(file) + + @file_metadata[file] = { + 'owner' => Etc.getgrgid(stats.gid).name, + 'group' => Etc.getpwuid(stats.uid).name, + 'mode' => stats.mode + } + end # def record_metadata + # Copy, recursively, from source to destination. # # Files will be hardlinked if possible, but copied otherwise. @@ -99,7 +116,7 @@ def copy(source, destination) begin @logger.debug("Linking", :source => source, :destination => destination) File.link(source, destination) - rescue Errno::EXDEV + rescue Errno::EXDEV, Errno::EPERM # Hardlink attempt failed, copy it instead @logger.debug("Copying", :source => source, :destination => destination) FileUtils.copy_entry(source, destination) From baaada1ca7f4d170fa4f807d1ebaa66d017dc1a1 Mon Sep 17 00:00:00 2001 From: Jordan Hagan Date: Mon, 14 May 2012 15:02:28 +1200 Subject: [PATCH 2/3] Changed logging to debug logging --- lib/fpm/package/dir.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fpm/package/dir.rb b/lib/fpm/package/dir.rb index b93ec46000..e448074060 100644 --- a/lib/fpm/package/dir.rb +++ b/lib/fpm/package/dir.rb @@ -76,14 +76,14 @@ def clone(source, destination) # Copy all files from 'path' into staging_path Find.find(source) do |file| - @logger.log("File", :file => file) + @logger.debug("File", :file => file) next if source == file && File.directory?(file) # ignore the directory itself target = File.join(destination, file) copy(file, target) record_metadata(file) end - @logger.log("Metadata", :metadata => @file_metadata) + @logger.debug("Metadata", :metadata => @file_metadata) end # def clone private From d58220412993500ca8c13e214b7a250b5d7e4e0f Mon Sep 17 00:00:00 2001 From: Jordan Hagan Date: Fri, 18 May 2012 10:36:25 +1200 Subject: [PATCH 3/3] Second prototype for preserving permissions using fakeroot. This method seems to work a lot better than my first prototype. --- lib/fpm/package.rb | 17 +++++++++- lib/fpm/package/deb.rb | 25 +++------------ lib/fpm/package/dir.rb | 70 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 24 deletions(-) diff --git a/lib/fpm/package.rb b/lib/fpm/package.rb index d3e43754a1..8ace3b848a 100644 --- a/lib/fpm/package.rb +++ b/lib/fpm/package.rb @@ -1,6 +1,7 @@ require "fpm/namespace" # local require "fpm/util" # local require "tmpdir" # stdlib +require "tempfile" # stdlib require "backports" # gem 'backports' require "socket" # stdlib, for Socket.gethostname require "shellwords" # stdlib, for Shellwords.escape @@ -179,7 +180,7 @@ def convert(klass) :@architecture, :@attributes, :@category, :@config_files, :@conflicts, :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer, :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version, - :@config_files, :@staging_path, :@file_metadata + :@config_files, :@staging_path, :@file_metadata, :@fakeroot_environment_path ] ivars.each do |ivar| #@logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar), @@ -243,10 +244,17 @@ def build_path(path=nil) end end # def build_path + def fakeroot_environment_path + @fakeroot_environment_path ||= Tempfile.new('fakeroot_environment').path + + return @fakeroot_environment_path + end + # Clean up any temporary storage used by this class. def cleanup cleanup_staging cleanup_build + cleanup_fakeroot_environment end # def cleanup def cleanup_staging @@ -263,6 +271,13 @@ def cleanup_build end end # def cleanup_build + def cleanup_fakeroot_environment + if File.exists?(fakeroot_environment_path) + @logger.debug("Cleaning up fakeroot environment file", :path => fakeroot_environment_path) + FileUtils.rm_r(fakeroot_environment_path) + end + end + # List all files in the staging_path # # The paths will all be relative to staging_path and will not include that diff --git a/lib/fpm/package/deb.rb b/lib/fpm/package/deb.rb index 6aa7b5e076..3f0fc0da47 100644 --- a/lib/fpm/package/deb.rb +++ b/lib/fpm/package/deb.rb @@ -197,7 +197,11 @@ def output(output_path) write_control_tarball # Tar up the staging_path and call it 'data.tar.gz' - datatar = build_data_tarball + datatar = build_path("data.tar.gz") + + fakeroot_command = "fakeroot -i #{fakeroot_environment_path} -s #{fakeroot_environment_path} " + datatar_command = "#{tar_cmd} -C #{staging_path} -zcf #{datatar} ." + safesystem(fakeroot_command + datatar_command) # pack up the .deb, which is just an 'ar' archive with 3 files # the 'debian-binary' file has to be first @@ -209,25 +213,6 @@ def output(output_path) @logger.log("Created deb package", :path => output_path) end # def output - def build_data_tarball - # Get a path to store the tarball at. - datatar = build_path("data.tar") - - # Create an empty tar archive - safesystem(tar_cmd, '-cf', datatar, '--files-from', '/dev/null') - - @file_metadata.each do |file, metadata| - # get a mode we can use with tar - mode = "%o" % (metadata['mode'] & 0777) - safesystem(tar_cmd, "-C", staging_path, '--owner', metadata['owner'], '--group', metadata['group'], '--mode', mode, '-rf', datatar, file) - end - - # This adds .gz to the end of the tar archive - safesystem('gzip', datatar) - - return "#{datatar}.gz" - end # def build_data_tarball - def default_output if iteration "#{name}_#{version}-#{iteration}_#{architecture}.#{type}" diff --git a/lib/fpm/package/dir.rb b/lib/fpm/package/dir.rb index e448074060..e79fe6a35b 100644 --- a/lib/fpm/package/dir.rb +++ b/lib/fpm/package/dir.rb @@ -76,7 +76,7 @@ def clone(source, destination) # Copy all files from 'path' into staging_path Find.find(source) do |file| - @logger.debug("File", :file => file) + @logger.debug("File", :filename => file) next if source == file && File.directory?(file) # ignore the directory itself target = File.join(destination, file) copy(file, target) @@ -84,6 +84,8 @@ def clone(source, destination) end @logger.debug("Metadata", :metadata => @file_metadata) + + setup_fakeroot(destination) end # def clone private @@ -92,12 +94,74 @@ def record_metadata(file) stats = File.stat(file) @file_metadata[file] = { - 'owner' => Etc.getgrgid(stats.gid).name, - 'group' => Etc.getpwuid(stats.uid).name, + 'group' => Etc.getgrgid(stats.gid).name, + 'owner' => Etc.getpwuid(stats.uid).name, 'mode' => stats.mode } end # def record_metadata + private + # We use fakeroot to allow us to preserve file ownership regardless of + # whether or not we are actually running as root + def setup_fakeroot(target) + dir_user = @attributes[:dir_user] + dir_group = @attributes[:dir_group] + + if dir_user.nil? and dir_group.nil? + @logger.debug("default, do nothing as fakeroot defaults to root:root") + return + end + + fakeroot_command = "fakeroot -i #{fakeroot_environment_path} -s #{fakeroot_environment_path} " + + if dir_user == '-' or dir_group == '-' + @file_metadata.each do |file, metadata| + safesystem(fakeroot_command + "chown #{metadata['owner']}.#{metadata['group']} #{target}/#{file}") + end + end + + if dir_user == '-' and dir_group == '-' + @logger.debug("We've already set both the owner and group on all files. Returning.") + return + end + + find_command = "find #{target} " + + if not dir_user.nil? and dir_user != '-' + @logger.debug("Checking if user exists...") + begin + Etc.getpwnam(dir_user) + rescue + raise "Unable to find user '#{dir_user}' on system." + end + + @logger.debug("--dir-user parameter found, setting group on all files to #{dir_user}") + + find_command += "-exec chown #{dir_user} {} \\; " + elsif dir_user.nil? + @logger.debug("--dir-user parameter not passed, defaulting to root") + find_command += "-exec chown root {} \\; " + end + + if not dir_group.nil? and dir_group != '-' + @logger.debug("Checking if group exists...") + begin + Etc.getgrnam(dir_group) + rescue + raise "Unable to find group '#{dir_group}'" + end + + @logger.debug("--dir-group parameter found, setting group on all files to #{dir_group}") + + find_command += "-exec chgrp #{dir_group} {} \\; " + elsif dir_group.nil? + @logger.debug("--dir-group parameter not passed, defaulting to root") + find_command += "-exec chgrp root {} \\; " + end + + safesystem(fakeroot_command + find_command) + end + # Copy, recursively, from source to destination. # # Files will be hardlinked if possible, but copied otherwise.