Skip to content

Commit

Permalink
fix: support alpine linux with musl libc and fuse 3.16
Browse files Browse the repository at this point in the history
Alpine linux uses musl libc platform which has different macros for device major and minor numbers.
Additionally Fuse > 3.12 has ABI changes which mean Fuse 3 on latest Ubuntu and Alpine were broken.

Resolves: #26
Resolves: #27
  • Loading branch information
lwoggardner authored Oct 26, 2024
1 parent 64ae66e commit 65d362d
Show file tree
Hide file tree
Showing 24 changed files with 196 additions and 72 deletions.
11 changes: 11 additions & 0 deletions .github/scripts/pre_install_alpine.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/ash

echo "PreInstall Alpine Linux for FUSE_PKG=${FUSE_PKG}"

#sudo chmod 666 /dev/fuse && ls -l /dev/fuse
#sudo chown root:$USER /etc/fuse.conf && ls -l /etc/fuse.conf

# Build lock file for this platform
if [ ! -f Gemfile.lock ]; then
bundle lock
fi
46 changes: 44 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,53 @@ on:
- "!main"
- "!release-please-action"
jobs:
build-alpine:
if: ${{ !contains(github.event.head_commit.message, '[no build-alpine]') }}
strategy:
matrix:
fuse_pkg: ['fuse','fuse3']
runs-on: ubuntu-latest
env:
TERM: color # Pretty spec output
GIT_REF: ${{ inputs.ref || github.ref }}
GIT_BASE_REF: ${{ github.base_ref || 'undefined' }}
BUNDLE_PATH: vendor/bundle.alpine
steps:
- uses: actions/checkout@v3
with:
ref: ${{ env.GIT_REF }}
- name: Setup Alpine Linux
uses: jirutka/setup-alpine@v1
with:
packages: >
build-base
${{ matrix.fuse_pkg }}
ruby
ruby-dev
ruby-bundler
- name: Preinstall
env:
FUSE_PKG: ${{ matrix.fuse_pkg }}
run: .github/scripts/pre_install_alpine.sh
shell: alpine.sh {0}
- name: Cache gems
uses: actions/cache@v4
with:
path: ${{ env.BUNDLE_PATH }}
key: alpine-ruby-gems${{ hashFiles('**/Gemfile.lock','/etc/os-release') }}
restore-keys: |
alpine-ruby-gems-
- name: Run tests
run: bundle install && bundle exec rake test # only test, no rubocop etc..
shell: alpine.sh {0}

build:
if: ${{ !contains(github.event.head_commit.message, '[no build]') }}

strategy:
matrix:
ruby-version: ['2.7' , '3.0', '3.1', '3.2']
os: ['ubuntu-latest'] # mac-os when/if Macfuse can be deployed on CI images
ruby-version: ['2.7','3.2','3.3']
os: ['ubuntu-latest','ubuntu-24.04'] # mac-os when/if Macfuse can be deployed on CI images
fuse_pkg: ['fuse','fuse3']

runs-on: ${{ matrix.os }}
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ AllCops:
NewCops: enable
Exclude:
- 'spec/**/*.rb'
- 'vendor/bundle/**/*'
- 'vendor/**/*'

Gemspec/DevelopmentDependencies:
EnforcedStyle: gemspec
Expand Down
6 changes: 3 additions & 3 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure('2') do |config|
%w[focal impish].each do |dist|
%w[jammy].each do |dist|
%w[fuse fuse3].each do |fuse_ver|
config.vm.define "#{fuse_ver}-#{dist}", auto_start: false do |dist_config|
dist_config.vm.box = "ubuntu/#{dist}64"
dist_config.vm.provision :shell, inline: <<-SHELL
apt-get update -y
apt-get install -y gnupg2 gcc make ruby ruby-dev libffi-dev ruby-bundler #{fuse_ver} lib#{fuse_ver}-dev
apt-get install -y gnupg2 gcc make libffi-dev #{fuse_ver} lib#{fuse_ver}-dev
SHELL
dist_config.vm.provision :shell, path: 'vagrant/install-rvm.sh', args: 'stable', privileged: false
# TODO: extract rubies from github workflow
%w[2.7].each do |v|
%w[3.3.5].each do |v|
dist_config.vm.provision :shell, path: 'vagrant/install-ruby.sh', args: [v, 'bundler'], privileged: false
end
dist_config.vm.provision :shell, inline: 'cd /vagrant; bundle install', privileged: false
Expand Down
7 changes: 1 addition & 6 deletions lib/ffi/accessors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -427,18 +427,13 @@ def ffi_attr_fill(from, writers: self.class.ffi_attr_writers, **args)
if from.is_a?(Hash)
args.merge!(from)
else
include_all = from.is_a?(Stat.class)
writers.each do |w|
r = w[0..-2] # strip trailing =
send(w, from.send(r)) if from.respond_to?(r, include_all)
send(w, from.public_send(r)) if from.respond_to?(r)
end
end
args.transform_keys! { |k| :"#{k}=" }

