/chef-expander/lib/chef/expander/daemonizable.rb

http://github.com/opscode/chef · Ruby · 150 lines · 100 code · 21 blank · 29 comment · 10 complexity · 1a423ee5467228245db59733e6fac27d MD5 · raw file

  1. #
  2. # Author:: Daniel DeLeo (<dan@opscode.com>)
  3. # Copyright:: Copyright (c) 2011 Opscode, Inc.
  4. # License:: Apache License, Version 2.0
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. #
  18. require 'etc'
  19. require 'chef/expander/loggable'
  20. module Chef
  21. module Expander
  22. class AlreadyRunning < RuntimeError
  23. end
  24. class NoSuchUser < ArgumentError
  25. end
  26. class NoSuchGroup < ArgumentError
  27. end
  28. module Daemonizable
  29. include Loggable
  30. # Daemonizes the process if configured to do so, and ensures that only one
  31. # copy of the process is running with a given config by obtaining an
  32. # exclusive lock on the pidfile. Also sets process user and group if so
  33. # configured.
  34. # ===Raises
  35. # * AlreadyRunning::: when another process has the exclusive lock on the pidfile
  36. # * NoSuchUser::: when a user is configured that doesn't exist
  37. # * NoSuchGroup::: when a group is configured that doesn't exist
  38. # * SystemCallError::: if there is an error creating the pidfile
  39. def configure_process
  40. Expander.config.daemonize? ? daemonize : ensure_exclusive
  41. set_user_and_group
  42. end
  43. def daemonize
  44. acquire_locks
  45. exit if fork
  46. Process.setsid
  47. exit if fork
  48. write_pid
  49. Dir.chdir('/')
  50. STDIN.reopen("/dev/null")
  51. STDOUT.reopen("/dev/null", "a")
  52. STDERR.reopen("/dev/null", "a")
  53. end
  54. # When not forking into the background, this ensures only one chef-expander
  55. # is running with a given config and writes the process id to the pidfile.
  56. def ensure_exclusive
  57. acquire_locks
  58. write_pid
  59. end
  60. def set_user_and_group
  61. return nil if Expander.config.user.nil?
  62. if Expander.config.group.nil?
  63. log.info {"Changing user to #{Expander.config.user}"}
  64. else
  65. log.info {"Changing user to #{Expander.config.user} and group to #{Expander.config.group}"}
  66. end
  67. unless (set_group && set_user)
  68. log.error {"Unable to change user to #{Expander.config.user} - Are you root?"}
  69. end
  70. end
  71. # Deletes the pidfile, releasing the exclusive lock on it in the process.
  72. def release_locks
  73. File.unlink(@pidfile.path) if File.exist?(@pidfile.path)
  74. @pidfile.close unless @pidfile.closed?
  75. end
  76. private
  77. def set_user
  78. Process::Sys.setuid(target_uid)
  79. true
  80. rescue Errno::EPERM => e
  81. log.debug {e}
  82. false
  83. end
  84. def set_group
  85. if gid = target_uid
  86. Process::Sys.setgid(gid)
  87. end
  88. true
  89. rescue Errno::EPERM
  90. log.debug {e}
  91. false
  92. end
  93. def target_uid
  94. user = Expander.config.user
  95. user.kind_of?(Fixnum) ? user : Etc.getpwnam(user).uid
  96. rescue ArgumentError => e
  97. log.debug {e}
  98. raise NoSuchUser, "Cannot change user to #{user} - failed to find the uid"
  99. end
  100. def target_gid
  101. if group = Expander.config.group
  102. group.kind_of?(Fixnum) ? group : Etc.getgrnam(group).gid
  103. else
  104. nil
  105. end
  106. rescue ArgumentError => e
  107. log.debug {e}
  108. raise NoSuchGroup, "Cannot change group to #{group} - failed to find the gid"
  109. end
  110. def acquire_locks
  111. @pidfile = File.open(Expander.config.pidfile, File::RDWR|File::CREAT, 0644)
  112. unless @pidfile.flock(File::LOCK_EX | File::LOCK_NB)
  113. pid = @pidfile.read.strip
  114. msg = "Another instance of chef-expander (pid: #{pid}) has a lock on the pidfile (#{Expander.config.pidfile}). \n"\
  115. "Configure a different pidfile to run multiple instances of chef-expander at once."
  116. raise AlreadyRunning, msg
  117. end
  118. rescue Exception
  119. @pidfile.close if @pidfile && !@pidfile.closed?
  120. raise
  121. end
  122. def write_pid
  123. @pidfile.truncate(0)
  124. @pidfile.print("#{Process.pid}\n")
  125. @pidfile.flush
  126. end
  127. end
  128. end
  129. end