diff options
author | Kevin P. Fleming <kpfleming@digium.com> | 2008-05-23 14:21:58 +0000 |
---|---|---|
committer | Kevin P. Fleming <kpfleming@digium.com> | 2008-05-23 14:21:58 +0000 |
commit | 3403af6f5eba79740c98abb2678f9a66ef5a8190 (patch) | |
tree | 93415a9598e5363284832757de0596988a421aa6 /dahdi_cfg.c | |
parent | db9cffbffeedcde4ee52027160b96a2548262c5d (diff) |
initial copy
git-svn-id: http://svn.asterisk.org/svn/dahdi/tools/trunk@4335 a0bf4364-ded3-4de4-8d8a-66a801d63aff
Diffstat (limited to 'dahdi_cfg.c')
-rw-r--r-- | dahdi_cfg.c | 1489 |
1 files changed, 1489 insertions, 0 deletions
diff --git a/dahdi_cfg.c b/dahdi_cfg.c new file mode 100644 index 0000000..a880f3e --- /dev/null +++ b/dahdi_cfg.c @@ -0,0 +1,1489 @@ +/* + * Configuration program for Zapata Telephony Interface + * + * Written by Mark Spencer <markster@digium.com> + * Based on previous works, designs, and architectures conceived and + * written by Jim Dixon <jim@lambdatel.com>. + * + * Copyright (C) 2001 Jim Dixon / Zapata Telephony. + * Copyright (C) 2001 Linux Support Services, Inc. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under thet erms 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Primary Author: Mark Spencer <markster@digium.com> + * Radio Support by Jim Dixon <jim@lambdatel.com> + */ + +#include <stdio.h> +#include <getopt.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#ifdef STANDALONE_ZAPATA +#include "kernel/zaptel.h" +#include "tonezone.h" +#else +#include <zaptel/zaptel.h> +#include <zaptel/tonezone.h> +#endif +#include "ztcfg.h" + +#define NUM_SPANS ZT_MAX_SPANS + +#define NUM_TONES 15 + +/* Assume no more than 1024 dynamics */ +#define NUM_DYNAMIC 1024 + +static int lineno=0; + +static FILE *cf; + +static char *filename=CONFIG_FILENAME; + +int rxtones[NUM_TONES + 1],rxtags[NUM_TONES + 1],txtones[NUM_TONES + 1]; +int bursttime = 0, debouncetime = 0, invertcor = 0, exttone = 0, corthresh = 0; +int txgain = 0, rxgain = 0, deemp = 0, preemp = 0; + +int corthreshes[] = {3125,6250,9375,12500,15625,18750,21875,25000,0} ; + +static int toneindex = 1; + +#define DEBUG_READER (1 << 0) +#define DEBUG_PARSER (1 << 1) +#define DEBUG_APPLY (1 << 2) +static int debug = 0; + +static int errcnt = 0; + +static int deftonezone = -1; + +static struct zt_lineconfig lc[ZT_MAX_SPANS]; + +static struct zt_chanconfig cc[ZT_MAX_CHANNELS]; + +static struct zt_dynamic_span zds[NUM_DYNAMIC]; + +static const char *sig[ZT_MAX_CHANNELS]; /* Signalling */ + +static int slineno[ZT_MAX_CHANNELS]; /* Line number where signalling specified */ + +static int spans=0; + +static int fo_real = 1; + +static int verbose = 0; + +static int force = 0; + +static int stopmode = 0; + +static int numdynamic = 0; + +static char zonestoload[ZT_TONE_ZONE_MAX][10]; + +static int numzones = 0; + +static int fd = -1; + +static const char *lbostr[] = { +"0 db (CSU)/0-133 feet (DSX-1)", +"133-266 feet (DSX-1)", +"266-399 feet (DSX-1)", +"399-533 feet (DSX-1)", +"533-655 feet (DSX-1)", +"-7.5db (CSU)", +"-15db (CSU)", +"-22.5db (CSU)" +}; + +static const char *laws[] = { + "Default", + "Mu-law", + "A-law" +}; + +static const char *sigtype_to_str(const int sig) +{ + switch (sig) { + case 0: + return "Unused"; + case ZT_SIG_EM: + return "E & M"; + case ZT_SIG_EM_E1: + return "E & M E1"; + case ZT_SIG_FXSLS: + return "FXS Loopstart"; + case ZT_SIG_FXSGS: + return "FXS Groundstart"; + case ZT_SIG_FXSKS: + return "FXS Kewlstart"; + case ZT_SIG_FXOLS: + return "FXO Loopstart"; + case ZT_SIG_FXOGS: + return "FXO Groundstart"; + case ZT_SIG_FXOKS: + return "FXO Kewlstart"; + case ZT_SIG_CAS: + return "CAS / User"; + case ZT_SIG_DACS: + return "DACS"; + case ZT_SIG_DACS_RBS: + return "DACS w/RBS"; + case ZT_SIG_CLEAR: + return "Clear channel"; + case ZT_SIG_SLAVE: + return "Slave channel"; + case ZT_SIG_HDLCRAW: + return "Raw HDLC"; + case ZT_SIG_HDLCNET: + return "Network HDLC"; + case ZT_SIG_HDLCFCS: + return "HDLC with FCS check"; + case ZT_SIG_HARDHDLC: + return "Hardware assisted D-channel"; + case ZT_SIG_MTP2: + return "MTP2"; + default: + return "Unknown"; + } +} + +int ind_ioctl(int channo, int fd, int op, void *data) +{ +ZT_INDIRECT_DATA ind; + + ind.chan = channo; + ind.op = op; + ind.data = data; + return ioctl(fd,ZT_INDIRECT,&ind); +} + +static void clear_fields() +{ + + memset(rxtones,0,sizeof(rxtones)); + memset(rxtags,0,sizeof(rxtags)); + memset(txtones,0,sizeof(txtones)); + bursttime = 0; + debouncetime = 0; + invertcor = 0; + exttone = 0; + txgain = 0; + rxgain = 0; + deemp = 0; + preemp = 0; +} + +static int error(char *fmt, ...) +{ + int res; + static int shown=0; + va_list ap; + if (!shown) { + fprintf(stderr, "Notice: Configuration file is %s\n", filename); + shown++; + } + res = fprintf(stderr, "line %d: ", lineno); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + errcnt++; + return res; +} + +static void trim(char *buf) +{ + /* Trim off trailing spaces, tabs, etc */ + while(strlen(buf) && (buf[strlen(buf) -1] < 33)) + buf[strlen(buf) -1] = '\0'; +} + +static int parseargs(char *input, char *output[], int maxargs, char sep) +{ + char *c; + int pos=0; + c = input; + output[pos++] = c; + while(*c) { + while(*c && (*c != sep)) c++; + if (*c) { + *c = '\0'; + c++; + while(*c && (*c < 33)) c++; + if (*c) { + if (pos >= maxargs) + return -1; + output[pos] = c; + trim(output[pos]); + pos++; + output[pos] = NULL; + /* Return error if we have too many */ + } else + return pos; + } + } + return pos; +} + +int dspanconfig(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int chans; + int timing; + argc = res = parseargs(args, realargs, 4, ','); + if (res != 4) { + error("Incorrect number of arguments to 'dynamic' (should be <driver>,<address>,<num channels>, <timing>)\n"); + } + res = sscanf(realargs[2], "%d", &chans); + if ((res == 1) && (chans < 1)) + res = -1; + if (res != 1) { + error("Invalid number of channels '%s', should be a number > 0.\n", realargs[2]); + } + + res = sscanf(realargs[3], "%d", &timing); + if ((res == 1) && (timing < 0)) + res = -1; + if (res != 1) { + error("Invalid timing '%s', should be a number > 0.\n", realargs[3]); + } + + + zap_copy_string(zds[numdynamic].driver, realargs[0], sizeof(zds[numdynamic].driver)); + zap_copy_string(zds[numdynamic].addr, realargs[1], sizeof(zds[numdynamic].addr)); + zds[numdynamic].numchans = chans; + zds[numdynamic].timing = timing; + + numdynamic++; + return 0; +} + +int spanconfig(char *keyword, char *args) +{ + static char *realargs[10]; + int res; + int argc; + int span; + int timing; + argc = res = parseargs(args, realargs, 7, ','); + if ((res < 5) || (res > 7)) { + error("Incorrect number of arguments to 'span' (should be <spanno>,<timing>,<lbo>,<framing>,<coding>[, crc4 | yellow [, yellow]])\n"); + } + res = sscanf(realargs[0], "%i", &span); + if (res != 1) { + error("Span number should be a valid span number, not '%s'\n", realargs[0]); + return -1; + } + res = sscanf(realargs[1], "%i", &timing); + if ((res != 1) || (timing < 0) || (timing > 15)) { + error("Timing should be a number from 0 to 15, not '%s'\n", realargs[1]); + return -1; + } + res = sscanf(realargs[2], "%i", &lc[spans].lbo); + if (res != 1) { + error("Line build-out (LBO) should be a number from 0 to 7 (usually 0) not '%s'\n", realargs[2]); + return -1; + } + if ((lc[spans].lbo < 0) || (lc[spans].lbo > 7)) { + error("Line build-out should be in the range 0 to 7, not %d\n", lc[spans].lbo); + return -1; + } + if (!strcasecmp(realargs[3], "d4")) { + lc[spans].lineconfig |= ZT_CONFIG_D4; + lc[spans].lineconfig &= ~ZT_CONFIG_ESF; + lc[spans].lineconfig &= ~ZT_CONFIG_CCS; + } else if (!strcasecmp(realargs[3], "esf")) { + lc[spans].lineconfig |= ZT_CONFIG_ESF; + lc[spans].lineconfig &= ~ZT_CONFIG_D4; + lc[spans].lineconfig &= ~ZT_CONFIG_CCS; + } else if (!strcasecmp(realargs[3], "ccs")) { + lc[spans].lineconfig |= ZT_CONFIG_CCS; + lc[spans].lineconfig &= ~(ZT_CONFIG_ESF | ZT_CONFIG_D4); + } else if (!strcasecmp(realargs[3], "cas")) { + lc[spans].lineconfig &= ~ZT_CONFIG_CCS; + lc[spans].lineconfig &= ~(ZT_CONFIG_ESF | ZT_CONFIG_D4); + } else { + error("Framing(T1)/Signalling(E1) must be one of 'd4', 'esf', 'cas' or 'ccs', not '%s'\n", realargs[3]); + return -1; + } + if (!strcasecmp(realargs[4], "ami")) { + lc[spans].lineconfig &= ~(ZT_CONFIG_B8ZS | ZT_CONFIG_HDB3); + lc[spans].lineconfig |= ZT_CONFIG_AMI; + } else if (!strcasecmp(realargs[4], "b8zs")) { + lc[spans].lineconfig |= ZT_CONFIG_B8ZS; + lc[spans].lineconfig &= ~(ZT_CONFIG_AMI | ZT_CONFIG_HDB3); + } else if (!strcasecmp(realargs[4], "hdb3")) { + lc[spans].lineconfig |= ZT_CONFIG_HDB3; + lc[spans].lineconfig &= ~(ZT_CONFIG_AMI | ZT_CONFIG_B8ZS); + } else { + error("Coding must be one of 'ami', 'b8zs' or 'hdb3', not '%s'\n", realargs[4]); + return -1; + } + if (argc > 5) { + if (!strcasecmp(realargs[5], "yellow")) + lc[spans].lineconfig |= ZT_CONFIG_NOTOPEN; + else if (!strcasecmp(realargs[5], "crc4")) { + lc[spans].lineconfig |= ZT_CONFIG_CRC4; + } else { + error("Only valid fifth arguments are 'yellow' or 'crc4', not '%s'\n", realargs[5]); + return -1; + } + } + if (argc > 6) { + if (!strcasecmp(realargs[6], "yellow")) + lc[spans].lineconfig |= ZT_CONFIG_NOTOPEN; + else { + error("Only valid sixth argument is 'yellow', not '%s'\n", realargs[6]); + return -1; + } + } + lc[spans].span = span; + lc[spans].sync = timing; + /* Valid span */ + spans++; + return 0; +} + +int apply_channels(int chans[], char *argstr) +{ + char *args[ZT_MAX_CHANNELS+1]; + char *range[3]; + int res,x, res2,y; + int chan; + int start, finish; + char argcopy[256]; + res = parseargs(argstr, args, ZT_MAX_CHANNELS, ','); + if (res < 0) + error("Too many arguments... Max is %d\n", ZT_MAX_CHANNELS); + for (x=0;x<res;x++) { + if (strchr(args[x], '-')) { + /* It's a range */ + zap_copy_string(argcopy, args[x], sizeof(argcopy)); + res2 = parseargs(argcopy, range, 2, '-'); + if (res2 != 2) { + error("Syntax error in range '%s'. Should be <val1>-<val2>.\n", args[x]); + return -1; + } + res2 =sscanf(range[0], "%i", &start); + if (res2 != 1) { + error("Syntax error. Start of range '%s' should be a number from 1 to %d\n", args[x], ZT_MAX_CHANNELS - 1); + return -1; + } else if ((start < 1) || (start >= ZT_MAX_CHANNELS)) { + error("Start of range '%s' must be between 1 and %d (not '%d')\n", args[x], ZT_MAX_CHANNELS - 1, start); + return -1; + } + res2 =sscanf(range[1], "%i", &finish); + if (res2 != 1) { + error("Syntax error. End of range '%s' should be a number from 1 to %d\n", args[x], ZT_MAX_CHANNELS - 1); + return -1; + } else if ((finish < 1) || (finish >= ZT_MAX_CHANNELS)) { + error("end of range '%s' must be between 1 and %d (not '%d')\n", args[x], ZT_MAX_CHANNELS - 1, finish); + return -1; + } + if (start > finish) { + error("Range '%s' should start before it ends\n", args[x]); + return -1; + } + for (y=start;y<=finish;y++) + chans[y]=1; + } else { + /* It's a single channel */ + res2 =sscanf(args[x], "%i", &chan); + if (res2 != 1) { + error("Syntax error. Channel should be a number from 1 to %d, not '%s'\n", ZT_MAX_CHANNELS - 1, args[x]); + return -1; + } else if ((chan < 1) || (chan >= ZT_MAX_CHANNELS)) { + error("Channel must be between 1 and %d (not '%d')\n", ZT_MAX_CHANNELS - 1, chan); + return -1; + } + chans[chan]=1; + } + } + return res; +} + +int parse_idle(int *i, char *s) +{ + char a,b,c,d; + if (s) { + if (sscanf(s, "%c%c%c%c", &a,&b,&c,&d) == 4) { + if (((a == '0') || (a == '1')) && ((b == '0') || (b == '1')) && ((c == '0') || (c == '1')) && ((d == '0') || (d == '1'))) { + *i = 0; + if (a == '1') + *i |= ZT_ABIT; + if (b == '1') + *i |= ZT_BBIT; + if (c == '1') + *i |= ZT_CBIT; + if (d == '1') + *i |= ZT_DBIT; + return 0; + } + } + } + error("CAS Signalling requires idle definition in the form ':xxxx' at the end of the channel definition, where xxxx represent the a, b, c, and d bits\n"); + return -1; +} + +static int parse_channel(char *channel, int *startchan) +{ + if (!channel || (sscanf(channel, "%i", startchan) != 1) || + (*startchan < 1)) { + error("DACS requires a starting channel in the form ':x' where x is the channel\n"); + return -1; + } + return 0; +} + +static int chanconfig(char *keyword, char *args) +{ + int chans[ZT_MAX_CHANNELS]; + int res = 0; + int x; + int master=0; + int dacschan = 0; + char *idle; + bzero(chans, sizeof(chans)); + strtok(args, ":"); + idle = strtok(NULL, ":"); + if (!strcasecmp(keyword, "dacs") || !strcasecmp(keyword, "dacsrbs")) { + res = parse_channel(idle, &dacschan); + } + if (!res) + res = apply_channels(chans, args); + if (res <= 0) + return -1; + for (x=1;x<ZT_MAX_CHANNELS;x++) + if (chans[x]) { + if (slineno[x]) { + error("Channel %d already configured as '%s' at line %d\n", x, sig[x], slineno[x]); + continue; + } + if ((!strcasecmp(keyword, "dacs") || !strcasecmp(keyword, "dacsrbs")) && slineno[dacschan]) { + error("DACS Destination channel %d already configured as '%s' at line %d\n", dacschan, sig[dacschan], slineno[dacschan]); + continue; + } else { + cc[dacschan].chan = dacschan; + cc[dacschan].master = dacschan; + slineno[dacschan] = lineno; + } + cc[x].chan = x; + cc[x].master = x; + slineno[x] = lineno; + if (!strcasecmp(keyword, "e&m")) { + cc[x].sigtype = ZT_SIG_EM; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "e&me1")) { + cc[x].sigtype = ZT_SIG_EM_E1; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "fxsls")) { + cc[x].sigtype = ZT_SIG_FXSLS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "fxsgs")) { + cc[x].sigtype = ZT_SIG_FXSGS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "fxsks")) { + cc[x].sigtype = ZT_SIG_FXSKS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "fxols")) { + cc[x].sigtype = ZT_SIG_FXOLS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "fxogs")) { + cc[x].sigtype = ZT_SIG_FXOGS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "fxoks")) { + cc[x].sigtype = ZT_SIG_FXOKS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "cas") || !strcasecmp(keyword, "user")) { + if (parse_idle(&cc[x].idlebits, idle)) + return -1; + cc[x].sigtype = ZT_SIG_CAS; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "dacs")) { + /* Setup channel for monitor */ + cc[x].idlebits = dacschan; + cc[x].sigtype = ZT_SIG_DACS; + sig[x] = sigtype_to_str(cc[x].sigtype); + /* Setup inverse */ + cc[dacschan].idlebits = x; + cc[dacschan].sigtype = ZT_SIG_DACS; + sig[x] = sigtype_to_str(cc[dacschan].sigtype); + dacschan++; + } else if (!strcasecmp(keyword, "dacsrbs")) { + /* Setup channel for monitor */ + cc[x].idlebits = dacschan; + cc[x].sigtype = ZT_SIG_DACS_RBS; + sig[x] = sigtype_to_str(cc[x].sigtype); + /* Setup inverse */ + cc[dacschan].idlebits = x; + cc[dacschan].sigtype = ZT_SIG_DACS_RBS; + sig[x] = sigtype_to_str(cc[dacschan].sigtype); + dacschan++; + } else if (!strcasecmp(keyword, "unused")) { + cc[x].sigtype = 0; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "indclear") || !strcasecmp(keyword, "bchan")) { + cc[x].sigtype = ZT_SIG_CLEAR; + sig[x] = sigtype_to_str(cc[x].sigtype); + } else if (!strcasecmp(keyword, "clear")) { + sig[x] = sigtype_to_str(ZT_SIG_CLEAR); + if (master) { + cc[x].sigtype = ZT_SIG_SLAVE; + cc[x].master = master; + } else { + cc[x].sigtype = ZT_SIG_CLEAR; + master = x; + } + } else if (!strcasecmp(keyword, "rawhdlc")) { + sig[x] = sigtype_to_str(ZT_SIG_HDLCRAW); + if (master) { + cc[x].sigtype = ZT_SIG_SLAVE; + cc[x].master = master; + } else { + cc[x].sigtype = ZT_SIG_HDLCRAW; + master = x; + } + } else if (!strcasecmp(keyword, "nethdlc")) { + sig[x] = sigtype_to_str(ZT_SIG_HDLCNET); + memset(cc[x].netdev_name, 0, sizeof(cc[x].netdev_name)); + if (master) { + cc[x].sigtype = ZT_SIG_SLAVE; + cc[x].master = master; + } else { + cc[x].sigtype = ZT_SIG_HDLCNET; + if (idle) { + zap_copy_string(cc[x].netdev_name, idle, sizeof(cc[x].netdev_name)); + } + master = x; + } + } else if (!strcasecmp(keyword, "fcshdlc")) { + sig[x] = sigtype_to_str(ZT_SIG_HDLCFCS); + if (master) { + cc[x].sigtype = ZT_SIG_SLAVE; + cc[x].master = master; + } else { + cc[x].sigtype = ZT_SIG_HDLCFCS; + master = x; + } + } else if (!strcasecmp(keyword, "dchan")) { + sig[x] = "D-channel"; + cc[x].sigtype = ZT_SIG_HDLCFCS; + } else if (!strcasecmp(keyword, "hardhdlc")) { + sig[x] = "Hardware assisted D-channel"; + cc[x].sigtype = ZT_SIG_HARDHDLC; + } else if (!strcasecmp(keyword, "mtp2")) { + sig[x] = "MTP2"; + cc[x].sigtype = ZT_SIG_MTP2; + } else { + fprintf(stderr, "Huh? (%s)\n", keyword); + } + } + return 0; +} + +static int setlaw(char *keyword, char *args) +{ + int res; + int law; + int x; + int chans[ZT_MAX_CHANNELS]; + + bzero(chans, sizeof(chans)); + res = apply_channels(chans, args); + if (res <= 0) + return -1; + if (!strcasecmp(keyword, "alaw")) { + law = ZT_LAW_ALAW; + } else if (!strcasecmp(keyword, "mulaw")) { + law = ZT_LAW_MULAW; + } else if (!strcasecmp(keyword, "deflaw")) { + law = ZT_LAW_DEFAULT; + } else { + fprintf(stderr, "Huh??? Don't know about '%s' law\n", keyword); + return -1; + } + for (x=0;x<ZT_MAX_CHANNELS;x++) { + if (chans[x]) + cc[x].deflaw = law; + } + return 0; +} + +static int registerzone(char *keyword, char *args) +{ + if (numzones >= ZT_TONE_ZONE_MAX) { + error("Too many tone zones specified\n"); + return 0; + } + zap_copy_string(zonestoload[numzones++], args, sizeof(zonestoload[0])); + return 0; +} + +static int defaultzone(char *keyword, char *args) +{ + struct tone_zone *z; + if (!(z = tone_zone_find(args))) { + error("No such tone zone known: %s\n", args); + return 0; + } + deftonezone = z->zone; + return 0; +} + +#if 0 +static int unimplemented(char *keyword, char *args) +{ + fprintf(stderr, "Warning: '%s' is not yet implemented\n", keyword); + return 0; +} +#endif + + +/* Radio functions */ + +int ctcss(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int rxtone; + int rxtag; + int txtone; + int isdcs = 0; + argc = res = parseargs(args, realargs, 3, ','); + if (res != 3) { + error("Incorrect number of arguments to 'ctcss' (should be <rxtone>,<rxtag>,<txtone>)\n"); + } + res = sscanf(realargs[0], "%d", &rxtone); + if ((res == 1) && (rxtone < 1)) + res = -1; + if (res != 1) { + error("Invalid rxtone '%s', should be a number > 0.\n", realargs[0]); + } + res = sscanf(realargs[1], "%i", &rxtag); + if ((res == 1) && (rxtag < 0)) + res = -1; + if (res != 1) { + error("Invalid rxtag '%s', should be a number > 0.\n", realargs[1]); + } + if ((*realargs[2] == 'D') || (*realargs[2] == 'd')) + { + realargs[2]++; + isdcs = 0x8000; + } + res = sscanf(realargs[2], "%d", &txtone); + if ((res == 1) && (rxtag < 0)) + res = -1; + if (res != 1) { + error("Invalid txtone '%s', should be a number > 0.\n", realargs[2]); + } + + if (toneindex >= NUM_TONES) + { + error("Cannot specify more then %d CTCSS tones\n",NUM_TONES); + } + rxtones[toneindex] = rxtone; + rxtags[toneindex] = rxtag; + txtones[toneindex] = txtone | isdcs; + toneindex++; + return 0; +} + +int dcsrx(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int rxtone; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'dcsrx' (should be <rxtone>)\n"); + } + res = sscanf(realargs[0], "%d", &rxtone); + if ((res == 1) && (rxtone < 1)) + res = -1; + if (res != 1) { + error("Invalid rxtone '%s', should be a number > 0.\n", realargs[0]); + } + + rxtones[0] = rxtone; + return 0; +} + +int tx(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int txtone; + int isdcs = 0; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'tx' (should be <txtone>)\n"); + } + if ((*realargs[0] == 'D') || (*realargs[0] == 'd')) + { + realargs[0]++; + isdcs = 0x8000; + } + res = sscanf(realargs[0], "%d", &txtone); + if ((res == 1) && (txtone < 1)) + res = -1; + if (res != 1) { + error("Invalid tx (tone) '%s', should be a number > 0.\n", realargs[0]); + } + + txtones[0] = txtone | isdcs; + return 0; +} + +int debounce_time(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'debouncetime' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 1)) + res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + + debouncetime = val; + return 0; +} + +int burst_time(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'bursttime' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 1)) + res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + + bursttime = val; + return 0; +} + +int tx_gain(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'txgain' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + + txgain = val; + return 0; +} + +int rx_gain(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'rxgain' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + + rxgain = val; + return 0; +} + +int de_emp(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'de-emp' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 1)) + res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + + deemp = val; + return 0; +} + +int pre_emp(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'pre_emp' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 1)) + res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + + preemp = val; + return 0; +} + +int invert_cor(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'invertcor' (should be <value>)\n"); + } + if ((*realargs[0] == 'y') || (*realargs[0] == 'Y')) val = 1; + else if ((*realargs[0] == 'n') || (*realargs[0] == 'N')) val = 0; + else + { + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 0)) + res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + } + invertcor = (val > 0); + return 0; +} + +int ext_tone(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'exttone' (should be <value>)\n"); + } + if ((*realargs[0] == 'y') || (*realargs[0] == 'Y')) val = 1; + else if ((*realargs[0] == 'n') || (*realargs[0] == 'N')) val = 0; + else if ((*realargs[0] == 'i') || (*realargs[0] == 'I')) val = 2; + else + { + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 0)) + res = -1; + if (val > 2) res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + } + exttone = val; + return 0; +} + +int cor_thresh(char *keyword, char *args) +{ + static char *realargs[10]; + int argc; + int res; + int val; + int x = 0; + argc = res = parseargs(args, realargs, 1, ','); + if (res != 1) { + error("Incorrect number of arguments to 'corthresh' (should be <value>)\n"); + } + res = sscanf(realargs[0], "%d", &val); + if ((res == 1) && (val < 1)) + res = -1; + for(x = 0; corthreshes[x]; x++) + { + if (corthreshes[x] == val) break; + } + if (!corthreshes[x]) res = -1; + if (res != 1) { + error("Invalid value '%s', should be a number > 0.\n", realargs[0]); + } + corthresh = x + 1; + return 0; +} + +int rad_apply_channels(int chans[], char *argstr) +{ + char *args[ZT_MAX_CHANNELS+1]; + char *range[3]; + int res,x, res2,y; + int chan; + int start, finish; + char argcopy[256]; + res = parseargs(argstr, args, ZT_MAX_CHANNELS, ','); + if (res < 0) + error("Too many arguments... Max is %d\n", ZT_MAX_CHANNELS); + for (x=0;x<res;x++) { + if (strchr(args[x], '-')) { + /* It's a range */ + zap_copy_string(argcopy, args[x], sizeof(argcopy)); + res2 = parseargs(argcopy, range, 2, '-'); + if (res2 != 2) { + error("Syntax error in range '%s'. Should be <val1>-<val2>.\n", args[x]); + return -1; + } + res2 =sscanf(range[0], "%i", &start); + if (res2 != 1) { + error("Syntax error. Start of range '%s' should be a number from 1 to %d\n", args[x], ZT_MAX_CHANNELS - 1); + return -1; + } else if ((start < 1) || (start >= ZT_MAX_CHANNELS)) { + error("Start of range '%s' must be between 1 and %d (not '%d')\n", args[x], ZT_MAX_CHANNELS - 1, start); + return -1; + } + res2 =sscanf(range[1], "%i", &finish); + if (res2 != 1) { + error("Syntax error. End of range '%s' should be a number from 1 to %d\n", args[x], ZT_MAX_CHANNELS - 1); + return -1; + } else if ((finish < 1) || (finish >= ZT_MAX_CHANNELS)) { + error("end of range '%s' must be between 1 and %d (not '%d')\n", args[x], ZT_MAX_CHANNELS - 1, finish); + return -1; + } + if (start > finish) { + error("Range '%s' should start before it ends\n", args[x]); + return -1; + } + for (y=start;y<=finish;y++) + chans[y]=1; + } else { + /* It's a single channel */ + res2 =sscanf(args[x], "%i", &chan); + if (res2 != 1) { + error("Syntax error. Channel should be a number from 1 to %d, not '%s'\n", ZT_MAX_CHANNELS - 1, args[x]); + return -1; + } else if ((chan < 1) || (chan >= ZT_MAX_CHANNELS)) { + error("Channel must be between 1 and %d (not '%d')\n", ZT_MAX_CHANNELS - 1, chan); + return -1; + } + chans[chan]=1; + } + } + return res; +} + +static int rad_chanconfig(char *keyword, char *args) +{ + int chans[ZT_MAX_CHANNELS]; + int res = 0; + int x,i,n; + struct zt_radio_param p; + + toneindex = 1; + bzero(chans, sizeof(chans)); + res = rad_apply_channels(chans, args); + if (res <= 0) + return -1; + for (x=1;x<ZT_MAX_CHANNELS;x++) { + if (chans[x]) { + p.radpar = ZT_RADPAR_NUMTONES; + if (ind_ioctl(x,fd,ZT_RADIO_GETPARAM,&p) == -1) + n = 0; else n = p.data; + if (n) + { + p.radpar = ZT_RADPAR_INITTONE; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot init tones for channel %d\n",x); + if (!rxtones[0]) for(i = 1; i <= n; i++) + { + if (rxtones[i]) + { + p.radpar = ZT_RADPAR_RXTONE; + p.index = i; + p.data = rxtones[i]; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set rxtone on channel %d\n",x); + } + if (rxtags[i]) + { + p.radpar = ZT_RADPAR_RXTONECLASS; + p.index = i; + p.data = rxtags[i]; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set rxtag on channel %d\n",x); + } + if (txtones[i]) + { + p.radpar = ZT_RADPAR_TXTONE; + p.index = i; + p.data = txtones[i]; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set txtone on channel %d\n",x); + } + } else { /* if we may have DCS receive */ + if (rxtones[0]) + { + p.radpar = ZT_RADPAR_RXTONE; + p.index = 0; + p.data = rxtones[0]; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set DCS rxtone on channel %d\n",x); + } + } + if (txtones[0]) + { + p.radpar = ZT_RADPAR_TXTONE; + p.index = 0; + p.data = txtones[0]; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set default txtone on channel %d\n",x); + } + } + if (debouncetime) + { + p.radpar = ZT_RADPAR_DEBOUNCETIME; + p.data = debouncetime; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set debouncetime on channel %d\n",x); + } + if (bursttime) + { + p.radpar = ZT_RADPAR_BURSTTIME; + p.data = bursttime; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set bursttime on channel %d\n",x); + } + p.radpar = ZT_RADPAR_DEEMP; + p.data = deemp; + ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p); + p.radpar = ZT_RADPAR_PREEMP; + p.data = preemp; + ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p); + p.radpar = ZT_RADPAR_TXGAIN; + p.data = txgain; + ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p); + p.radpar = ZT_RADPAR_RXGAIN; + p.data = rxgain; + ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p); + p.radpar = ZT_RADPAR_INVERTCOR; + p.data = invertcor; + ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p); + p.radpar = ZT_RADPAR_EXTRXTONE; + p.data = exttone; + ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p); + if (corthresh) + { + p.radpar = ZT_RADPAR_CORTHRESH; + p.data = corthresh - 1; + if (ind_ioctl(x,fd,ZT_RADIO_SETPARAM,&p) == -1) + error("Cannot set corthresh on channel %d\n",x); + } + } + } + clear_fields(); + return 0; +} + +/* End Radio functions */ + +static void printconfig(int fd) +{ + int x,y; + int ps; + int configs=0; + struct zt_versioninfo vi; + + strcpy(vi.version, "Unknown"); + strcpy(vi.echo_canceller, "Unknown"); + + if (ioctl(fd, ZT_GETVERSION, &vi)) + error("Unable to read Zaptel version information.\n"); + + printf("\nZaptel Version: %s\n" + "Echo Canceller: %s\n" + "Configuration\n" + "======================\n\n", vi.version, vi.echo_canceller); + for (x=0;x<spans;x++) + printf("SPAN %d: %3s/%4s Build-out: %s\n", + x+1, ( lc[x].lineconfig & ZT_CONFIG_D4 ? "D4" : + lc[x].lineconfig & ZT_CONFIG_ESF ? "ESF" : + lc[x].lineconfig & ZT_CONFIG_CCS ? "CCS" : "CAS" ), + ( lc[x].lineconfig & ZT_CONFIG_AMI ? "AMI" : + lc[x].lineconfig & ZT_CONFIG_B8ZS ? "B8ZS" : + lc[x].lineconfig & ZT_CONFIG_HDB3 ? "HDB3" : "???" ), + lbostr[lc[x].lbo]); + for (x=0;x<numdynamic;x++) { + printf("Dynamic span %d: driver %s, addr %s, channels %d, timing %d\n", + x +1, zds[x].driver, zds[x].addr, zds[x].numchans, zds[x].timing); + } + if (verbose > 1) { + printf("\nChannel map:\n\n"); + for (x=1;x<ZT_MAX_CHANNELS;x++) { + if ((cc[x].sigtype != ZT_SIG_SLAVE) && (cc[x].sigtype)) { + configs++; + ps = 0; + if ((cc[x].sigtype & __ZT_SIG_DACS) == __ZT_SIG_DACS) + printf("Channel %02d %s to %02d", x, sig[x], cc[x].idlebits); + else { + printf("Channel %02d: %s (%s)", x, sig[x], laws[cc[x].deflaw]); + for (y=1;y<ZT_MAX_CHANNELS;y++) + if (cc[y].master == x) { + printf("%s%02d", ps++ ? " " : " (Slaves: ", y); + } + } + if (ps) printf(")\n"); else printf("\n"); + } else + if (cc[x].sigtype) configs++; + } + } else + for (x=1;x<ZT_MAX_CHANNELS;x++) + if (cc[x].sigtype) + configs++; + printf("\n%d channels to configure.\n\n", configs); +} + +static struct handler { + char *keyword; + int (*func)(char *keyword, char *args); +} handlers[] = { + { "span", spanconfig }, + { "dynamic", dspanconfig }, + { "loadzone", registerzone }, + { "defaultzone", defaultzone }, + { "e&m", chanconfig }, + { "e&me1", chanconfig }, + { "fxsls", chanconfig }, + { "fxsgs", chanconfig }, + { "fxsks", chanconfig }, + { "fxols", chanconfig }, + { "fxogs", chanconfig }, + { "fxoks", chanconfig }, + { "rawhdlc", chanconfig }, + { "nethdlc", chanconfig }, + { "fcshdlc", chanconfig }, + { "hardhdlc", chanconfig }, + { "mtp2", chanconfig }, + { "dchan", chanconfig }, + { "bchan", chanconfig }, + { "indclear", chanconfig }, + { "clear", chanconfig }, + { "unused", chanconfig }, + { "cas", chanconfig }, + { "dacs", chanconfig }, + { "dacsrbs", chanconfig }, + { "user", chanconfig }, + { "alaw", setlaw }, + { "mulaw", setlaw }, + { "deflaw", setlaw }, + { "ctcss", ctcss }, + { "dcsrx", dcsrx }, + { "rxdcs", dcsrx }, + { "tx", tx }, + { "debouncetime", debounce_time }, + { "bursttime", burst_time }, + { "exttone", ext_tone }, + { "invertcor", invert_cor }, + { "corthresh", cor_thresh }, + { "rxgain", rx_gain }, + { "txgain", tx_gain }, + { "deemp", de_emp }, + { "preemp", pre_emp }, + { "channel", rad_chanconfig }, + { "channels", rad_chanconfig }, +}; + +static char *readline() +{ + static char buf[256]; + char *c; + do { + if (!fgets(buf, sizeof(buf), cf)) + return NULL; + /* Strip comments */ + c = strchr(buf, '#'); + if (c) + *c = '\0'; + trim(buf); + lineno++; + } while (!strlen(buf)); + return buf; +} + +static void usage(char *argv0, int exitcode) +{ + char *c; + c = strrchr(argv0, '/'); + if (!c) + c = argv0; + else + c++; + fprintf(stderr, + "Usage: %s [options]\n" + " Valid options are:\n" + " -c <filename> -- Use <filename> instead of " CONFIG_FILENAME "\n" + " -d [level] -- Generate debugging output. (Default level is 1.)\n" + " -f -- Always reconfigure every channel\n" + " -h -- Generate this help statement\n" + " -s -- Shutdown spans only\n" + " -t -- Test mode only, do not apply\n" + " -v -- Verbose (more -v's means more verbose)\n" + ,c); + exit(exitcode); +} + +int main(int argc, char *argv[]) +{ + int c; + char *buf; + char *key, *value; + int x,found; + while((c = getopt(argc, argv, "fthc:vsd::")) != -1) { + switch(c) { + case 'c': + filename=optarg; + break; + case 'h': + usage(argv[0], 0); + break; + case '?': + usage(argv[0], 1); + break; + case 'v': + verbose++; + break; + case 'f': + force++; + break; + case 't': + fo_real = 0; + break; + case 's': + stopmode = 1; + break; + case 'd': + if (optarg) + debug = atoi(optarg); + else + debug = 1; + break; + } + } + if (fd == -1) fd = open(MASTER_DEVICE, O_RDWR); + if (fd < 0) + error("Unable to open master device '%s'\n", MASTER_DEVICE); + cf = fopen(filename, "r"); + if (cf) { + while((buf = readline())) { + if (debug & DEBUG_READER) + fprintf(stderr, "Line %d: %s\n", lineno, buf); + key = value = buf; + while(value && *value && (*value != '=')) value++; + if (value) + *value='\0'; + if (value) + value++; + while(value && *value && (*value < 33)) value++; + if (*value) { + trim(key); + if (debug & DEBUG_PARSER) + fprintf(stderr, "Keyword: [%s], Value: [%s]\n", key, value); + } else + error("Syntax error. Should be <keyword>=<value>\n"); + found=0; + for (x=0;x<sizeof(handlers) / sizeof(handlers[0]);x++) { + if (!strcasecmp(key, handlers[x].keyword)) { + found++; + handlers[x].func(key, value); + break; + } + } + if (!found) + error("Unknown keyword '%s'\n", key); + } + if (debug & DEBUG_READER) + fprintf(stderr, "<End of File>\n"); + fclose(cf); + } else { + error("Unable to open configuration file '%s'\n", filename); + } + + if (!errcnt) { + if (verbose) { + printconfig(fd); + } + if (fo_real) { + if (debug & DEBUG_APPLY) { + printf("About to open Master device\n"); + fflush(stdout); + } + for (x=0;x<numdynamic;x++) { + /* destroy them all */ + ioctl(fd, ZT_DYNAMIC_DESTROY, &zds[x]); + } + if (stopmode) { + for (x=0;x<spans;x++) { + if (ioctl(fd, ZT_SHUTDOWN, &lc[x].span)) { + fprintf(stderr, "Zaptel shutdown failed: %s\n", strerror(errno)); + close(fd); + exit(1); + } + } + } else { + for (x=0;x<spans;x++) { + if (ioctl(fd, ZT_SPANCONFIG, lc + x)) { + fprintf(stderr, "ZT_SPANCONFIG failed on span %d: %s (%d)\n", lc[x].span, strerror(errno), errno); + close(fd); + exit(1); + } + } + for (x=0;x<numdynamic;x++) { + if (ioctl(fd, ZT_DYNAMIC_CREATE, &zds[x])) { + fprintf(stderr, "Zaptel dynamic span creation failed: %s\n", strerror(errno)); + close(fd); + exit(1); + } + } + for (x=1;x<ZT_MAX_CHANNELS;x++) { + struct zt_params current_state; + int master; + int needupdate = force; + + if (debug & DEBUG_APPLY) { + printf("Configuring device %d\n", x); + fflush(stdout); + } + if (!cc[x].sigtype) + continue; + + if (!needupdate) { + memset(¤t_state, 0, sizeof(current_state)); + current_state.channo = cc[x].chan | ZT_GET_PARAMS_RETURN_MASTER; + if (ioctl(fd, ZT_GET_PARAMS, ¤t_state)) + needupdate = 1; + } + + if (!needupdate) { + master = current_state.channo >> 16; + + if (cc[x].sigtype != current_state.sigtype) { + needupdate++; + if (verbose > 1) + printf("Changing signalling on channel %d from %s to %s\n", + cc[x].chan, sigtype_to_str(current_state.sigtype), + sigtype_to_str(cc[x].sigtype)); + } + + if ((cc[x].deflaw != ZT_LAW_DEFAULT) && (cc[x].deflaw != current_state.curlaw)) { + needupdate++; + if (verbose > 1) + printf("Changing law on channel %d from %s to %s\n", + cc[x].chan, laws[current_state.curlaw], + laws[cc[x].deflaw]); + } + + if (cc[x].master != master) { + needupdate++; + if (verbose > 1) + printf("Changing master of channel %d from %d to %d\n", + cc[x].chan, master, + cc[x].master); + } + + if (cc[x].idlebits != current_state.idlebits) { + needupdate++; + if (verbose > 1) + printf("Changing idle bits of channel %d from %d to %d\n", + cc[x].chan, current_state.idlebits, + cc[x].idlebits); + } + } + + if (needupdate && ioctl(fd, ZT_CHANCONFIG, &cc[x])) { + fprintf(stderr, "ZT_CHANCONFIG failed on channel %d: %s (%d)\n", x, strerror(errno), errno); + if (errno == EINVAL) { + fprintf(stderr, "Did you forget that FXS interfaces are configured with FXO signalling\n" + "and that FXO interfaces use FXS signalling?\n"); + } + close(fd); + exit(1); + } + } + for (x=0;x<numzones;x++) { + if (debug & DEBUG_APPLY) { + printf("Loading tone zone for %s\n", zonestoload[x]); + fflush(stdout); + } + if (tone_zone_register(fd, zonestoload[x])) + if (errno != EBUSY) + error("Unable to register tone zone '%s'\n", zonestoload[x]); + } + if (debug & DEBUG_APPLY) { + printf("Doing startup\n"); + fflush(stdout); + } + if (deftonezone > -1) { + if (ioctl(fd, ZT_DEFAULTZONE, &deftonezone)) { + fprintf(stderr, "ZT_DEFAULTZONE failed: %s (%d)\n", strerror(errno), errno); + close(fd); + exit(1); + } + } + for (x=0;x<spans;x++) { + if (ioctl(fd, ZT_STARTUP, &lc[x].span)) { + fprintf(stderr, "Zaptel startup failed: %s\n", strerror(errno)); + close(fd); + exit(1); + } + } + } + } + } else { + fprintf(stderr, "\n%d error(s) detected\n\n", errcnt); + exit(1); + } + exit(0); +} |