if (invalid = args.keys - writers).any?
raise ArgumentError, "Cannot fill #{self.class.name} using #{invalid}"
end

args.each_pair { |k, v| send(k, v) }
self
end
Expand Down
40 changes: 33 additions & 7 deletions lib/ffi/devt.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

require 'ffi'

module FFI
# Calculate major/minor device numbers for use with mknod etc..
# @see makedev(3)
Expand All @@ -27,10 +26,13 @@ module Device
# @return [Integer] the minor component of dev
attach_function :minor, :"#{prefix}minor", [:int], :int
rescue FFI::NotFoundError
case Platform::NAME
when 'x86_64-darwin'
# From https://github.com/golang/go/issues/8106 these functions are not defined on Darwin.
class << self

class << self
# rubocop:disable Naming/MethodParameterName
case RUBY_PLATFORM
when 'x86_64-darwin'
# From https://github.com/golang/go/issues/8106 these functions are not defined on Darwin.

# define major(x) ((int32_t)(((u_int32_t)(x) >> 24) & 0xff))
def major(dev)
(dev >> 24) & 0xff
Expand All @@ -45,9 +47,33 @@ def minor(dev)
def makedev(major, minor)
(major << 24) | minor
end

when 'x86_64-linux-musl' # eg alpine linux
# #define major(x) \
# ((unsigned)( (((x)>>31>>1) & 0xfffff000) | (((x)>>8) & 0x00000fff) ))
def major(x)
((x >> 31 >> 1) & 0xfffff000) | ((x >> 8) & 0x00000fff)
end

# #define minor(x) \
# ((unsigned)( (((x)>>12) & 0xffffff00) | ((x) & 0x000000ff) ))
#
def minor(x)
((x >> 12) & 0xffffff00) | (x & 0x000000ff)
end

# #define makedev(x,y) ( \
# (((x)&0xfffff000ULL) << 32) | \
# (((x)&0x00000fffULL) << 8) | \
# (((y)&0xffffff00ULL) << 12) | \
# (((y)&0x000000ffULL)) )
def makedev(x, y)
((x & 0xfffff000) << 32) | ((x & 0x00000fff) << 8) | ((y & 0xffffff00) << 12) | (y & 0x000000ff)
end
else
raise
end
else
raise
# rubocop:enable Naming/MethodParameterName
end
end
end
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/adapter/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def fuse_wrappers(*wrappers)
)
return wrappers unless defined?(super)

super(*wrappers)
super
end

module_function
Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/adapter/debug.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def fuse_wrappers(*wrappers)
wrappers << proc { |fm, *args, &b| debug_callback(fm, *args, **conf, &b) } if debug?
return wrappers unless defined?(super)

super(*wrappers)
super
end

# @!visibility private
Expand Down
14 changes: 7 additions & 7 deletions lib/ffi/libfuse/adapter/fuse2_compat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ module Prepend
if FUSE_MAJOR_VERSION == 2
# @!visibility private
def getattr(path, stat, fuse_file_info = nil)
super(path, stat, fuse_file_info)
super
end

def truncate(path, size, fuse_file_info = nil)
super(path, size, fuse_file_info)
super
end

def init(fuse_conn_info, fuse_config = nil)
super(fuse_conn_info, fuse_config)
super
end

def chown(path, uid, gid, fuse_file_info = nil)
super(path, uid, gid, fuse_file_info)
super
end

def chmod(path, mode, fuse_file_info = nil)
super(path, mode, fuse_file_info)
super
end

def utimens(path, times, fuse_file_info = nil)
super(path, times, fuse_file_info)
super
end

def readdir(path, buffer, filler, offset, fuse_file_info, fuse_readdir_flag = 0)
Expand All @@ -45,7 +45,7 @@ def fuse_respond_to?(fuse_method)
# fgetattr and ftruncate already fallback to the respective basic method
return false if %i[getdir fgetattr ftruncate].include?(fuse_method)

super(fuse_method)
super
end

def fuse_options(args)
Expand Down
14 changes: 7 additions & 7 deletions lib/ffi/libfuse/adapter/fuse3_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@ def getattr(*args)
fi = args.pop
return fgetattr(*args, fi) if fi && fuse_super_respond_to?(:fgetattr)

