/* serial.c - common serial port functions for Network UPS Tools drivers Copyright (C) 2003 Russell Kroll This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" #include "timehead.h" #include "serial.h" #include "main.h" #include "attribute.h" #include #include #include #include #include #include #include #ifdef HAVE_UU_LOCK #include #endif #include "nut_stdint.h" static unsigned int comm_failures = 0; static void ser_open_error(const char *port) __attribute__((noreturn)); static void ser_open_error(const char *port) { struct stat fs; struct passwd *user; struct group *group; printf("\n"); printf("Unable to open %s: %s\n\n", port, strerror(errno)); /* check for bad port definitions first */ if (stat(port, &fs)) { printf("Things to try:\n\n"); printf(" - Check 'port=' in ups.conf\n\n"); printf(" - Check owner/permissions of all parts of path\n\n"); fatalx(EXIT_FAILURE, "Fatal error: unusable configuration"); } user = getpwuid(getuid()); if (user) printf(" Current user id: %s (%d)\n", user->pw_name, (int) user->pw_uid); user = getpwuid(fs.st_uid); if (user) printf("Serial port owner: %s (%d)\n", user->pw_name, (int) fs.st_uid); group = getgrgid(fs.st_gid); if (group) printf("Serial port group: %s (%d)\n", group->gr_name, (int) fs.st_gid); printf(" Mode of port: %04o\n\n", (int) fs.st_mode & 07777); printf("Things to try:\n\n"); printf(" - Use another port (with the right permissions)\n\n"); printf(" - Fix the port owner/group or permissions on this port\n\n"); printf(" - Run this driver as another user (upsdrvctl -u or 'user=...' in ups.conf).\n"); printf(" See upsdrvctl(8) and ups.conf(5).\n\n"); fatalx(EXIT_FAILURE, "Fatal error: unusable configuration"); } static void lock_set(int fd, const char *port) { int ret; if (fd < 0) fatal_with_errno(EXIT_FAILURE, "lock_set: programming error: fd = %d", fd); if (do_lock_port == 0) return; #ifdef HAVE_UU_LOCK ret = uu_lock(xbasename(port)); if (ret != 0) fatalx(EXIT_FAILURE, "Can't uu_lock %s: %s", xbasename(port), uu_lockerr(ret)); #elif defined(HAVE_FLOCK) ret = flock(fd, LOCK_EX | LOCK_NB); if (ret != 0) fatalx(EXIT_FAILURE, "%s is locked by another process", port); #elif defined(HAVE_LOCKF) lseek(fd, 0L, SEEK_SET); ret = lockf(fd, F_TLOCK, 0L); if (ret != 0) fatalx(EXIT_FAILURE, "%s is locked by another process", port); #else upslog_with_errno(LOG_WARNING, "Warning: no locking method is available"); #endif } /* Non fatal version of ser_open */ int ser_open_nf(const char *port) { int fd; fd = open(port, O_RDWR | O_NOCTTY | O_EXCL | O_NONBLOCK); if (fd < 0) { return -1; } lock_set(fd, port); return fd; } int ser_open(const char *port) { int res; res = ser_open_nf(port); if(res == -1) { ser_open_error(port); } return res; } int ser_set_speed_nf(int fd, const char *port, speed_t speed) { struct termios tio; NUT_UNUSED_VARIABLE(port); if (tcgetattr(fd, &tio) != 0) { return -1; } tio.c_cflag = CS8 | CLOCAL | CREAD; tio.c_iflag = IGNPAR; tio.c_oflag = 0; tio.c_lflag = 0; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; #ifdef HAVE_CFSETISPEED cfsetispeed(&tio, speed); cfsetospeed(&tio, speed); #else #error This system lacks cfsetispeed() and has no other means to set the speed #endif tcflush(fd, TCIFLUSH); tcsetattr(fd, TCSANOW, &tio); return 0; } int ser_set_speed(int fd, const char *port, speed_t speed) { int res; res = ser_set_speed_nf(fd,port,speed); if(res == -1) { fatal_with_errno(EXIT_FAILURE, "tcgetattr(%s)", port); } return 0; } static int ser_set_control(int fd, int line, int state) { if (state) { return ioctl(fd, TIOCMBIS, &line); } else { return ioctl(fd, TIOCMBIC, &line); } } int ser_set_dtr(int fd, int state) { return ser_set_control(fd, TIOCM_DTR, state); } int ser_set_rts(int fd, int state) { return ser_set_control(fd, TIOCM_RTS, state); } static int ser_get_control(int fd, int line) { int flags; ioctl(fd, TIOCMGET, &flags); return (flags & line); } int ser_get_dsr(int fd) { return ser_get_control(fd, TIOCM_DSR); } int ser_get_cts(int fd) { return ser_get_control(fd, TIOCM_CTS); } int ser_get_dcd(int fd) { return ser_get_control(fd, TIOCM_CD); } int ser_flush_io(int fd) { return tcflush(fd, TCIOFLUSH); } int ser_close(int fd, const char *port) { if (fd < 0) fatal_with_errno(EXIT_FAILURE, "ser_close: programming error: fd=%d port=%s", fd, port); if (close(fd) != 0) return -1; #ifdef HAVE_UU_LOCK if (do_lock_port) uu_unlock(xbasename(port)); #endif return 0; } ssize_t ser_send_char(int fd, unsigned char ch) { return ser_send_buf_pace(fd, 0, &ch, 1); } static ssize_t send_formatted(int fd, const char *fmt, va_list va, useconds_t d_usec) { int ret; char buf[LARGEBUF]; #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif ret = vsnprintf(buf, sizeof(buf), fmt, va); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif if (ret >= (int)sizeof(buf)) { upslogx(LOG_WARNING, "vsnprintf needed more than %d bytes", (int)sizeof(buf)); } return ser_send_buf_pace(fd, d_usec, buf, strlen(buf)); } /* send the results of the format string with d_usec delay after each char */ ssize_t ser_send_pace(int fd, useconds_t d_usec, const char *fmt, ...) { ssize_t ret; va_list ap; va_start(ap, fmt); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif ret = send_formatted(fd, fmt, ap, d_usec); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif va_end(ap); return ret; } /* send the results of the format string with no delay */ ssize_t ser_send(int fd, const char *fmt, ...) { ssize_t ret; va_list ap; va_start(ap, fmt); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif ret = send_formatted(fd, fmt, ap, 0); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif va_end(ap); return ret; } /* send buflen bytes from buf with no delay */ ssize_t ser_send_buf(int fd, const void *buf, size_t buflen) { return ser_send_buf_pace(fd, 0, buf, buflen); } /* send buflen bytes from buf with d_usec delay after each char */ ssize_t ser_send_buf_pace(int fd, useconds_t d_usec, const void *buf, size_t buflen) { ssize_t ret = 0; ssize_t sent; const char *data = buf; assert(buflen < SSIZE_MAX); for (sent = 0; sent < (ssize_t)buflen; sent += ret) { /* Conditions above ensure that (buflen - sent) > 0 below */ ret = write(fd, &data[sent], (d_usec == 0) ? (size_t)((ssize_t)buflen - sent) : 1); if (ret < 1) { return ret; } usleep(d_usec); } return sent; } ssize_t ser_get_char(int fd, void *ch, time_t d_sec, useconds_t d_usec) { /* Per standard below, we can cast here, because required ranges are * effectively the same (and signed -1 for suseconds_t), and at most long: * https://pubs.opengroup.org/onlinepubs/009604599/basedefs/sys/types.h.html */ return select_read(fd, ch, 1, d_sec, (suseconds_t)d_usec); } ssize_t ser_get_buf(int fd, void *buf, size_t buflen, time_t d_sec, useconds_t d_usec) { memset(buf, '\0', buflen); return select_read(fd, buf, buflen, d_sec, (suseconds_t)d_usec); } /* keep reading until buflen bytes are received or a timeout occurs */ ssize_t ser_get_buf_len(int fd, void *buf, size_t buflen, time_t d_sec, useconds_t d_usec) { ssize_t ret; ssize_t recv; char *data = buf; assert(buflen < SSIZE_MAX); memset(buf, '\0', buflen); for (recv = 0; recv < (ssize_t)buflen; recv += ret) { ret = select_read(fd, &data[recv], (size_t)((ssize_t)buflen - recv), d_sec, (suseconds_t)d_usec); if (ret < 1) { return ret; } } return recv; } /* reads a line up to , discarding anything else that may follow, with callouts to the handler if anything matches the alertset */ ssize_t ser_get_line_alert(int fd, void *buf, size_t buflen, char endchar, const char *ignset, const char *alertset, void handler(char ch), time_t d_sec, useconds_t d_usec) { ssize_t i, ret; char tmp[64]; char *data = buf; ssize_t count = 0, maxcount; assert(buflen < SSIZE_MAX && buflen > 0); memset(buf, '\0', buflen); maxcount = (ssize_t)buflen - 1; /* for trailing \0 */ while (count < maxcount) { ret = select_read(fd, tmp, sizeof(tmp), d_sec, (suseconds_t)d_usec); if (ret < 1) { return ret; } for (i = 0; i < ret; i++) { if ((count == maxcount) || (tmp[i] == endchar)) { return count; } if (strchr(ignset, tmp[i])) continue; if (strchr(alertset, tmp[i])) { if (handler) handler(tmp[i]); continue; } data[count++] = tmp[i]; } } return count; } /* as above, only with no alertset handling (just a wrapper) */ ssize_t ser_get_line(int fd, void *buf, size_t buflen, char endchar, const char *ignset, time_t d_sec, useconds_t d_usec) { return ser_get_line_alert(fd, buf, buflen, endchar, ignset, "", NULL, d_sec, d_usec); } ssize_t ser_flush_in(int fd, const char *ignset, int verbose) { ssize_t ret, extra = 0; char ch; while ((ret = ser_get_char(fd, &ch, 0, 0)) > 0) { if (strchr(ignset, ch)) continue; extra++; if (verbose == 0) continue; if (isprint((unsigned char)ch & 0xFF)) upslogx(LOG_INFO, "ser_flush_in: read char %c", ch); else upslogx(LOG_INFO, "ser_flush_in: read char 0x%02x", ch); } return extra; } void ser_comm_fail(const char *fmt, ...) { int ret; char why[SMALLBUF]; va_list ap; /* this means we're probably here because select was interrupted */ if (exit_flag != 0) return; /* ignored, since we're about to exit anyway */ comm_failures++; if ((comm_failures == SER_ERR_LIMIT) || ((comm_failures % SER_ERR_RATE) == 0)) { upslogx(LOG_WARNING, "Warning: excessive comm failures, " "limiting error reporting"); } /* once it's past the limit, only log once every SER_ERR_LIMIT calls */ if ((comm_failures > SER_ERR_LIMIT) && ((comm_failures % SER_ERR_LIMIT) != 0)) return; va_start(ap, fmt); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif ret = vsnprintf(why, sizeof(why), fmt, ap); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif va_end(ap); if ((ret < 1) || (ret >= (int) sizeof(why))) upslogx(LOG_WARNING, "ser_comm_fail: vsnprintf needed " "more than %d bytes", (int)sizeof(why)); upslogx(LOG_WARNING, "Communications with UPS lost: %s", why); } void ser_comm_good(void) { if (comm_failures == 0) return; upslogx(LOG_NOTICE, "Communications with UPS re-established"); comm_failures = 0; }