minix/minix/drivers/system/log/log.c
David van Moolenbroek 818c405ae1 LOG: fix bugs in userland write handler
- report correct number of bytes written;
- correctly return partial writes on failure;
- do not overwrite result if there is a pending read.

Change-Id: I92aeeaee1eccb47c2aa2b6666a2f560c3cb17f42
2015-07-26 11:56:36 +00:00

362 lines
10 KiB
C

/* This file contains a driver for:
* /dev/klog - system log device
*
* Changes:
* 21 July 2005 - Support for diagnostic messages (Jorrit N. Herder)
* 7 July 2005 - Created (Ben Gras)
*/
#include "log.h"
#include <sys/time.h>
#include <sys/select.h>
#include <minix/endpoint.h>
#define LOG_DEBUG 0 /* enable/ disable debugging */
#define NR_DEVS 1 /* number of minor devices */
#define MINOR_KLOG 0 /* /dev/klog */
#define LOGINC(n, i) do { (n) = (((n) + (i)) % LOG_SIZE); } while(0)
struct logdevice logdevices[NR_DEVS];
static ssize_t log_read(devminor_t minor, u64_t position, endpoint_t endpt,
cp_grant_id_t grant, size_t size, int flags, cdev_id_t id);
static ssize_t log_write(devminor_t minor, u64_t position, endpoint_t endpt,
cp_grant_id_t grant, size_t size, int flags, cdev_id_t id);
static int log_open(devminor_t minor, int access, endpoint_t user_endpt);
static int log_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id);
static int log_select(devminor_t minor, unsigned int ops, endpoint_t endpt);
static int subread(struct logdevice *log, size_t size, endpoint_t endpt,
cp_grant_id_t grant);
/* Entry points to this driver. */
static struct chardriver log_dtab = {
.cdr_open = log_open,
.cdr_read = log_read,
.cdr_write = log_write,
.cdr_cancel = log_cancel,
.cdr_select = log_select
};
/* SEF functions and variables. */
static void sef_local_startup(void);
static int sef_cb_init_fresh(int type, sef_init_info_t *info);
EXTERN int sef_cb_lu_prepare(int state);
EXTERN int sef_cb_lu_state_isvalid(int state);
EXTERN void sef_cb_lu_state_dump(int state);
static void sef_cb_signal_handler(int signo);
/*===========================================================================*
* main *
*===========================================================================*/
int main(void)
{
/* SEF local startup. */
sef_local_startup();
/* Call the generic receive loop. */
chardriver_task(&log_dtab);
return(OK);
}
/*===========================================================================*
* sef_local_startup *
*===========================================================================*/
static void sef_local_startup()
{
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_lu(sef_cb_init_fresh);
/* Register live update callbacks. */
sef_setcb_lu_prepare(sef_cb_lu_prepare);
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid);
sef_setcb_lu_state_dump(sef_cb_lu_state_dump);
/* Register signal callbacks. */
sef_setcb_signal_handler(sef_cb_signal_handler);
/* Let SEF perform startup. */
sef_startup();
}
/*===========================================================================*
* sef_cb_init_fresh *
*===========================================================================*/
static int sef_cb_init_fresh(int UNUSED(type), sef_init_info_t *UNUSED(info))
{
/* Initialize the log driver. */
int i;
/* Initialize log devices. */
for(i = 0; i < NR_DEVS; i++) {
logdevices[i].log_size = logdevices[i].log_read =
logdevices[i].log_write =
logdevices[i].log_selected = 0;
logdevices[i].log_source = NONE;
}
/* Register for diagnostics notifications. */
sys_diagctl_register();
/* Announce we are up! */
chardriver_announce();
return(OK);
}
/*===========================================================================*
* sef_cb_signal_handler *
*===========================================================================*/
static void sef_cb_signal_handler(int signo)
{
/* Only check for a pending message from the kernel, ignore anything else. */
if (signo != SIGKMESS) return;
do_new_kmess();
}
/*===========================================================================*
* subwrite *
*===========================================================================*/
static int
subwrite(struct logdevice *log, size_t size, endpoint_t endpt,
cp_grant_id_t grant, char *localbuf)
{
size_t count, offset;
int overflow, r, result;
devminor_t minor;
char *buf;
message m;
/* With a sufficiently large input size, we might wrap around the ring buffer
* multiple times.
*/
result = 0;
for (offset = 0; offset < size; offset += count) {
count = size - offset;
if (log->log_write + count > LOG_SIZE)
count = LOG_SIZE - log->log_write;
buf = log->log_buffer + log->log_write;
if(localbuf != NULL) {
memcpy(buf, localbuf, count);
localbuf += count;
}
else {
if((r=sys_safecopyfrom(endpt, grant, offset,
(vir_bytes)buf, count)) != OK) {
/* return any partial success upon error */
result = (offset > 0) ? (int)offset : r;
break;
}
}
LOGINC(log->log_write, count);
log->log_size += count;
if(log->log_size > LOG_SIZE) {
overflow = log->log_size - LOG_SIZE;
log->log_size -= overflow;
LOGINC(log->log_read, overflow);
}
result += (int)count;
}
if (log->log_size > 0 && log->log_source != NONE) {
/* Someone who was suspended on read can now be revived. */
r = subread(log, log->log_iosize, log->log_source, log->log_grant);
chardriver_reply_task(log->log_source, log->log_id, r);
log->log_source = NONE;
}
if (log->log_size > 0 && (log->log_selected & CDEV_OP_RD)) {
/* Someone(s) who was/were select()ing can now be awoken. If there was
* a blocking read (above), this can only happen if the blocking read
* didn't swallow all the data (log_size > 0).
*/
minor = log-logdevices;
#if LOG_DEBUG
printf("select sending CDEV_SEL2_REPLY\n");
#endif
chardriver_reply_select(log->log_select_proc, minor, CDEV_OP_RD);
log->log_selected &= ~CDEV_OP_RD;
}
return result;
}
/*===========================================================================*
* log_append *
*===========================================================================*/
void
log_append(char *buf, int count)
{
int skip = 0;
if(count < 1) return;
if(count > LOG_SIZE) skip = count - LOG_SIZE;
count -= skip;
buf += skip;
subwrite(&logdevices[0], count, SELF, GRANT_INVALID, buf);
}
/*===========================================================================*
* subread *
*===========================================================================*/
static int
subread(struct logdevice *log, size_t size, endpoint_t endpt,
cp_grant_id_t grant)
{
size_t offset, count;
char *buf;
int r;
for (offset = 0; log->log_size > 0 && offset < size; offset += count) {
count = size - offset;
if (count > log->log_size)
count = log->log_size;
if (log->log_read + count > LOG_SIZE)
count = LOG_SIZE - log->log_read;
buf = log->log_buffer + log->log_read;
if((r=sys_safecopyto(endpt, grant, offset, (vir_bytes)buf,
count)) != OK)
return r;
LOGINC(log->log_read, count);
log->log_size -= count;
}
return offset;
}
/*===========================================================================*
* log_read *
*===========================================================================*/
static ssize_t log_read(devminor_t minor, u64_t UNUSED(position),
endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags,
cdev_id_t id)
{
/* Read from one of the driver's minor devices. */
struct logdevice *log;
int r;
if (minor < 0 || minor >= NR_DEVS) return EIO;
log = &logdevices[minor];
/* If there's already someone hanging to read, don't accept new work. */
if (log->log_source != NONE) return OK;
if (!log->log_size && size > 0) {
if (flags & CDEV_NONBLOCK) return EAGAIN;
/* No data available; let caller block. */
log->log_source = endpt;
log->log_iosize = size;
log->log_grant = grant;
log->log_id = id;
#if LOG_DEBUG
printf("blocked %d (%d)\n", log->log_source, id);
#endif
return EDONTREPLY;
}
return subread(log, size, endpt, grant);
}
/*===========================================================================*
* log_write *
*===========================================================================*/
static ssize_t log_write(devminor_t minor, u64_t UNUSED(position),
endpoint_t endpt, cp_grant_id_t grant, size_t size, int UNUSED(flags),
cdev_id_t UNUSED(id))
{
/* Write to one of the driver's minor devices. */
struct logdevice *log;
int r;
if (minor < 0 || minor >= NR_DEVS) return EIO;
log = &logdevices[minor];
return subwrite(log, size, endpt, grant, NULL);
}
/*============================================================================*
* log_open *
*============================================================================*/
static int log_open(devminor_t minor, int UNUSED(access),
endpoint_t UNUSED(user_endpt))
{
if (minor < 0 || minor >= NR_DEVS) return(ENXIO);
return(OK);
}
/*============================================================================*
* log_cancel *
*============================================================================*/
static int log_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id)
{
if (minor < 0 || minor >= NR_DEVS)
return EINVAL;
/* Not for the suspended request? Must be a stale cancel request. Ignore. */
if (logdevices[minor].log_source != endpt || logdevices[minor].log_id != id)
return EDONTREPLY;
logdevices[minor].log_source = NONE;
return EINTR; /* this is the reply to the original, interrupted request */
}
/*============================================================================*
* log_select *
*============================================================================*/
static int log_select(devminor_t minor, unsigned int ops, endpoint_t endpt)
{
int want_ops, ready_ops = 0;
if (minor < 0 || minor >= NR_DEVS)
return ENXIO;
want_ops = ops & (CDEV_OP_RD | CDEV_OP_WR | CDEV_OP_ERR);
/* Read blocks when there is no log. */
if ((want_ops & CDEV_OP_RD) && logdevices[minor].log_size > 0) {
#if LOG_DEBUG
printf("log can read; size %d\n", logdevices[minor].log_size);
#endif
ready_ops |= CDEV_OP_RD;
}
/* Write never blocks. */
if (want_ops & CDEV_OP_WR) ready_ops |= CDEV_OP_WR;
/* Enable select calback if not all requested operations were ready to go,
* and notify was enabled.
*/
want_ops &= ~ready_ops;
if ((ops & CDEV_NOTIFY) && want_ops) {
logdevices[minor].log_selected |= want_ops;
logdevices[minor].log_select_proc = endpt;
#if LOG_DEBUG
printf("log setting selector.\n");
#endif
}
#if LOG_DEBUG
printf("log returning ops %d\n", ready_ops);
#endif
return(ready_ops);
}