super(*args)
super
end

def truncate(*args)
fi = args.pop
return ftruncate(*args, fi) if fi && fuse_super_respond_to?(:ftruncate)

super(*args)
super
end

def chown(*args)
args.pop
super(*args)
super
end

def chmod(*args)
args.pop
super(*args)
super
end

# TODO: Fuse3 deprecated flag utime_omit_ok - which meant that UTIME_OMIT and UTIME_NOW are passed through
Expand All @@ -50,14 +50,14 @@ def chmod(*args)
# but there is no way to handle OMIT
def utimens(*args)
args.pop
super(*args) if defined?(super)
super if defined?(super)
end

def init(*args)
args.pop

# TODO: populate FuseConfig with output from fuse_flags/FuseConnInfo where appropriate
super(*args)
super
end

def readdir(*args, &block)
Expand All @@ -71,7 +71,7 @@ def readdir(*args, &block)
proc { |buf, name, stat, off| a.call(buf, name, stat, off, 0) }
end

super(*args, &block)
super
end

def fuse_respond_to(fuse_callback)
Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/adapter/interrupt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def fuse_wrappers(*wrappers)
}
return wrappers unless defined?(super)

super(*wrappers)
super
end

module_function
Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/adapter/pathname.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def fuse_wrappers(*wrappers)
}
return wrappers unless defined?(super)

super(*wrappers)
super
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/adapter/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def utimens(path, times, *fuse3_args)

# Calls super if defined and storing result to protect from GC until {#destroy}
def init(*args)
o = super(*args) if fuse_super_respond_to?(:init)
o = super if fuse_super_respond_to?(:init)
handles << o if o
end

Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/adapter/safe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def fuse_wrappers(*wrappers)
}
return wrappers unless defined?(super)

super(*wrappers)
super
end

# @return [Integer] the default errno to return for rescued errors. ENOTRECOVERABLE unless overridden
Expand Down
4 changes: 2 additions & 2 deletions lib/ffi/libfuse/filesystem/virtual_dir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class VirtualDir < VirtualNode

def initialize(accounting: Accounting.new)
@entries = {}
super(accounting: accounting)
super
end

# @!endgroup
Expand Down Expand Up @@ -157,7 +157,7 @@ def create(path, mode = FuseContext.get.mask(0o644), ffi = nil, &file)
# TODO: Strictly should understand setgid and sticky bits of this dir's mode when creating new files
new_file = file ? file.call(name) : new_file(name)
if entry_fuse_respond_to?(new_file, :create)
new_file.public_send(:create, '/', mode, ffi)
new_file.create('/', mode, ffi)
else
# TODO: generate a sensible device number
entry_send(new_file, :mknod, '/', mode, 0)
Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/filesystem/virtual_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module VirtualFile

# Create an empty synthetic file
def initialize(accounting: nil)
super(accounting: accounting)
super
end

# @!group FUSE Callbacks
Expand Down
2 changes: 1 addition & 1 deletion lib/ffi/libfuse/filesystem/virtual_link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module VirtualLink
include VirtualNode
def initialize(accounting: nil)
@target = target
super(accounting: accounting)
super
end

def readlink(_path, size)
Expand Down
4 changes: 2 additions & 2 deletions lib/ffi/libfuse/fuse3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ def io

private

def native_fuse_loop_mt(max_idle_threads: 10, **_options)
Libfuse.fuse_loop_mt3(@fuse, FuseLoopConfig.new.fill(max_idle_threads: max_idle_threads))
def native_fuse_loop_mt(**options)
Libfuse.fuse_loop_mt3(@fuse, FuseLoopConfig.create(**options))
end

def unmount
Expand Down
4 changes: 3 additions & 1 deletion lib/ffi/libfuse/fuse_cmdline_opts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ class FuseCmdlineOpts < FFI::Struct
show_version: :bool_int,
show_help: :bool_int,
clone_fd: :bool_int,
max_idle_threads: :int
max_idle_threads: :uint
}
spec[:max_threads] = :uint if FUSE_MINOR_VERSION >= 12

layout(spec)

bool, = spec.partition { |_, v| v == :bool_int }
ffi_attr_reader(*bool.map { |k, _| "#{k}?" })

ffi_attr_reader(:max_idle_threads, :mountpoint)
ffi_attr_reader(:max_threads) if FUSE_MINOR_VERSION >= 12
end
end
end
Loading

0 comments on commit 65d362d

Please sign in to comment.