make log reopens even more robust in threaded apps
[unicorn.git] / lib / unicorn / util.rb
blobe9dd57f31161c686146e41c7b1c3eb313d2f04b9
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   module 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         rescue IOError, Errno::EBADF
28           false
29       end
31       def chown_logs(uid, gid)
32         ObjectSpace.each_object(File) do |fp|
33           fp.chown(uid, gid) if is_log?(fp)
34         end
35       end
37       # This reopens ALL logfiles in the process that have been rotated
38       # using logrotate(8) (without copytruncate) or similar tools.
39       # A +File+ object is considered for reopening if it is:
40       #   1) opened with the O_APPEND and O_WRONLY flags
41       #   2) opened with an absolute path (starts with "/")
42       #   3) the current open file handle does not match its original open path
43       #   4) unbuffered (as far as userspace buffering goes, not O_SYNC)
44       # Returns the number of files reopened
45       def reopen_logs
46         to_reopen = []
47         nr = 0
48         ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
50         to_reopen.each do |fp|
51           orig_st = begin
52             fp.stat
53           rescue IOError, Errno::EBADF
54             next
55           end
57           begin
58             b = File.stat(fp.path)
59             next if orig_st.ino == b.ino && orig_st.dev == b.dev
60           rescue Errno::ENOENT
61           end
63           begin
64             File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
65             fp.sync = true
66             new_st = fp.stat
68             # this should only happen in the master:
69             if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
70               fp.chown(orig_st.uid, orig_st.gid)
71             end
73             nr += 1
74           rescue IOError, Errno::EBADF
75             # not much we can do...
76           end
77         end
79         nr
80       end
82       # creates and returns a new File object.  The File is unlinked
83       # immediately, switched to binary mode, and userspace output
84       # buffering is disabled
85       def tmpio
86         fp = begin
87           TmpIO.open("#{Dir::tmpdir}/#{rand}",
88                      File::RDWR|File::CREAT|File::EXCL, 0600)
89         rescue Errno::EEXIST
90           retry
91         end
92         File.unlink(fp.path)
93         fp.binmode
94         fp.sync = true
95         fp
96       end
98     end
100   end