boxgrinder-build 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/CHANGELOG +11 -0
  2. data/Manifest +6 -7
  3. data/Rakefile +11 -6
  4. data/bash_completion +37 -0
  5. data/bin/boxgrinder-build +20 -5
  6. data/boxgrinder-build.gemspec +4 -4
  7. data/lib/boxgrinder-build.rb +2 -1
  8. data/lib/boxgrinder-build/appliance.rb +26 -24
  9. data/lib/boxgrinder-build/helpers/augeas-helper.rb +1 -1
  10. data/lib/boxgrinder-build/helpers/ec2-helper.rb +2 -2
  11. data/lib/boxgrinder-build/helpers/guestfs-helper.rb +1 -1
  12. data/lib/boxgrinder-build/helpers/s3-helper.rb +2 -2
  13. data/lib/boxgrinder-build/managers/plugin-manager.rb +1 -1
  14. data/lib/boxgrinder-build/plugins/delivery/ebs/ebs-plugin.rb +1 -1
  15. data/lib/boxgrinder-build/plugins/delivery/elastichosts/elastichosts-plugin.rb +2 -2
  16. data/lib/boxgrinder-build/plugins/delivery/openstack/openstack-plugin.rb +3 -3
  17. data/lib/boxgrinder-build/plugins/delivery/s3/s3-plugin.rb +16 -10
  18. data/lib/boxgrinder-build/plugins/os/centos/centos-plugin.rb +1 -1
  19. data/lib/boxgrinder-build/plugins/os/fedora/fedora-plugin.rb +1 -1
  20. data/lib/boxgrinder-build/plugins/os/rhel/rhel-plugin.rb +1 -1
  21. data/lib/boxgrinder-build/plugins/os/rpm-based/kickstart.rb +5 -1
  22. data/lib/boxgrinder-build/plugins/os/rpm-based/rpm-based-os-plugin.rb +8 -3
  23. data/lib/boxgrinder-build/plugins/os/rpm-based/rpm-dependency-validator.rb +1 -1
  24. data/lib/boxgrinder-build/plugins/os/sl/sl-plugin.rb +1 -1
  25. data/lib/boxgrinder-build/plugins/platform/ec2/ec2-plugin.rb +1 -1
  26. data/lib/boxgrinder-build/util/concurrent/get_set.rb +46 -0
  27. data/lib/boxgrinder-build/util/permissions/fs-monitor.rb +182 -0
  28. data/lib/boxgrinder-build/util/permissions/fs-observer.rb +82 -0
  29. data/lib/boxgrinder-build/util/permissions/user-switcher.rb +42 -0
  30. data/rubygem-boxgrinder-build.spec +25 -3
  31. data/spec/appliance-spec.rb +69 -82
  32. data/spec/helpers/augeas-helper-spec.rb +0 -2
  33. data/spec/helpers/guestfs-helper-spec.rb +1 -3
  34. data/spec/helpers/image-helper-spec.rb +0 -2
  35. data/spec/helpers/linux-helper-spec.rb +0 -2
  36. data/spec/helpers/package-helper-spec.rb +0 -2
  37. data/spec/helpers/plugin-helper-spec.rb +0 -2
  38. data/spec/helpers/s3-helper-spec.rb +0 -2
  39. data/spec/managers/plugin-manager-spec.rb +0 -2
  40. data/spec/plugins/base-plugin-spec.rb +0 -2
  41. data/spec/plugins/delivery/ebs/ebs-plugin-spec.rb +0 -2
  42. data/spec/plugins/delivery/elastichosts/elastichosts-plugin-spec.rb +3 -5
  43. data/spec/plugins/delivery/libvirt/libvirt-plugin-spec.rb +19 -17
  44. data/spec/plugins/delivery/local/local-plugin-spec.rb +0 -2
  45. data/spec/plugins/delivery/s3/s3-plugin-spec.rb +6 -8
  46. data/spec/plugins/delivery/sftp/sftp-plugin-spec.rb +0 -2
  47. data/spec/plugins/os/centos/centos-plugin-spec.rb +0 -2
  48. data/spec/plugins/os/fedora/fedora-plugin-spec.rb +0 -2
  49. data/spec/plugins/os/rhel/rhel-plugin-spec.rb +0 -2
  50. data/spec/plugins/os/rpm-based/kickstart-spec.rb +0 -2
  51. data/spec/plugins/os/rpm-based/rpm-based-os-plugin-spec.rb +16 -4
  52. data/spec/plugins/os/rpm-based/rpm-dependency-validator-spec.rb +0 -2
  53. data/spec/plugins/platform/ec2/ec2-plugin-spec.rb +0 -2
  54. data/spec/plugins/platform/virtualbox/virtualbox-plugin-spec.rb +1 -3
  55. data/spec/plugins/platform/virtualpc/virtualpc-plugin-spec.rb +0 -1
  56. data/spec/plugins/platform/vmware/vmware-plugin-spec.rb +25 -24
  57. data/spec/rcov_helper.rb +2 -0
  58. data/spec/spec_helper.rb +9 -0
  59. data/spec/util/concurrent/get-set-spec.rb +43 -0
  60. data/spec/util/permissions/fs-monitor-spec.rb +233 -0
  61. data/spec/util/permissions/fs-observer-spec.rb +141 -0
  62. data/spec/util/permissions/user-switcher-spec.rb +69 -0
  63. metadata +20 -5
