summaryrefslogtreecommitdiff
path: root/lib/chef/daemon.rb
blob: e5479905af0f45cce30434ec6eaa0543f9877454 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
# Copyright:: Copyright (c) 2008 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# I love you Merb (lib/merb-core/server.rb)

require 'chef/config'
require 'etc'

class Chef
  class Daemon
    class << self
      attr_accessor :name

      # Daemonize the current process, managing pidfiles and process uid/gid
      #
      # === Parameters
      # name<String>:: The name to be used for the pid file
      #
      def daemonize(name)
        @name = name
        pid = pid_from_file
        unless running?
          remove_pid_file()
          Chef::Log.info("Daemonizing..")
          begin
            exit if fork
            Process.setsid
            exit if fork
            Chef::Log.info("Forked, in #{Process.pid}. Privileges: #{Process.euid} #{Process.egid}")
            File.umask Chef::Config[:umask]
            $stdin.reopen("/dev/null")
            $stdout.reopen("/dev/null", "a")
            $stderr.reopen($stdout)
            save_pid_file
            at_exit { remove_pid_file }
          rescue NotImplementedError => e
            Chef::Application.fatal!("There is no fork: #{e.message}")
          end
        else
          Chef::Application.fatal!("Chef is already running pid #{pid}")
        end
      end

      # Check if Chef is running based on the pid_file
      # ==== Returns
      # Boolean::
      # True if Chef is running
      # False if Chef is not running
      #
      def running?
        if pid_from_file.nil?
          false
        else
          Process.kill(0, pid_from_file)
          true
        end
      rescue Errno::ESRCH, Errno::ENOENT
        false
      rescue Errno::EACCES => e
        Chef::Application.fatal!("You don't have access to the PID file at #{pid_file}: #{e.message}")
      end

      # Check if this process if forked from a Chef daemon
      # ==== Returns
      # Boolean::
      # True if this process is forked
      # False if this process is not forked
      #
      def forked?
        if running? and Process.ppid == pid_from_file.to_i
          # chef daemon is running and this process is a child of it
          true
        elsif not running? and Process.ppid == 1
          # an orphaned fork, its parent becomes init, launchd, etc. after chef daemon dies
          true
        else
          false
        end
      end

      # Gets the pid file for @name
      # ==== Returns
      # String::
      #   Location of the pid file for @name
      def pid_file
         Chef::Config[:pid_file] or "/tmp/#{@name}.pid"
      end

      # Suck the pid out of pid_file
      # ==== Returns
      # Integer::
      #   The PID from pid_file
      # nil::
      #   Returned if the pid_file does not exist.
      #
      def pid_from_file
        File.read(pid_file).chomp.to_i
      rescue Errno::ENOENT, Errno::EACCES
        nil
      end

      # Store the PID on the filesystem
      # This uses the Chef::Config[:pid_file] option, or "/tmp/name.pid" otherwise
      #
      def save_pid_file
        file = pid_file
        begin
          FileUtils.mkdir_p(File.dirname(file))
        rescue Errno::EACCES => e
          Chef::Application.fatal!("Failed store pid in #{File.dirname(file)}, permission denied: #{e.message}")
        end

        begin
          File.open(file, "w") { |f| f.write(Process.pid.to_s) }
        rescue Errno::EACCES => e
          Chef::Application.fatal!("Couldn't write to pidfile #{file}, permission denied: #{e.message}")
        end
      end

      # Delete the PID from the filesystem
      def remove_pid_file
        if not forked? then
          FileUtils.rm(pid_file) if File.exists?(pid_file)
        end
      end

      # Change process user/group to those specified in Chef::Config
      #
      def change_privilege
        Dir.chdir("/")

        if Chef::Config[:user] and Chef::Config[:group]
          Chef::Log.info("About to change privilege to #{Chef::Config[:user]}:#{Chef::Config[:group]}")
          _change_privilege(Chef::Config[:user], Chef::Config[:group])
        elsif Chef::Config[:user]
          Chef::Log.info("About to change privilege to #{Chef::Config[:user]}")
          _change_privilege(Chef::Config[:user])
        end
      end

      # Change privileges of the process to be the specified user and group
      #
      # ==== Parameters
      # user<String>:: The user to change the process to.
      # group<String>:: The group to change the process to.
      #
      # ==== Alternatives
      # If group is left out, the user will be used (changing to user:user)
      #
      def _change_privilege(user, group=user)
        uid, gid = Process.euid, Process.egid

        begin
          target_uid = Etc.getpwnam(user).uid
        rescue ArgumentError => e
          Chef::Application.fatal!("Failed to get UID for user #{user}, does it exist? #{e.message}")
          return false
        end

        begin
          target_gid = Etc.getgrnam(group).gid
        rescue ArgumentError => e
          Chef::Application.fatal!("Failed to get GID for group #{group}, does it exist? #{e.message}")
          return false
        end

        if (uid != target_uid) or (gid != target_gid)
          Process.initgroups(user, target_gid)
          Process::GID.change_privilege(target_gid)
          Process::UID.change_privilege(target_uid)
        end
        true
      rescue Errno::EPERM => e
        Chef::Application.fatal!("Permission denied when trying to change #{uid}:#{gid} to #{target_uid}:#{target_gid}. #{e.message}")
      end
    end
  end
end