1 /*
2 *
3 * Copyright (c) 2003 The Regents of the University of California. All
4 * rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * - Neither the name of the University nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS''
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
19 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 */
30
31
32 /*
33 * FUSD - The Framework for UserSpace Devices - Example program
34 *
35 * Jeremy Elson <jelson@circlemud.org>
36 *
37 * logring.c: Implementation of a circular buffer log device
38 *
39 * logring makes it easy to access the most recent (and only the most
40 * recent) output from a process. It works just like "tail -f" on a
41 * log file, except that the storage required never grows. This can be
42 * useful in embedded systems where there isn't enough memory or disk
43 * space for keeping complete log files, but the most recent debugging
44 * messages are sometimes needed (e.g., after an error is observed).
45 *
46 * Logring uses FUSD to implement a character device, /dev/logring,
47 * that acts like a named pipe that has a finite, circular buffer.
48 * The size of the buffer is given as a command-line argument. As
49 * more data is written into the buffer, the oldest data is discarded.
50 * A process that reads from the logring device will first read the
51 * existing buffer, then block and see new data as it's written,
52 * similar to monitoring a log file using "tail -f".
53 *
54 * Non-blocking reads are supported; if a process needs to get the
55 * current contents of the log without blocking to wait for new data,
56 * it can set the O_NONBLOCK flag when it does the open(), or set it
57 * later using ioctl().
58 *
59 * The select() interface is also supported; programs can select on
60 * /dev/logring to be notified when new data is available.
61 *
62 * Run this example program by typing "logring X", where X is the size
63 * of the circular buffer in bytes. Then, type "cat /dev/logring" in
64 * one shell. The cat process will block, waiting for data, similar
65 * to "tail -f". From another shell, write to the logring (e.g.,
66 * "echo Hi there > /dev/logring".) The 'cat' process will see the
67 * message appear.
68 *
69 * Note: this example program is based on "emlog", a true Linux kernel
70 * module with identical functionality. If you find logring useful,
71 * but want to use it on a system that does not have FUSD, check out
72 * emlog at http://www.circlemud.org/~jelson/software/emlog.
73 *
74 * $Id: logring.c,v 1.9 2007-12-08 04:05:01 girod Exp $
75 */
76
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <string.h>
80 #include <errno.h>
81 #include <fcntl.h>
82
83 #include <fusd.h>
84
85
86 /* per-client structure to keep track of who has an open FD to us */
87 struct logring_client {
88
89 /* used to store outstanding read and polldiff requests */
90 struct fusd_file_info *read;
91 struct fusd_file_info *polldiff;
92
93 /* to construct the linked list */
94 struct logring_client *next;
95 };
96
97 /* list of currently open file descriptors */
98 struct logring_client *client_list = NULL;
99
100 char *logring_data = NULL; /* the data buffer used for the logring */
101 int logring_size = 0; /* buffer space in the logring */
102 int logring_writeindex = 0; /* write point in the logring array */
103 int logring_readindex = 0; /* read point in the logring array */
104 int logring_offset = 0; /* how far into the total stream is
105 * logring_read pointing? */
106
107 /* amount of data in the queue */
108 #define LOGRING_QLEN (logring_writeindex >= logring_readindex ? \
109 logring_writeindex - logring_readindex : \
110 logring_size - logring_readindex + logring_writeindex)
111
112 /* stream byte number of the last byte in the queue */
113 #define LOGRING_FIRST_EMPTY_BYTE (logring_offset + LOGRING_QLEN)
114
115 #define MIN(x, y) ((x) < (y) ? (x) : (y))
116
117 /************************************************************************/
118
119 /*
120 * this function removes an element from a linked list. the
121 * pointer-manipulation insanity below is a trick that prevents the
122 * "element to be removed is the head of the list" from being a
123 * special case.
124 */
125 void client_list_remove(struct logring_client *c)
126 {
127 struct logring_client **ptr;
128
129 if (c == NULL || client_list == NULL)
130 return;
131
132 for (ptr = &client_list; *ptr != c; ptr = &((**ptr).next)) {
133 if (!*ptr) {
134 fprintf(stderr, "trying to remove a client that isn't in the list\n");
135 return;
136 }
137 }
138 *ptr = c->next;
139 }
140
141
142 /* open on /dev/logring: create state for this client */
143 static int logring_open(struct fusd_file_info *file)
144 {
145 /* create state for this client */
146 struct logring_client *c = malloc(sizeof(struct logring_client));
147
148 if (c == NULL)
149 return -ENOBUFS;
150
151 /* initialize fields of this client state */
152 memset(c, 0, sizeof(struct logring_client));
153
154 /* save the pointer to this state so it gets returned to us later */
155 file->private_data = c;
156
157 /* add this client to the client list */
158 c->next = client_list;
159 client_list = c;
160
161 return 0;
162 }
163
164
165 /* close on /dev/logring: destroy state for this client */
166 static int logring_close(struct fusd_file_info *file)
167 {
168 struct logring_client *c;
169
170 if ((c = (struct logring_client *) file->private_data) != NULL) {
171
172 /* take this client off our client list */
173 client_list_remove(c);
174
175 /* if there is a read outstanding, free the state */
176 if (c->read != NULL) {
177 fusd_destroy(c->read);
178 c->read = NULL;
179 }
180 /* destroy any outstanding polldiffs */
181 if (c->polldiff != NULL) {
182 fusd_destroy(c->polldiff);
183 c->polldiff = NULL;
184 }
185
186 /* get rid of the struct */
187 free(c);
188 file->private_data = NULL;
189 }
190 return 0;
191 }
192
193
194
195 /*
196 * This function "completes" a read: that is, matches up a client who
197 * is requesting data with data that's waiting to be served.
198 *
199 * This function is called in two cases:
200 *
201 * 1- When a new read request comes in (it might be able to complete
202 * immediately, if there's data waiting that the client hasn't seen
203 * yet)
204 *
205 * 2- When new data comes in (the new data might be able to complete
206 * a read that had been previously blocked)
207 */
208 void logring_complete_read(struct logring_client *c)
209 {
210 loff_t *user_offset;
211 char *user_buffer;
212 size_t user_length;
213 int bytes_copied = 0, n, start_point, retval;
214
215
216 /* if there is no outstanding read, do nothing */
217 if (c == NULL || c->read == NULL)
218 return;
219
220 /* retrieve the read callback's arguments */
221 user_offset = fusd_get_offset(c->read);
222 user_buffer = fusd_get_read_buffer(c->read);
223 user_length = fusd_get_length(c->read);
224
225 /* is the client trying to read data that has scrolled off? */
226 if (*user_offset < logring_offset)
227 *user_offset = logring_offset;
228
229 /* is there new data this user hasn't seen yet, or are we at EOF? */
230 /* If we have reached EOF:
231 * If this is a nonblocking read, return EAGAIN.
232 * else return without doing anything; keep the read blocked.
233 */
234 if (*user_offset >= LOGRING_FIRST_EMPTY_BYTE) {
235 if (c->read->flags & O_NONBLOCK) {
236 retval = -EAGAIN;
237 goto done;
238 } else {
239 return;
240 }
241 }
242
243 /* find the smaller of the total bytes we have available and what
244 * the user is asking for */
245 user_length = MIN(user_length, LOGRING_FIRST_EMPTY_BYTE - *user_offset);
246 retval = user_length;
247
248 /* figure out where to start copying data from, based on user's offset */
249 start_point =
250 (logring_readindex + (*user_offset-logring_offset)) % logring_size;
251
252 /* copy the (possibly noncontiguous) data into user's buffer) */
253 while (user_length) {
254 n = MIN(user_length, logring_size - start_point);
255 memcpy(user_buffer + bytes_copied, logring_data + start_point, n);
256 bytes_copied += n;
257 user_length -= n;
258 start_point = (start_point + n) % logring_size;
259 }
260
261 /* advance the user's file pointer */
262 *user_offset += retval;
263
264 done:
265 /* and complete the read system call */
266 fusd_return(c->read, retval);
267 c->read = NULL;
268 }
269
270
271
272 /*
273 * read on /dev/logring: store the fusd_file_info pointer. then call
274 * complete_read, which will immediately call fusd_return, if there is
275 * data already waiting.
276 *
277 * Note that this shows a trick we use commonly in FUSD drivers: you
278 * are allowed to call fusd_return() from within a callback as long as
279 * you return -FUSD_NOREPLY. In other words, a driver can EITHER
280 * return a real return value from its callback, OR call fusd_return
281 * explicitly, but not both.
282 */
283 static ssize_t logring_read(struct fusd_file_info *file, char *buffer,
284 size_t len, loff_t *offset)
285 {
286 struct logring_client *c = (struct logring_client *) file->private_data;
287
288 if (c == NULL || c->read != NULL) {
289 fprintf(stderr, "logring_read's arguments are confusd, alas");
290 return -EINVAL;
291 }
292
293 c->read = file;
294 logring_complete_read(c);
295 return -FUSD_NOREPLY;
296 }
297
298
299 /*
300 * complete_polldiff: if a client has an outstanding 'polldiff'
301 * request, possibly return updated poll-state information to the
302 * kernel, if indeed the state has changed.
303 */
304 void logring_complete_polldiff(struct logring_client *c)
305
306 {
307 int curr_state, cached_state;
308
309 /* if there is no outstanding polldiff, do nothing */
310 if (c == NULL || c->polldiff == NULL)
311 return;
312
313 /* figure out the "current" state: i.e. whether or not the logring
314 * is readable for this client based on its current position in the
315 * stream. The logring is *always* writable. */
316 if (*(fusd_get_offset(c->polldiff)) < LOGRING_FIRST_EMPTY_BYTE)
317 curr_state = FUSD_NOTIFY_INPUT | FUSD_NOTIFY_OUTPUT; /* read and write */
318 else
319 curr_state = FUSD_NOTIFY_OUTPUT; /* writable only */
320
321 /* cached_state is what the kernel *thinks* the state is */
322 cached_state = fusd_get_poll_diff_cached_state(c->polldiff);
323
324 /* if the state is not what the kernel thinks it is, notify the
325 kernel of the change */
326 if (curr_state != cached_state) {
327 fusd_return(c->polldiff, curr_state);
328 c->polldiff = NULL;
329 }
330 }
331
332
333 /* This function is only called on behalf of clients who are trying to
334 * use select(). The kernel keeps us up to date on what it thinks the
335 * current "poll state" is, i.e. readable and/or writable. The kernel
336 * calls this function every time its assumption about the current
337 * poll state changes. Every time the driver's notion of the state
338 * differs from what the kernel thinks it is, it should return the
339 * poll_diff request with the updated state. Note that a 2nd request
340 * may come from the kernel before the driver has returned the first
341 * one; if this happens, use fusd_destroy() to get rid of the older one.
342 */
343 int logring_polldiff(struct fusd_file_info *file, unsigned int flags)
344 {
345 struct logring_client *c = (struct logring_client *) file->private_data;
346
347 if (c == NULL)
348 return -EIO;
349
350 /* if we're already holding a polldiff request that we haven't
351 * replied to yet, destroy the old one and hold onto only the new
352 * one */
353 if (c->polldiff != NULL) {
354 fusd_destroy(c->polldiff);
355 c->polldiff = NULL;
356 }
357
358 c->polldiff = file;
359 logring_complete_polldiff(c);
360 return -FUSD_NOREPLY;
361 }
362
363
364 /*
365 * a write on /dev/logring: first, copy the data from the user into our
366 * data queue. Then, complete any reads and polldiffs that might be
367 * outstanding.
368 */
369 ssize_t logring_write(struct fusd_file_info *file, const char *buffer,
370 size_t len, loff_t *offset)
371 {
372 struct logring_client *c;
373 int overflow = 0, bytes_copied = 0, n, retval;
374
375 /* if the message is longer than the buffer, just take the beginning
376 * of it, in hopes that the reader (if any) will have time to read
377 * before we wrap around and obliterate it */
378 len = MIN(len, logring_size - 1);
379 retval = len;
380
381 if (len + LOGRING_QLEN >= (logring_size-1)) {
382 overflow = 1;
383
384 /* in case of overflow, figure out where the new buffer will
385 * begin. we start by figuring out where the current buffer ENDS:
386 * logring_offset + LOGRING_QLEN. we then advance the end-offset
387 * by the length of the current write, and work backwards to
388 * figure out what the oldest unoverwritten data will be (i.e.,
389 * size of the buffer). was that all quite clear? :-) */
390 logring_offset = logring_offset + LOGRING_QLEN + len - logring_size + 1;
391 }
392
393 while (len) {
394 /* how many contiguous bytes are available from the write point to
395 * the end of the circular buffer? */
396 n = MIN(len, logring_size - logring_writeindex);
397 memcpy(logring_data + logring_writeindex, buffer + bytes_copied, n);
398 bytes_copied += n;
399 len -= n;
400 logring_writeindex = (logring_writeindex + n) % logring_size;
401 }
402
403 /* if there was an overflow (i.e., new data wrapped around and
404 * overwrote old data that had not yet been read), then, reset the
405 * read point to be whatever the oldest data is that we have. */
406 if (overflow)
407 logring_readindex = (logring_writeindex + 1) % logring_size;
408
409 /* now, complete any blocked reads and/or polldiffs */
410 for (c = client_list; c != NULL; c = c->next) {
411 logring_complete_read(c);
412 logring_complete_polldiff(c);
413 }
414
415 /* now tell the client how many bytes we acutally wrote */
416 return retval;
417 }
418
419
420 int main(int argc, char *argv[])
421 {
422 char *name;
423
424 /* size must be provided, and an optional logring name */
425 if (argc != 2 && argc != 3) {
426 fprintf(stderr, "usage: %s <logring-size> [logring-name]\n", argv[0]);
427 exit(1);
428 }
429
430 name = (argc == 3 ? argv[2] : "/dev/logring");
431
432 /* convert the arg to an int and alloc memory for the logring */
433 if ((logring_size = atoi(argv[1])) <= 0) {
434 fprintf(stderr, "invalid logring size; it must be >0\n");
435 exit(1);
436 }
437
438 if ((logring_data = (char *) malloc(sizeof(char) * logring_size)) == NULL) {
439 fprintf(stderr, "couldn't allocate %d bytes!\n", logring_size);
440 exit(1);
441 }
442
443 /* register the fusd device */
444 fusd_simple_register(name, 0666, NULL,
445 open: logring_open, close: logring_close,
446 read: logring_read, write: logring_write,
447 poll_diff: logring_polldiff);
448
449 printf("calling fusd_run; reads from /dev/logring will now block\n"
450 "until someone writes to /dev/logring...\n");
451 fusd_run();
452
453 return 0;
454 }
455
456
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.