/* * File: IO.c * * Copyright (C) 2000-2007 Jorge Arellano Cid * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /* * Dillo's event driven IO engine */ #include #include #include #include "../msg.h" #include "../chain.h" #include "../klist.h" #include "IO.h" #include "iowatch.hh" /* * Symbolic defines for shutdown() function * (Not defined in the same header file, for all distros --Jcid) */ #define IO_StopRd 1 #define IO_StopWr 2 #define IO_StopRdWr (IO_StopRd | IO_StopWr) typedef struct { int Key; /* Primary Key (for klist) */ int Op; /* IORead | IOWrite */ int FD; /* Current File Descriptor */ int Flags; /* Flag array (look definitions above) */ int Status; /* errno code */ Dstr *Buf; /* Internal buffer */ void *Info; /* CCC Info structure for this IO */ } IOData_t; /* * Local data */ static Klist_t *ValidIOs = NULL; /* Active IOs list. It holds pointers to * IOData_t structures. */ /* * Forward declarations */ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2); /* IO API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* * Return a new, initialized, 'io' struct */ static IOData_t *IO_new(int op) { IOData_t *io = dNew0(IOData_t, 1); io->Op = op; io->FD = -1; io->Flags = 0; io->Key = 0; io->Buf = dStr_sized_new(IOBufLen); return io; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* * Register an IO in ValidIOs */ static void IO_ins(IOData_t *io) { if (io->Key == 0) { io->Key = a_Klist_insert(&ValidIOs, io); } _MSG("IO_ins: io->Key=%d, Klist_length=%d\n", io->Key, a_Klist_length(ValidIOs)); } /* * Remove an IO from ValidIOs */ static void IO_del(IOData_t *io) { if (io->Key != 0) { a_Klist_remove(ValidIOs, io->Key); } io->Key = 0; _MSG(" -->ValidIOs: %d\n", a_Klist_length(ValidIOs)); } /* * Return a io by its Key (NULL if not found) */ static IOData_t *IO_get(int Key) { return (IOData_t *)a_Klist_get_data(ValidIOs, Key); } /* * Free an 'io' struct */ static void IO_free(IOData_t *io) { dStr_free(io->Buf, 1); dFree(io); } /* * Close an open FD, and remove io controls. * (This function can be used for Close and Abort operations) * BUG: there's a race condition for Abort. The file descriptor is closed * twice, and it could be reused for something else in between. It's simple * to fix, but it'd be better to design a canonical way to Abort the CCC. */ static void IO_close_fd(IOData_t *io, int CloseCode) { int events = 0; _MSG("====> begin IO_close_fd (%d) Key=%d CloseCode=%d Flags=%d ", io->FD, io->Key, CloseCode, io->Flags); /* With HTTP, if we close the writing part, the reading one also gets * closed! (other clients may set 'IOFlag_ForceClose') */ if (((io->Flags & IOFlag_ForceClose) || (CloseCode == IO_StopRdWr)) && io->FD != -1) { dClose(io->FD); } else { _MSG(" NOT CLOSING "); } /* Remove this IOData_t reference, from our ValidIOs list * We don't deallocate it here, just remove from the list.*/ IO_del(io); /* Stop the polling on this FD */ if (CloseCode & IO_StopRd) { events |= DIO_READ; } if (CloseCode & IO_StopWr) { events |= DIO_WRITE; } a_IOwatch_remove_fd(io->FD, events); _MSG(" end IO close (%d) <=====\n", io->FD); } /* * Read data from a file descriptor into a specific buffer */ static bool_t IO_read(IOData_t *io) { char Buf[IOBufLen]; ssize_t St; bool_t ret = FALSE; int io_key = io->Key; _MSG(" IO_read\n"); /* this is a new read-buffer */ dStr_truncate(io->Buf, 0); io->Status = 0; while (1) { St = read(io->FD, Buf, IOBufLen); if (St > 0) { dStr_append_l(io->Buf, Buf, St); continue; } else if (St < 0) { if (errno == EINTR) { continue; } else if (errno == EAGAIN) { ret = TRUE; break; } else { io->Status = errno; break; } } else { /* St == 0 */ break; } } if (io->Buf->len > 0) { /* send what we've got so far */ a_IO_ccc(OpSend, 2, FWD, io->Info, io, NULL); } if (St == 0) { /* TODO: design a general way to avoid reentrancy problems with CCC. */ /* The following check is necessary because the above CCC operation * may abort the whole Chain. */ if ((io = IO_get(io_key))) { /* All data read (EOF) */ _MSG("IO_read: io->Key=%d io_key=%d\n", io->Key, io_key); a_IO_ccc(OpEnd, 2, FWD, io->Info, io, NULL); } } return ret; } /* * Write data, from a specific buffer, into a file descriptor */ static bool_t IO_write(IOData_t *io) { ssize_t St; bool_t ret = FALSE; _MSG(" IO_write\n"); io->Status = 0; while (1) { St = write(io->FD, io->Buf->str, io->Buf->len); if (St < 0) { /* Error */ if (errno == EINTR) { continue; } else if (errno == EAGAIN) { ret = TRUE; break; } else { io->Status = errno; break; } } else if (St < io->Buf->len) { /* Not all data written */ dStr_erase (io->Buf, 0, St); } else { /* All data in buffer written */ dStr_truncate(io->Buf, 0); break; } } return ret; } /* * Handle background IO for a given FD (reads | writes) * (This function gets called when there's activity in the FD) */ static int IO_callback(IOData_t *io) { bool_t ret = FALSE; _MSG("IO_callback:: (%s) FD = %d\n", (io->Op == IORead) ? "IORead" : "IOWrite", io->FD); if (io->Op == IORead) { /* Read */ ret = IO_read(io); } else if (io->Op == IOWrite) { /* Write */ ret = IO_write(io); } return (ret) ? 1 : 0; } /* * Handle the READ event of a FD. */ static void IO_fd_read_cb(int fd, void *data) { int io_key = VOIDP2INT(data); IOData_t *io = IO_get(io_key); /* There should be no more events on already closed FDs --Jcid */ if (io == NULL) { MSG_ERR("IO_fd_read_cb: call on already closed io!\n"); a_IOwatch_remove_fd(fd, DIO_READ); } else { if (IO_callback(io) == 0) a_IOwatch_remove_fd(fd, DIO_READ); } } /* * Handle the WRITE event of a FD. */ static void IO_fd_write_cb(int fd, void *data) { int io_key = VOIDP2INT(data); IOData_t *io = IO_get(io_key); if (io == NULL) { /* There must be no more events on already closed FDs --Jcid */ MSG_ERR("IO_fd_write_cb: call on already closed io!\n"); a_IOwatch_remove_fd(fd, DIO_WRITE); } else { if (IO_callback(io) == 0) a_IOwatch_remove_fd(fd, DIO_WRITE); } } /* * Receive an IO request (IORead | IOWrite), * Set a watch for it, and let it flow! */ static void IO_submit(IOData_t *r_io) { if (r_io->FD < 0) { MSG_ERR("IO_submit: FD not initialized\n"); return; } /* Insert this IO in ValidIOs */ IO_ins(r_io); _MSG("IO_submit:: (%s) FD = %d\n", (r_io->Op == IORead) ? "IORead" : "IOWrite", r_io->FD); /* Set FD to background and to close on exec. */ fcntl(r_io->FD, F_SETFL, O_NONBLOCK | fcntl(r_io->FD, F_GETFL)); fcntl(r_io->FD, F_SETFD, FD_CLOEXEC | fcntl(r_io->FD, F_GETFD)); if (r_io->Op == IORead) { a_IOwatch_add_fd(r_io->FD, DIO_READ, IO_fd_read_cb, INT2VOIDP(r_io->Key)); } else if (r_io->Op == IOWrite) { a_IOwatch_add_fd(r_io->FD, DIO_WRITE, IO_fd_write_cb, INT2VOIDP(r_io->Key)); } } /* * CCC function for the IO module * ( Data1 = IOData_t* ; Data2 = NULL ) */ void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2) { IOData_t *io; DataBuf *dbuf; dReturn_if_fail( a_Chain_check("a_IO_ccc", Op, Branch, Dir, Info) ); if (Branch == 1) { if (Dir == BCK) { /* Write data using select */ switch (Op) { case OpStart: io = IO_new(IOWrite); io->Info = Info; Info->LocalKey = io; break; case OpSend: io = Info->LocalKey; if (Data2 && !strcmp(Data2, "FD")) { io->FD = *(int*)Data1; /* SockFD */ } else { dbuf = Data1; dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size); IO_submit(io); } break; case OpEnd: case OpAbort: io = Info->LocalKey; if (io->Buf->len > 0) { char *newline = memchr(io->Buf->str, '\n', io->Buf->len); int msglen = newline ? newline - io->Buf->str : 2048; MSG_WARN("IO_write, closing with pending data not sent: " "\"%s\"\n", dStr_printable(io->Buf, msglen)); } /* close FD, remove from ValidIOs and remove its watch */ IO_close_fd(io, Op == OpEnd ? IO_StopWr : IO_StopRdWr); IO_free(io); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } else { /* 1 FWD */ /* Write-data status */ switch (Op) { default: MSG_WARN("Unused CCC\n"); break; } } } else if (Branch == 2) { if (Dir == BCK) { /* This part catches the reader's messages */ switch (Op) { case OpStart: io = IO_new(IORead); Info->LocalKey = io; io->Info = Info; break; case OpSend: io = Info->LocalKey; if (Data2 && !strcmp(Data2, "FD")) { io->FD = *(int*)Data1; /* SockFD */ IO_submit(io); } break; case OpEnd: case OpAbort: io = Info->LocalKey; IO_close_fd(io, Op == OpEnd ? IO_StopRd : IO_StopRdWr); IO_free(io); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } else { /* 2 FWD */ /* Send read-data */ io = Data1; switch (Op) { case OpSend: dbuf = a_Chain_dbuf_new(io->Buf->str, io->Buf->len, 0); a_Chain_fcb(OpSend, Info, dbuf, NULL); dFree(dbuf); break; case OpEnd: a_Chain_fcb(OpEnd, Info, NULL, NULL); IO_close_fd(io, IO_StopRdWr); /* IO_StopRd would leak FDs */ IO_free(io); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } } }