workaround reopen atomicity issues for stdio vs non-stdio
[unicorn.git] / lib / unicorn / util.rb
blob94c4e37c29fed9e64a7f7bea8d54f985ae9150ec
1 # -*- encoding: binary -*-
3 module Unicorn::Util
5 # :stopdoc:
6   def self.is_log?(fp)
7     append_flags = File::WRONLY | File::APPEND
9     ! fp.closed? &&
10       fp.stat.file? &&
11       fp.sync &&
12       (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
13     rescue IOError, Errno::EBADF
14       false
15   end
17   def self.chown_logs(uid, gid)
18     ObjectSpace.each_object(File) do |fp|
19       fp.chown(uid, gid) if is_log?(fp)
20     end
21   end
22 # :startdoc:
24   # This reopens ALL logfiles in the process that have been rotated
25   # using logrotate(8) (without copytruncate) or similar tools.
26   # A +File+ object is considered for reopening if it is:
27   #   1) opened with the O_APPEND and O_WRONLY flags
28   #   2) the current open file handle does not match its original open path
29   #   3) unbuffered (as far as userspace buffering goes, not O_SYNC)
30   # Returns the number of files reopened
31   #
32   # In Unicorn 3.5.x and earlier, files must be opened with an absolute
33   # path to be considered a log file.
34   def self.reopen_logs
35     to_reopen = []
36     nr = 0
37     ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
39     to_reopen.each do |fp|
40       orig_st = begin
41         fp.stat
42       rescue IOError, Errno::EBADF # race
43         next
44       end
46       begin
47         b = File.stat(fp.path)
48         next if orig_st.ino == b.ino && orig_st.dev == b.dev
49       rescue Errno::ENOENT
50       end
52       begin
53         # stdin, stdout, stderr are special.  The following dance should
54         # guarantee there is no window where `fp' is unwritable in MRI
55         # (or any correct Ruby implementation).
56         #
57         # Fwiw, GVL has zero bearing here.  This is tricky because of
58         # the unavoidable existence of stdio FILE * pointers for
59         # std{in,out,err} in all programs which may use the standard C library
60         if fp.fileno <= 2
61           # We do not want to hit fclose(3)->dup(2) window for std{in,out,err}
62           # MRI will use freopen(3) here internally on std{in,out,err}
63           fp.reopen(fp.path, "a")
64         else
65           # We should not need this workaround, Ruby can be fixed:
66           #    http://bugs.ruby-lang.org/issues/9036
67           # MRI will not call call fclose(3) or freopen(3) here
68           # since there's no associated std{in,out,err} FILE * pointer
69           # This should atomically use dup3(2) (or dup2(2)) syscall
70           File.open(fp.path, "a") { |tmpfp| fp.reopen(tmpfp) }
71         end
73         fp.sync = true
74         fp.flush # IO#sync=true may not implicitly flush
75         new_st = fp.stat
77         # this should only happen in the master:
78         if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
79           fp.chown(orig_st.uid, orig_st.gid)
80         end
82         nr += 1
83       rescue IOError, Errno::EBADF
84         # not much we can do...
85       end
86     end
87     nr
88   end
89 end