/*
- * Copyright (C) 2006-2007 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2006-2009 Andre Noll <maan@systemlinux.org>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
/** \file fd.c Helper functions for file descriptor handling. */
+#include <regex.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/mman.h>
#include "para.h"
#include "error.h"
+#include "string.h"
+
+/**
+ * Write a buffer to a file descriptor, re-write on short writes.
+ *
+ * \param fd The file descriptor.
+ * \param buf The buffer to be sent.
+ * \param len The length of \a buf.
+ *
+ * \return Standard. In any case, the number of bytes that have been written is
+ * stored in \a len.
+ */
+int write_all(int fd, const char *buf, size_t *len)
+{
+ size_t total = *len;
+
+ assert(total);
+ *len = 0;
+ while (*len < total) {
+ int ret = write(fd, buf + *len, total - *len);
+ if (ret == -1)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ *len += ret;
+ }
+ return 1;
+}
+
+/**
+ * Write a buffer to a non-blocking file descriptor.
+ *
+ * \param fd The file descriptor.
+ * \param buf the buffer to write.
+ * \param len the number of bytes of \a buf.
+ * \param max_bytes_per_write Do not write more than that many bytes at once.
+ *
+ * If \a max_bytes_per_write is non-zero, do not send more than that many bytes
+ * per write().
+ *
+ * EAGAIN is not considered an error condition. For example CCID3 has a
+ * sending wait queue which fills up and is emptied asynchronously. The EAGAIN
+ * case means that there is currently no space in the wait queue, but this can
+ * change at any moment.
+ *
+ * \return Negative on errors, number of bytes written else.
+ */
+int write_nonblock(int fd, const char *buf, size_t len,
+ size_t max_bytes_per_write)
+{
+ size_t written = 0;
+ int ret = 0;
+
+ while (written < len) {
+ size_t num = len - written;
+
+ if (max_bytes_per_write && max_bytes_per_write < num)
+ num = max_bytes_per_write;
+ ret = write(fd, buf + written, num);
+ if (ret < 0 && errno == EAGAIN)
+ return written;
+ if (ret < 0)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ written += ret;
+ }
+ return written;
+}
/**
* Check whether a file exists.
int para_select(int n, fd_set *readfds, fd_set *writefds,
struct timeval *timeout_tv)
{
- int ret, err;
- do {
+ int ret;
+ do
ret = select(n, readfds, writefds, NULL, timeout_tv);
- err = errno;
- } while (ret < 0 && err == EINTR);
+ while (ret < 0 && errno == EINTR);
if (ret < 0)
return -ERRNO_TO_PARA_ERROR(errno);
return ret;
*
* \return Standard.
*/
-int mark_fd_blocking(int fd)
+__must_check int mark_fd_blocking(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0)
*
* \return Standard.
*/
-int mark_fd_nonblocking(int fd)
+__must_check int mark_fd_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0)
*/
void para_fd_set(int fd, fd_set *fds, int *max_fileno)
{
-
- if (fd < 0 || fd >= FD_SETSIZE) {
- PARA_EMERG_LOG("fatal: tried to add invalid fd %d\n", fd);
- exit(EXIT_FAILURE);
- }
+ assert(fd >= 0 && fd < FD_SETSIZE);
#if 0
{
int flags = fcntl(fd, F_GETFL);
}
/**
-* Paraslash's wrapper for fgets(3).
-
-* \param line Pointer to the buffer to store the line.
-* \param size The size of the buffer given by \a line.
-* \param f The stream to read from.
-*
-* \return Unlike the standard fgets() function, an integer value
-* is returned. On success, this function returns 1. On errors, -E_FGETS
-* is returned. A zero return value indicates an end of file condition.
-*/
+ * Paraslash's wrapper for fgets(3).
+ *
+ * \param line Pointer to the buffer to store the line.
+ * \param size The size of the buffer given by \a line.
+ * \param f The stream to read from.
+ *
+ * \return Unlike the standard fgets() function, an integer value
+ * is returned. On success, this function returns 1. On errors, -E_FGETS
+ * is returned. A zero return value indicates an end of file condition.
+ */
__must_check int para_fgets(char *line, int size, FILE *f)
{
again:
* \param flags Exactly one of MAP_SHARED and MAP_PRIVATE.
* \param fd The file to mmap from.
* \param offset Mmap start.
+ * \param map Result pointer.
*
- * \return This function either returns a valid pointer to the mapped area
- * or calls exit() on errors.
+ * \return Standard.
+ *
+ * \sa mmap(2).
*/
-void *para_mmap(size_t length, int prot, int flags, int fd, off_t offset)
+int para_mmap(size_t length, int prot, int flags, int fd, off_t offset,
+ void *map)
{
- void *ret = mmap(NULL, length, prot, flags, fd, offset);
- if (ret != MAP_FAILED)
- return ret;
- PARA_EMERG_LOG("mmap failed: %s\n", strerror(errno));
- PARA_EMERG_LOG("length: %zu, flags: %d, fd: %d, offset: %zu\n",
- length, flags, fd, (size_t)offset);
- exit(EXIT_FAILURE);
+ void **m = map;
+
+ errno = EINVAL;
+ if (!length)
+ goto err;
+ *m = mmap(NULL, length, prot, flags, fd, offset);
+ if (*m != MAP_FAILED)
+ return 1;
+err:
+ *m = NULL;
+ return -ERRNO_TO_PARA_ERROR(errno);
}
/**
if (*dir)
return 1;
ret = -ERRNO_TO_PARA_ERROR(errno);
-/* Ignore return value of fchdir() and close(). We're busted anyway. */
- if (cwd)
- fchdir(*cwd);
+ /* Ignore return value of fchdir() and close(). We're busted anyway. */
+ if (cwd) {
+ int __a_unused ret2 = fchdir(*cwd); /* STFU, gcc */
+ }
close_cwd:
if (cwd)
close(*cwd);
goto out;
}
*size = file_status.st_size;
- ret = -E_EMPTY;
- PARA_DEBUG_LOG("%s: size %zu\n", path, *size);
- if (!*size)
- goto out;
- *map = mmap(NULL, *size, mmap_prot, mmap_flags, fd, 0);
- if (*map == MAP_FAILED) {
- *map = NULL;
- ret = -E_MMAP;
- goto out;
- }
- ret = 1;
+ ret = para_mmap(*size, mmap_prot, mmap_flags, fd, 0, map);
out:
if (ret < 0 || !fd_ptr)
close(fd);
* \param start The start address of the memory mapping.
* \param length The size of the mapping.
*
- * \return Positive on success, \p -E_MUNMAP on errors.
+ * \return Standard.
*
* \sa munmap(2), mmap_full_file().
*/
int para_munmap(void *start, size_t length)
{
+ int err;
if (munmap(start, length) >= 0)
return 1;
+ err = errno;
PARA_ERROR_LOG("munmap (%p/%zu) failed: %s\n", start, length,
- strerror(errno));
- return -E_MUNMAP;
+ strerror(err));
+ return -ERRNO_TO_PARA_ERROR(err);
}
/**
- * check a file descriptor for writability
+ * Check a file descriptor for writability.
*
- * \param fd the file descriptor
+ * \param fd The file descriptor.
*
* \return positive if fd is ready for writing, zero if it isn't, negative if
* an error occurred.
int write_ok(int fd)
{
- struct timeval tv = {0, 0};
+ struct timeval tv;
fd_set wfds;
- int ret;
-again:
+
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
- ret = select(fd + 1, NULL, &wfds, NULL, &tv);
- if (ret < 0 && errno == EINTR)
- goto again;
+ return para_select(fd + 1, NULL, &wfds, &tv);
+}
+
+/**
+ * Ensure that file descriptors 0, 1, and 2 are valid.
+ *
+ * Common approach that opens /dev/null until it gets a file descriptor greater
+ * than two.
+ *
+ * \sa okir's Black Hats Manual.
+ */
+void valid_fd_012(void)
+{
+ while (1) {
+ int fd = open("/dev/null", O_RDWR);
+ if (fd < 0)
+ exit(EXIT_FAILURE);
+ if (fd > 2) {
+ close(fd);
+ break;
+ }
+ }
+}
+
+/**
+ * Traverse the given directory recursively.
+ *
+ * \param dirname The directory to traverse.
+ * \param func The function to call for each entry.
+ * \param private_data Pointer to an arbitrary data structure.
+ *
+ * For each regular file under \a dirname, the supplied function \a func is
+ * called. The full path of the regular file and the \a private_data pointer
+ * are passed to \a func. Directories for which the calling process has no
+ * permissions to change to are silently ignored.
+ *
+ * \return Standard.
+ */
+int for_each_file_in_dir(const char *dirname,
+ int (*func)(const char *, void *), void *private_data)
+{
+ DIR *dir;
+ struct dirent *entry;
+ int cwd_fd, ret2, ret = para_opendir(dirname, &dir, &cwd_fd);
+
+ if (ret < 0)
+ return ret == -ERRNO_TO_PARA_ERROR(EACCES)? 1 : ret;
+ /* scan cwd recursively */
+ while ((entry = readdir(dir))) {
+ mode_t m;
+ char *tmp;
+ struct stat s;
+
+ if (!strcmp(entry->d_name, "."))
+ continue;
+ if (!strcmp(entry->d_name, ".."))
+ continue;
+ if (lstat(entry->d_name, &s) == -1)
+ continue;
+ m = s.st_mode;
+ if (!S_ISREG(m) && !S_ISDIR(m))
+ continue;
+ tmp = make_message("%s/%s", dirname, entry->d_name);
+ if (!S_ISDIR(m)) {
+ ret = func(tmp, private_data);
+ free(tmp);
+ if (ret < 0)
+ goto out;
+ continue;
+ }
+ /* directory */
+ ret = for_each_file_in_dir(tmp, func, private_data);
+ free(tmp);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ closedir(dir);
+ ret2 = para_fchdir(cwd_fd);
+ if (ret2 < 0 && ret >= 0)
+ ret = ret2;
+ close(cwd_fd);
return ret;
}