@@ -20,7 +20,7 @@ require 'boxgrinder-build/plugins/os/rhel/rhel-plugin'
20
20
 
21
21
  module BoxGrinder
22
22
  class CentOSPlugin < RHELPlugin
23
- plugin :type => :os, :name => :centos, :full_name => "CentOS", :versions => ["5", "6"]
23
+ plugin :type => :os, :name => :centos, :full_name => "CentOS", :versions => ["5", "6"], :require_root => true
24
24
 
25
25
  def after_init
26
26
  super
@@ -20,7 +20,7 @@ require 'boxgrinder-build/plugins/os/rpm-based/rpm-based-os-plugin'
20
20
 
21
21
  module BoxGrinder
22
22
  class FedoraPlugin < RPMBasedOSPlugin
23
- plugin :type => :os, :name => :fedora, :full_name => "Fedora", :versions => ["13", "14", "15", "16", "rawhide"]
23
+ plugin :type => :os, :name => :fedora, :full_name => "Fedora", :versions => ["13", "14", "15", "16", "rawhide"], :require_root => true
24
24
 
25
25
  def after_init
26
26
  super
@@ -20,7 +20,7 @@ require 'boxgrinder-build/plugins/os/rpm-based/rpm-based-os-plugin'
20
20
 
21
21
  module BoxGrinder
22
22
  class RHELPlugin < RPMBasedOSPlugin
23
- plugin :type => :os, :name => :rhel, :full_name => "Red Hat Enterprise Linux", :versions => ['5', '6']
23
+ plugin :type => :os, :name => :rhel, :full_name => "Red Hat Enterprise Linux", :versions => ['5', '6'], :require_root => true
24
24
 
25
25
  def after_init
26
26
  super
@@ -37,7 +37,7 @@ module BoxGrinder
37
37
 
38
38
  def create
39
39
  template = "#{File.dirname(__FILE__)}/src/appliance.ks.erb"
40
- kickstart = ERB.new(File.read(template)).result(build_definition.send(:binding))
40
+ kickstart = ERB.new(File.read(template)).result(build_definition.get_binding)
41
41
  File.open(@kickstart_file, 'w') { |f| f.write(kickstart) }
42
42
 
43
43
  @kickstart_file
@@ -54,6 +54,10 @@ module BoxGrinder
54
54
  self[sym.to_s]
55
55
  end
56
56
 
57
+ def definition.get_binding
58
+ binding
59
+ end
60
+
57
61
  cost = 40
58
62
 
59
63
  definition['mount_points'] = @linux_helper.partition_mount_points(@appliance_config.hardware.partitions)
@@ -147,7 +147,7 @@ module BoxGrinder
147
147
  @log.debug "Cleaning appliance-creator mount points..."
148
148
 
149
149
  Dir["#{@dir.tmp}/imgcreate-*"].each do |dir|
150
- dev_mapper = @exec_helper.execute "mount | grep #{dir} | awk '{print $1}'"
150
+ dev_mapper = @exec_helper.execute("mount | grep #{dir} | awk '{print $1}'").split("\n")
151
151
 
152
152
  mappings = {}
153
153
 
@@ -158,7 +158,7 @@ module BoxGrinder
158
158
  end
159
159
  end
160
160
 
161
- (['/var/cache/yum', '/dev/shm', '/dev/pts', '/proc', '/sys'] + @appliance_config.hardware.partitions.keys.reverse).each do |mount_point|
161
+ (['/var/cache/yum', '/dev/shm', '/dev/pts', '/proc', '/sys'] + @appliance_config.hardware.partitions.keys.sort.reverse).each do |mount_point|
162
162
  @log.trace "Unmounting '#{mount_point}'..."
