Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserving permissions prototype #195

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/fpm/command.rb
Expand Up @@ -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 " \
Expand Down
18 changes: 17 additions & 1 deletion 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
Expand Down Expand Up @@ -155,6 +156,7 @@ def initialize
@dependencies = []
@scripts = {}
@config_files = []
@file_metadata = {}

staging_path
build_path
Expand All @@ -178,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
:@config_files, :@staging_path, :@file_metadata, :@fakeroot_environment_path
]
ivars.each do |ivar|
#@logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar),
Expand Down Expand Up @@ -242,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
Expand All @@ -262,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
Expand Down
5 changes: 4 additions & 1 deletion lib/fpm/package/deb.rb
Expand Up @@ -198,7 +198,10 @@ def output(output_path)

# 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, ".")

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
Expand Down
83 changes: 82 additions & 1 deletion lib/fpm/package/dir.rb
Expand Up @@ -3,6 +3,7 @@
require "fileutils"
require "find"
require "socket"
require "etc"

# A directory package.
#
Expand Down Expand Up @@ -75,12 +76,92 @@ def clone(source, destination)
# Copy all files from 'path' into staging_path

Find.find(source) do |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)
record_metadata(file)
end

@logger.debug("Metadata", :metadata => @file_metadata)

setup_fakeroot(destination)
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] = {
'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.
Expand All @@ -99,7 +180,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)
Expand Down