496 lines
13 KiB
C
496 lines
13 KiB
C
/* *****************************************************************************
|
|
* stream/streams.c -- built-in STREAMS stream.
|
|
* Copyright (C) 2016-2017 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
|
*
|
|
* This file is part of libp7.
|
|
* libp7 is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 3.0 of the License,
|
|
* or (at your option) any later version.
|
|
*
|
|
* libp7 is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with libp7; if not, see <http://www.gnu.org/licenses/>.
|
|
* ************************************************************************** */
|
|
#include <libp7/internals.h>
|
|
#ifndef P7_DISABLED_STREAMS
|
|
# include <stdlib.h>
|
|
# include <string.h>
|
|
# include <sys/stat.h>
|
|
# include <fcntl.h>
|
|
# include <unistd.h>
|
|
# include <errno.h>
|
|
# include <sys/ioctl.h>
|
|
# include <termios.h>
|
|
|
|
/* the cookie type */
|
|
# define BUFSIZE 2048
|
|
typedef struct {
|
|
int _readfd, _writefd;
|
|
|
|
/* buffer [control] */
|
|
unsigned char _buffer[BUFSIZE];
|
|
ssize_t _start, _end;
|
|
} streams_cookie_t;
|
|
|
|
/* ************************************************************************** */
|
|
/* Serial devices on Linux */
|
|
/* ************************************************************************** */
|
|
# ifdef __linux__
|
|
# include <dirent.h>
|
|
|
|
/**
|
|
* p7_slist_streams:
|
|
* List serial devices under Linux.
|
|
*
|
|
* Links in the /dev/serial/by-id/ should resolve to relative paths, but
|
|
* I also managed absolute paths, in case.
|
|
*
|
|
* @arg callback the callback.
|
|
* @arg cookie the cookie.
|
|
* @return the error.
|
|
*/
|
|
|
|
int p7_slist_streams(p7_list_device_t callback, void *cookie)
|
|
{
|
|
/* open the thing */
|
|
char path[1025]; strcpy(path, "/dev/serial/by-id/");
|
|
DIR *d = opendir(path);
|
|
if (!d) return (0);
|
|
|
|
/* prepare */
|
|
char *f = strchr(path, 0), devname[1025];
|
|
|
|
/* read the entries */
|
|
struct dirent *dr; struct stat st;
|
|
while ((dr = readdir(d))) {
|
|
/* check type */
|
|
strcpy(f, dr->d_name);
|
|
if (lstat(path, &st) || (st.st_mode & S_IFMT) != S_IFLNK)
|
|
continue;
|
|
|
|
/* get destination path and send it */
|
|
devname[readlink(path, devname, 1025)] = 0;
|
|
if (devname[0] == '/')
|
|
(*callback)(cookie, devname);
|
|
else {
|
|
strcpy(f, devname);
|
|
(*callback)(cookie, realpath(path, devname));
|
|
}
|
|
}
|
|
|
|
/* close the dir and we're done listing */
|
|
closedir(d);
|
|
return (0);
|
|
}
|
|
|
|
# endif
|
|
/* ************************************************************************** */
|
|
/* Settings, close callbacks */
|
|
/* ************************************************************************** */
|
|
/**
|
|
* setcomm_for_fd:
|
|
* Set communication things for a STREAMS device.
|
|
*
|
|
* Some flags have the same effects as `cfmakeraw()` (see termios(3)).
|
|
* Other effects are linked to the settings (see `libp7/stream.h`).
|
|
*
|
|
* @arg fd the file descriptor.
|
|
* @arg settings the settings to set.
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int setcomm_for_fd(const int fd, const p7_streamsettings_t *settings)
|
|
{
|
|
/* get the speed */
|
|
speed_t speed;
|
|
switch (settings->speed) {
|
|
case P7_B1200: speed = B1200; break;
|
|
case P7_B2400: speed = B2400; break;
|
|
case P7_B4800: speed = B4800; break;
|
|
case P7_B9600: speed = B9600; break;
|
|
case P7_B19200: speed = B19200; break;
|
|
case P7_B38400: speed = B38400; break;
|
|
case P7_B57600: speed = B57600; break;
|
|
case P7_B115200: speed = B115200; break;
|
|
default:
|
|
logr_info("Speed was unsupported by termios: %u", settings->speed);
|
|
return (p7_error_unsupported);
|
|
}
|
|
|
|
/* get the current configuration */
|
|
struct termios term;
|
|
if (tcgetattr(fd, &term) < 0)
|
|
return (p7_error_unknown);
|
|
|
|
/* set the speed */
|
|
cfsetispeed(&term, speed);
|
|
cfsetospeed(&term, speed);
|
|
|
|
/* input flags */
|
|
term.c_iflag &= ~(IGNBRK | IGNCR | BRKINT | PARMRK | ISTRIP | INLCR
|
|
| ICRNL | IGNPAR | IXON | IXOFF);
|
|
if (settings->flags & P7_XONMASK) term.c_iflag |= IXON;
|
|
if (settings->flags & P7_XOFFMASK) term.c_iflag |= IXOFF;
|
|
|
|
/* output flags, local modes */
|
|
term.c_oflag = 0;
|
|
term.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
|
|
/* control flags */
|
|
term.c_cflag &= ~(PARENB | PARODD | CREAD | CSTOPB | CSIZE);
|
|
term.c_cflag |= CREAD | CS8;
|
|
if (settings->flags & P7_TWOSTOPBITS) term.c_cflag |= CSTOPB;
|
|
if (settings->flags & P7_PARENB) term.c_cflag |= PARENB;
|
|
if (settings->flags & P7_PARODD) term.c_cflag |= PARODD;
|
|
|
|
/* control characters */
|
|
term.c_cc[VSTART] = settings->cc[P7_XON];
|
|
term.c_cc[VSTOP] = settings->cc[P7_XOFF];
|
|
term.c_cc[VMIN] = 0; /* ? */
|
|
|
|
/* update the termios settings! */
|
|
if (tcsetattr(fd, TCSANOW, &term))
|
|
return (p7_error_unknown);
|
|
|
|
/* get line status */
|
|
int status; if (ioctl(fd, TIOCMGET, &status) >= 0) status = 0;
|
|
status &= ~(TIOCM_DTR | TIOCM_RTS);
|
|
|
|
/* activate DTR and RTS */
|
|
if ((settings->flags & P7_DTRMASK) == P7_DTRCTL_ENABLE
|
|
|| (settings->flags & P7_DTRMASK) == P7_DTRCTL_HANDSHAKE)
|
|
status |= TIOCM_DTR;
|
|
if ((settings->flags & P7_RTSMASK) == P7_RTSCTL_ENABLE
|
|
|| (settings->flags & P7_RTSMASK) == P7_RTSCTL_HANDSHAKE)
|
|
status |= TIOCM_RTS;
|
|
if (status && ioctl(fd, TIOCMSET, &status) < 0)
|
|
return (p7_error_unknown);
|
|
|
|
/* no error! */
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* p7_streams_setcomm:
|
|
* Set the communication status.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @arg settings the settings to set.
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int p7_streams_setcomm(void *vcookie,
|
|
const p7_streamsettings_t *settings)
|
|
{
|
|
int err;
|
|
streams_cookie_t *cookie = (streams_cookie_t*)vcookie;
|
|
|
|
/* set attributes */
|
|
if ((err = setcomm_for_fd(cookie->_readfd, settings)))
|
|
return (err);
|
|
if (cookie->_readfd != cookie->_writefd
|
|
&& (err = setcomm_for_fd(cookie->_writefd, settings)))
|
|
return (err);
|
|
|
|
/* no error */
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* p7_streams_settm:
|
|
* Set timeouts.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @arg timeouts the timeouts.
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int p7_streams_settm(void *vcookie, const p7_streamtimeouts_t *timeouts)
|
|
{
|
|
streams_cookie_t *cookie = (void*)cookie;
|
|
|
|
/* set on the read thing */
|
|
struct termios term;
|
|
if (!tcgetattr(cookie->_readfd, &term)) {
|
|
/* set the timeout */
|
|
term.c_cc[VTIME] = timeouts->read / 100;
|
|
|
|
/* update */
|
|
if (tcsetattr(cookie->_readfd, TCSANOW, &term))
|
|
return (0);
|
|
}
|
|
|
|
/* set on the write thing */
|
|
if (cookie->_readfd != cookie->_writefd
|
|
&& !tcgetattr(cookie->_writefd, &term)) {
|
|
term.c_cc[VTIME] = timeouts->write / 100;
|
|
|
|
/* update */
|
|
if (tcsetattr(cookie->_writefd, TCSANOW, &term))
|
|
return (0);
|
|
}
|
|
|
|
/* 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);
|
|
if (cookie->_readfd != cookie->_writefd)
|
|
close(cookie->_writefd);
|
|
free(cookie);
|
|
return (0);
|
|
}
|
|
/* ************************************************************************** */
|
|
/* Character stream callbacks */
|
|
/* ************************************************************************** */
|
|
/**
|
|
* p7_streams_read:
|
|
* Read from a terminal.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @arg data the data pointer.
|
|
* @arg size the data size.
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int p7_streams_read(void *vcookie,
|
|
unsigned char *dest, size_t size)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* 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) 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);
|
|
}
|
|
/* ************************************************************************** */
|
|
/* SCSI request callback */
|
|
/* ************************************************************************** */
|
|
# if 0 && defined(__linux__)
|
|
# include <scsi/sg.h>
|
|
|
|
/**
|
|
* p7_streams_scsi_request:
|
|
* Make an SCSI request using the SG interface.
|
|
*
|
|
* @arg vcookie the cookie (uncasted).
|
|
* @arg request the SCSI request.
|
|
* @return the error code (0 if ok).
|
|
*/
|
|
|
|
static int p7_streams_scsi_request(void *vcookie, p7_scsi_t *request)
|
|
{
|
|
streams_cookie_t *cookie = (streams_cookie_t*)vcookie;
|
|
|
|
/* correct the request */
|
|
int err = p7_scsi_correct(request);
|
|
if (err) return (err);
|
|
|
|
/* make an `sg_io_hdr_t` out of the request */
|
|
sg_io_hdr_t h = {
|
|
/* main input data */
|
|
.interface_id = 'S', /* magic */
|
|
.flags = 0, /* no special things, just normal SCSI */
|
|
.dxfer_direction = request->direction, /* FIXME: macro to macro? */
|
|
.cmd_len = request->cmd_len, .cmdp = request->cmd, /* command */
|
|
.timeout = 0, /* FIXME (also, in ms) */
|
|
|
|
/* data buffer */
|
|
.iovec_count = 0,
|
|
.dxfer_len = request->data_len,
|
|
.dxferp = request->data, /* data buffer */
|
|
|
|
/* sense buffer */
|
|
.max_sb_len = request->slen, .sbp = request->sense, /* sense buffer */
|
|
};
|
|
|
|
/* make the request */
|
|
if (ioctl(cookie->_writefd, SG_IO, &h) < 0) switch (errno) {
|
|
case EACCES:
|
|
logr_error("Root perms required!");
|
|
default:
|
|
logr_error("Errno: %s (0x%04X)", strerror(errno), errno);
|
|
return (p7_error_unknown);
|
|
}
|
|
|
|
/* everything went well */
|
|
return (0);
|
|
}
|
|
|
|
# endif
|
|
/* ************************************************************************** */
|
|
/* Stream initialization */
|
|
/* ************************************************************************** */
|
|
/**
|
|
* p7_sopen_streams:
|
|
* Initialize libp7 with char device.
|
|
*
|
|
* @arg stream the stream to make.
|
|
* @arg path the path (NULL if use the readfd/writefd).
|
|
* @arg readfd the read file descriptor.
|
|
* @arg writefd the write file descriptor.
|
|
* @return the error (0 if ok)
|
|
*/
|
|
|
|
int p7_sopen_streams(p7_stream_t *stream, const char *path,
|
|
int readfd, int writefd)
|
|
{
|
|
int err;
|
|
|
|
/* open the stream if required */
|
|
if (path) {
|
|
readfd = open(path, O_RDWR | O_NOCTTY);
|
|
writefd = readfd;
|
|
}
|
|
|
|
/* check if devices are valid */
|
|
if (readfd < 0 || writefd < 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);
|
|
}
|
|
|
|
/* try to read */
|
|
if (read(readfd, NULL, 0) < 0) {
|
|
err = p7_error_noread;
|
|
goto fail;
|
|
} else if (write(writefd, NULL, 0) < 0) {
|
|
err = p7_error_nowrite;
|
|
goto fail;
|
|
}
|
|
|
|
/* allocate cookie */
|
|
streams_cookie_t *cookie = malloc(sizeof(streams_cookie_t));
|
|
if (!cookie) { err = g1m_error_alloc; goto fail; }
|
|
*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);
|
|
stream->flags = p7_streamflag_serial; /* TODO: USB? SCSI? */
|
|
stream->cookie = cookie;
|
|
stream->read = p7_streams_read;
|
|
stream->write = p7_streams_write;
|
|
stream->setcomm = p7_streams_setcomm;
|
|
stream->settm = p7_streams_settm;
|
|
stream->close = p7_streams_close;
|
|
|
|
/* end */
|
|
err = 0;
|
|
fail:
|
|
if (err) {
|
|
close(readfd);
|
|
if (readfd != writefd)
|
|
close(writefd);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
#endif
|