ruby-watchcat-pure 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README +2 -0
  2. data/lib/watchcat.rb +151 -41
  3. metadata +2 -2
data/README CHANGED
@@ -18,6 +18,8 @@ Ruby/Watchcatd allows a Ruby application to register itself with watchcatd.
18
18
  Ruby/Watchcatd was tested with Ruby versions >= 1.8.4 and requires watchcatd
19
19
  version 1.1 and libwcat version 1.0 to be installed (see References below).
20
20
 
21
+ For FreeBSD support, you need at least watchcatd 1.2 (and libwcat 1.1 if you
22
+ are using the C extension).
21
23
 
22
24
  == Installation
23
25
 
data/lib/watchcat.rb CHANGED
@@ -13,7 +13,7 @@
13
13
  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
14
  # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
15
  #
16
- # $Id: watchcat.rb 24 2008-08-04 14:47:08Z andre $
16
+ # $Id: watchcat.rb 29 2008-08-29 18:52:47Z andre $
17
17
  #++
18
18
 
19
19
  # Pure-ruby version of libwcat.
@@ -30,7 +30,7 @@ require 'socket'
30
30
  #
31
31
  class Watchcat
32
32
  DEFAULT_TIMEOUT = 60
33
- DEFAULT_DEVICE = '/dev/watchcat'
33
+ DEFAULT_DEVICE = '/var/run/watchcat.socket'
34
34
  DEFAULT_SIGNAL = Signal.list['KILL']
35
35
 
36
36
  # Create a new Watchcat object. The parameter hash may have the following
@@ -46,8 +46,8 @@ class Watchcat
46
46
  # Should be a string which is added to the log generated by watchcatd
47
47
  # when it signals a process. (default: nil)
48
48
  # +device+::
49
- # The watchcat device. (default: +/dev/watchcat+). Use for debugging
50
- # purposes.
49
+ # The watchcat device. (default: +/var/run/watchcat.socket+). Use for
50
+ # debugging purposes.
51
51
  #
52
52
  # If a block is given, the Watchcat object will be yielded and automatically
53
53
  # closed on block termination.
@@ -60,46 +60,25 @@ class Watchcat
60
60
  raise ArgumentError, 'timeout must be an integer'
61
61
  end
62
62
 
63
- case args[:signal]
64
- when nil
65
- signal = DEFAULT_SIGNAL
66
- when String
67
- signal = Signal.list[args[:signal].sub(/^SIG/, '')]
68
- raise ArgumentError, "invalid signal name" if signal.nil?
69
- when Fixnum
70
- signal = args[:signal]
71
- else
72
- raise ArgumentError, "signal must be an integer or a string"
73
- end
74
-
75
- @sock = UNIXSocket.new(device)
76
- if Fcntl.const_defined? :F_SETFD
77
- @sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
78
- end
79
-
80
- msg = "version: 1\ntimeout: #{timeout}\nsignal: #{signal}"
81
- if info.nil?
82
- msg << "\n\n"
83
- else
84
- info.gsub!(/\n/, '_')
85
- msg << "\ninfo: #{info}\n\n"
86
- end
63
+ signal = signal_number(args[:signal])
64
+ @sock = create_socket(device)
65
+ msg = build_message(timeout, signal, info)
87
66
 
88
67
  safe_write(@sock, msg)
89
- if safe_read(@sock, 256) == "ok\n"
90
- if block_given?
91
- begin
92
- yield(self)
93
- ensure
94
- @sock.close
95
- end
96
- end
97
- return self
98
- else
68
+ unless safe_read(@sock, 256) == "ok\n"
99
69
  @sock.close
100
- # Probably not the best error, but it matches the C library.
70
+ # Probably not the best error, but it matches libwcat.
101
71
  raise Errno::EPERM
102
72
  end
73
+
74
+ if block_given?
75
+ begin
76
+ yield(self)
77
+ ensure
78
+ @sock.close
79
+ end
80
+ end
81
+ return self
103
82
  end
104
83
 
105
84
  # Send a heartbeat to watchcatd, telling it we're still alive.
@@ -120,14 +99,52 @@ class Watchcat
120
99
 
121
100
  private
122
101
 
102
+ def signal_number(value)
103
+ case value
104
+ when nil
105
+ signal = DEFAULT_SIGNAL
106
+ when String
107
+ signal = Signal.list[args[:signal].sub(/^SIG/, '')]
108
+ raise ArgumentError, "invalid signal name" if signal.nil?
109
+ when Fixnum
110
+ signal = args[:signal]
111
+ else
112
+ raise ArgumentError, "signal must be an integer or a string"
113
+ end
114
+ end
115
+
116
+ def create_socket(device)
117
+ sock = UNIXSocket.new(device)
118
+ if Fcntl.const_defined? :F_SETFD
119
+ sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
120
+ end
121
+ return sock
122
+ end
123
+
124
+ def build_message(timeout, signal, info)
125
+ msg = "version: 1\ntimeout: #{timeout}\nsignal: #{signal}"
126
+ if info.nil?
127
+ msg << "\n\n"
128
+ else
129
+ info.gsub!(/\n/, '_')
130
+ msg << "\ninfo: #{info}\n\n"
131
+ end
132
+ return msg
133
+ end
134
+
123
135
  def safe_write(fd, buf)
