Skip to content

Commit

Permalink
Remove the need to invoke mknod using FFI.
Browse files Browse the repository at this point in the history
This removes fpm's direct use of FFI and removes `ffi` as a direct
dependency. For #1795

Cases:
* A unix socket.
* A named pipe
* A charDev should now fail (like /dev/tty)
* A blockDev should now fail (like /dev/sda1)

NOTE: In this change, chardev and blockdev support have been removed.
These "copies" previously were just calling `mknod` with identical
mode, basically copying the `mode` from stat(2) to mknod(2).
Exceptions are now thrown for chardev and blockdev.

Test cases:

    # Try to package a named pipe.
    % mkfifo /tmp/z.pipe
    % bundle exec bin/fpm -s dir -t rpm -n example /tmp/z.pipe
    Created package {:path=>"example-1.0-1.x86_64.rpm"}

    % rpm -qlvp example-1.0-1.x86_64.rpm
    prw-rw-r--    1 root     root                        0 Jun 17 22:40 /tmp/z.pipe

    # Create the unix socket
    % nc -lU /tmp/z.sock

    # Package it into an rpm
    % bin/fpm -s dir -t rpm -n example /tmp/z.sock |& less
    {:timestamp=>"2021-06-17T22:33:27.780347-0700", :message=>"Created package", :path=>"example-1.0-1.x86_64.rpm"}

    # Verify the file is of socket type ('s' at beginning of file mode
    % rpm -qlvp example-1.0-1.x86_64.rpm
    srwxrwxr-x    1 root     root                        0 Jun 17 22:33 /tmp/z.sock
  • Loading branch information
jordansissel committed Jun 19, 2021
1 parent 2886ad1 commit a9efa34
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 34 deletions.
5 changes: 0 additions & 5 deletions fpm.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ Gem::Specification.new do |spec|
# Ref: https://github.com/jordansissel/fpm/issues/1592
spec.add_dependency("childprocess", "< 1.0.0") # license: ???

# For calling functions in dynamic libraries
# This is pinned because 1.13.0 requires Ruby 2.3 or later
# Ref: https://github.com/jordansissel/fpm/issues/1708
spec.add_dependency("ffi", "~> 1.12.0") # license: GPL3/LGPL3

spec.add_development_dependency("rake", "~> 10") # license: MIT

# For creating FreeBSD package archives (xz-compressed tars)
Expand Down
50 changes: 21 additions & 29 deletions lib/fpm/util.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
require "fpm/namespace"
require "childprocess"
require "ffi"
require "fileutils"

# Some utility functions
module FPM::Util
extend FFI::Library
ffi_lib FFI::Library::LIBC

# mknod is __xmknod in glibc a wrapper around mknod to handle
# various stat struct formats. See bits/stat.h in glibc source
begin
attach_function :mknod, :mknod, [:string, :uint, :ulong], :int
rescue FFI::NotFoundError
# glibc/io/xmknod.c int __xmknod (int vers, const char *path, mode_t mode, dev_t *dev)
attach_function :xmknod, :__xmknod, [:int, :string, :uint, :pointer], :int
end

# Raised if safesystem cannot find the program to run.
class ExecutableNotFound < StandardError; end

# Raised if a safesystem program exits nonzero
class ProcessFailed < StandardError; end

# Raised when a named pipe cannot be copied due to missing functions in fpm and ruby.
class NamedPipeCannotBeCopied < StandardError; end

# Raised when an attempting to copy a special file such as a block device.
class UnsupportedSpecialFile < StandardError; end

# Is the given program in the system's PATH?
def program_in_path?(program)
# return false if path is not set
Expand Down Expand Up @@ -308,19 +302,6 @@ def tar_cmd_supports_sort_names_and_set_mtime?
return @@tar_cmd_deterministic
end

# wrapper around mknod ffi calls
def mknod_w(path, mode, dev)
rc = -1
case %x{uname -s}.chomp
when 'Linux'
# bits/stat.h #define _MKNOD_VER_LINUX 0
rc = xmknod(0, path, mode, FFI::MemoryPointer.new(dev))
else
rc = mknod(path, mode, dev)
end
rc
end

def copy_metadata(source, destination)
source_stat = File::lstat(source)
dest_stat = File::lstat(destination)
Expand All @@ -346,10 +327,21 @@ def copy_metadata(source, destination)

def copy_entry(src, dst, preserve=false, remove_destination=false)
case File.ftype(src)
when 'fifo', 'characterSpecial', 'blockSpecial', 'socket'
st = File.stat(src)
rc = mknod_w(dst, st.mode, st.dev)
raise SystemCallError.new("mknod error", FFI.errno) if rc == -1
when 'fifo'
if File.respond_to?(:mkfifo)
File.mkfifo(dst)
elsif program_exists?("mkfifo")
safesystem("mkfifo", dst)
else
raise NamedPipeCannotBeCopied("Unable to copy. Cannot find program 'mkfifo' and Ruby is missing the 'File.mkfifo' method: #{src}")
end
when 'socket'
require "socket"
# In 2019, Ruby's FileUtils added this as a way to "copy" a unix socket.
# Reference: https://github.com/ruby/fileutils/pull/36/files
UNIXServer.new(dst).close()
when 'characterSpecial', 'blockSpecial'
raise UnsupportedSpecialFile.new("File is device which fpm doesn't know how to copy (#{File.ftype(src)}): #{src}")
when 'directory'
FileUtils.mkdir(dst) unless File.exists? dst
else
Expand Down

0 comments on commit a9efa34

Please sign in to comment.