1 #! /usr/bin/python
2
3 # Copyright (C) 2003 Brian Warner <warner-fusd@lothar.com>
4 #
5 # This program is free software, and can be distributed under the same
6 # terms as the rest of FUSD (the BSD 3-clause license). See the file
7 # ../LICENSE for details.
8
9 import _fusd
10 from _fusd import NOTIFY_INPUT, NOTIFY_OUTPUT, NOTIFY_EXCEPT
11 import errno
12
13 # fusd.run() never returns. To integrate with other event loops, use
14 # Device.handle as a fileno and call Device.dispatch() when that fileno
15 # becomes readable.
16
17 run = _fusd.run
18
19
20 class OpenFile:
21 """OpenFile: one instance per open() of a device node
22
23 Each time a process does an open() of your device node, a new instance
24 of this class (or a subclass) will be created. There will be a
25 one-to-one correspondence between file pointers and OpenFile instances.
26
27 read()/write()/ioctl() system calls on that file pointer will result in
28 do_read/do_write/do_ioctl method invocations on this object. Each call
29 gets a Request object, from which the parameters of the system call can
30 be retrieved. Data to be returned to the caller is given to methods of
31 the request object. request.finish(retval) must eventually be called,
32 either in the do_ method or later (say, for blocking reads).
33 """
34
35 poll_state = NOTIFY_OUTPUT
36 poll_req = None
37
38 def __init__(self, device):
39 self.device = device
40 self.flags = None # should probably be updated by req.flags
41
42 def do_read(self, req):
43 """do_read(self, request)
44
45 This will be called each time the user does a read() of the device.
46
47 'request' is a ReadRequest object with the following useful
48 attributes:
49
50 request.length (ro): the 'size' parameter passed to read()
51 request.flags (rw): the flags with which the file was opened
52 request.pid (ro): the PID of the process making the read() call
53 request.uid (ro): the UID of the user owning the process doing read()
54 request.gid (ro): the GID of the user owning the process
55 request.offset (rw): the offset at which the read is being done
56
57 'request' has the following methods:
58
59 request.setdata(offset, data): update the return data buffer,
60 starting at 'offset', by copying 'data' into the buffer. Attempts
61 to overwrite the either end of the buffer (negative offsets,
62 oversized 'data' arguments) will be caught and an IndexError
63 exception raised.
64
65 request.finish(retval): complete the request, returning 'retval' to
66 the user's read() system call.
67
68 request.destroy(): free the ReadRequest object. This should only be
69 done if close() is called while a read() request is still
70 outstanding.
71
72
73 do_read() should usually do the following:
74 return data by doing req.setdata(offset, data)
75 update the offset by doing req.offset += len(data)
76 allow the read() call to return by doing req.finish(len(data))
77
78 It may defer these until later (to implement blocking reads), but
79 eventually the request must be completed by calling req.finish. It
80 is an error to let an uncompleted request fall out of scope.
81
82 To return an error to the user doing read(), provide a negative
83 error number like req.finish(-errno.EIO). To indicate EOF, provide
84 zero, like req.finish(0).
85
86 The default implementation of do_read() will use read() to get a
87 chunk of data. To implement a blocking read, or to return an error
88 other than EOF, you will need to override do_read().
89
90 """
91
92 data = self.read(req, req.length, req.offset)
93 assert (len(data) <= req.length)
94 req.offset += len(data)
95 rc = len(data)
96 req.setdata(0, data)
97 req.finish(rc)
98
99 def read(self, req, length, offset):
100 """read(self, req, length, offset)
101
102 The default implementation of do_read() will call this each time the
103 user does a read() of the device. It is expected to return some
104 amount of data, no more than 'length' bytes, which start at 'offset'
105 of the imaginary data stream the device pretends to represent. The
106 user wants 'length', but the device can return fewer than that. The
107 offset will be incremented by the actual number of bytes returned.
108 Returning 0 indicates End Of File and will usually cause the user
109 program to close the device.
110 """
111 raise NotImplementedError, "your subclass must implement this method"
112
113
114 def do_write(self, req):
115 """do_write(self, request)
116
117 This will be called each time the user does a write() of the device.
118
119 'request' is a WriteRequest object with the following useful
120 attributes:
121
122 request.length (ro): the 'size' parameter passed to write()
123 request.flags (rw): the flags with which the file was opened
124 request.pid (ro): the PID of the process making the write() call
125 request.uid (ro): the UID of the user owning the process doing write()
126 request.gid (ro): the GID of the user owning the process
127 request.offset (rw): the offset at which the write is being done
128
129 'request' has the following methods:
130
131 request.getdata(offset, length): copy data from the write buffer,
132 'length' bytes starting at 'offset'. Attempts to read past the
133 either end of the buffer (negative offsets, oversized 'length'
134 arguments) will be caught and an IndexError exception raised.
135
136 request.finish(retval): complete the request, returning 'retval' to
137 the user's write() system call.
138
139 request.destroy(): free the WriteRequest object. This should only be
140 done if close() is called while a write() request is still
141 outstanding.
142
143
144 do_write() should usually do the following:
145 decide how many bytes can be handled, say 'handled'
146 get data from the request by doing req.setdata(0, handled)
147 do something with that data
148 update the offset by doing req.offset += handled
149 allow the read() call to return by doing req.finish(handled)
150
151 It may defer these until later (to implement blocking writes), but
152 eventually the request must be completed by calling req.finish. It
153 is an error to let an uncompleted request fall out of scope.
154
155 To return an error to the user doing write(), provide a negative
156 error number like req.finish(-errno.EIO). To indicate EOF, provide
157 zero, like req.finish(0). (?? valid for writes?)
158
159 The default implementation of do_write() will call self.write(),
160 which will simply receive everything the user tried to write. To
161 implement a blocking write, or to implement partial writes, you will
162 need to override do_read().
163
164 """
165 rc = self.write(req, req.offset, req.getdata())
166 req.offset += rc
167 req.finish(rc)
168
169 def write(self, req, offset, data):
170 raise NotImplementedError, "your subclass must implement this method"
171
172
173 def do_ioctl(self, req):
174 """do_ioctl(self, request)
175
176 This will be called each time the user does a ioctl() on the device.
177
178 'request' is an IoctlRequest object with the following useful
179 attributes:
180
181 request.length (ro): ???
182 request.flags (rw): the flags with which the file was opened
183 request.pid (ro): the PID of the process making the read() call
184 request.uid (ro): the UID of the user owning the process doing read()
185 request.gid (ro): the GID of the user owning the process
186 request.offset (rw): the file's current offset value
187 request.cmd (ro): the ioctl number, second argument to ioctl() call.
188 This is system-dependent, but is usually a bit-field composed of
189 a direction, a type, a number, and a size.
190 request.direction (ro): _IOC_DIR, indicates in, in+out, out, neither
191 request.dir_string (ro): 'none', 'write', 'read', 'readwrite'
192 request.type (ro): _IOC_TYPE, a one-byte subsystem category
193 request.nr (ro): _IOC_NR, a one-byte method number
194 request.size (ro): _IOC_SIZE, indicates size of the argument which
195 is copied in or out
196
197 'request' has the following methods:
198
199 request.getdata(offset, length): copy data from the argument buffer
200 request.setdata(offset, data): copy data into the argument buffer
201 these behave just as in read/write
202
203 request.finish(retval): complete the request, returning 'retval' to
204 the user's ioctl() system call.
205
206 request.destroy(): free the IoctlRequest object. This should only be
207 done if close() is called while a ioctl() request is still
208 outstanding.
209
210 do_ioctl() should probably look at request.dir_string, do
211 request.getdata() if it is 'write' or 'readwrite', then do something
212 with the resulting data. If .dir_string is 'read', it should return
213 request.size bytes of data by doing request.setdata(data) . When
214 done, it should do request.finish(0).
215
216 If your device does not implement any ioctls, returning an error
217 code with request.finish(-errno.ENOSYS) is probably appropriate.
218
219 The 'struct' module will probably be useful, as most
220 ioctls pass a C struct as their argument.
221
222 """
223
224 request.finish(-errno.ENOSYS)
225
226
227 # these methods control select()/poll() calls performed on the device.
228 # The initial state is controlled by self.poll_state, and NOTIFY_OUTPUT
229 # means the device is writable but not readable. To make a device that
230 # is always readable and writable (as far as select() is concerned), set
231 # poll_state to NOTIFY_INPUT | NOTIFY_OUTPUT. To change this state, call
232 # startReading/etc. The start/stop calls will wake up any clients that
233 # are waiting upon the status to change.
234
235 def startReading(self):
236 self.poll_state |= NOTIFY_INPUT
237 if self.poll_req:
238 # trigger pending request
239 self.poll_req.finish(self.poll_state)
240 self.poll_req = None
241 def stopReading(self):
242 self.poll_state &= ~NOTIFY_INPUT
243
244 def startWriting(self):
245 self.poll_state |= NOTIFY_OUTPUT
246 if self.poll_req:
247 self.poll_req.finish(self.poll_state)
248 self.poll_req = None
249 def stopWriting(self):
250 self.poll_state &= ~NOTIFY_OUTPUT
251
252 def startException(self):
253 self.poll_state |= NOTIFY_EXCEPT
254 if self.poll_req:
255 self.poll_req.finish(self.poll_state)
256 self.poll_req = None
257 def stopException(self):
258 self.poll_state &= ~NOTIFY_EXCEPT
259
260 def do_poll_diff(self, req, cached_state):
261 if self.poll_state == cached_state:
262 if self.poll_req:
263 self.poll_req.destroy()
264 self.poll_req = req
265 else:
266 req.finish(self.poll_state)
267
268
269 class Device:
270 openFileClass = OpenFile
271 def __init__(self, name, mode):
272 # our Device pointer is stored as device_info, so it will be
273 # provided with all operations. It will also be searched for do_FOO
274 # methods.
275 self.handle = _fusd.register(name, mode, self)
276 # self.handle is a fileno
277
278 def unregister(self):
279 rc = _fusd.unregister(self.handle, self)
280 return rc
281
282 def dispatch(self):
283 """dispatch() will handle all pending requests for the given device.
284 self.handle is a fileno, which can be used in select() and poll(),
285 or handed to other event loops.
286 """
287 _fusd.dispatch(self.handle)
288
289 def do_open(self, flags, pid, uid, gid):
290 fp = self.openFileClass(self)
291 # fp is stored as private_info, and will be provided with each
292 # operation involving this open file
293 fp.flags = flags
294 rc = self.open(fp, pid, uid, gid)
295 return (rc, fp.flags, fp)
296
297 def open(self, fp, pid, uid, gid):
298 """open(): called when the device node is opened
299
300 open() gets to reject the opening attempt (perhaps based upon user
301 id number) by returning non-zero. It can also add attributes to the
302 new file pointer by modifying 'fp' before it returns.
303 """
304
305 print "open(flags=0x%x, pid=%d, uid/gid=%d/%d)" % (fp.flags,
306 pid, uid, gid)
307 return 0 # success
308
309 def do_close(self, fp, flags, pid, uid, gid):
310 fp.flags = flags
311 rc = self.close(fp, pid, uid, gid)
312 return (rc,)
313
314 def close(self, fp, pid, uid, gid):
315 """close(): called when the open file pointer is finally closed
316
317 close() is a good place to remove the OpenFile from a list of
318 currently open files.
319 """
320
321 print "close(flags=0x%x, pid=%d, uid/gid=%d/%d)" % (fp.flags,
322 pid, uid, gid)
323 return 0
324
325 # useful examples
326
327 class SampleDevice(Device):
328 class SampleFile(OpenFile):
329 def read(self, req, length, offset):
330 print "read[%d,%d]" % (offset, length)
331 if offset == 0:
332 return "hello world\n"
333 else:
334 return ""
335 def write(self, req, offset, data):
336 print "write[%d]" % offset, data
337 return len(data)
338 if req.dir_string in ("write", "readwrite"):
339 data = req.getdata()
340 else:
341 data = ""
342
343 def do_ioctl(self, req):
344 if req.dir_string in ("write", "readwrite"):
345 data = req.getdata()
346 else:
347 data = ""
348 print "ioctl(%x: %s/%x/%x/%d): [%d]%s" % (req.cmd,
349 req.dir_string,
350 req.type, req.nr,
351 req.size,
352 len(data), data)
353 if req.dir_string in ("read", "readwrite"):
354 req.setdata("x" * req.size)
355 req.finish(0)
356 openFileClass = SampleFile
357
358 # other devices: to use, make a Device with .openFileClass = NullFile, etc
359 class NullFile(OpenFile):
360 """/dev/null equivalent"""
361 poll_state = NOTIFY_INPUT | NOTIFY_OUTPUT
362 def read(self, req, length, offset):
363 return ""
364 def write(self, req, offset, data):
365 return len(data)
366 def do_ioctl(self, req):
367 req.finish(-errno.ENOSYS)
368
369 class ZeroFile(NullFile):
370 """/dev/zero equivalent"""
371 def read(self, req, length, offset):
372 return "\x00" * length
373
374 class FullFile(OpenFile):
375 """/dev/full equivalent"""
376 poll_state = NOTIFY_OUTPUT
377 def read(self, req, length, offset):
378 while 1:
379 pass # ??
380 def write(self, req, offset, data):
381 return 0
382 def do_ioctl(self, req):
383 req.finish(-errno.ENOSYS)
384
385 # OutFile/InFile act like a pty pair: once set up, writing to one will cause
386 # data to be available for reading from the other. They also serve as
387 # examples of the startReading/etc select/poll handling code.
388
389 class OutFile(OpenFile):
390 readQueue = ""
391 readReq = None
392 def addToQueue(self, data):
393 self.readQueue += data
394 self.startReading()
395 def do_read(self, req):
396 assert(not self.readReq)
397 if self.readQueue:
398 self.read(req)
399 else:
400 self.readReq = req
401 def read(self, req):
402 l = min(req.length, len(self.readQueue))
403 req.setdata(0, self.readQueue[:l])
404 self.readQueue = self.readQueue[l:]
405 if not self.readQueue:
406 self.stopReading()
407 req.finish(l)
408
409 def startReading(self):
410 if self.readReq:
411 self.read(self.readReq)
412 self.readReq = None
413 OpenFile.startReading(self)
414
415 class OutDevice(Device):
416 openFileClass = OutFile
417 target = None
418 def open(self, fp, pid, uid, gid):
419 print "opened", fp
420 self.target = fp
421 return 0
422
423 class InFile(OpenFile):
424 def write(self, req, offset, data):
425 if self.device.partner:
426 print "partner:", self.device.partner
427 print "target:", self.device.partner.target
428 self.device.partner.target.addToQueue(data)
429 return len(data)
430
431 def setup_pty(indevice, outdevice):
432 d1 = OutDevice(outdevice, 0666)
433 d2 = Device(indevice, 0666)
434 d2.openFileClass = InFile
435 d2.partner = d1
436 run()
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.