libp7/src/stream/streams.c

311 lines
8.0 KiB
C

/* ************************************************************************** */
/* _____ _ */
/* handle/streams.c |_ _|__ _ _| |__ ___ _ _ */
/* | Project: libp7 | |/ _ \| | | | '_ \ / _ \ | | | */
/* | | (_) | |_| | | | | __/ |_| | */
/* By: thomas <thomas@touhey.fr> |_|\___/ \__,_|_| |_|\___|\__, |.fr */
/* Last updated: 2017/01/04 15:01:48 |___/ */
/* */
/* ************************************************************************** */
#include <libp7/internals.h>
#include <string.h>
/* ************************************************************************** */
/* *nix streams device initialization and callbacks */
/* ************************************************************************** */
#ifdef __linux__
# include <errno.h>
# include <stdlib.h>
# include <termios.h>
# include <unistd.h>
# include <fcntl.h>
# include <sys/stat.h>
/* the cookie */
# define BUFSIZE 2048
typedef struct {
int _readfd, _writefd;
unsigned char _buffer[2048];
ssize_t _start, _end;
} streams_cookie_t;
/**
* p7_streams_read:
* Read from a terminal.
*
* @arg vcookie the cookie (uncasted).
* @arg data the data pointer.
* @arg size the data size.
* @arg timeout the timeout in milliseconds.
* @return the error code (0 if ok).
*/
static int p7_streams_read(void *vcookie,
unsigned char *dest, size_t size, unsigned int timeout)
{
streams_cookie_t *cookie = (streams_cookie_t*)vcookie;
int fd = cookie->_readfd;
/* transmit what's already in the buffer */
if (cookie->_start <= cookie->_end) {
size_t tocopy = cookie->_end - cookie->_start + 1;
if (tocopy > size) tocopy = size;
memcpy(dest, &cookie->_buffer[cookie->_start], tocopy);
cookie->_start += tocopy;
dest += tocopy;
size -= tocopy;
}
/* get terminal structure */
struct termios term;
if (tcgetattr(cookie->_readfd, &term))
return (p7_error_nocalc);
/* set timeout */
term.c_cc[VTIME] = timeout / 100; /* deciseconds are expected! */
tcsetattr(cookie->_readfd, TCSANOW, &term);
/* main receiving loop */
while (size) {
/* receive */
ssize_t recv = read(fd, cookie->_buffer, BUFSIZE);
/* check error */
if (recv < 0) switch (errno) {
case ENODEV: case EIO:
return (p7_error_nocalc);
default:
logr_fatal("error was %d: %s", errno, strerror(errno));
return (p7_error_unknown);
}
/* get the current size to copy */
size_t tocopy = (size_t)recv;
if (tocopy > size) tocopy = size;
/* copy to destination */
memcpy(dest, cookie->_buffer, tocopy);
dest += tocopy;
size -= tocopy;
/* correct start and end points */
cookie->_start = tocopy;
cookie->_end = (size_t)recv - 1;
}
/* no error */
return (0);
}
/**
* p7_streams_write:
* Write to a terminal.
*
* @arg vcookie the cookie (uncasted)
* @arg data the source.
* @arg size the source size.
* @return the error (0 if ok).
*/
static int p7_streams_write(void *vcookie,
const unsigned char *data, size_t size)
{
streams_cookie_t *cookie = (streams_cookie_t*)vcookie;
int fd = cookie->_writefd;
/* set timeout */
struct termios term;
if (!tcgetattr(cookie->_writefd, &term)) {
term.c_cc[VTIME] = 0;
term.c_cc[VMIN] = 0;
tcsetattr(cookie->_writefd, TCSANOW, &term);
}
/* send */
while (size) {
ssize_t wr = write(fd, data, size);
if (wr < 0) break;
size -= (size_t)wr;
}
/* be sure it's written, or check the error */
if (size || tcdrain(fd)) switch (errno) {
case ENODEV:
return (p7_error_nocalc);
default:
logr_fatal("errno was %d: %s", errno, strerror(errno));
return (p7_error_unknown);
}
/* no error! */
return (0);
}
/**
* p7_streams_setcomm:
* Set the communication status.
*
* @arg vcookie the cookie (uncasted).
* @arg speed the speed.
* @arg parity the parity.
* @arg stopbits the number of stop bits.
* @return the error code (0 if ok).
*/
static int p7_streams_setcomm(void *vcookie,
int speed, int parity, int stopbits)
{
streams_cookie_t *cookie = (streams_cookie_t*)vcookie;
/* get attributes */
logr_info("Setting terminal properties: %d baud/s, %d stop bits, %s parity",
speed, stopbits, !parity ? "NONE" : (parity % 2) ? "ODD" : "EVEN");
struct termios rdterm, wrterm;
if (tcgetattr(cookie->_readfd, &rdterm)
|| tcgetattr(cookie->_writefd, &wrterm)) {
logr_warn("Could not get read or write terminal props, nevermind.");
return (0);
}
/* prepare values */
speed = (speed == P7_B9600) ? B9600 : B19200;
/* set attributes */
cfsetspeed(&rdterm, speed);
cfsetspeed(&wrterm, speed);
rdterm.c_cflag &= ~(PARENB | PARODD | CSTOPB);
rdterm.c_cflag |= (!!parity * PARENB) | (PARODD * (parity % 2))
| (CSTOPB * (stopbits - 1));
wrterm.c_cflag &= ~(PARENB | PARODD | CSTOPB);
wrterm.c_cflag |= (!!parity * PARENB) | (PARODD * (parity % 2))
| (CSTOPB * (stopbits - 1));
/* save attributes */
tcsetattr(cookie->_readfd, TCSADRAIN, &rdterm);
tcsetattr(cookie->_writefd, TCSADRAIN, &wrterm);
/* no error */
return (0);
}
/**
* p7_streams_close:
* Close the cookie.
*
* @arg vcookie the cookie (uncasted).
* @return the error code (0 if ok).
*/
static int p7_streams_close(void *vcookie)
{
streams_cookie_t *cookie = (streams_cookie_t*)vcookie;
close(cookie->_readfd);
close(cookie->_writefd);
free(cookie);
return (0);
}
/**
* p7_fdinit:
* Initialize libp7 with char device.
*
* @arg handle the handle to create
* @arg flags the flags.
* @arg name the name of the handle.
* @arg readfd the read file descriptor.
* @arg writefd the write file descriptor.
* @return the error (0 if ok)
*/
int p7_fdinit(p7_handle_t **handle, unsigned int flags,
const char *name, int readfd, int writefd)
{
int err;
/* check if it's valid devices */
if (readfd < 0 || writefd < 0)
return (p7_error_nostream);
/* get attributes (while checking file descriptors are terminals) */
struct termios rdterm, wrterm;
if (tcgetattr(readfd, &rdterm) || tcgetattr(writefd, &wrterm)) {
logr_fatal("Read or write streams are not terminals, abandon ship!");
return (p7_error_nochar);
}
/* set read thingy attribute */
cfmakeraw(&rdterm);
rdterm.c_iflag |= IGNPAR;
rdterm.c_cflag |= CS8 | CLOCAL | CREAD;
rdterm.c_cc[VMIN] = 0;
tcsetattr(readfd, TCSANOW, &rdterm);
/* set write thingy attribute */
cfmakeraw(&wrterm);
wrterm.c_cflag |= CS8 | CLOCAL | CREAD;
tcsetattr(writefd, TCSANOW, &wrterm);
/* try to read */
if (read(readfd, NULL, 0) < 0) {
err = p7_error_noread;
goto test_failed;
} else if (write(writefd, NULL, 0) < 0) {
err = p7_error_nowrite;
goto test_failed;
}
/* allocate cookie */
streams_cookie_t *cookie = malloc(sizeof(streams_cookie_t));
if (!cookie) return (p7_error_alloc);
*cookie = (streams_cookie_t){
._readfd = readfd,
._writefd = writefd,
._start = 0,
._end = -1
};
/* init for real */
logr_info("Initializing STREAMS stream with fds: (%d,%d)", readfd, writefd);
return (p7_sinit(handle, flags, name, (p7_stream_t){
.cookie = cookie,
.read = p7_streams_read,
.write = p7_streams_write,
.setcomm = p7_streams_setcomm,
.close = p7_streams_close
}));
test_failed:
close(readfd);
close(writefd);
return (err);
}
/* ************************************************************************** */
/* Use a file descriptor on other systems (placebo) */
/* ************************************************************************** */
#else
/**
* p7_fdinit:
* Initialize libp7 with a file descriptor. Placebo.
*
* @arg handle the handle to create
* @arg flags the flags.
* @arg name the name of the handle.
* @arg readfd the read file descriptor.
* @arg writefd the write file descriptor.
* @return the error (0 if ok)
*/
int p7_fdinit(p7_handle_t **handle, unsigned int flags,
const char *name, int readfd, int writefd)
{
(void)handle;
(void)flags;
(void)name;
(void)readfd;
(void)writefd;
return (p7_error_nocalc);
}
#endif