/* $Id$ */ /*** This file is part of ivam2. ivam2 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. ivam2 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 ivam2; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "modem.h" #include "lock.h" #include "exec.h" #include "main.h" #include "msntab.h" #include "dle.h" #include "dtmffifo.h" /* Baudrate for the TTY. Should be greater the 64000. */ #define BAUD_RATE B115200 #define RESET_IDLE_TIME 2 #define RING_TIME 5 #define INIT_TIMEOUT 5 #define ACCEPT_TIMEOUT 5 #define BASIC_TIMEOUT 5 #define CHILD_TIMEOUT 10 #define HANGUP_TIMEOUT 10 #define ISDN_DELAY_USEC 125L /* The time a single byte written to the ISDN takes to be played */ #define ISDN_LATENCY_USEC 50000L /* Request the next byte to be written to the ISDN this many usecs before the last one is finished playing */ #define INPUT_RANGE_DEFAULT -1 #define OUTPUT_RANGE_DEFAULT -1 #define INPUT_RANGE_CONNECTION 96 #define OUTPUT_RANGE_CONNECTION 1024 #define INPUT_RANGE_CHILD 64 #define OUTPUT_RANGE_CHILD 1024 static const char* const init_at_commands[] = { "\nAT&F\n", /* Reset to fabric defaults */ "OK\r\n", "ATI\n", /* Make sure this is an isdn4linux device */ "Linux ISDN\r\nOK\r\n", "AT+FCLASS=8\n", /* switch to audio mode */ "OK\r\n", "AT+VIP\n", /* Reset audio parameters */ "OK\r\n", "AT+VSM=6\n", /* Set uLaw encoding */ "OK\r\n", "ATS18=1\n", "OK\r\n", "ATS14=4\n", "OK\r\n", "ATS13.4=0\n", "OK\r\n", "ATS13.0=1\n", /* direct tty send */ "OK\r\n", "ATS13.6=1\n", /* Get special RUNG messages */ "OK\r\n", "ATS23=1\n", "OK\r\n", "ATS16=4\n", /* send packet size (set to 4 instead of 1 to avoid a play distortion with a B1) */ "OK\r\n", "AT\n", //S12.3=0\n", /* DCD always on */ "OK\r\n", "AT\n", //S12.6=0\n", /* DSR always on */ "OK\r\n", "ATS0=0\n", /* No automatic call answering */ "OK\r\n", "AT&L%s\n", /* Listen on this MSN */ "OK\r\n", }; #define INIT_AT_COMMANDS ((sizeof(init_at_commands)/sizeof(init_at_commands[0]))/2) static const char hup_sequence[] = { DLE, DC4, DLE, ETX, 0 }; static const char ath_sequence[] = "\nATH\n"; static int modem_output_request_cb(struct buffio *b, void *user); static int modem_output_empty_cb(struct buffio *b, void *user); static int modem_input_ready_cb(struct buffio *b, void *user); static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user); static void modem_init_cycle(struct modem *m); static void modem_timeout(struct modem *m, int t); static void copy_child_to_modem(struct modem *m); static void copy_modem_to_child(struct modem *m); static int modem_reopen(struct modem *m); void modem_close(struct modem *m); struct modem *modem_open(const char *dev, const char*msn) { struct modem *m = NULL; char res_dev[PATH_MAX]; int r; assert(dev); if ((r = device_lock(dev)) != 0) { if (r > 0) daemon_log(LOG_WARNING, "Device '%s' locked.", dev); goto fail; } m = malloc(sizeof(struct modem)); assert(m); memset(m, 0, sizeof(struct modem)); realpath(dev, res_dev); m->dev = strdup(res_dev); assert(m->dev); if (!strncmp(m->dev, "/dev/", 5)) m->name = m->dev+5; else m->name = m->dev; m->child_pid = -1; m->child_buffio = NULL; m->tabentry = NULL; m->listen_msn = msn ? strdup(msn) : NULL; if (modem_reopen(m) < 0) goto fail; modem_init_cycle(m); return m; fail: if (m) modem_close(m); return NULL; } void modem_close(struct modem *m) { assert(m); daemon_log(LOG_INFO, "[%s] Closing modem.", m->name); modem_timeout(m, 0); if (m->dtmf_fifo) dtmf_fifo_free(m->dtmf_fifo); if (m->tabentry) msntab_unref(m->tabentry); if (m->child_pid != -1) child_process_kill(m->child_pid); if (m->child_buffio) buffio_free(m->child_buffio); if (m->buffio) buffio_free(m->buffio); device_unlock(m->dev); free(m->dev); free(m->caller_number); free(m->ring_number); free(m->listen_msn); free(m); } static int error_cb(struct buffio *b, void *user) { struct modem *m = user; assert(m && (m->buffio == b || m->child_buffio == b)); daemon_log(LOG_ERR, "[%s] Buffio (%s) failure, exiting...", m->name, m->buffio == b ? "modem" : "child"); return -1; } static void modem_timeout(struct modem *m, int t) { assert(m); assert(event_source && event_source->on_time); event_source->cancel_time(event_source, m->timeout, oop_timeout_cb, m); if (t > 0) { gettimeofday(&m->timeout, NULL); m->timeout.tv_sec += t; assert(event_source && event_source->on_time); event_source->on_time(event_source, m->timeout, oop_timeout_cb, m); } } static void modem_next_command(struct modem *m) { const char *p; char tmp[64]; assert(m && m->buffio && m->state == MODEM_STATE_INIT); assert(m->command_index < INIT_AT_COMMANDS); p = init_at_commands[m->command_index*2]; if (m->command_index == 0) buffio_command(m->buffio, hup_sequence); else if (!strncmp(p, "AT&L", 4)) { char *l = m->listen_msn ? m->listen_msn : "*"; daemon_log(LOG_INFO, "[%s] Listening on '%s'.", m->name, l); snprintf(tmp, sizeof(tmp), p, l); p = tmp; } buffio_command(m->buffio, p); } static void modem_init_cycle(struct modem *m) { assert(m); daemon_log(LOG_INFO, "[%s] Initializing modem.", m->name); m->state = MODEM_STATE_INIT; m->command_index = 0; modem_timeout(m, INIT_TIMEOUT); modem_next_command(m); } static void modem_sched_reset(struct modem *m) { daemon_log(LOG_INFO, "[%s] Reinitializing in %i seconds.", m->name, RESET_IDLE_TIME); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); } static void modem_force_hangup(struct modem *m) { struct termios pts; assert(m && m->buffio); tcgetattr(m->buffio->ifd, &pts); cfsetospeed(&pts, B0); cfsetispeed(&pts, B0); tcsetattr(m->buffio->ifd, TCSANOW, &pts); tcgetattr(m->buffio->ifd, &pts); cfsetospeed(&pts, BAUD_RATE); cfsetispeed(&pts, BAUD_RATE); tcsetattr(m->buffio->ifd, TCSANOW, &pts); } static void modem_hangup_seq(struct modem *m) { assert(m); if (m->child_buffio) { if (buffio_output_is_empty(m->child_buffio)) buffio_close_output_fd(m->child_buffio); if (m->child_buffio->ifd == -1 && m->child_buffio->ofd == -1) { buffio_free(m->child_buffio); m->child_buffio = NULL; } } if (m->child_buffio && m->child_buffio->ofd != -1) { /* Draining output is required */ daemon_log(LOG_INFO, "[%s] Waiting for child drain.", m->name); m->state = MODEM_STATE_CHILD_DRAIN; modem_timeout(m, CHILD_TIMEOUT); } else if (m->child_buffio) { /* Draining input is required */ daemon_log(LOG_INFO, "[%s] Waiting for child EOF.", m->name); m->state = MODEM_STATE_CHILD_WAIT_EOF; buffio_close_output_fd(m->child_buffio); buffio_flush_input(m->child_buffio); modem_timeout(m, CHILD_TIMEOUT); } else if (m->child_pid != (pid_t) -1) { daemon_log(LOG_INFO, "[%s] Killing child.", m->name); m->state = MODEM_STATE_CHILD_KILL; child_process_kill(m->child_pid); modem_timeout(m, CHILD_TIMEOUT); } else modem_sched_reset(m); } static void modem_hangup(struct modem *m, int b) { size_t l; assert(m); daemon_log(LOG_INFO, "[%s] Hanging up.", m->name); buffio_set_input_range(m->buffio, INPUT_RANGE_DEFAULT); buffio_set_output_range(m->buffio, OUTPUT_RANGE_DEFAULT); buffio_set_delay_usec(m->buffio, 0, 0); buffio_set_prebuf(m->buffio, 0); l = strlen(ath_sequence) + (b ? strlen(hup_sequence) : 0); if (buffio_can_write(m->buffio, l)) { if (b) buffio_command(m->buffio, hup_sequence); buffio_command(m->buffio, ath_sequence); m->state = MODEM_STATE_HANGUP; } else m->state = b ? MODEM_STATE_PRE_HANGUP_HUPSEQ : MODEM_STATE_PRE_HANGUP_ATHSEQ; modem_timeout(m, HANGUP_TIMEOUT); } static void modem_accept(struct modem *m) { assert(m); daemon_log(LOG_INFO, "[%s] Accepting call.", m->name); m->state = MODEM_STATE_ATA; modem_timeout(m, ACCEPT_TIMEOUT); buffio_command(m->buffio, "ATA\n"); } static void child_exit_cb(pid_t t, int status, void *user) { struct modem *m = user; assert(m && m->child_pid == t); if (WIFEXITED(status)) daemon_log(WEXITSTATUS(status) == 0 ? LOG_INFO : LOG_WARNING, "Child exited with return code %i.", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) daemon_log(LOG_WARNING, "[%s] Child exited by signal %i.", m->name, WTERMSIG(status)); else daemon_log(LOG_ERR, "[%s] Child exited due to unknown cause.", m->name); m->child_pid = (pid_t) -1; if (m->dtmf_fifo) { dtmf_fifo_free(m->dtmf_fifo); m->dtmf_fifo = NULL; } switch (m->state) { case MODEM_STATE_CHILD_KILL: modem_hangup_seq(m); break; default: break; } } static int child_input_ready_cb(struct buffio *b, void *user) { struct modem *m = (struct modem*) user; assert(m && m->child_buffio == b); if (m->state == MODEM_STATE_CONNECTION) { //daemon_log(LOG_INFO, "child_input_ready"); copy_child_to_modem(m); } else buffio_flush_input(m->child_buffio); return 0; } static int child_eof_cb(struct buffio *b, void *user) { struct modem *m = user; assert(m && m->child_buffio == b); daemon_log(LOG_INFO, "Got child EOF."); switch (m->state) { case MODEM_STATE_CONNECTION: modem_hangup(m, 1); break; case MODEM_STATE_CHILD_WAIT_EOF: modem_hangup_seq(m); break; default: break; } return 0; } static int child_epipe_cb(struct buffio *b, void *user) { struct modem *m = user; assert(m && m->child_buffio == b); daemon_log(LOG_INFO, "[%s] Got child EPIPE.", m->name); assert(m->child_buffio); buffio_flush_output(m->child_buffio); switch (m->state) { case MODEM_STATE_CONNECTION: modem_hangup(m, 1); break; case MODEM_STATE_CHILD_DRAIN: modem_hangup_seq(m); break; default: break; } return 0; } static int child_output_request_cb(struct buffio *b, void *user) { struct modem *m = user; assert(m && m->child_buffio == b); switch (m->state) { case MODEM_STATE_CONNECTION: copy_modem_to_child(m); break; case MODEM_STATE_CHILD_DRAIN: modem_hangup_seq(m); break; default: break; } return 0; } static int modem_start_child(struct modem *m) { int ifd, ofd; assert(m && m->child_pid == (pid_t) -1 && m->tabentry && m->state == MODEM_STATE_CONNECTION); assert(!m->dtmf_fifo); if (!(m->dtmf_fifo = dtmf_fifo_new())) return -1; setenv("RINGMSN", m->ring_number ? m->ring_number : "", 1); setenv("CALLERMSN", m->caller_number ? m->caller_number : "", 1); assert(m->dtmf_fifo->fname); setenv("DTMFFIFO", m->dtmf_fifo->fname, 1); if (m->listen_msn) setenv("LISTENMSN", m->listen_msn, 1); else unsetenv("LISTENMSN"); assert(m->tabentry->args && m->tabentry->args[0]); if ((m->child_pid = child_process_create(m->tabentry->args[0], m->tabentry->args, &ifd, &ofd, child_exit_cb, m, m->tabentry->pipehack)) < 0) { dtmf_fifo_free(m->dtmf_fifo); return -1; } assert(ifd >= 0 && ofd >= 0 && !m->child_buffio); m->child_buffio = buffio_new(ifd, ofd); buffio_set_input_range(m->child_buffio, INPUT_RANGE_CHILD); buffio_set_output_range(m->child_buffio, OUTPUT_RANGE_CHILD); m->child_buffio->user = m; m->child_buffio->eof_cb = child_eof_cb; m->child_buffio->epipe_cb = child_epipe_cb; m->child_buffio->input_ready_cb = child_input_ready_cb; m->child_buffio->output_request_cb = child_output_request_cb; m->child_buffio->error_cb = error_cb; return 0; } static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user) { struct modem *m = (struct modem*) user; assert(source && user); switch (m->state) { case MODEM_STATE_INIT: case MODEM_STATE_CALLER_NUMBER: case MODEM_STATE_RING_EXPECT: case MODEM_STATE_RING: case MODEM_STATE_PRE_HANGUP_HUPSEQ: case MODEM_STATE_PRE_HANGUP_ATHSEQ: case MODEM_STATE_HANGUP: daemon_log(LOG_ERR, "[%s] Timeout reached.", m->name); return OOP_HALT; case MODEM_STATE_RINGING: modem_accept(m); return OOP_CONTINUE; case MODEM_STATE_ATA: case MODEM_STATE_VTXVRX: daemon_log(LOG_ERR, "[%s] Connection not established.", m->name); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); return OOP_CONTINUE; case MODEM_STATE_IDLE: modem_init_cycle(m); return OOP_CONTINUE; case MODEM_STATE_CHILD_DRAIN: daemon_log(LOG_INFO, "[%s] Child output buffer did not drain, flushing.", m->name); assert(m->child_buffio); buffio_flush_output(m->child_buffio); modem_hangup_seq(m); return OOP_CONTINUE; case MODEM_STATE_CHILD_WAIT_EOF: daemon_log(LOG_INFO, "[%s] Child did not EOF, closing child pipes.", m->name); assert(m->child_buffio); buffio_free(m->child_buffio); m->child_buffio = NULL; modem_hangup_seq(m); return OOP_CONTINUE; case MODEM_STATE_CHILD_KILL: daemon_log(LOG_INFO, "[%s] Child did not react on killing, exiting.", m->name); return OOP_HALT; default: assert(0); } } static int modem_input_ready_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { case MODEM_STATE_INIT: { const char *p; assert(m->command_index < INIT_AT_COMMANDS); p = init_at_commands[m->command_index*2+1]; if (buffio_find_input(m->buffio, p)) { m->command_index++; if (m->command_index >= INIT_AT_COMMANDS) { daemon_log(LOG_INFO, "[%s] Modem successfully initialised, waiting for calls.", m->name); m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_timeout(m, 0); modem_input_ready_cb(b, user); } else modem_next_command(m); } else buffio_dump_lines(m->buffio); break; } case MODEM_STATE_CALLER_NUMBER_EXPECT: if (buffio_find_input(m->buffio, "CALLER NUMBER: ")) { m->state = MODEM_STATE_CALLER_NUMBER; modem_timeout(m, BASIC_TIMEOUT); modem_input_ready_cb(b, user); } else buffio_dump_lines(m->buffio); break; case MODEM_STATE_CALLER_NUMBER: { char c[64]; if (buffio_read_line(m->buffio, c, sizeof(c))) { c[strcspn(c, "\n\r")] = 0; free(m->caller_number); m->caller_number = strdup(c); assert(m->caller_number); m->state = MODEM_STATE_RING_EXPECT; modem_input_ready_cb(b, user); } break; } case MODEM_STATE_RING_EXPECT: if (buffio_find_input(m->buffio, "RING/")) { m->state = MODEM_STATE_RING; modem_input_ready_cb(b, user); } else buffio_dump_lines(m->buffio); break; case MODEM_STATE_RING: { char c[64]; if (buffio_read_line(m->buffio, c, sizeof(c))) { struct tabentry *t; c[strcspn(c, "\n\r")] = 0; free(m->ring_number); m->ring_number = strdup(c); assert(m->ring_number); modem_timeout(m, 0); daemon_log(LOG_INFO, "[%s] Incoming call from [%s] to [%s]", m->name, m->caller_number, m->ring_number); if ((t = msntab_check_call(m->ring_number, m->caller_number)) && (t->action == CALL_ACTION_ACCEPT || t->action == CALL_ACTION_HANGUP)) { if (m->tabentry) msntab_unref(m->tabentry); m->tabentry = t; if (t->rings <= 0) modem_accept(m); else { daemon_log(LOG_INFO, "[%s] Will accept call after %u rings (%u seconds).", m->name, t->rings, t->rings*RING_TIME); m->state = MODEM_STATE_RINGING; modem_timeout(m, RING_TIME*t->rings); } break; } daemon_log(LOG_INFO, "[%s] Ignoring call.", m->name); if (t) msntab_unref(t); m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_input_ready_cb(b, user); } break; } case MODEM_STATE_RINGING: if (buffio_find_input(m->buffio, "RUNG\r\n")) { daemon_log(LOG_INFO, "[%s] Peer hung up prematurely.", m->name); m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_timeout(m, 0); modem_input_ready_cb(b, user); } else buffio_dump_lines(m->buffio); break; case MODEM_STATE_ATA: if (buffio_find_input(m->buffio, "VCON\r\n")) { assert(m->tabentry); if (m->tabentry->action == CALL_ACTION_ACCEPT) { daemon_log(LOG_INFO, "[%s] Call accepted, changing to voice mode.", m->name); buffio_command(m->buffio, "AT+VTX+VRX\n"); m->state = MODEM_STATE_VTXVRX; } else if (m->tabentry->action == CALL_ACTION_HANGUP) { modem_timeout(m, 0); daemon_log(LOG_INFO, "[%s] Call accepted for hang up.", m->name); modem_hangup(m, 0); } else assert(0); } else if (buffio_find_input(m->buffio, "NO CARRIER\r\n")) { daemon_log(LOG_INFO, "[%s] Failed to accept call, carrier lost", m->name); modem_sched_reset(m); } else buffio_dump_lines(m->buffio); break; case MODEM_STATE_VTXVRX: if (buffio_find_input(m->buffio, "CONNECT\r\n\r\nCONNECT\r\n")) { modem_timeout(m, 0); m->state = MODEM_STATE_CONNECTION; daemon_log(LOG_INFO, "[%s] Voice connection established.", m->name); if (modem_start_child(m) < 0) { daemon_log(LOG_ERR, "[%s] Failed to start child process", m->name); modem_hangup(m, 1); } else { m->dle_flag = 0; buffio_set_input_range(m->buffio, INPUT_RANGE_CONNECTION); buffio_set_output_range(m->buffio, OUTPUT_RANGE_CONNECTION); buffio_set_delay_usec(m->buffio, ISDN_DELAY_USEC, ISDN_LATENCY_USEC); buffio_set_prebuf(m->buffio, 1); m->flush_msg = 0; modem_input_ready_cb(b, user); } } else if (buffio_find_input(m->buffio, "NO CARRIER\r\n")) { daemon_log(LOG_INFO, "[%s] Failed to accept call, carrier lost", m->name); modem_sched_reset(m); } break; case MODEM_STATE_CONNECTION: copy_modem_to_child(m); break; default: break; } return 0; } static int modem_output_empty_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { case MODEM_STATE_PRE_HANGUP_HUPSEQ: if (b) buffio_command(m->buffio, hup_sequence); /* pass over */ case MODEM_STATE_PRE_HANGUP_ATHSEQ: buffio_command(m->buffio, ath_sequence); m->state = MODEM_STATE_HANGUP; /* Don't restart timeout here */ break; case MODEM_STATE_HANGUP: modem_force_hangup(m); modem_hangup_seq(m); break; default: break; } return 0; } static int modem_output_request_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { case MODEM_STATE_CONNECTION: //daemon_log(LOG_INFO, "modem_output_request"); copy_child_to_modem(m); break; default: break; } return 0; } static int modem_dle_cb(uint8_t c, void *user) { struct modem *m = user; assert(m); switch (c) { case ETX : case DC4 : daemon_log(LOG_INFO, "[%s] Recieved hangup sequence from peer.", m->name); assert(m->state == MODEM_STATE_CONNECTION); buffio_flush_output(m->buffio); modem_hangup(m, 1); return -1; default: assert(m->dtmf_fifo); dtmf_fifo_pass(m->dtmf_fifo, c); return 0; } } static void copy_modem_to_child(struct modem *m) { const uint8_t *sp; uint8_t *dp; size_t sl, dl; assert(m->buffio && m->child_buffio); for (;;) { sp = buffio_read_ptr(m->buffio, &sl); dp = buffio_write_ptr(m->child_buffio, &dl); if (sp && sl && (!dp || !dl)) { if (!m->flush_msg) { daemon_log(LOG_INFO, "[%s] Child too slow, output buffer overflow, flushing.", m->name); m->flush_msg = 1; } buffio_flush_output(m->child_buffio); continue; } break; } if (sp && sl && dp && dl) { //daemon_log(LOG_INFO, "copy modem->child (%lu->%lu)", (unsigned long) m->buffio->input_length, (unsigned long) (m->child_buffio->output_range - m->child_buffio->output_length)); sl = dle_decode(sp, sl, dp, &dl, modem_dle_cb, m, &m->dle_flag); /* It may be the case that dle_decode recieved a hangup sequence! */ if (m->state == MODEM_STATE_CONNECTION) buffio_read_ptr_inc(m->buffio, sl); buffio_write_ptr_inc(m->child_buffio, dl); } } static void copy_child_to_modem(struct modem *m) { size_t sl, dl; const uint8_t *sp; uint8_t *dp; assert(m && m->child_buffio && m->buffio); sp = buffio_read_ptr(m->child_buffio, &sl); dp = buffio_write_ptr(m->buffio, &dl); if (sp && dp && sl && dl) { //daemon_log(LOG_INFO, "copy child->modem (%lu->%lu)", (unsigned long) m->child_buffio->input_length, (unsigned long) (m->buffio->output_range - m->buffio->output_length)); sl = dle_encode(sp, sl, dp, &dl); buffio_read_ptr_inc(m->child_buffio, sl); buffio_write_ptr_inc(m->buffio, dl); } } static int modem_reopen(struct modem *m) { int fd; struct termios ts; assert(m && m->dev); if (m->buffio) { daemon_log(LOG_INFO, "[%s] Closing TTY device.", m->name); buffio_close_output_fd(m->buffio); buffio_close_input_fd(m->buffio); } daemon_log(LOG_INFO, "[%s] Trying to open modem on TTY device ...", m->name); if ((fd = open(m->dev, O_RDWR|O_NDELAY|O_NOCTTY)) < 0) { daemon_log(LOG_ERR, "[%s] Failed to open device: %s", m->name, strerror(errno)); goto fail; } daemon_log(LOG_INFO, "[%s] TTY device successfully opened.", m->name); if (tcgetattr(fd, &ts) < 0) { daemon_log(LOG_ERR, "[%s] Failed to get TTY attributes: %s", m->name, strerror(errno)); goto fail; } cfmakeraw(&ts); ts.c_cflag |= HUPCL | CS8 | CLOCAL; ts.c_iflag |= IGNPAR | IGNBRK; ts.c_cc[VMIN] = 1; ts.c_cc[VTIME] = 0; cfsetospeed(&ts, BAUD_RATE); cfsetispeed(&ts, BAUD_RATE); tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, &ts) < 0) { daemon_log(LOG_ERR, "[%s] Failed to set TTY attributes: %s", m->name, strerror(errno)); goto fail; } if (!m->buffio) { m->buffio = buffio_new(fd, fd); assert(m->buffio); m->buffio->output_request_cb = modem_output_request_cb; m->buffio->output_empty_cb = modem_output_empty_cb; m->buffio->input_ready_cb = modem_input_ready_cb; m->buffio->error_cb = error_cb; m->buffio->user = m; buffio_set_input_range(m->buffio, INPUT_RANGE_DEFAULT); buffio_set_output_range(m->buffio, OUTPUT_RANGE_DEFAULT); buffio_set_delay_usec(m->buffio, 0, 0); } else buffio_set_fds(m->buffio, fd, fd); return 0; fail: if (fd >= 0) close(fd); return -1; }