163
163
  @exec_helper.execute "umount -d #{dir}/install_root#{mount_point}"
164
164
  end
@@ -251,6 +251,11 @@ module BoxGrinder
251
251
 
252
252
  def install_repos(guestfs)
253
253
  @log.debug "Installing repositories from appliance definition file..."
254
+
255
+ # It seems that the directory is not always created by default if default repos are inhibited (e.g. SL6)
256
+ yum_d = '/etc/yum.repos.d/'
257
+ guestfs.mkdir_p(yum_d) if guestfs.exists(yum_d) == 0
258
+
254
259
  @appliance_config.repos.each do |repo|
255
260
  if repo['ephemeral']
256
261
  @log.debug "Repository '#{repo['name']}' is an ephemeral repo. It'll not be installed in the appliance."
@@ -264,7 +269,7 @@ module BoxGrinder
264
269
  repo_file << ("#{type}=#{repo[type]}\n") unless repo[type].nil?
265
270
  end
266
271
 
267
- guestfs.write_file("/etc/yum.repos.d/#{repo['name']}.repo", repo_file, 0)
272
+ guestfs.write_file("#{yum_d}#{repo['name']}.repo", repo_file, 0)
268
273
  end
269
274
  @log.debug "Repositories installed."
270
275
  end
@@ -80,7 +80,7 @@ module BoxGrinder
80
80
  for name in package_list
81
81
  found = false
82
82
 
83
- repoquery_output.each do |line|
83
+ repoquery_output.each_line do |line|
84
84
  line = line.strip
85
85
 
86
86
  package = line.match( /^([\S]+)-\d+:/ )
@@ -20,7 +20,7 @@ require 'boxgrinder-build/plugins/os/rhel/rhel-plugin'
20
20
 
21
21
  module BoxGrinder
22
22
  class ScientificLinuxPlugin < RHELPlugin
23
- plugin :type => :os, :name => :sl, :full_name => "Scientific Linux", :versions => ["5", "6"]
23
+ plugin :type => :os, :name => :sl, :full_name => "Scientific Linux", :versions => ["5", "6"], :require_root => true
24
24
 
