~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Linux Cross Reference
cvs/emstar/fusd/examples/logring.c


  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 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

This page was automatically generated by the LXR engine.
Visit the LXR main site for more information.