cake
/
libp7
Archived
1
0
Fork 1
This repository has been archived on 2024-03-16. You can view files and clone it, but cannot push or open issues or pull requests.
libp7/src/stream/streams.c

334 lines
8.6 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>
#ifndef P7_DISABLED_STREAMS
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <termios.h>
# include <unistd.h>
# include <errno.h>
# ifdef __linux__
/* ************************************************************************** */
/* Serial devices on Linux */
/* ************************************************************************** */
/**
* p7_fdcominit:
* Initialize serial communication.
*
* @arg handle the link to the handle.
* @arg flags the flags.
* @arg com the com port.
* @return the error code (0 if ok)
*/
int p7_fdcominit(p7_handle_t **handle, unsigned int flags, int com)
{
/* prepare the path */
logr_info("Looking for specific Linux devices.");
char path[20]; sprintf(path, "/dev/ttyUSB%d", com - 1);
/* open the stream */
int fd = open(path, O_RDWR | O_NOCTTY);
if (fd < 0) switch (errno) {
/* no such device */
case ENODEV: case ENOENT: case ENXIO:
case EPIPE: case ESPIPE:
logr_error("couldn't open calculator");
return (p7_error_nocalc);
/* no access */
case EACCES:
logr_error("permission denied");
return (p7_error_noaccess);
/* default */
default:
logr_error("unknown error: %s (0x%X)", strerror(errno), errno);
return (p7_error_unknown);
}
/* check if we have got the thing */
if (fd < 0) switch (errno) {
case ENOENT: return (p7_error_nocalc);
case EACCES: return (p7_error_noaccess);
default: return (p7_error_nocalc);
}
/* init to real */
return (p7_fdinit(handle, flags, NULL, fd, fd));
}
# endif
/* ************************************************************************** */
/* *nix streams device initialization and callbacks */
/* ************************************************************************** */
/* 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)) {
/* 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;
int isterm = 0;
if (!tcgetattr(cookie->_writefd, &term)) {
isterm = 1;
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 || (isterm && 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)) {
/* 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);
}
if (!tcgetattr(writefd, &wrterm)) {
/* 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);
}
#endif