25
25
  SL_REPOS = {
26
26
  "5" => {
@@ -22,7 +22,7 @@ require 'tempfile'
22
22
 
23
23
  module BoxGrinder
24
24
  class EC2Plugin < BasePlugin
25
- plugin :type => :platform, :name => :ec2, :full_name => "Amazon Elastic Compute Cloud (Amazon EC2)"
25
+ plugin :type => :platform, :name => :ec2, :full_name => "Amazon Elastic Compute Cloud (Amazon EC2)", :require_root => true
26
26
 
27
27
  def after_init
28
28
  register_deliverable(:disk => "#{@appliance_config.name}.ec2")
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright 2012 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'thread'
20
+
21
+ class GetSet
22
+ def initialize(initial_state=false)
23
+ @val = initial_state
24
+ @mutex = Mutex.new
25
+ end
26
+
27
+ # Atomic get-and-set.
28
+ #
29
+ # When used with a block, the existing value is provided as
30
+ # an argument to the block. The block's return value sets the
31
+ # object's value state.
32
+ #
33
+ # When used without a block; if a nil +set_val+ parameter is
34
+ # provided the existing state is returned. Else the object
35
+ # value state is set to +set_val+
36
+ def get_set(set_val=nil, &blk)
37
+ @mutex.synchronize do
38
+ if block_given?
39
+ @val = blk.call(@val)
40
+ else
41
+ @val = set_val unless set_val.nil?
42
+ end
43
+ @val
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,182 @@
1
+ #
2
+ # Copyright 2012 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'thread'
20
+ require 'observer'
21
+ require 'pathname'
22
+ require 'singleton'
23
+
24
+ require 'boxgrinder-build/util/concurrent/get_set'
25
+
26
+ module BoxGrinder
27
+ class FSMonitor
28
+ include Singleton
29
+ include Observable
30
+
31
+ def initialize
32
+ @flag = GetSet.new
33
+ @lock_a = Mutex.new
34
+ @lock_b = Mutex.new
35
+ set_hooks
36
+ end
37
+
38
+ # Start capturing paths. Providing a block automatically stops the
39
+ # capture process upon termination of the scope.
40
+ #
41
+ # @param Array<#update> Observers to be notified of capture
42
+ # events. Each observer should expect a hash{} containing a
43
+ # +:command+, and potentially +:data+.
44
+ #
45
+ # @yield Block that automatically calls #stop at the end of scope
46
+ # to cease capture.
47
+ def capture(*observers, &block)
48
+ @lock_a.synchronize do
49
+ add_observers(observers)
50
+ _capture(&block)
51
+
52
+ if block_given?
53
+ yield
54
+ _stop
55
+ end
56
+ end
57
+ end
58
+
59
+ # Explicitly stop capturing paths. This should be utilised if
60
+ # capture was not used with a block. Fires the +:stop_capture+
61
+ # command to indicate that capturing has ceased.
62
+ def stop
63
+ @lock_a.synchronize { _stop }
64
+ end
65
+
66
+ # Stop any capturing and delete all observers. Useful for testing.
67
+ # @see #stop
68
+ def reset
69
+ @lock_a.synchronize do
70
+ _stop
71
+ delete_observers
72
+ end
73
+ end
74
+
75
+ # Add a path string. Called by the hooked methods when an
76
+ # applicable action is triggered and capturing is enabled. Fires
77
+ # the +:add_path+ command, and includes the full path as +:data+.
78
+ #
79
+ # If no observers have been assigned before a path is added, they
80
+ # will be silently lost.
81
+ #
82
+ # @param [String] path Filesystem path.
83
+ # @return [Boolean] False if no observers were present.
84
+ def add_path(path)
85
+ @lock_b.synchronize do
86
+ changed(true)
87
+ notify_observers(:command => :add_path, :data => realpath(path))
88
+ end
89
+ end
90
+
91
+ # Trigger ownership change immediately, but without ceasing. Fires
92
+ # the +:chown+ command on all observers.
93
+ #
94
+ # @return [boolean] False if no observers were present.
95
+ def trigger
96
+ changed(true)
97
+ notify_observers(:command => :chown)
98
+ end
99
+
100
+ private # Not threadsafe
101
+
102
+ # The hooks will all use the same get-and-set to determine when to
103
+ # begin/cease capturing paths.
104
+ def _capture
105
+ @flag.get_set(true)
106
+ end
107
+
108
+ def _stop
109
+ @flag.get_set(false)
110
+ changed(true)
111
+ notify_observers(:command => :stop_capture)
112
+ end
113
+
114
+ # Hooks to capture any standard file, link or directory creation. Other
115
+ # methods (e.g. FileUtils#mkdir_p, FileUtils#move), ultimately bottom out
116
+ # into these primitive functions.
117
+ def set_hooks
118
+ # Final splat var captures any other variables we are not interested in,
119
+ # and avoids them being squashed into the final var.
120
+ eigen_capture(File, [:open, :new], @flag) do |klazz, path, mode, *other|
121
+ add_path(path) if klazz == File && mode =~ /^(t|b)?((w|a)[+]?)(t|b)?$/
122
+ end
123
+
124
+ eigen_capture(File, [:rename, :symlink, :link], @flag) do |klazz, old, new, *other|
125
+ add_path(new)
126
+ end
127
+
128
+ eigen_capture(Dir, :mkdir, @flag) do |klazz, path, *other|
129
+ add_path(root_dir(path))
130
+ end
131
+ end
132
+
133
+ # Hooks into class methods by accessing the eigenclass (virtual class).
134
+ def eigen_capture(klazz, m_sym, flag, &blk)
135
+ v_klazz = (class << klazz; self; end)
136
+ instance_capture(v_klazz, m_sym, flag, &blk)
137
+ end
138
+
139
+ def instance_capture(klazz, m_sym, flag, &blk)
140
+ Array(m_sym).each{ |sym| alias_and_capture(klazz, sym, flag, &blk) }
141
+ end
142
+
143
+ # Cracks open the target class, and injects a wrapper to enable
144
+ # monitoring. By aliasing the original method the wrapper intercepts the
145
+ # call, which it forwards onto the 'real' method before executing the hook.
146
+ #
147
+ # The hook's functionality is provided via a &block, which is passed the
148
+ # caller's +self+ in addition to the wrapped method's parameters.
149
+ #
150
+ # Locking the +flag+ signals the hook to begin capturing.
151
+ def alias_and_capture(klazz, m_sym, flag, &blk)
152
+ alias_m_sym = "__alias_#{m_sym}"
153
+
154
+ klazz.class_eval do
155
+ alias_method alias_m_sym, m_sym
156
+
157
+ define_method(m_sym) do |*args, &blx|
158
+ response = send(alias_m_sym, *args, &blx)
159
+ blk.call(self, *args) if flag.get_set
160
+ response
161
+ end
162
+ end
163
+ end
164
+
165
+ def add_observers(observers)
166
+ observers.each{ |o| add_observer(o) unless o.nil? }
167
+ end
168
+
169
+ # Transform relative to absolute path
170
+ def realpath(path)
171
+ Pathname.new(path).realpath.to_s
172
+ end
173
+
174
+ # For a path relative such as 'x/y/z' returns 'x'. Useful for #mkdir_p
175
+ # where the entire new path is returned at once.
176
+ def root_dir(relpath)
177
+ r = relpath.match(%r(^[/]?.+?[/$]))
178
+ return relpath if r.nil?
179
+ r[0] || relpath
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,82 @@
1
+ #
2
+ # Copyright 2012 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'fileutils'
20
+ require 'set'
21
+
22
+ module BoxGrinder
23
+ class FSObserver
24
+ attr_accessor :path_set
25
+ attr_accessor :filter_set
26
+
27
+ # @param [Integer] user The uid to switch from root to
28
+ # @param [Integer] group The gid to switch from root to
29
+ # @param [Hash] opts The options to create a observer with
30
+ # @option opts [Array<String>] :paths Additional paths to change
31
+ # ownership of.
32
+ # @option opts [String] :paths Additional path to to change
33
+ # ownership of
34
+ def initialize(user, group, opts={})
35
+ @path_set = Set.new(Array(opts[:paths]))
36
+ # Filter some default directories, plus any subdirectories of
37
+ # paths we discover at runtime
38
+ @filter_set = Set.new([%r(^/(etc|dev|sys|bin|sbin|etc|lib|lib64|boot|run|proc|selinux|tmp)(/|$))])
39
+ @user = user
40
+ @group = group
41
+ end
42
+
43
+ # Receives updates from FSMonitor#add_path
44
+ #
45
+ # @param [Hash] opts The options to update the observer
46
+ # @option opts [:symbol] :command The command to instruct the
47
+ # observer to execute.
48
+ # * +:add_path+ Indicates the +:data+ field contains a path.
49
+ # * +:stop_capture+ indicates that capturing has ceased. The
50
+ # observer will change ownership of the files, and switch
51
+ # to the user specified at #initialize.
52
+ # @option opts [String] :data Contains a resource path when the
53
+ # * +:add_path+ Command is called, otherwise ignored.
54
+ def update(update={})
55
+ case update[:command]
56
+ when :add_path
57
+ unless match_filter?(update[:data])
58
+ @path_set.add(update[:data])
59
+ @filter_set.merge(subdirectory_regex(update[:data]))
60
+ end
61
+ when :stop_capture, :chown
62
+ do_chown
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def do_chown
69
+ @path_set.each do |p|
70
+ FileUtils.chown_R(@user, @group, p, :force => true) if File.exist?(p)
71
+ end
72
+ end
73
+
74
+ def match_filter?(path)
75
+ @filter_set.inject(false){ |accum, filter| accum || !!(path =~ filter) }
76
+ end
77
+
78
+ def subdirectory_regex(paths)
79
+ Array(paths).collect{ |p| Regexp.new("^#{p}/") }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Copyright 2012 Red Hat, Inc.
3
+ #
4
+ # This is free software; you can redistribute it and/or modify it
5
+ # under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 3 of
7
+ # the License, or (at your option) any later version.
8
+ #
9
+ # This software is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this software; if not, write to the Free
16
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
+
19
+ require 'tmpdir'
20
+
21
+ module BoxGrinder
22
+ class UserSwitcher
23
+
24
+ def UserSwitcher.change_user(u, g, &blk)
25
+ prev_u, prev_g = Process.uid, Process.gid
26
+ set_user(u, g)
27
+ blk.call
28
+ set_user(prev_u, prev_g)
29
+ end
30
+
31
+ private
32
+
33
+ # Working around bugs.... we can rely on the saved id to be able
34
+ # to sneak back to the previous user later.
35
+ def UserSwitcher.set_user(u, g)
36
+ return if Process.uid == u && Process.gid == g
37
+ # If already set to the given value
38
+ Process.egid, Process.gid = g, g
39
+ Process.euid, Process.uid = u, u
40
+ end
41
+ end
42
+ end