124
136
  act = Signal.trap('PIPE', 'IGN')
125
137
  begin
126
- fd.syswrite(buf)
138
+ if RUBY_PLATFORM =~ /freebsd/i
139
+ FreeBSD.sendmsg(fd, " #{buf}") # XXX prepend an extra byte
140
+ else
141
+ fd.syswrite(buf)
142
+ end
127
143
  rescue Errno::EINTR
128
144
  retry
145
+ ensure
146
+ Signal.trap('PIPE', act)
129
147
  end
130
- Signal.trap('PIPE', act)
131
148
  end
132
149
 
133
150
  def safe_read(fd, len)
@@ -140,3 +157,96 @@ private
140
157
  return buf
141
158
  end
142
159
  end
160
+
161
+ module FreeBSD # :nodoc:
162
+ extend(self)
163
+
164
+ INT_SIZE = [0].pack("i_").size
165
+ INT32_SIZE = 4
166
+ SHORT_SIZE = [0].pack("s_").size
167
+ CMGROUP_MAX = 16
168
+ ALIGNBYTES = [0].pack("L_").size - 1
169
+
170
+ private
171
+
172
+ def align(p, alignbytes = ALIGNBYTES)
173
+ (p + alignbytes) & ~alignbytes
174
+ end
175
+
176
+ def sizeof(p)
177
+ align(p, 3)
178
+ end
179
+
180
+ #
181
+ # This code depends on structs cmsghdr and cmsgcred being as shown below.
182
+ # It also depends on sendmsg(2) being syscall number 28 and on the
183
+ # SOL_SOCKET and SCM_CREDS macros having the same values as the constants
184
+ # defined below.
185
+ #
186
+ # struct cmsghdr {
187
+ # socklen_t cmsg_len; /* __uint32_t */
188
+ # int cmsg_level; /* int */
189
+ # int cmsg_type; /* int */
190
+ # };
191
+ #
192
+ # struct cmsgcred {
193
+ # pid_t cmcred_pid; /* __int32_t */
194
+ # uid_t cmcred_uid; /* __uint32_t */
195
+ # uid_t cmcred_euid; /* __uint32_t */
196
+ # gid_t cmcred_gid; /* __uint32_t */
197
+ # short cmcred_ngroups; /* short */
198
+ # gid_t cmcred_groups[CMGROUP_MAX]; /* __uint32_t * 16 */
199
+ # };
200
+ #
201
+
202
+ SYS_SENDMSG = 28
203
+ SOL_SOCKET = 0xffff
204
+ SCM_CREDS = 0x03
205
+
206
+ CMSGCRED_SIZE = sizeof(4*INT_SIZE + SHORT_SIZE + CMGROUP_MAX * INT32_SIZE)
207
+ CMSGHDR_SIZE = sizeof(INT32_SIZE + 2 * INT_SIZE)
208
+
209
+ public
210
+
211
+ def sendmsg(fd, buf)
212
+ iov = [buf, buf.length].pack("pL_")
213
+
214
+ cmsg_space = cmsg_space(CMSGCRED_SIZE)
215
+ cmsg_data_len = cmsg_space - INT32_SIZE - 2*INT_SIZE
216
+
217
+ cmsghdr = ([
218
+ cmsg_len(CMSGCRED_SIZE), # cmsg_len
219
+ SOL_SOCKET, # cmsg_level
220
+ SCM_CREDS # cmsg_type
221
+ ] + [0] * cmsg_data_len).pack("I_i_i_C#{cmsg_data_len}")
222
+
223
+ msg_control_ptr = pointer(cmsghdr)
224
+ msg_controllen = cmsg_space
225
+
226
+ msghdr = [
227
+ 0, # msg_name
228
+ 0, # msg_namelen
229
+ pointer(iov), # msg_iov
230
+ 1, # msg_iovlen
231
+ pointer(cmsghdr), # msg_control
232
+ cmsg_space, # msg_controllen
233
+ 0 # msg_flags
234
+ ].pack("L_L_L_L_L_L_L_")
235
+
236
+ syscall(SYS_SENDMSG, fd.fileno, pointer(msghdr), 0)
237
+ end
238
+
239
+ private
240
+
241
+ def pointer(buf)
242
+ [buf].pack("P").unpack("L_").first
243
+ end
244
+
245
+ def cmsg_len(l)
246
+ align(CMSGHDR_SIZE) + l
247
+ end
248
+
249
+ def cmsg_space(l)
250
+ align(CMSGHDR_SIZE) + align(l)
251
+ end
252
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-watchcat-pure
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Nathan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-06 00:00:00 -03:00
12
+ date: 2008-08-29 00:00:00 -03:00
13
13
  default_executable:
14
14
  dependencies: []
15
15