util: simplify chown_logs
[unicorn.git] / lib / unicorn / util.rb
blob5ceda5cdb4116c0f636f60de588f57508accc044
1 # -*- encoding: binary -*-
3 require 'fcntl'
4 require 'tmpdir'
6 module Unicorn
8   class TmpIO < ::File
10     # for easier env["rack.input"] compatibility
11     def size
12       # flush if sync
13       stat.size
14     end
15   end
17   class Util
18     class << self
20       def is_log?(fp)
21         append_flags = File::WRONLY | File::APPEND
23         ! fp.closed? &&
24           fp.sync &&
25           fp.path[0] == ?/ &&
26           (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
27       end
29       def chown_logs(uid, gid)
30         ObjectSpace.each_object(File) do |fp|
31           fp.chown(uid, gid) if is_log?(fp)
32         end
33       end
35       # This reopens ALL logfiles in the process that have been rotated
36       # using logrotate(8) (without copytruncate) or similar tools.
37       # A +File+ object is considered for reopening if it is:
38       #   1) opened with the O_APPEND and O_WRONLY flags
39       #   2) opened with an absolute path (starts with "/")
40       #   3) the current open file handle does not match its original open path
41       #   4) unbuffered (as far as userspace buffering goes, not O_SYNC)
42       # Returns the number of files reopened
43       def reopen_logs
44         nr = 0
46         ObjectSpace.each_object(File) do |fp|
47           is_log?(fp) or next
48           orig_st = fp.stat
49           begin
50             b = File.stat(fp.path)
51             next if orig_st.ino == b.ino && orig_st.dev == b.dev
52           rescue Errno::ENOENT
53           end
55           open_arg = 'a'
56           if fp.respond_to?(:external_encoding) && enc = fp.external_encoding
57             open_arg << ":#{enc.to_s}"
58             enc = fp.internal_encoding and open_arg << ":#{enc.to_s}"
59           end
60           fp.reopen(fp.path, open_arg)
61           fp.sync = true
62           new_st = fp.stat
63           if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
64             fp.chown(orig_st.uid, orig_st.gid)
65           end
66           nr += 1
67         end # each_object
68         nr
69       end
71       # creates and returns a new File object.  The File is unlinked
72       # immediately, switched to binary mode, and userspace output
73       # buffering is disabled
74       def tmpio
75         fp = begin
76           TmpIO.open("#{Dir::tmpdir}/#{rand}",
77                      File::RDWR|File::CREAT|File::EXCL, 0600)
78         rescue Errno::EEXIST
79           retry
80         end
81         File.unlink(fp.path)
82         fp.binmode
83         fp.sync = true
84         fp
85       end
87     end
89   end
90 end