311 lines
8.0 KiB
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
|