/* emi.c */ /* Copyright 1997-2000 by Eberhard Mattes Donated to the public domain. No warranty. 1997-07-19 Initial version 2000-05-01 Add emi_fill() 2000-05-12 Fix emi_fill() */ #include #include #include #include #include #include #include "libemfw.h" #include "emio.h" /* Open the file whose name is specified by the string pointed to by FNAME. The file must already exist. BUFSIZE is the size of the buffer to be used for the file. Return NULL on error. */ EMI_FILE *emi_open (const char *fname, size_t bufsize) { int fd; if (fname == NULL || bufsize == 0) { errno = EINVAL; return NULL; } fd = open (fname, O_RDONLY); if (fd == -1) return NULL; return emi_fdopen (fd, bufsize); } /* Make an EMI_FILE from a file descriptor. BUFSIZE is the size of the buffer to be used for the file. Return NULL on error. */ EMI_FILE *emi_fdopen (int fd, size_t bufsize) { EMI_FILE *f; if (bufsize == 0) { errno = EINVAL; return NULL; } f = (EMI_FILE *)malloc (sizeof (EMI_FILE)); if (f == NULL) { errno = ENOMEM; return NULL; } f->buf = (char *)malloc (bufsize); if (f->buf == NULL) { free (f); errno = ENOMEM; return NULL; } f->fd = fd; f->flags = 0; f->bufsize = bufsize; f->limit = -1; /* Unlimited */ f->timeout = 0; /* No timeout */ f->error = 0; f->amount = 0; f->ptr = f->end = f->buf; return f; } /* Close F. Return 0 on success, -1 on error. */ int emi_close (EMI_FILE *f) { int fd; DEBUG_ASSERT (f != NULL); fd = f->fd; DEBUG_ASSERT (f->buf != NULL); free (f->buf); free (f); return close (fd); } /* Helper function for emi_read(), emi_peek(), and emi_getc1(). Read up to SIZE bytes from the file F to the array pointed to by DST. Return the actual number of bytes read. Return -1 on error. This is where the time out is implemented. */ static int emi_read1 (EMI_FILE *f, void *dst, int size) { int n; DEBUG_ASSERT (f != NULL); DEBUG_ASSERT (dst != NULL); DEBUG_ASSERT (size > 0); /* Handle limit. */ DEBUG_ASSERT (f->limit >= -1); if (f->limit != -1 && f->limit < size) size = (int)f->limit; if (size == 0) { f->flags |= EMI_FLAG_EOF; return 0; } /* Handle timeout. */ if (f->timeout != 0) { fd_set rfds; struct timeval tv; DEBUG_ASSERT (f->timeout > 0); FD_ZERO (&rfds); FD_SET (f->fd, &rfds); /* TODO: Check for overflow */ tv.tv_sec = f->timeout; tv.tv_usec = 0; n = select (f->fd + 1, &rfds, (fd_set *)0, (fd_set *)0, &tv); if (n <= 0) { if (n == 0) errno = ETIMEDOUT; f->flags |= EMI_FLAG_ERROR; f->error = errno; return -1; } } n = read (f->fd, dst, size); if (n == -1) { f->flags |= EMI_FLAG_ERROR; f->error = errno; } else if (n == 0) f->flags |= EMI_FLAG_EOF; else { f->flags &= ~EMI_FLAG_EOF; if (f->limit != -1) { f->limit -= n; DEBUG_ASSERT (f->limit >= 0); } f->amount += n; } return n; } /* Read up to SIZE bytes from F to the buffer pointed to by DST. Return the actual number of bytes read. Return -1 on error. */ int emi_read (EMI_FILE *f, void *dst, int size) { int total = 0, n; DEBUG_ASSERT (f != NULL); DEBUG_ASSERT (dst != NULL); if (size < 0) { errno = EINVAL; return -1; } if (f->flags & EMI_FLAG_ERROR) { errno = f->error; return -1; } /* Get data from the buffer if there's any data in the buffer. */ if (f->ptr != f->end) { n = (int) (f->end - f->ptr); DEBUG_ASSERT (n >= 0 && n <= f->bufsize); if (n > size) n = size; memcpy (dst, f->ptr, n); f->ptr += n; total += n; size -= n; dst = (void *)((char *)dst + n); } if (size == 0) return total; /* Read directly to the target object if buffering seems to be inefficient due to the amount of data requested. */ DEBUG_ASSERT (f->ptr == f->end); DEBUG_ASSERT (size > 0); if (size > f->bufsize / 2) { n = emi_read1 (f, dst, size); if (n == -1) return -1; total += n; return total; } /* Read to the stream buffer, then copy from the stream buffer to the target object. */ DEBUG_ASSERT (size <= f->bufsize); n = emi_read1 (f, f->buf, f->bufsize); if (n == -1) return -1; f->ptr = f->buf; f->end = f->buf + n; if (n > size) n = size; memcpy (dst, f->ptr, n); f->ptr += n; total += n; DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end); return total; } /* Fill the buffer until it contains SIZE bytes (or less when hitting EOF or the buffer size). Return the number of bytes in the buffer (but never a value greater than SIZE). Return -1 on error. */ int emi_fill (EMI_FILE *f, int size) { int have, need, add; DEBUG_ASSERT (f != NULL); if (size < 0) { errno = EINVAL; return -1; } if (f->flags & EMI_FLAG_ERROR) { errno = f->error; return -1; } if (size > f->bufsize) size = (int) f->bufsize; for (;;) { have = (int) (f->end - f->ptr); DEBUG_ASSERT (have >= 0 && have <= f->bufsize); if (have >= size) return size < have ? size : have; /* Enough */ need = size - have; DEBUG_ASSERT (have + need <= f->bufsize); add = emi_read1 (f, (char *)f->ptr + have, need); if (add == -1) return -1; /* Error */ if (add == 0) return have; /* EOF */ f->end += add; DEBUG_ASSERT (f->ptr + have + add == f->end); } } /* Artificially limit the input from F to LIMIT bytes. If LIMIT is -1, the limit will be disabled. */ int emi_limit (EMI_FILE *f, long limit) { DEBUG_ASSERT (f != NULL); if (limit < -1) { errno = EINVAL; return -1; } f->limit = limit; return 0; } /* Set the time out for F to TIMEOUT seconds. If TIMEOUT is 0, time out will be disabled. */ int emi_timeout (EMI_FILE *f, int timeout) { DEBUG_ASSERT (f != NULL); if (timeout < 0) { errno = EINVAL; return -1; } f->timeout = timeout; return 0; } /* Return the total number of bytes read. If USER is 0, return the number of bytes read to the buffer. If USER is non-zero, return the number of bytes returned to the caller. */ unsigned long emi_amount (EMI_FILE *f, int user) { if (f == NULL) return 0; /* This simplifies quit() of squid-gw */ if (user) { DEBUG_ASSERT (f->ptr <= f->end); return f->amount - (f->end - f->ptr); } else return f->amount; } /* Copy up to SIZE bytes from F to the buffer pointed to by DST, without actually consuming those bytes. That is, emi_peek() looks ahead and calling or not calling emi_peek() does not make a difference (except in the case of errors) for future read operations. SIZE is limited by the buffer size of F. Return the actual number of bytes read. Return -1 on error. */ int emi_peek (EMI_FILE *f, void *dst, int size) { int buffered, nread, off; DEBUG_ASSERT (f != NULL); DEBUG_ASSERT (dst != NULL); if (size < 0 || size > f->bufsize) { errno = EINVAL; return -1; } if (f->flags & EMI_FLAG_ERROR) { errno = f->error; return -1; } /* We have to fill the buffer if there are less than SIZE bytes in the buffer. */ buffered = f->end - f->ptr; DEBUG_ASSERT (buffered >= 0 && buffered <= f->bufsize); if (size > buffered) { /* There might not be enough space left at the end of the buffer. In that case, we have to move down the active buffer contents. */ DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end); if (f->bufsize - (f->ptr - f->buf) < size) { memmove (f->buf, f->ptr, buffered); f->ptr = f->buf; f->end = f->buf + buffered; DEBUG_ASSERT (f->bufsize - (f->ptr - f->buf) >= size); } off = f->end - f->buf; nread = emi_read1 (f, f->buf + off, f->bufsize - off); if (nread == -1) return -1; f->end += nread; buffered += nread; DEBUG_ASSERT (buffered >= 0 && buffered <= f->bufsize); DEBUG_ASSERT (f->end - f->ptr == buffered); } if (buffered < size) size = buffered; memcpy (dst, f->ptr, size); return size; } /* Helper function for the emi_getc() macro. This function has the same interface as emi_getc(): On success (subject to buffering), return the next character of F converted to unsigned char. Return EMI_EOF on error or when reaching EOF. */ int emi_getc1 (EMI_FILE *f) { int n; DEBUG_ASSERT (f != NULL); DEBUG_ASSERT (f->ptr == f->end); if (f->flags & EMI_FLAG_ERROR) { errno = f->error; return EMI_EOF; } n = emi_read1 (f, f->buf, f->bufsize); if (n == -1 || n == 0) return EMI_EOF; f->ptr = f->buf; f->end = f->buf + n; return (unsigned char)*f->ptr++; } #ifndef NDEBUG /* This function is defined only if DEBUG_ASSERT() is not disabled. If NDEBUG is defined, there's no point in this function as DEBUG_ASSERT() would be a no-op. This happens when compiling emi.c with NDEBUG undefined and the application with NDEBUG defined. We want to get a linker error in that case. */ void emi_getc_assert (EMI_FILE *f) { DEBUG_ASSERT (f != NULL); DEBUG_ASSERT (f->end >= f->buf && f->end <= f->buf + f->bufsize); DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end); } #endif