diff --git a/faxd/Class0.c++ b/faxd/Class0.c++ index e422e5d..5ad006a 100644 --- a/faxd/Class0.c++ +++ b/faxd/Class0.c++ @@ -47,7 +47,7 @@ Class0Modem::setupModem(bool isSetup) return (false); // Query service support information fxStr s; - if (doQuery(conf.classQueryCmd, s, 500) && parseRange(s, modemServices)) + if (doQuery(conf.classQueryCmd, s, 5000) && parseRange(s, modemServices)) traceBits(modemServices & SERVICE_ALL, serviceNames); if ((modemServices & SERVICE_DATA) == 0) return (false); @@ -89,9 +89,9 @@ Class0Modem::setupFlowControl(FlowControl fc) } CallStatus -Class0Modem::dial(const char* number, fxStr& emsg) +Class0Modem::dial(const char* number, const char* origin, fxStr& emsg) { - return (ClassModem::dial(number, emsg)); + return (ClassModem::dial(number, origin, emsg)); } /* diff --git a/faxd/Class0.h b/faxd/Class0.h index 5ffa44f..6fed785 100644 --- a/faxd/Class0.h +++ b/faxd/Class0.h @@ -37,7 +37,7 @@ public: bool setupModem(bool isSend = true); virtual bool setupFlowControl(FlowControl fc); - CallStatus dial(const char* number, fxStr& emsg); + CallStatus dial(const char* number, const char* origin, fxStr& emsg); CallStatus dialResponse(fxStr& emsg); bool isFaxModem() const; // XXX safe to cast diff --git a/faxd/Class1.c++ b/faxd/Class1.c++ index eeaaa62..c3136fa 100644 --- a/faxd/Class1.c++ +++ b/faxd/Class1.c++ @@ -103,6 +103,7 @@ Class1Modem::Class1Modem(FaxServer& s, const ModemConfig& c) fxAssert(ecmStuffedBlock != NULL, "ECM procedure error (stuffed block)."); gotCTRL = false; repeatPhaseB = false; + silenceHeard = false; } Class1Modem::~Class1Modem() @@ -112,6 +113,13 @@ Class1Modem::~Class1Modem() free(ecmStuffedBlock); } +bool +Class1Modem::atCmd(const fxStr& cmd, ATResponse r, long ms) +{ + silenceHeard = false; + return (ClassModem::atCmd(cmd, r, ms)); +} + /* * Check if the modem is a Class 1 modem and, * if so, configure it for use. @@ -123,7 +131,7 @@ Class1Modem::setupModem(bool isSend) return (false); // Query service support information fxStr s; - if (doQuery(conf.classQueryCmd, s, 500) && FaxModem::parseRange(s, modemServices)) + if (doQuery(conf.classQueryCmd, s, 5000) && FaxModem::parseRange(s, modemServices)) traceBits(modemServices & SERVICE_ALL, serviceNames); if ((modemServices & serviceType) == 0) return (false); @@ -172,8 +180,8 @@ Class1Modem::setupModem(bool isSend) primaryV34Rate = atoi(conf.class1EnableV34Cmd.extract(pos, conf.class1EnableV34Cmd.next(pos, ',') - pos)); modemParams.br |= BIT(primaryV34Rate) - 1; } - modemParams.wd = BIT(WD_A4) | BIT(WD_B4) | BIT(WD_A3); - modemParams.ln = LN_ALL; + modemParams.wd = conf.class1PageWidthSupport; + modemParams.ln = conf.class1PageLengthSupport; modemParams.df = BIT(DF_1DMH) | BIT(DF_2DMR); modemParams.bf = BF_DISABLE; modemParams.st = ST_ALL; @@ -458,6 +466,35 @@ Class1Modem::decodePWD(fxStr& ascii, const HDLCFrame& binary) return decodeTSI(ascii, binary); } +bool +Class1Modem::switchingPause(fxStr& emsg) +{ + if (!silenceHeard && !atCmd(conf.class1SwitchingCmd, AT_OK)) { + emsg = "Failure to receive silence."; + protoTrace(emsg); + if (wasTimeout()) abortReceive(); + return (false); + } + /* + * We necessarily use class1SwitchingCmd rather frequently... + * sometimes this is programmed to be immediately before our signalling, + * and sometimes this is programmed to be immediately after the remote + * signalling. That doesn't therefore give us very good control on + * ensuring that class1SwitchingCmd is not issued in duplicate in some + * kinds of program flow. + * + * Ideally we'd be able to standardize on issuing class1SwitchingCmd + * only in one case or another (but not both), but that's not particularly + * an easy ideal to realize given the number of exceptions to that rule + * that would neccesarily be required. + * + * So for now we just set this flag here, and we unset it in atCmd, and + * if the flag is set when we come back here, then we can avoid duplication. + */ + silenceHeard = true; + return (true); +} + /* * Pass data to modem, filtering DLE's and * optionally including the end-of-data @@ -696,19 +733,15 @@ bool Class1Modem::recvRawFrame(HDLCFrame& frame) { /* - * The spec says that a frame that takes between - * 2.55 and 3.45 seconds to be received may be - * discarded; we also add some time for DCE - * to detect and strip flags. * We need to be generous here ... given that * some frames can be long and some devices * can add lots of flags to the signalling. - * The previous value of 5 seconds appears - * to have been too conservative; if the modem + * Adhering to the spec too closely, 5 sec, + * appears to be too conservative; if the modem * has said CONNECT, then it should be * responsible enough to carry-through with * things. A timeout here is only needed to - * reign in modems that don't carry through. + * reign-in modems that don't carry-through. */ startTimeout(10000); /* @@ -866,9 +899,9 @@ Class1Modem::syncECMFrame() // look for the first sync flag time_t start = Sys::now(); - startTimeout(3600); // twelve times T.4 A.3.1 + startTimeout(30000); // despite indications by T.4 A.3.1 do { - if ((unsigned) Sys::now()-start >= 3) { + if ((unsigned) Sys::now()-start >= 30) { protoTrace("Timeout awaiting synchronization sequence"); setTimeout(true); return (false); @@ -876,7 +909,7 @@ Class1Modem::syncECMFrame() bit = getModemBit(0); } while (bit != 0 && !didBlockEnd()); do { - if ((unsigned) Sys::now()-start >= 3) { + if ((unsigned) Sys::now()-start >= 30) { protoTrace("Timeout awaiting synchronization sequence"); setTimeout(true); return (false); @@ -1011,13 +1044,6 @@ Class1Modem::recvECMFrame(HDLCFrame& frame) frame.put(byte); bitpos = 8; byte = 0; - /* - * Ensure that a corrupt frame doesn't overflow the frame buffer. - */ - if (frame.getLength() > ((frameSize+6)*4)) { // 4 times valid size - protoTrace("HDLC frame length invalid."); - return (false); - } } } if (bit == 0) ones = 0; @@ -1033,11 +1059,22 @@ Class1Modem::recvECMFrame(HDLCFrame& frame) frame.put(0xff); frame.put(0xc0); frame.put(0x61); frame.put(0x96); frame.put(0xd3); rcpframe = true; } - } while (ones != 6 && bit != EOF && !rcpframe); - bit = getModemBit(60000); // trailing bit on flag - if (!rcpframe) { - if (frame.getLength() > 0) - traceHDLCFrame("-->", frame, true); + } while (ones != 6 && bit != EOF && !rcpframe && frame.getLength() < frameSize+6); + if (ones == 6) bit = getModemBit(60000); // trailing bit on flag + if (!rcpframe && frame.getLength() < frameSize+6) { + /* + * The HDLC frame was terminated early by a flag. T.30 A.3.5 states that + * frame size cannot change during one page, and T.4 A.3.6.2 seems to provide + * for padding in order to get that last frame on a block to always line up + * on a byte and frame boundary. However, the NOTE 2 there seemse to give + * leniency to that requirement, and in fact many senders will send short + * frames on the last frame of a block. So we run a couple of additional + * checks here (in addition to FCS checking) to limit the remote chance + * of FCS actually checking out on corrupt data (although that may be very + * remote indeed). We don't do these "trailing flag" tests on normal-sized + * frames because we deliberately don't look for a trailing flag when we + * get enough data. + */ if (bit) { // should have been zero protoTrace("Bad HDLC terminating flag received."); return (false); @@ -1047,6 +1084,7 @@ Class1Modem::recvECMFrame(HDLCFrame& frame) return (false); } } + traceHDLCFrame("-->", frame, true); if (bit == EOF) { protoTrace("EOF received."); return (false); @@ -1325,45 +1363,70 @@ Class1Modem::transmitData(int br, u_char* data, u_int cc, * retransmit the frame. */ bool -Class1Modem::recvFrame(HDLCFrame& frame, u_char dir, long ms, bool readPending) +Class1Modem::recvFrame(HDLCFrame& frame, u_char dir, long ms, bool readPending, bool docrp) { bool gotframe; u_short crpcnt = 0; + gotCONNECT = true; if (useV34) { do { - if (crpcnt) tracePPR(dir == FCF_SNDR ? "SEND send" : "RECV send", FCF_CRP); + if (crpcnt) traceFCF(dir == FCF_SNDR ? "SEND send" : "RECV send", FCF_CRP); frame.reset(); gotframe = recvRawFrame(frame); - } while (!gotframe && !gotRTNC && !gotEOT && crpcnt++ < 3 && !wasTimeout() && transmitFrame(dir|FCF_CRP)); + } while (!gotframe && !gotRTNC && !gotEOT && docrp && crpcnt++ < 3 && !wasTimeout() && transmitFrame(dir|FCF_CRP)); return (gotframe); } startTimeout(ms); - if (!readPending) readPending = atCmd(rhCmd, AT_NOTHING, 0) && waitFor(AT_CONNECT, 0); + if (!readPending) { + /* + * Hopefully the modem is smart enough to *not* do +FCERROR after +FRH=3, + * as it is only a stumbling-block for us and cannot be beneficial. But, + * in case it adheres blindly to the spec, we'll repeat ourselves here + * until we timeout or we do get the V.21 carrier. We do slow the looping + * with a pause to prevent unwanted massive amounts of tracing. The pause + * needs to be short enough, though, that the modem will still pick up + * any V.21 signalling if it misses that much of the startup. + */ + do { + readPending = atCmd(rhCmd, AT_NOTHING, 0) && waitFor(AT_CONNECT, 0); + if (lastResponse == AT_FCERROR) pause(200); + } while (lastResponse == AT_FCERROR && !wasTimeout()); + } if (readPending) { stopTimeout("waiting for HDLC flags"); if (wasTimeout()){ abortReceive(); return (false); } + fxStr emsg; do { if (crpcnt) { - tracePPR(dir == FCF_SNDR ? "SEND send" : "RECV send", FCF_CRP); + traceFCF(dir == FCF_SNDR ? "SEND send" : "RECV send", FCF_CRP); startTimeout(ms); if (!(atCmd(rhCmd, AT_NOTHING, 0) && waitFor(AT_CONNECT,0))) { stopTimeout("waiting for v.21 carrier"); - if (wasTimeout()) abortReceive(); + if (wasTimeout()) { + abortReceive(); + setTimeout(false); + } return (false); } stopTimeout("waiting for v.21 carrier"); } frame.reset(); gotframe = recvRawFrame(frame); - } while (!gotframe && crpcnt++ < 3 && !wasTimeout() && - atCmd(conf.class1SwitchingCmd, AT_OK) && transmitFrame(dir|FCF_CRP)); + } while (!gotframe && docrp && crpcnt++ < 3 && !wasTimeout() && + switchingPause(emsg) && transmitFrame(dir|FCF_CRP)); return (gotframe); - } else if (lastResponse == AT_ERROR) gotEOT = true; // on hook + } else { + gotCONNECT = false; + if (lastResponse == AT_ERROR) gotEOT = true; // on hook + } stopTimeout("waiting for v.21 carrier"); - if (wasTimeout()) abortReceive(); + if (wasTimeout()) { + abortReceive(); + setTimeout(false); + } return (false); } @@ -1401,12 +1464,19 @@ Class1Modem::recvTCF(int br, HDLCFrame& buf, const u_char* bitrev, long ms) * second TCF--perhaps if it is too long it * won't permit us to send the nak in time? * - * It needs to be longer than 1.5 seconds, though + * The initial timer needs to be longer than 1.5 seconds * to support senders that may not start the zeros - * until a second or two after CONNECT. + * until a second or two after CONNECT. This is + * also why we restart our timeout after the zeros + * start. */ + bool zerosstarted = false; startTimeout(ms); do { + if (c == 0x00 && !zerosstarted) { + zerosstarted = true; + startTimeout(ms); + } if (c == DLE) { c = getModemChar(0); if (c == ETX) { @@ -1422,7 +1492,6 @@ Class1Modem::recvTCF(int br, HDLCFrame& buf, const u_char* bitrev, long ms) setTimeout(true); break; } - } while ((c = getModemChar(0)) != EOF); } } diff --git a/faxd/Class1.h b/faxd/Class1.h index 8c21a0f..b73d186 100644 --- a/faxd/Class1.h +++ b/faxd/Class1.h @@ -77,6 +77,8 @@ protected: bool recvdDCN; // received DCN frame bool messageReceived; // expect/don't expect message carrier bool repeatPhaseB; // return to beginning of Phase B before next page + bool silenceHeard; // indicates whether the last command was +FRS + time_t lastMCF; // indicates the time of the last MCF signal u_int lastPPM; // last PPM during receive bool sendCFR; // received TCF was not confirmed u_short ecmBitPos; // bit position to populate on ecmByte @@ -110,6 +112,7 @@ protected: // V.34 indicators bool useV34; // whether or not V.8 handhaking was used bool gotEOT; // V.34-fax heard EOT signal + bool gotCONNECT; // whether or not CONNECT was seen bool gotCTRL; // current channel indicator bool gotRTNC; // retrain control channel u_short primaryV34Rate; // rate indication for primary channel @@ -122,6 +125,7 @@ protected: virtual bool setupFlowControl(FlowControl fc); // transmission support bool sendPrologue(FaxParams& dcs_caps, const fxStr& tsi); + void checkReceiverDIS(Class2Params&); bool dropToNextBR(Class2Params&); bool raiseToNextBR(Class2Params&); bool sendTraining(Class2Params&, int, fxStr& emsg); @@ -139,7 +143,7 @@ protected: u_int f3, const fxStr& nsf, u_int f4, const fxStr& id, u_int f5, FaxParams& dics, - u_int timer, fxStr& emsg); + u_int timer, bool notransmit, fxStr& emsg); bool recvDCSFrames(HDLCFrame& frame); bool recvTraining(); bool recvPPM(int& ppm, fxStr& emsg); @@ -155,6 +159,8 @@ protected: }; virtual ATResponse atResponse(char* buf, long ms = 30*1000); virtual bool waitFor(ATResponse wanted, long ms = 30*1000); + virtual bool atCmd(const fxStr& cmd, ATResponse = AT_OK, long ms = 30*1000); + bool switchingPause(fxStr& emsg); void encodeTSI(fxStr& binary, const fxStr& ascii); void encodeNSF(fxStr& binary, const fxStr& ascii); const fxStr& decodeTSI(fxStr& ascii, const HDLCFrame& binary); @@ -179,13 +185,14 @@ protected: bool sendClass1Data(const u_char* data, u_int cc, const u_char* bitrev, bool eod, long ms); bool sendClass1ECMData(const u_char* data, u_int cc, const u_char* bitrev, bool eod, u_int ppmcmd, fxStr& emsg); - bool recvFrame(HDLCFrame& frame, u_char dir, long ms = 10*1000, bool readPending = false); + bool recvFrame(HDLCFrame& frame, u_char dir, long ms = 10*1000, bool readPending = false, bool docrp = true); bool recvTCF(int br, HDLCFrame&, const u_char* bitrev, long ms); bool recvRawFrame(HDLCFrame& frame); bool recvECMFrame(HDLCFrame& frame); bool waitForDCEChannel(bool awaitctrl); bool renegotiatePrimary(bool constrain); bool syncECMFrame(); + void abortPageECMRecv(TIFF* tif, const Class2Params& params, u_char* block, u_int fcount, u_short seq, bool pagedataseen); bool recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg); void blockData(u_int byte, bool flag); bool blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxStr& emsg); diff --git a/faxd/Class1Poll.c++ b/faxd/Class1Poll.c++ index 9524fd4..d561f4e 100644 --- a/faxd/Class1Poll.c++ +++ b/faxd/Class1Poll.c++ @@ -66,5 +66,5 @@ Class1Modem::pollBegin(const fxStr& cig0, 0, fxStr::null, FCF_CIG, cig, FCF_DTC, dtc, - conf.t1Timer, emsg); + conf.t1Timer, false, emsg); } diff --git a/faxd/Class1Recv.c++ b/faxd/Class1Recv.c++ index 2473ea2..326d3bf 100644 --- a/faxd/Class1Recv.c++ +++ b/faxd/Class1Recv.c++ @@ -93,6 +93,7 @@ Class1Modem::recvBegin(fxStr& emsg) recvdDCN = false; // haven't seen DCN lastPPM = FCF_DCN; // anything will do sendCFR = false; // TCF was not received + lastMCF = 0; // no MCF heard yet fxStr nsf; encodeNSF(nsf, HYLAFAX_VERSION); @@ -107,7 +108,7 @@ Class1Modem::recvBegin(fxStr& emsg) FCF_NSF|FCF_RCVR, nsf, FCF_CSI|FCF_RCVR, lid, FCF_DIS|FCF_RCVR, dis, - conf.class1RecvIdentTimer, emsg); + conf.class1RecvIdentTimer, false, emsg); } /* @@ -141,38 +142,39 @@ Class1Modem::recvIdentification( u_int f3, const fxStr& nsf, u_int f4, const fxStr& id, u_int f5, FaxParams& dics, - u_int timer, fxStr& emsg) + u_int timer, bool notransmit, fxStr& emsg) { u_int t1 = howmany(timer, 1000); // in seconds - u_int trecovery = howmany(conf.class1TrainingRecovery, 1000); time_t start = Sys::now(); HDLCFrame frame(conf.class1FrameOverhead); - bool framesSent; + bool framesSent = false; emsg = "No answer (T.30 T1 timeout)"; - /* - * Transmit (PWD) (SUB) (CSI) DIS frames when the receiving - * station or (PWD) (SEP) (CIG) DTC when initiating a poll. - */ - if (f1) { - startTimeout(7550); - framesSent = sendFrame(f1, pwd, false); - stopTimeout("sending PWD frame"); - } else if (f2) { - startTimeout(7550); - framesSent = sendFrame(f2, addr, false); - stopTimeout("sending SUB/SEP frame"); - } else if (f3) { - startTimeout(7550); - framesSent = sendFrame(f3, (const u_char*)HYLAFAX_NSF, nsf, false); - stopTimeout("sending NSF frame"); - } else { - startTimeout(7550); - framesSent = sendFrame(f4, id, false); - stopTimeout("sending CSI/CIG frame"); + if (!notransmit) { + /* + * Transmit (PWD) (SUB) (CSI) DIS frames when the receiving + * station or (PWD) (SEP) (CIG) DTC when initiating a poll. + */ + if (f1) { + startTimeout(7550); + framesSent = sendFrame(f1, pwd, false); + stopTimeout("sending PWD frame"); + } else if (f2) { + startTimeout(7550); + framesSent = sendFrame(f2, addr, false); + stopTimeout("sending SUB/SEP frame"); + } else if (f3) { + startTimeout(7550); + framesSent = sendFrame(f3, (const u_char*)HYLAFAX_NSF, nsf, false); + stopTimeout("sending NSF frame"); + } else { + startTimeout(7550); + framesSent = sendFrame(f4, id, false); + stopTimeout("sending CSI/CIG frame"); + } } for (;;) { - if (framesSent) { + if (framesSent && !notransmit) { if (f1) { startTimeout(7550); framesSent = sendFrame(f2, addr, false); @@ -194,7 +196,7 @@ Class1Modem::recvIdentification( stopTimeout("sending DIS/DCS frame"); } } - if (framesSent) { + if (framesSent || notransmit) { /* * Wait for a response to be received. We wait T2 * rather than T4 due to empirical evidence for that need. @@ -208,12 +210,23 @@ Class1Modem::recvIdentification( bool gotframe = true; while (gotframe) { if (!recvDCSFrames(frame)) { - if (frame.getFCF() == FCF_DCN) { - emsg = "RSPREC error/got DCN"; - recvdDCN = true; - return (false); - } else // XXX DTC/DIS not handled - emsg = "RSPREC invalid response received"; + switch (frame.getFCF()) { + case FCF_DCN: + emsg = "RSPREC error/got DCN"; + recvdDCN = true; + return (false); + case FCF_CRP: + case FCF_MPS: + case FCF_EOP: + case FCF_EOM: + if (!useV34 && !switchingPause(emsg)) return (false); + transmitFrame(signalSent); + traceFCF("RECV send", (u_char) signalSent[2]); + break; + default: // XXX DTC/DIS not handled + emsg = "RSPREC invalid response received"; + break; + } break; } gotframe = false; @@ -253,26 +266,35 @@ Class1Modem::recvIdentification( * DCS from the other side. First verify there is * time to make another attempt... */ - if (Sys::now()+trecovery-start >= t1) + if ((u_int) Sys::now()-start >= t1) break; - /* - * Delay long enough to miss any training that the - * other side might have sent us. Otherwise the - * caller will miss our retransmission since it'll - * be in the process of sending training. - */ - pause(conf.class1TrainingRecovery); - /* - * Retransmit ident frames. - */ - if (f1) - framesSent = transmitFrame(f1, pwd, false); - else if (f2) - framesSent = transmitFrame(f2, addr, false); - else if (f3) - framesSent = transmitFrame(f3, (const u_char*)HYLAFAX_NSF, nsf, false); - else - framesSent = transmitFrame(f4, id, false); + if (frame.getFCF() != FCF_CRP) { + /* + * Historically we waited "Class1TrainingRecovery" (1500 ms) + * at this point to try to try to avoid retransmitting while + * the sender is also transmitting. Sometimes it proved to be + * a faulty approach. Really what we're trying to do is to + * not be transmitting at the same time as the other end is. + * The best way to do that is to make sure that there is + * silence on the line, and we do that with Class1SwitchingCmd. + */ + if (!switchingPause(emsg)) { + return (false); + } + } + if (!notransmit) { + /* + * Retransmit ident frames. + */ + if (f1) + framesSent = transmitFrame(f1, pwd, false); + else if (f2) + framesSent = transmitFrame(f2, addr, false); + else if (f3) + framesSent = transmitFrame(f3, (const u_char*)HYLAFAX_NSF, nsf, false); + else + framesSent = transmitFrame(f4, id, false); + } } return (false); } @@ -421,7 +443,8 @@ Class1Modem::recvTraining() * Send training response; we follow the spec * by delaying 75ms before switching carriers. */ - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) return (false); + fxStr emsg; + if (!switchingPause(emsg)) return (false); if (ok) { /* * Send CFR later so that we can cancel @@ -474,20 +497,34 @@ const u_int Class1Modem::modemPPMCodes[8] = { bool Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) { - if (lastPPM == FCF_MPS && prevPage && pageGood) { + if (lastPPM == FCF_MPS && prevPage) { /* * Resume sending HDLC frame (send data) * The carrier is already raised. Thus we * use sendFrame() instead of transmitFrame(). */ - startTimeout(7550); - (void) sendFrame((sendERR ? FCF_ERR : FCF_MCF)|FCF_RCVR); - stopTimeout("sending HDLC frame"); + if (pageGood) { + startTimeout(7550); + (void) sendFrame((sendERR ? FCF_ERR : FCF_MCF)|FCF_RCVR); + stopTimeout("sending HDLC frame"); + lastMCF = Sys::now(); + } else if (conf.badPageHandling == FaxModem::BADPAGE_RTNSAVE) { + startTimeout(7550); + (void) sendFrame(FCF_RTN|FCF_RCVR); + stopTimeout("sending HDLC frame"); + FaxParams dis = modemDIS(); + if (!recvIdentification(0, fxStr::null, 0, fxStr::null, + 0, fxStr::null, 0, fxStr::null, 0, dis, + conf.class1RecvIdentTimer, true, emsg)) { + return (false); + } + } } time_t t2end = 0; signalRcvd = 0; sendERR = false; + gotCONNECT = true; do { u_int timer = conf.t2Timer; @@ -561,11 +598,11 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) */ time_t nocarrierstart = Sys::now(); do { - messageReceived = waitFor(AT_NOCARRIER, 2*1000); + messageReceived = waitFor(AT_NOCARRIER, 5*1000); } while (!messageReceived && Sys::now() < (nocarrierstart + 5)); if (messageReceived) prevPage++; - timer = conf.t1Timer; // wait longer for PPM + timer = (80*1024*8) / ((curcap->br+1)*2400) * 1000; // wait longer for PPM (estimate 80KB) } } else { if (wasTimeout()) { @@ -573,17 +610,17 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) setTimeout(false); } bool getframe = false; + long wait = (80*1024*8) / ((curcap->br+1)*2400) * 1000; if (rmResponse == AT_FRH3) getframe = waitFor(AT_CONNECT, 0); - else if (rmResponse != AT_NOCARRIER) getframe = atCmd(rhCmd, AT_CONNECT, conf.t1Timer); // wait longer + else if (rmResponse != AT_NOCARRIER && rmResponse != AT_ERROR) getframe = atCmd(rhCmd, AT_CONNECT, wait); // wait longer if (getframe) { HDLCFrame frame(conf.class1FrameOverhead); if (recvFrame(frame, FCF_RCVR, conf.t2Timer, true)) { - tracePPM("RECV recv", frame.getFCF()); + traceFCF("RECV recv", frame.getFCF()); signalRcvd = frame.getFCF(); messageReceived = true; } } - if (wasTimeout()) abortReceive(); } } if (signalRcvd != 0) { @@ -680,7 +717,7 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) trainok = recvTraining(); messageReceived = (!trainok && lastResponse == AT_FRH3); } - } while (!trainok && traincount++ < 3 && recvFrame(frame, FCF_RCVR, timer)); + } while (!trainok && traincount++ < 3 && lastResponse != AT_FRH3 && recvFrame(frame, FCF_RCVR, timer)); if (messageReceived && lastResponse == AT_FRH3 && waitFor(AT_CONNECT,0)) { messageReceived = false; if (recvFrame(frame, FCF_RCVR, conf.t2Timer, true)) { @@ -700,23 +737,38 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) if (!getRecvEOLCount()) { // We have a null page, don't save it because it makes readers fail. pageGood = false; - if (params.ec != EC_DISABLE) return (false); + if (params.ec != EC_DISABLE) { + if (emsg == "") { + /* + * We negotiated ECM, got no valid ECM image data, and the + * ECM page reception routines did not set an error message. + * The empty emsg is due to the ECM routines detecting a + * non-ECM-specific partial-page signal and wants it to + * be handled here. The sum total of all of this, and the + * fact that we got MPS/EOP/EOM tells us that the sender + * is not using ECM. In an effort to salvage the session we'll + * disable ECM now and try to continue. + */ + params.ec = EC_DISABLE; + } else + return (false); + } } - if (!pageGood) recvResetPage(tif); - if (signalRcvd == 0) tracePPM("RECV recv", lastPPM); + if (!pageGood && conf.badPageHandling == FaxModem::BADPAGE_RTN) + recvResetPage(tif); + if (signalRcvd == 0) traceFCF("RECV recv", lastPPM); /* * [Re]transmit post page response. */ - if (pageGood) { + if (pageGood || (conf.badPageHandling == FaxModem::BADPAGE_RTNSAVE && getRecvEOLCount())) { + if (!pageGood) lastPPM = FCF_MPS; // FaxModem::BADPAGE_RTNSAVE /* * If post page message confirms the page * that we just received, write it to disk. */ if (messageReceived) { - if (!useV34 && emsg == "" && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - } + if (!useV34 && emsg == "") (void) switchingPause(emsg); /* * On servers where disk access may be bottlenecked or stressed, * the TIFFWriteDirectory call can lag. The strategy, then, is @@ -757,10 +809,10 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) do { if (emsg != "") break; (void) transmitFrame(params.ec != EC_DISABLE ? FCF_RNR : FCF_CRP|FCF_RCVR); - tracePPR("RECV send", params.ec != EC_DISABLE ? FCF_RNR : FCF_CRP); + traceFCF("RECV send", params.ec != EC_DISABLE ? FCF_RNR : FCF_CRP); HDLCFrame rrframe(conf.class1FrameOverhead); if (gotresponse = recvFrame(rrframe, FCF_RCVR, conf.t2Timer)) { - tracePPM("RECV recv", rrframe.getFCF()); + traceFCF("RECV recv", rrframe.getFCF()); if (rrframe.getFCF() == FCF_DCN) { protoTrace("RECV recv DCN"); emsg = "COMREC received DCN"; @@ -769,7 +821,7 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) } else if (params.ec != EC_DISABLE && rrframe.getFCF() != FCF_RR) { protoTrace("Ignoring invalid response to RNR."); } - if (!useV34) atCmd(conf.class1SwitchingCmd, AT_OK); + if (!useV34) (void) switchingPause(emsg); } } while (!gotEOT && !recvdDCN && !gotresponse && ++rnrcnt < 2 && Sys::now()-rrstart < 60); if (!gotresponse) emsg = "No response to RNR repeated 3 times."; @@ -778,7 +830,7 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) } } while (!gotEOT && !recvdDCN && tbuf[0] != 0 && Sys::now()-rrstart < 60); Sys::read(fcfd[0], NULL, 1); - exit(0); + _exit(0); default: // parent Sys::close(fcfd[0]); TIFFWriteDirectory(tif); @@ -805,8 +857,13 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) } } else { (void) transmitFrame((sendERR ? FCF_ERR : FCF_MCF)|FCF_RCVR); + lastMCF = Sys::now(); } - tracePPR("RECV send", sendERR ? FCF_ERR : FCF_MCF); + if (pageGood) { + traceFCF("RECV send", sendERR ? FCF_ERR : FCF_MCF); + lastMCF = Sys::now(); + } else + traceFCF("RECV send", FCF_RTN); } /* * Reset state so that the next call looks @@ -836,18 +893,31 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) * to implement than using +FRH and more reliable than * using +FTS */ - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; + if (!switchingPause(emsg)) { return (false); } - (void) transmitFrame(FCF_RTN|FCF_RCVR); - tracePPR("RECV send", FCF_RTN); - /* - * Reset the TIFF-related state so that subsequent - * writes will overwrite the previous data. - */ - messageReceived = true; // expect DCS next signalRcvd = 0; + if (params.ec == EC_DISABLE && !getRecvEOLCount() && (Sys::now() - lastMCF < 4)) { + /* + * We last transmitted MCF a very, very short time ago, received no image data + * since then, and now we're seeing a PPM again. In non-ECM mode the chances of + * this meaning that we simply missed a very short page is extremely remote. It + * probably means that the sender did not properly hear our MCF and that we just + * need to retransmit it. + */ + (void) transmitFrame(FCF_MCF|FCF_RCVR); + traceFCF("RECV send", FCF_MCF); + messageReceived = false; // expect Phase C + } else { + u_int rtnfcf = conf.badPageHandling == FaxModem::BADPAGE_DCN ? FCF_DCN : FCF_RTN; + (void) transmitFrame(rtnfcf|FCF_RCVR); + traceFCF("RECV send", rtnfcf); + /* + * Reset the TIFF-related state so that subsequent + * writes will overwrite the previous data. + */ + messageReceived = true; // expect DCS next + } } break; case FCF_DCN: // DCN @@ -882,7 +952,7 @@ Class1Modem::recvPage(TIFF* tif, u_int& ppm, fxStr& emsg, const fxStr& id) t2end = Sys::now() + howmany(conf.t2Timer, 1000); } } - } while (!wasTimeout() && lastResponse != AT_EMPTYLINE); + } while (gotCONNECT && !wasTimeout() && lastResponse != AT_EMPTYLINE); emsg = "T.30 T2 timeout, expected page not received"; if (prevPage && conf.saveUnconfirmedPages && getRecvEOLCount()) { TIFFWriteDirectory(tif); @@ -933,6 +1003,19 @@ Class1Modem::raiseRecvCarrier(bool& dolongtrain, fxStr& emsg) return (true); } +void +Class1Modem::abortPageECMRecv(TIFF* tif, const Class2Params& params, u_char* block, u_int fcount, u_short seq, bool pagedataseen) +{ + if (pagedataseen) { + writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); + if (conf.saveUnconfirmedPages) { + protoTrace("RECV keeping unconfirmed page"); + prevPage++; + } + } + free(block); +} + /* * Receive Phase C data in T.30-A ECM mode. */ @@ -969,7 +1052,8 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) abortReceive(); // return to command mode setTimeout(false); } - if (lastResponse != AT_NOCARRIER && atCmd(rhCmd, AT_CONNECT, conf.t1Timer)) { // wait longer + long wait = (80*1024*8) / ((curcap->br+1)*2400) * 1000; + if (lastResponse != AT_NOCARRIER && atCmd(rhCmd, AT_CONNECT, wait)) { // wait longer // sender is transmitting V.21 instead, we may have // missed the first signal attempt, but should catch // the next attempt. This "simulates" adaptive receive. @@ -977,19 +1061,14 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) gotRTNC = true; } else { if (wasTimeout()) abortReceive(); - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } } if (useV34 || gotRTNC) { // V.34 mode or if +FRH:3 in adaptive reception if (!gotEOT) { - bool gotprimary; + bool gotprimary = false; if (useV34) gotprimary = waitForDCEChannel(false); while (!sendERR && !gotEOT && (gotRTNC || (ctrlFrameRcvd != fxStr::null))) { /* @@ -1013,13 +1092,13 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) gotrtncframe = recvFrame(rtncframe, FCF_RCVR, conf.t2Timer, true); } if (gotrtncframe) { - tracePPM("RECV recv", rtncframe.getFCF()); + traceFCF("RECV recv", rtncframe.getFCF()); switch (rtncframe.getFCF()) { case FCF_PPS: if (rtncframe.getLength() > 5) { u_int fc = frameRev[rtncframe[6]] + 1; if ((fc == 256 || fc == 1) && !dataseen) fc = 0; // distinguish 0 from 1 and 256 - tracePPM("RECV recv", rtncframe.getFCF2()); + traceFCF("RECV recv", rtncframe.getFCF2()); protoTrace("RECV received %u frames of block %u of page %u", \ fc, frameRev[rtncframe[5]]+1, frameRev[rtncframe[4]]+1); switch (rtncframe.getFCF2()) { @@ -1030,22 +1109,16 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) case FCF_PRI_EOM: case FCF_PRI_MPS: case FCF_PRI_EOP: - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!useV34 && !switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } if (frameRev[rtncframe[4]] > prevPage || (frameRev[rtncframe[4]] == prevPage && frameRev[rtncframe[5]] >= prevBlock)) { (void) transmitFrame(FCF_PPR, fxStr(ppr, 32)); - tracePPR("RECV send", FCF_PPR); + traceFCF("RECV send", FCF_PPR); } else { (void) transmitFrame(FCF_MCF|FCF_RCVR); - tracePPR("RECV send", FCF_MCF); + traceFCF("RECV send", FCF_MCF); } break; } @@ -1053,7 +1126,7 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) break; case FCF_EOR: if (rtncframe.getLength() > 5) { - tracePPM("RECV recv", rtncframe.getFCF2()); + traceFCF("RECV recv", rtncframe.getFCF2()); switch (rtncframe.getFCF2()) { case 0: // PPS-NULL case FCF_EOM: @@ -1073,18 +1146,12 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) if (signalRcvd) lastblock = true; sendERR = true; } else { - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!useV34 && !switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } (void) transmitFrame(FCF_ERR|FCF_RCVR); - tracePPR("RECV send", FCF_ERR); + traceFCF("RECV send", FCF_ERR); } break; } @@ -1096,12 +1163,7 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) if (useV34) { // T.30 F.3.4.5 Note 1 does not permit CTC in V.34-fax emsg = "Received invalid CTC signal in V.34-Fax."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } /* @@ -1112,36 +1174,24 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) dcs = rtncframe[3] | (rtncframe[4]<<8); curcap = findSRCapability(dcs&DCS_SIGRATE, recvCaps); // requisite pause before sending response (CTR) - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } (void) transmitFrame(FCF_CTR|FCF_RCVR); - tracePPR("RECV send", FCF_CTR); + traceFCF("RECV send", FCF_CTR); dolongtrain = true; pprcnt = 0; break; } case FCF_CRP: // command repeat... just repeat whatever we last sent - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!useV34 && !switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } transmitFrame(signalSent); - tracePPR("RECV send", (u_char) signalSent[2]); + traceFCF("RECV send", (u_char) signalSent[2]); break; case FCF_DCN: emsg = "COMREC received DCN"; @@ -1153,9 +1203,14 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) // the earlier message-handling routines try to cope with the signal. signalRcvd = rtncframe.getFCF(); messageReceived = true; - prevPage--; // counteract the forthcoming increment - // maybe we should save an unconfirmed page here? - return (true); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); + if (getRecvEOLCount() == 0) { + prevPage--; // counteract the forthcoming increment + return (true); + } else { + emsg = "COMREC invalid response received"; // plain ol' error + return (false); + } } if (!sendERR) { // as long as we're not trying to send the ERR signal (set above) if (useV34) gotprimary = waitForDCEChannel(false); @@ -1166,18 +1221,14 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) abortReceive(); // return to command mode setTimeout(false); } - if (lastResponse != AT_NOCARRIER && atCmd(rhCmd, AT_CONNECT, conf.t1Timer)) { // wait longer + long wait = (80*1024*8) / ((curcap->br+1)*2400) * 1000; + if (lastResponse != AT_NOCARRIER && atCmd(rhCmd, AT_CONNECT, wait)) { // wait longer // simulate adaptive receive emsg = ""; // clear the failure gotRTNC = true; } else { if (wasTimeout()) abortReceive(); - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } else gotprimary = true; @@ -1201,24 +1252,14 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) else emsg = "Failed to properly detect high-speed data carrier."; } protoTrace(emsg); - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } if (gotEOT) { // intentionally not an else of the previous if if (useV34 && emsg == "") emsg = "Received premature V.34 termination."; protoTrace(emsg); - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } @@ -1273,23 +1314,13 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) if (!gotEOT && !gotCTRL && !waitForDCEChannel(true)) { emsg = "Failed to properly open V.34 control channel."; protoTrace(emsg); - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } if (gotEOT) { emsg = "Received premature V.34 termination."; protoTrace(emsg); - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } else { @@ -1313,15 +1344,17 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) * that the sender has not disconnected. It is possible * then, that T2 will not be long enough to receive the * partial-page signal because the sender is still transmitting - * high-speed data. So we wait T1 instead. + * high-speed data. So we calculate the wait for 80KB (the + * ECM block plus some wriggle-room) at the current bitrate. */ - gotpps = recvFrame(ppsframe, FCF_RCVR, conf.t1Timer); // wait longer - } while (!gotpps && !wasTimeout() && lastResponse != AT_NOCARRIER && ++recvFrameCount < 5); + long wait = (80*1024*8) / ((useV34 ? primaryV34Rate : curcap->br+1)*2400) * 1000; + gotpps = recvFrame(ppsframe, FCF_RCVR, wait); // wait longer + } while (!gotpps && gotCONNECT && !wasTimeout() && !gotEOT && ++recvFrameCount < 5); if (gotpps) { - tracePPM("RECV recv", ppsframe.getFCF()); + traceFCF("RECV recv", ppsframe.getFCF()); if (ppsframe.getFCF() == FCF_PPS) { // sender may violate T.30-A.4.3 and send another signal (i.e. DCN) - tracePPM("RECV recv", ppsframe.getFCF2()); + traceFCF("RECV recv", ppsframe.getFCF2()); } switch (ppsframe.getFCF()) { /* @@ -1377,12 +1410,14 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) * 3) The sender sent only one frame but for some reason we did not see * any data, and so the frame-count in the PPS signal ended up getting * interpreted as a zero. Only in the case that the frame happens to be - * the last frame in a block we will send MCF (to accomodate #1), and so - * this frame will then be lost. This should be rare and have little - * impact on actual image data loss when it does occur. + * the last frame in a block and we're dealing with MH, MR, or MMR data + * we will send MCF (to accomodate #1), and so this frame will then be + * lost. This should be rare and have little impact on actual image data + * loss when it does occur. This approach cannot be followed with JPEG + * and JBIG data formats. */ if (fcount) { - for (u_int i = 0; i <= (fcount - (fc ? 1 : 2)); i++) { + for (u_int i = 0; i <= (fcount - ((fc || params.df > DF_2DMMR) ? 1 : 2)); i++) { u_int pprpos, pprval; for (pprpos = 0, pprval = i; pprval >= 8; pprval -= 8) pprpos++; if (ppr[pprpos] & frameRev[1 << pprval]) blockgood = false; @@ -1392,14 +1427,8 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) } // requisite pause before sending response (PPR/MCF) - if (!blockgood && !useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!blockgood && !useV34 && !switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } @@ -1415,7 +1444,7 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) if (signalRcvd == 0) { // inform the remote that one or more frames were invalid transmitFrame(FCF_PPR, fxStr(ppr, 32)); - tracePPR("RECV send", FCF_PPR); + traceFCF("RECV send", FCF_PPR); pprcnt++; if (pprcnt > 4) pprcnt = 4; // could've been 4 before increment } @@ -1431,25 +1460,20 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) pprcnt = 0; if (signalRcvd != 0 || recvFrame(rtnframe, FCF_RCVR, conf.t2Timer)) { bool gotrtnframe = true; - if (signalRcvd == 0) tracePPM("RECV recv", rtnframe.getFCF()); + if (signalRcvd == 0) traceFCF("RECV recv", rtnframe.getFCF()); else signalRcvd = 0; // reset it, we're in-sync now recvFrameCount = 0; lastResponse = AT_NOTHING; - while (rtnframe.getFCF() == FCF_PPS && lastResponse != AT_NOCARRIER && recvFrameCount < 5 && gotrtnframe) { + while (rtnframe.getFCF() == FCF_PPS && !gotEOT && recvFrameCount < 5 && gotrtnframe) { // we sent PPR, but got PPS again... - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!useV34 && !switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } transmitFrame(FCF_PPR, fxStr(ppr, 32)); - tracePPR("RECV send", FCF_PPR); - gotrtnframe = recvFrame(rtnframe, FCF_RCVR, conf.t2Timer); + traceFCF("RECV send", FCF_PPR); + if (gotrtnframe = recvFrame(rtnframe, FCF_RCVR, conf.t2Timer)) + traceFCF("RECV recv", rtnframe.getFCF()); recvFrameCount++; } u_int dcs; // possible bits 1-16 of DCS in FIF @@ -1458,34 +1482,23 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) if (useV34) { // T.30 F.3.4.5 Note 1 does not permit CTC in V.34-fax emsg = "Received invalid CTC signal in V.34-Fax."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } // use 16-bit FIF to alter speed, curcap dcs = rtnframe[3] | (rtnframe[4]<<8); curcap = findSRCapability(dcs&DCS_SIGRATE, recvCaps); // requisite pause before sending response (CTR) - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + if (!switchingPause(emsg)) { + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } (void) transmitFrame(FCF_CTR|FCF_RCVR); - tracePPR("RECV send", FCF_CTR); + traceFCF("RECV send", FCF_CTR); dolongtrain = true; break; case FCF_EOR: - tracePPM("RECV recv", rtnframe.getFCF2()); + traceFCF("RECV recv", rtnframe.getFCF2()); /* * It may be wise to disconnect here if MMR is being * used because there will surely be image data loss. @@ -1510,12 +1523,7 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) break; default: emsg = "COMREC invalid response to repeated PPR received"; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } sendERR = true; // do it later @@ -1526,22 +1534,12 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) recvdDCN = true; default: if (emsg == "") emsg = "COMREC invalid response to repeated PPR received"; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } else { emsg = "T.30 T2 timeout, expected signal not received"; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } @@ -1562,12 +1560,7 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) break; default: emsg = "COMREC invalid post-page signal received"; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } @@ -1575,43 +1568,33 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) case FCF_DCN: emsg = "COMREC received DCN"; gotEOT = true; - recvdDCN = true; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + recvdDCN = true; + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); default: // The message is not ECM-specific: fall out of ECM receive, and let // the earlier message-handling routines try to cope with the signal. signalRcvd = ppsframe.getFCF(); messageReceived = true; - prevPage--; // counteract the forthcoming increment - // maybe we should save an unconfirmed page here? - return (true); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); + if (getRecvEOLCount() == 0) { + prevPage--; // counteract the forthcoming increment + return (true); + } else { + emsg = "COMREC invalid response received"; // plain ol' error + return (false); + } } } else { emsg = "T.30 T2 timeout, expected signal not received"; - if (conf.saveUnconfirmedPages && pagedataseen) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, pagedataseen); return (false); } } else { if (wasTimeout()) abortReceive(); if (syncattempts++ > 20) { emsg = "Cannot synchronize ECM frame reception."; - if (conf.saveUnconfirmedPages) { - protoTrace("RECV keeping unconfirmed page"); - writeECMData(tif, block, (fcount * frameSize), params, (seq |= 2)); - prevPage++; - } - free(block); + abortPageECMRecv(tif, params, block, fcount, seq, true); return(false); } } @@ -1662,13 +1645,13 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) bool gotresponse = true; u_short rnrcnt = 0; do { - if (!useV34) atCmd(conf.class1SwitchingCmd, AT_OK); + if (!useV34) (void) switchingPause(emsg); if (emsg != "") break; (void) transmitFrame(FCF_RNR|FCF_RCVR); - tracePPR("RECV send", FCF_RNR); + traceFCF("RECV send", FCF_RNR); HDLCFrame rrframe(conf.class1FrameOverhead); if (gotresponse = recvFrame(rrframe, FCF_RCVR, conf.t2Timer)) { - tracePPM("RECV recv", rrframe.getFCF()); + traceFCF("RECV recv", rrframe.getFCF()); if (rrframe.getFCF() == FCF_DCN) { protoTrace("RECV recv DCN"); emsg = "COMREC received DCN"; @@ -1683,7 +1666,7 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) } else tbuf[0] = 0; // parent finished writeECMData } while (!gotEOT && !recvdDCN && tbuf[0] != 0 && Sys::now()-rrstart < 60); Sys::read(fcfd[0], NULL, 1); - exit(0); + _exit(0); default: // parent Sys::close(fcfd[0]); writeECMData(tif, block, cc, params, seq); @@ -1700,9 +1683,9 @@ Class1Modem::recvPageECMData(TIFF* tif, const Class2Params& params, fxStr& emsg) if (!lastblock) { // confirm block received as good - if (!useV34) atCmd(conf.class1SwitchingCmd, AT_OK); + if (!useV34) (void) switchingPause(emsg); (void) transmitFrame((sendERR ? FCF_ERR : FCF_MCF)|FCF_RCVR); - tracePPR("RECV send", sendERR ? FCF_ERR : FCF_MCF); + traceFCF("RECV send", sendERR ? FCF_ERR : FCF_MCF); } prevBlock++; } while (! lastblock); @@ -1760,7 +1743,7 @@ Class1Modem::recvPageData(TIFF* tif, fxStr& emsg) * Complete a receive session. */ bool -Class1Modem::recvEnd(fxStr&) +Class1Modem::recvEnd(fxStr& emsg) { if (!recvdDCN && !gotEOT) { u_int t1 = howmany(conf.t1Timer, 1000); // T1 timer in seconds @@ -1771,20 +1754,20 @@ Class1Modem::recvEnd(fxStr&) HDLCFrame frame(conf.class1FrameOverhead); do { if (recvFrame(frame, FCF_RCVR, conf.t2Timer)) { - tracePPM("RECV recv", frame.getFCF()); + traceFCF("RECV recv", frame.getFCF()); switch (frame.getFCF()) { case FCF_PPS: case FCF_EOP: case FCF_CRP: - if (!useV34) atCmd(conf.class1SwitchingCmd, AT_OK); + if (!useV34) (void) switchingPause(emsg); (void) transmitFrame(FCF_MCF|FCF_RCVR); - tracePPR("RECV send", FCF_MCF); + traceFCF("RECV send", FCF_MCF); break; case FCF_DCN: recvdDCN = true; break; default: - if (!useV34) atCmd(conf.class1SwitchingCmd, AT_OK); + if (!useV34) (void) switchingPause(emsg); transmitFrame(FCF_DCN|FCF_RCVR); recvdDCN = true; break; diff --git a/faxd/Class1Send.c++ b/faxd/Class1Send.c++ index 815d79e..a578bb8 100644 --- a/faxd/Class1Send.c++ +++ b/faxd/Class1Send.c++ @@ -123,6 +123,21 @@ Class1Modem::sendBegin() #define BATCH_FIRST 1 #define BATCH_LAST 2 +void +Class1Modem::checkReceiverDIS(Class2Params& params) +{ + if (useV34) { + if (params.ec == EC_DISABLE) { + protoTrace("V.34-Fax session, but DIS signal contains no ECM bit; ECM forced."); + params.ec = EC_ENABLE256; + } + if (params.br != BR_33600) { + protoTrace("V.34-Fax session, but DIS signal contains no V.8 bit; compensating."); + params.br = BR_33600; + } + } +} + /* * Get the initial DIS command. */ @@ -140,6 +155,10 @@ Class1Modem::getPrologue(Class2Params& params, bool& hasDoc, fxStr& emsg, u_int& if (batched & BATCH_FIRST) // receive carrier raised framerecvd = recvFrame(frame, FCF_SNDR, conf.t2Timer, true); else { // receive carrier not raised + // We're not really switching directions of communication, but we don't want + // to start listening for prologue frames until we're sure that the receiver + // has dropped the carrier used to signal MCF. + if (!useV34) (void) switchingPause(emsg); // The receiver will allow T2 to elapse intentionally here. // To keep recvFrame from timing out we double our wait. framerecvd = recvFrame(frame, FCF_SNDR, conf.t2Timer * 2); @@ -163,11 +182,8 @@ Class1Modem::getPrologue(Class2Params& params, bool& hasDoc, fxStr& emsg, u_int& case FCF_DIS: dis_caps = frame.getDIS(); params.setFromDIS(dis_caps); + checkReceiverDIS(params); curcap = NULL; // force initial setup - if (useV34 && params.ec == EC_DISABLE) { - protoTrace("V.34-Fax session, but DIS signal contains no ECM bit; ECM forced."); - params.ec = EC_ENABLE256; - } break; } } while (frame.moreFrames() && recvFrame(frame, FCF_SNDR, conf.t2Timer)); @@ -197,20 +213,11 @@ Class1Modem::getPrologue(Class2Params& params, bool& hasDoc, fxStr& emsg, u_int& } } /* - * This delay is only supposed to be done if the signal - * is gone (see p.105 of Rec. T.30). We just assume - * it rather than send a command to the modem to check. - * Getting NO CARRIER after EOM seems necessary, so we - * usually do this at least twice before any HDLC frames - * are received. Using +FTS may be better than looping, - * but many modems won't support that. - */ - if (!useV34) pause(200); - /* * Wait up to T1 for a valid DIS. */ if ((unsigned) Sys::now()-start >= t1) break; + if (!useV34) (void) switchingPause(emsg); framerecvd = recvFrame(frame, FCF_SNDR, conf.t2Timer); } emsg = "No answer (T.30 T1 timeout)"; @@ -323,8 +330,7 @@ Class1Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info, * "before sending any signals using V.27 ter/V.29/V.33/V.17 * modulation system" */ - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - protoTrace("Failure to receive silence."); + if (!switchingPause(emsg)) { return (send_failed); } } @@ -404,7 +410,7 @@ Class1Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info, return (send_retry); } ppr = frame.getFCF(); - tracePPR("SEND recv", ppr); + traceFCF("SEND recv", ppr); } else { // ECM protocol already got post-page response ppr = signalRcvd; @@ -421,7 +427,7 @@ Class1Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info, pph.remove(0,2+5+1);// discard page-chop+handling info else pph.remove(0,3); // discard page-handling info - if (params.ec == EC_DISABLE) atCmd(conf.class1SwitchingCmd, AT_OK); + if (params.ec == EC_DISABLE) (void) switchingPause(emsg); ntrys = 0; if (morePages) { // meaning, more pages in this file, but there may be other files if (!TIFFReadDirectory(tif)) { @@ -515,9 +521,7 @@ Class1Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info, protoTrace(emsg); return (send_failed); case FCF_CRP: - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (send_retry); } break; @@ -573,8 +577,8 @@ Class1Modem::sendPrologue(FaxParams& dcs_caps, const fxStr& tsi) bool frameSent; if (useV34) frameSent = true; else { - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - protoTrace("Failure to receive silence."); + fxStr emsg; + if (!switchingPause(emsg)) { return (false); } frameSent = (atCmd(thCmd, AT_NOTHING) && atResponse(rbuf, 7550) == AT_CONNECT); @@ -652,6 +656,7 @@ Class1Modem::isCapable(u_int sr, FaxParams& dis) bool Class1Modem::sendTraining(Class2Params& params, int tries, fxStr& emsg) { + bool again = false; u_short attempt = 0; if (tries == 0) { emsg = "DIS/DTC received 3 times; DCS not recognized"; @@ -662,7 +667,7 @@ Class1Modem::sendTraining(Class2Params& params, int tries, fxStr& emsg) params.update(false); // we should respect the frame-size preference indication by the remote (DIS_FRAMESIZE) if (params.ec != EC_DISABLE && - (conf.class1ECMFrameSize == 64 || dis_caps.isBitEnabled(FaxParams::BITNUM_FRAMESIZE_DIS))) { + (params.ec == EC_ENABLE64 || conf.class1ECMFrameSize == 64 || dis_caps.isBitEnabled(FaxParams::BITNUM_FRAMESIZE_DIS))) { params.setBit(FaxParams::BITNUM_FRAMESIZE_DCS, true); // we don't want to add this bit if not using ECM frameSize = 64; } else @@ -814,25 +819,23 @@ Class1Modem::sendTraining(Class2Params& params, int tries, fxStr& emsg) case FCF_FTT: // failure to train, retry break; case FCF_DIS: // new capabilities, maybe - { FaxParams newDIS = frame.getDIS(); - if (newDIS != dis_caps) { - /* - dis_caps = newDIS; - params.setFromDIS(dis_caps); - * - * The above code was commented because to - * use the newDIS we need to do more work like - * sendClientCapabilitiesOK() - * sendSetupParams() - * So we ignore newDIS. - * It will work if old dis 'less' then newDIS. - */ - curcap = NULL; - if (useV34 && params.ec == EC_DISABLE) { - protoTrace("V.34-Fax session, but DIS signal contains no ECM bit; ECM forced."); - params.ec = EC_ENABLE256; + { + FaxParams newDIS = frame.getDIS(); + if (newDIS != dis_caps) { + /* + dis_caps = newDIS; + params.setFromDIS(dis_caps); + * + * The above code was commented because to + * use the newDIS we need to do more work like + * sendClientCapabilitiesOK() + * sendSetupParams() + * So we ignore newDIS. + * It will work if old dis 'less' then newDIS. + */ + checkReceiverDIS(params); + curcap = NULL; } - } } return (sendTraining(params, --tries, emsg)); default: @@ -854,8 +857,18 @@ Class1Modem::sendTraining(Class2Params& params, int tries, fxStr& emsg) goto done; } } else { - // delay to give other side time to reset - pause(conf.class1TrainingRecovery); + /* + * Historically we waited "Class1TrainingRecovery" (1500 ms) + * at this point to try to try to avoid retransmitting while + * the receiver is also transmitting. Sometimes it proved to be + * a faulty approach. Really what we're trying to do is to + * not be transmitting at the same time as the other end is. + * The best way to do that is to make sure that there is + * silence on the line, and we do that with Class1SwitchingCmd. + */ + if (useV34 || !switchingPause(emsg)) { + return (false); + } } } while (--t > 0); /* @@ -863,7 +876,18 @@ Class1Modem::sendTraining(Class2Params& params, int tries, fxStr& emsg) * the signalling rate to the next lower rate supported * by the local & remote sides and try again. */ - } while (!useV34 && dropToNextBR(params)); + if (!useV34) { + do { + /* + * We don't fallback to V.17 9600 or V.17 7200 because + * after V.17 14400 and V.17 12000 fail they're not likely + * to succeed, and some receivers will give up after three + * failed TCFs. + */ + again = dropToNextBR(params); + } while (again && (params.br == BR_9600 || params.br == BR_7200) && curcap->mod != V29); + } + } while (!useV34 && again); failed: emsg = "Failure to train remote modem at 2400 bps or minimum speed"; done: @@ -1082,8 +1106,7 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt setXONXOFF(FLOW_XONXOFF, FLOW_NONE, ACT_FLUSH); if (!useV34) { // T.30 5.3.2.4 (03/93) gives this to be a 75ms minimum - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - protoTrace("Failure to receive silence."); + if (!switchingPause(emsg)) { return (false); } /* @@ -1150,6 +1173,18 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt ATResponse r; while ((r = atResponse(rbuf, getDataTimeout())) == AT_OTHER); if (!(r == AT_OK)) { + if (r == AT_NOCARRIER) { + /* + * The NO CARRIER result here is not per-spec. However, + * some modems capable of detecting hangup conditions will + * use this to indicate a disconnection. Because we did + * not check for modem responses during the entire data + * transfer we flush the modem input so as to avoid reading + * any modem responses related to misinterpreted Phase C + * data that occurred after the hangup. + */ + flushModemInput(); + } return (false); } } @@ -1199,32 +1234,30 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt startTimeout(3000); sendFrame(FCF_PPS|FCF_SNDR, fxStr(pps, 4)); stopTimeout("sending PPS frame"); - tracePPM("SEND send", FCF_PPS); - tracePPM("SEND send", pps[0]); + traceFCF("SEND send", FCF_PPS); + traceFCF("SEND send", pps[0]); // Some receivers will almost always miss our first PPS message, and // in those cases waiting T2 for a response will cause the remote to // hang up. So, using T4 here is imperative so that our second PPS // message happens before the remote decides to hang up. As we're in // a "RESPONSE REC" operation, anyway, this is correct behavior. - if (gotppr = recvFrame(pprframe, FCF_SNDR, conf.t4Timer)) { - tracePPR("SEND recv", pprframe.getFCF()); + // + // We don't use CRP here, because it isn't well-received. + if (gotppr = recvFrame(pprframe, FCF_SNDR, conf.t4Timer, false, false)) { + traceFCF("SEND recv", pprframe.getFCF()); if (pprframe.getFCF() == FCF_CRP) { gotppr = false; crpcnt++; ppscnt = 0; - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } } } } while (!gotppr && (++ppscnt < 3) && (crpcnt < 3) && !(useV34 && gotEOT)); if (gotppr) { - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } if (pprframe.getFCF() == FCF_RNR) { @@ -1247,22 +1280,20 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt startTimeout(3000); sendFrame(FCF_RR|FCF_SNDR); stopTimeout("sending RR frame"); - tracePPM("SEND send", FCF_RR); + traceFCF("SEND send", FCF_RR); // T.30 states that we must wait no more than T4 between unanswered RR signals. - if (gotmsg = recvFrame(pprframe, FCF_SNDR, conf.t4Timer)) { - tracePPR("SEND recv", pprframe.getFCF()); + if (gotmsg = recvFrame(pprframe, FCF_SNDR, conf.t4Timer, false, false)) { // no CRP, stick to RR only + traceFCF("SEND recv", pprframe.getFCF()); if (pprframe.getFCF() == FCF_CRP) { gotmsg = false; crpcnt++; rrcnt = 0; - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } } } - } while (!gotmsg && (++rrcnt < 3) && (crpcnt < 3)); + } while (!gotmsg && (++rrcnt < 3) && (crpcnt < 3) && (useV34 || switchingPause(emsg))); if (!gotmsg) { emsg = "No response to RR repeated 3 times."; protoTrace(emsg); @@ -1274,9 +1305,7 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt gotppr = true; break; case FCF_RNR: - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } break; @@ -1344,7 +1373,7 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt blockgood = true; signalRcvd = FCF_MCF; } - if (!useV34 && curcap->mod == V17 && badframes == frameNumber) { + if (!useV34 && curcap->mod == V17 && badframes == frameNumber && FaxModem::getPageNumberOfCall() == 1) { // looks like a V.17 modulator incompatibility that managed to pass TCF // we set hasV17Trouble to help future calls to this destination protoTrace("The destination appears to have trouble with V.17."); @@ -1377,16 +1406,14 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt startTimeout(3000); sendFrame(FCF_CTC|FCF_SNDR, fxStr(ctc, 2)); stopTimeout("sending CTC frame"); - tracePPM("SEND send", FCF_CTC); + traceFCF("SEND send", FCF_CTC); if (gotctr = recvFrame(ctrframe, FCF_SNDR, conf.t4Timer)) { - tracePPR("SEND recv", ctrframe.getFCF()); + traceFCF("SEND recv", ctrframe.getFCF()); if (ctrframe.getFCF() == FCF_CRP) { gotctr = false; crpcnt++; ctccnt = 0; - if (!atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!switchingPause(emsg)) { return (false); } } @@ -1406,17 +1433,19 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt } else { /* * At this point data corruption is inevitable if all data - * frames have not been received correctly. This is tolerable - * with MH and MR data, as the effect of the corruption - * will be limited, but with MMR data all data following the - * corrupt frame will be meaningless: the page image will be - * truncated on the receiver's end. So if this is the case - * we disconnect, and hopefully we try again successfully. If - * this happens repeatedly to the same destination, then - * disabling MMR to this destination would be advisable. + * frames have not been received correctly. With MH and MR + * data formats this may be tolerable if the bad frames are + * few and not in an unfortunate sequence. With MMR data the + * receiving decoder should truncate the image at the point + * of the corruption. The effect of corruption in JBIG or JPEG + * data is quite unpredictable. So if all frames have not been + * received correctly and we're looking at an unacceptable + * imaging situation on the receiver's end, then we disconnect, + * and hopefully we try again successfully. */ - if (blockgood == false && params.df == DF_2DMMR) { - emsg = "Failure to transmit clean MMR image data."; + if (blockgood == false && (params.df >= DF_2DMMR || + (params.df <= DF_2DMR && badframes > frameNumber/2))) { + emsg = "Failure to transmit clean ECM image data."; protoTrace(emsg); return (false); } @@ -1429,17 +1458,15 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt startTimeout(3000); sendFrame(FCF_EOR|FCF_SNDR, fxStr(pps, 1)); stopTimeout("sending EOR frame"); - tracePPM("SEND send", FCF_EOR); - tracePPM("SEND send", pps[0]); + traceFCF("SEND send", FCF_EOR); + traceFCF("SEND send", pps[0]); if (goterr = recvFrame(errframe, FCF_SNDR, conf.t4Timer)) { - tracePPR("SEND recv", errframe.getFCF()); + traceFCF("SEND recv", errframe.getFCF()); if (errframe.getFCF() == FCF_CRP) { goterr = false; crpcnt++; eorcnt = 0; - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } } @@ -1471,22 +1498,20 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt startTimeout(3000); sendFrame(FCF_RR|FCF_SNDR); stopTimeout("sending RR frame"); - tracePPM("SEND send", FCF_RR); + traceFCF("SEND send", FCF_RR); // T.30 states that we must wait no more than T4 between unanswered RR signals. - if (gotmsg = recvFrame(errframe, FCF_SNDR, conf.t4Timer)) { - tracePPR("SEND recv", errframe.getFCF()); + if (gotmsg = recvFrame(errframe, FCF_SNDR, conf.t4Timer, false, false)) { // no CRP, stick to RR only + traceFCF("SEND recv", errframe.getFCF()); if (errframe.getFCF() == FCF_CRP) { gotmsg = false; crpcnt++; rrcnt = 0; - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } } } - } while (!gotmsg && (++rrcnt < 3) && (crpcnt < 3)); + } while (!gotmsg && (++rrcnt < 3) && (crpcnt < 3) && (useV34 || switchingPause(emsg))); if (!gotmsg) { emsg = "No response to RR repeated 3 times."; protoTrace(emsg); @@ -1497,9 +1522,7 @@ Class1Modem::blockFrame(const u_char* bitrev, bool lastframe, u_int ppmcmd, fxSt goterr = true; break; case FCF_RNR: - if (!useV34 && !atCmd(conf.class1SwitchingCmd, AT_OK)) { - emsg = "Failure to receive silence."; - protoTrace(emsg); + if (!useV34 && !switchingPause(emsg)) { return (false); } break; @@ -1560,8 +1583,11 @@ Class1Modem::sendClass1ECMData(const u_char* data, u_int cc, const u_char* bitre } ecmFrame[ecmFramePos++] = frameRev[data[i]]; if (ecmFramePos == (frameSize + 4)) { - if (!blockFrame(bitrev, ((i == (cc - 1)) && eod), ppmcmd, emsg)) + bool lastframe = ((i == (cc - 1)) && eod); + if (!blockFrame(bitrev, lastframe, ppmcmd, emsg)) return (false); + if (lastframe) + return (true); } } if (eod) { @@ -1800,8 +1826,12 @@ Class1Modem::sendPage(TIFF* tif, Class2Params& params, u_int pageChop, u_int ppm /* * After a page chop rowsperstrip is no longer valid, as the strip will - * be shorter. Therefore, convertPhaseCData (for the benefit of JBIG) and - * correctPhaseCData deliberately update rowsperstrip. + * be shorter. Therefore, convertPhaseCData (for the benefit of supporting + * the sending of JBIG NEWLEN markers) and correctPhaseCData (only in the + * case of MMR data) deliberately update rowsperstrip. Page-chopped and + * un-converted MH and MR data will not have updated rowsperstrip. + * However, this only amounts to a allocating more memory than is needed, + * and this is not consequential. */ if (conf.softRTFCC && params.df != newparams.df) { @@ -1948,6 +1978,18 @@ Class1Modem::sendPage(TIFF* tif, Class2Params& params, u_int pageChop, u_int ppm while ((r = atResponse(rbuf, getDataTimeout())) == AT_OTHER) ; rc = (r == AT_OK); + if (r == AT_NOCARRIER) { + /* + * The NO CARRIER result here is not per-spec. However, + * some modems capable of detecting hangup conditions will + * use this to indicate a disconnection. Because we did + * not check for modem responses during the entire data + * transfer we flush the modem input so as to avoid reading + * any modem responses related to misinterpreted Phase C + * data that occurred after the hangup. + */ + flushModemInput(); + } } if (flowControl == FLOW_XONXOFF) setXONXOFF(FLOW_NONE, FLOW_NONE, ACT_DRAIN); @@ -1966,8 +2008,9 @@ bool Class1Modem::sendPPM(u_int ppm, HDLCFrame& mcf, fxStr& emsg) { for (int t = 0; t < 3; t++) { - tracePPM("SEND send", ppm); - if (transmitFrame(ppm|FCF_SNDR) && recvFrame(mcf, FCF_SNDR, conf.t4Timer)) + traceFCF("SEND send", ppm); + // don't use CRP here because it isn't well-received + if (transmitFrame(ppm|FCF_SNDR) && recvFrame(mcf, FCF_SNDR, conf.t4Timer, false, false)) return (true); if (abortRequested()) return (false); @@ -1996,6 +2039,8 @@ Class1Modem::sendPPM(u_int ppm, HDLCFrame& mcf, fxStr& emsg) void Class1Modem::sendEnd() { + fxStr emsg; + if (!useV34) (void) switchingPause(emsg); transmitFrame(FCF_DCN|FCF_SNDR); // disconnect setInputBuffering(true); } diff --git a/faxd/Class2.c++ b/faxd/Class2.c++ index 18c419a..5c56383 100644 --- a/faxd/Class2.c++ +++ b/faxd/Class2.c++ @@ -32,6 +32,7 @@ Class2Modem::Class2Modem(FaxServer& s, const ModemConfig& c) : FaxModem(s,c) { hangupCode[0] = '\0'; serviceType = 0; // must be set in derived class + useExtendedDF = false; // T.32 Amendment 1 extension for data format is detectable } Class2Modem::~Class2Modem() @@ -51,7 +52,7 @@ Class2Modem::setupModem(bool isSend) return (false); // Query service support information fxStr s; - if (doQuery(conf.classQueryCmd, s, 500) && FaxModem::parseRange(s, modemServices)) + if (doQuery(conf.classQueryCmd, s, 5000) && FaxModem::parseRange(s, modemServices)) traceBits(modemServices & SERVICE_ALL, serviceNames); if ((modemServices & serviceType) == 0) return (false); @@ -373,11 +374,12 @@ Class2Modem::setupDCC() params.br = getBestSignallingRate(); params.wd = getBestPageWidth(); params.ln = getBestPageLength(); - params.df = getBestDataFormat(); + params.df = useExtendedDF ? modemParams.df : getBestDataFormat(); + params.df &= ~BIT(DF_JBIG); // let's not actually do JBIG yet params.ec = getBestECM(); params.bf = BF_DISABLE; params.st = getBestScanlineTime(); - return class2Cmd(dccCmd, params); + return class2Cmd(dccCmd, params, true); } /* @@ -410,18 +412,41 @@ Class2Modem::parseClass2Capabilities(const char* cap, Class2Params& params, bool params.br = fxmin(params.br, (u_int) BR_33600); params.wd = fxmin(params.wd, (u_int) WD_A3); params.ln = fxmin(params.ln, (u_int) LN_INF); - params.df = fxmin(params.df, (u_int) DF_2DMMR); - /* - * Table 21 T.32 does not match Table 2 T.30 very well in some aspects. - * Data format is one of those things. When dealing with DIS we use DF - * as a bitmap to suit T.30, but a T.32-following modem will only report - * one supported receiver format (and not all of them). Thus when - * parsing T.32 DIS we must convert the modem response to a bitmap. - * However, due to the inconguency between T.30 and T.32 the bitmap will - * only contain the the reported format and the required format. - */ - if (isDIS) { - params.df = BIT(params.df) | BIT(DF_1DMH); + if (useExtendedDF) { + /* + * The T.32-A1 DF extension presents us with a bitmap-like presentation + * similar to VR here... but leaves 2D-MMR = 3 for backwards-compatibility. + * + * 0 = 1D-MH, 1 = 2D-MR, 3 = 2D-MMR, 4 = JBIG-L0, 8 = JBIG + */ + u_int dfscan = params.df; + if (isDIS) { + params.df = BIT(DF_1DMH); + if (dfscan & 0x1) params.df |= BIT(DF_2DMR); + if (dfscan & 0x2) params.df |= BIT(DF_2DMMR); // don't require MR for MMR + if (dfscan & 0x4) params.df |= BIT(DF_JBIG); // JBIG L0 is JBIG to us + if (dfscan & 0x8) params.df |= BIT(DF_JBIG); + } else { + params.df = DF_1DMH; + if (dfscan & 0x3) params.df = DF_2DMMR; + else if (dfscan & 0x1) params.df = DF_2DMR; + else if (dfscan & 0x4) params.df = DF_JBIG; // JBIG L0 is JBIG to us + else if (dfscan & 0x8) params.df = DF_JBIG; + } + } else { + params.df = fxmin(params.df, (u_int) DF_2DMMR); + /* + * Table 21 T.32 does not match Table 2 T.30 very well in some aspects. + * Data format is one of those things. When dealing with DIS we use DF + * as a bitmap to suit T.30, but a T.32-following modem will only report + * one supported receiver format (and not all of them). Thus when + * parsing T.32 DIS we must convert the modem response to a bitmap. + * However, due to the inconguency between T.30 and T.32 the bitmap will + * only contain the the reported format and the required format. + */ + if (isDIS) { + params.df = BIT(params.df) | BIT(DF_1DMH); + } } if (params.ec > EC_ECLFULL) // unknown, disable use params.ec = EC_DISABLE; @@ -507,19 +532,22 @@ Class2Modem::stripQuotes(const char* cp) * Construct the Calling Station Identifier (CSI) string * for the modem. We permit any ASCII printable characters * in the string though the spec says otherwise. The max - * length is 20 characters (per the spec). + * length is 20 characters (per the spec), and we pad the + * string to that length to avoid buggy modems from putting + * garbage in the void. */ void Class2Modem::setLID(const fxStr& number) { lid.resize(0); - for (u_int i = 0, n = number.length(); i < n; i++) { - char c = number[i]; - if (isprint(c) || c == ' ') - lid.append(c); + for (u_int i = 0, n = number.length(); i < 20; i++) { + if (i < n) { + char c = number[i]; + if (isprint(c) || c == ' ') + lid.append(c); + } else + lid.append(' '); } - if (lid.length() > 20) - lid.resize(20); class2Cmd(lidCmd, lid); // for DynamicConfig } @@ -587,13 +615,13 @@ Class2Modem::class2Cmd(const fxStr& cmd, int a0, ATResponse r, long ms) * Send = and wait response. */ bool -Class2Modem::class2Cmd(const fxStr& cmd, const Class2Params& p, ATResponse r, long ms) +Class2Modem::class2Cmd(const fxStr& cmd, const Class2Params& p, bool isDCC, ATResponse r, long ms) { bool ecm20 = false; if (conf.class2ECMType == ClassModem::ECMTYPE_CLASS20 || (conf.class2ECMType == ClassModem::ECMTYPE_UNSET && serviceType != SERVICE_CLASS2)) ecm20 = true; - return atCmd(cmd | "=" | p.cmd(conf.class2UseHex, ecm20), r, ms); + return atCmd(cmd | "=" | p.cmd(conf.class2UseHex, ecm20, (isDCC && useExtendedDF)), r, ms); } /* @@ -624,7 +652,24 @@ Class2Modem::parseRange(const char* cp, Class2Params& p) p.br &= BR_ALL; p.wd &= WD_ALL; p.ln &= LN_ALL; - p.df &= DF_ALL; + if ((p.df & 0x10) && (p.df & 0x100)) { // supports JBIG via T.32-A1 extension + /* + * Old T.32 Table 21 does not provide for JBIG data formats. + * In amendment 1 the ITU has extended the DF parameter to include JBIG by + * assigning "4" to JBIG L0 and "8" to JBIG. The +FCC response for DF may + * look like (00-0F) or possibly even (00-01,03-05,07-09,0B-0D,0F). However, + * in so doing the ITU has also changed the DF parameter (in +FIS and +FCS + * responses) into a bitmap. Yet, an +FCC response for DF of "(5DDD)" or "(0F)" + * is not backwards-compatible with older T.32. So the +FCC response is in + * this backwards-compatible presentation (00-0F), and +FCS and +FIS responses + * are not. + */ + useExtendedDF = true; + // No Class 2 modem is known to actually *work* with JBIG yet. + //p.df = BIT(DF_1DMH) | BIT(DF_2DMR) | BIT(DF_2DMMR) | BIT(DF_JBIG); + p.df = BIT(DF_1DMH) | BIT(DF_2DMR) | BIT(DF_2DMMR); + } else + p.df &= DF_ALL; p.ec &= EC_ALL; p.bf &= BF_ALL; p.st &= ST_ALL; @@ -793,7 +838,7 @@ Class2Modem::tracePPM(const char* dir, u_int ppm) FCF_PRI_EOP, 0 }; - FaxModem::tracePPM(dir, ppm2fcf[ppm&7]); + FaxModem::traceFCF(dir, ppm2fcf[ppm&7]); } void @@ -809,5 +854,5 @@ Class2Modem::tracePPR(const char* dir, u_int ppr) 0, 0 }; - FaxModem::tracePPR(dir, ppr2fcf[ppr&7]); + FaxModem::traceFCF(dir, ppr2fcf[ppr&7]); } diff --git a/faxd/Class2.h b/faxd/Class2.h index 9caa68f..0d55a0e 100644 --- a/faxd/Class2.h +++ b/faxd/Class2.h @@ -66,6 +66,7 @@ protected: bool xmitWaitForXON; // if true, wait for XON when sending bool hostDidCQ; // if true, copy quality done on host bool hasPolling; // if true, modem does polled recv + bool useExtendedDF; // if true, modem has Agere data format extension char recvDataTrigger; // char to send to start recv'ing data char hangupCode[4]; // hangup reason (from modem) bool hadHangup; // true if +FHNG:/+FHS: received @@ -124,7 +125,7 @@ protected: ATResponse = AT_OK, long ms = 30*1000); bool class2Cmd(const fxStr& cmd, const fxStr& a0, ATResponse = AT_OK, long ms = 30*1000); - bool class2Cmd(const fxStr& cmd, const Class2Params&, + bool class2Cmd(const fxStr& cmd, const Class2Params&, bool isDCC, ATResponse =AT_OK, long ms = 30*1000); // parsing routines for capability¶meter strings bool parseClass2Capabilities(const char* cap, Class2Params&, bool isDIS); diff --git a/faxd/Class2Recv.c++ b/faxd/Class2Recv.c++ index a0d2979..460dca6 100644 --- a/faxd/Class2Recv.c++ +++ b/faxd/Class2Recv.c++ @@ -343,15 +343,20 @@ Class2Modem::recvPPM(TIFF* tif, int& ppr) bool Class2Modem::parseFPTS(TIFF* tif, const char* cp, int& ppr) { - uint32 lc = 0; + u_long lc = 0; int blc = 0; int cblc = 0; ppr = 0; - if (sscanf(cp, "%d,%d,%d,%d", &ppr, &lc, &blc, &cblc) > 0) { + if (sscanf(cp, "%d,%ld,%d,%d", &ppr, &lc, &blc, &cblc) > 0) { /* * In practice we cannot trust the modem line count when we're - * not using ECM due to transmission errors. + * not using ECM due to transmission errors and also due to + * bugs in the modems' own decoders (like MMR). + * + * Furthermore, there exists a discrepancy between many modem's + * behaviors and the specification. Some give lc in hex and + * others in decimal, and so this would further complicate things. */ if (!conf.class2UseLineCount) { lc = getRecvEOLCount(); @@ -381,8 +386,7 @@ Class2Modem::recvEnd(fxStr&) { if (!hadHangup) { if (isNormalHangup()) { - if (atCmd("AT+FDR", AT_NOTHING)) // wait for DCN - (void) atResponse(rbuf, conf.t1Timer); + (void) atCmd("AT+FDR", AT_FHNG); // wait for DCN } else (void) atCmd(abortCmd); // abort session } diff --git a/faxd/Class2Send.c++ b/faxd/Class2Send.c++ index b5f3033..81be8b6 100644 --- a/faxd/Class2Send.c++ +++ b/faxd/Class2Send.c++ @@ -60,7 +60,7 @@ Class2Modem::sendSetup(FaxRequest& req, const Class2Params& dis, fxStr& emsg) return (false); } if (conf.class2DDISCmd != "") { - if (!class2Cmd(conf.class2DDISCmd, dis)) { + if (!class2Cmd(conf.class2DDISCmd, dis, false)) { emsg = fxStr::format("Unable to setup session parameters " "prior to call%s", cmdFailed); return (false); @@ -258,7 +258,7 @@ Class2Modem::sendPhaseB(TIFF* tif, Class2Params& next, FaxMachineInfo& info, * current T.30 session parameters. */ if (pageInfoChanged(params, next)) { - if (!class2Cmd(disCmd, next)) { + if (!class2Cmd(disCmd, next, false)) { emsg = "Unable to set session parameters"; break; } @@ -518,6 +518,8 @@ Class2Modem::sendPageData(TIFF* tif, u_int pageChop) bool Class2Modem::sendRTC(Class2Params params) { + if (params.df == DF_JBIG) return (true); // nothing to do + // determine the number of trailing zeros on the last byte of data u_short zeros = 0; for (short i = 7; i >= 0; i--) { diff --git a/faxd/ClassModem.c++ b/faxd/ClassModem.c++ index 5d9520e..4cec899 100644 --- a/faxd/ClassModem.c++ +++ b/faxd/ClassModem.c++ @@ -127,13 +127,35 @@ ClassModem::dataService() } CallStatus -ClassModem::dial(const char* number, fxStr& emsg) +ClassModem::dial(const char* number, const char* origin, fxStr& emsg) { dialedNumber = fxStr(number); protoTrace("DIAL %s", number); - fxStr buf = fxStr::format((const char*) conf.dialCmd, number); + fxStr dialcmd = conf.dialCmd; + u_int destpos = dialcmd.find(0, "%s"); + u_int origpos = dialcmd.find(0, "%d"); + if (destpos == dialcmd.length() && origpos == dialcmd.length()) { + // neither %d nor %s appear in the cmd, use dialcmd as-is + } else if (origpos == dialcmd.length()) { + // just %s appears in the cmd + dialcmd = fxStr::format((const char*) dialcmd, number); + } else if (destpos == dialcmd.length()) { + // just %d appears in the cmd + dialcmd[origpos+1] = 's'; // change %d to %s + dialcmd = fxStr::format((const char*) dialcmd, origin); + } else { + // both %d and %s appear in the cmd + dialcmd[origpos+1] = 's'; // change %d to %s + if (origpos < destpos) { + // %d appears before %s + dialcmd = fxStr::format((const char*) dialcmd, origin, number); + } else { + // %s appears before %d + dialcmd = fxStr::format((const char*) dialcmd, number, origin); + } + } emsg = ""; - CallStatus cs = (atCmd(buf, AT_NOTHING) ? dialResponse(emsg) : FAILURE); + CallStatus cs = (atCmd(dialcmd, AT_NOTHING) ? dialResponse(emsg) : FAILURE); if (cs != OK && emsg == "") { emsg = callStatus[cs]; } @@ -274,7 +296,7 @@ ClassModem::answerCall(AnswerType atype, fxStr& emsg, const char* number) case ANSTYPE_VOICE: answerCmd = conf.answerVoiceCmd; break; case ANSTYPE_DIAL: answerCmd = conf.answerDialCmd; - dial(number, emsg); // no error-checking + dial(number, NULL, emsg); // no error-checking break; } if (answerCmd == "") @@ -773,6 +795,10 @@ ClassModem::atResponse(char* buf, long ms) if (strneq(buf, "CONNECT", 7)) lastResponse = AT_CONNECT; break; + case 'D': + if (strneq(buf, "DTMF", 4)) + lastResponse = AT_DTMF; + break; case 'E': if (strneq(buf, "ERROR", 5)) lastResponse = AT_ERROR; @@ -936,13 +962,45 @@ ClassModem::atCmd(const fxStr& cmd, ATResponse r, long ms) resp = (u_char) cmd[++i]; if (resp != AT_NOTHING) { // XXX check return? - (void) waitFor(resp, ms); // XXX ms + // The timeout setting here (60*1000) used to be "ms" (which + // defaults to 30 s), but we find that's too short, especially + // for long-running escape sequences. It really needs to be + // escape-configurable, but for now we just make it 60 s. + (void) waitFor(resp, 60*1000); respPending = false; } break; case ESC_FLUSH: // flush input flushModemInput(); break; + case ESC_PLAY: + { + fxStr filename = fxStr("etc/play"); + filename.append(cmd[++i]); + filename.append(".raw"); + protoTrace("Playing file \"%s\".", (const char*) filename); + int fd = open((const char*) filename, O_RDONLY); + if (fd > 0) { + u_char buf[1024]; + int len, pos; + do { + pos = 0; + do { + len = read(fd, &buf[pos], 1); + if (buf[pos] == 0x10) buf[++pos] = 0x10; + pos++; + } while (len > 0 && (u_int) pos < sizeof(buf) - 1); + putModem(&buf[0], pos, getDataTimeout()); + } while (len > 0); + close(fd); + } else { + protoTrace("Unable to open file \"%s\" for reading.", (const char*) filename); + } + u_char buf[2]; + buf[0] = DLE; buf[1] = ETX; + putModem(buf, 2, getDataTimeout()); + } + break; } } while (++i < cmdlen && isEscape(cmd[i])); pos = i; // next segment starts here @@ -1278,7 +1336,7 @@ ClassModem::parseRange(const char* cp, u_int& a0) void ClassModem::setSpeakerVolume(SpeakerVolume l) { - atCmd(conf.setVolumeCmd[l]); + atCmd(conf.setVolumeCmd[l], AT_OK, 5000); } void @@ -1297,6 +1355,7 @@ ClassModem::waitForRings(u_short rings, CallType& type, CallID& callid) time_t start = Sys::now(); do { switch (atResponse(rbuf, conf.ringTimeout)) { + case AT_DTMF: case AT_OTHER: // check distinctive ring if (streq(conf.ringData, rbuf)) type = CALLTYPE_DATA; @@ -1422,3 +1481,6 @@ CallType ClassModem::findCallType(int vec[]) return CALLTYPE_UNKNOWN; } +bool ClassModem::doCallIDDisplay(int i) const { return conf.idConfig[i].display; } +bool ClassModem::doCallIDRecord(int i) const { return conf.idConfig[i].record; } +const fxStr& ClassModem::getCallIDLabel(int i) const { return conf.idConfig[i].label; } diff --git a/faxd/ClassModem.h b/faxd/ClassModem.h index 69f554e..df74b89 100644 --- a/faxd/ClassModem.h +++ b/faxd/ClassModem.h @@ -68,6 +68,7 @@ typedef struct { #define ESC_DELAY (0x80|0x04) // delay period of time #define ESC_WAITFOR (0x80|0x08) // wait for modem response #define ESC_FLUSH (0x80|0x10) // flush input queue +#define ESC_PLAY (0x80|0x20) // play (voice) file #ifdef OFF #undef OFF // workaround for SCO @@ -177,7 +178,8 @@ public: AT_DLEETX = 13, // dle/etx characters AT_DLEEOT = 14, // dle+eot characters (end of transmission) AT_XON = 15, // xon character - AT_OTHER = 16 // unknown response (not one of above) + AT_DTMF = 16, // DTMF detection + AT_OTHER = 17 // unknown response (not one of above) }; private: ModemServer& server; // server for getting to device @@ -244,6 +246,9 @@ public: const fxStr& getRevision() const; fxStr getCapabilities() const; u_int getModemServices() const; + bool doCallIDDisplay(int i) const; + bool doCallIDRecord(int i) const; + const fxStr& getCallIDLabel(int i) const; // data transfer timeout controls void setDataTimeout(long secs, u_int br); long getDataTimeout() const; @@ -298,13 +303,13 @@ public: /* * Send support: * - * if (dial(number, params, emsg) == OK) { + * if (dial(number, origin, emsg) == OK) { * ...do stuff... * } * hangup(); */ virtual bool dataService(); - virtual CallStatus dial(const char* number, fxStr& emsg); + virtual CallStatus dial(const char* number, const char* origin, fxStr& emsg); /* * Receive support: diff --git a/faxd/CopyQuality.c++ b/faxd/CopyQuality.c++ index 71a518b..20948ff 100644 --- a/faxd/CopyQuality.c++ +++ b/faxd/CopyQuality.c++ @@ -103,6 +103,7 @@ FaxModem::resetLineCounts() recvEOLCount = 0; // count of EOL codes recvBadLineCount = 0; // rows with a decoding error recvConsecutiveBadLineCount = 0; // max consecutive bad rows + linesWereA4Width = 0; // rows that measured 1728 pels } void @@ -229,7 +230,7 @@ FaxModem::recvPageDLEData(TIFF* tif, bool checkQuality, * is done instead of replacing the missing data with white * to avoid visual disconnect of blackened areas. */ - if (decodedPixels < rowpixels) { + if ((u_int) decodedPixels < rowpixels) { u_int filledchars = (decodedPixels + 7) / 8; u_short rembits = decodedPixels % 8; memcpy(recvRow + filledchars, curGood + filledchars, rowSize - filledchars); @@ -237,12 +238,12 @@ FaxModem::recvPageDLEData(TIFF* tif, bool checkQuality, // now deal with the transitional character u_char remmask = 0; for (u_short bit = 0; bit < 8; bit++) { - remmask<<1; + remmask<<=1; if (bit < rembits) remmask |= 1; } recvRow[filledchars-1] = (recvRow[filledchars-1] & remmask) | (curGood[filledchars-1] & ~remmask); } - } else if (decodedPixels >= rowpixels) { + } else if ((u_int) decodedPixels >= rowpixels) { /* * If we get a long pixel count, then our correction mechanism * involves trimming horizontal "streaks" at the end of the @@ -261,9 +262,22 @@ FaxModem::recvPageDLEData(TIFF* tif, bool checkQuality, } } } - recvBadLineCount++; - cblc++; - lastRowBad = true; + /* + * Some senders signal the wrong page width in DCS, such as A3, + * and then proceed to deliver A4 page image data. The copy + * quality correction mechanism will allow the image data to get + * through, with white space on the right, but it's better to + * avoid sending RTN, as it will only cause more of the same or + * will cause the sender to disconnect. So if the we seem to + * be getting A4 page data when something else was negotiated + * we don't count those lines as bad. + */ + linesWereA4Width += decodedPixels == 1728 ? 1 : 0; + if (decodedPixels != 1728 || linesWereA4Width < ((recvEOLCount + 1) * 95 / 100)) { + recvBadLineCount++; + cblc++; + lastRowBad = true; + } } if (decodedPixels) memcpy(curGood, recvRow, (size_t) rowSize); /* @@ -388,12 +402,12 @@ FaxModem::recvSetupTIFF(TIFF* tif, long, int fillOrder, const fxStr& id) TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE); TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32) params.pageWidth()); if (params.df == DF_JPEG_COLOR || params.df == DF_JPEG_GREY) { + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); #ifdef PHOTOMETRIC_ITULAB + /* If PHOTOMETRIC_ITULAB is not available the admin cannot enable color fax anyway. + This is done so that older libtiffs without it can build fine. */ TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_ITULAB); -#else - printf("Attempt to save JPEG Grey/Colour data without PHOTOMETRIC_ITULAB support. This should not happen.\n"); #endif - TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); // libtiff requires IMAGELENGTH to be set before SAMPLESPERPIXEL, // or StripOffsets and StripByteCounts will have SAMPLESPERPIXEL values @@ -545,7 +559,7 @@ FaxModem::writeECMData(TIFF* tif, u_char* buf, u_int cc, const Class2Params& par } // write the line count to the pipe Sys::write(counterFd[1], (const char*) &recvEOLCount, sizeof(recvEOLCount)); - exit(0); + _exit(0); default: // parent Sys::close(decoderFd[0]); Sys::close(counterFd[1]); @@ -661,9 +675,9 @@ FaxModem::writeECMData(TIFF* tif, u_char* buf, u_int cc, const Class2Params& par sdnormcount = 0; } u_long clength = 256*256*256*buf[i+2]+256*256*buf[i+3]+256*buf[i+4]+buf[i+5]; - if (clength > 256) clength = 256; // keep it sane + if (clength >= cc - i - 5) clength = 0; // bogus comment fxStr comment = ""; - for (u_long cpos = 0; i+6+cpos < cc && cpos < clength; cpos++) { + for (u_long cpos = 0; i+6+cpos < cc && cpos < clength && cpos < 256; cpos++) { if (!isprint(buf[i+6+cpos])) break; // only printables comment.append(buf[i+6+cpos]); } @@ -730,20 +744,107 @@ FaxModem::writeECMData(TIFF* tif, u_char* buf, u_int cc, const Class2Params& par { u_long framelength = 0; u_long framewidth = 0; + u_long fsize = 0; for (u_int i = 0; i < cc-2; i++) { - if (buf[i] == 0xFF && buf[i+1] == 0xC0) { + if (buf[i] == 0xFF && buf[i+1] >= 0xC0 && buf[i+1] <= 0xCF && + buf[i+1] != 0xC4 && buf[i+1] != 0xC8 && buf[i+1] != 0xCC) { + u_short type = buf[i+1] - 0xC0; + fsize = 256*buf[i+2]; + fsize += buf[i+3]; framelength = 256*buf[i+5]; framelength += buf[i+6]; framewidth = 256*buf[i+7]; framewidth += buf[i+8]; - copyQualityTrace("Found Start of Frame (SOF) Marker, size: %lu x %lu", framewidth, framelength); + copyQualityTrace("Found Start of Frame (SOF%u) Marker, size: %lu x %lu", type, framewidth, framelength); if (framelength < 65535 && framelength > recvEOLCount) recvEOLCount = framelength; - } - if (buf[i] == 0xFF && buf[i+1] == 0xDC) { + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0xC8) { + copyQualityTrace("Found JPEG Extensions (JPG) Marker"); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] == 0xC4) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + copyQualityTrace("Found Define Huffman Tables (DHT) Marker"); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0xCC) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + copyQualityTrace("Found Define Arithmatic Coding Conditionings (DAC) Marker"); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] >= 0xD0 && buf[i+1] <= 0xD7) { + copyQualityTrace("Found Restart (RST) Marker"); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] == 0xD8) { + copyQualityTrace("Found Start of Image (SOI) Marker"); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] == 0xD9) { + copyQualityTrace("Found End of Image (EOI) Marker"); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] == 0xDA) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + copyQualityTrace("Found Start of Scan (SOS) Marker"); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0xDB) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + copyQualityTrace("Found Define Quantization Tables (DQT) Marker"); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0xDC) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; framelength = 256*buf[i+4]; framelength += buf[i+5]; copyQualityTrace("Found Define Number of Lines (DNL) Marker, lines: %lu", framelength); if (framelength < 65535) recvEOLCount = framelength; + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0xDD) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + copyQualityTrace("Found Define Restart Interval (DRI) Marker"); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0xDE) { + copyQualityTrace("Found Define Hierarchial Progression (DHP) Marker"); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] == 0xDF) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + copyQualityTrace("Found Expand Reference Components (EXP) Marker"); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] >= 0xE0 && buf[i+1] <= 0xEF) { + u_short type = buf[i+1] - 0xE0; + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + fxStr comment = ""; + for (u_long cpos = 0; i+4+cpos < cc && cpos < fsize && cpos < 256; cpos++) { + if (!isprint(buf[i+4+cpos])) break; // only printables + comment.append(buf[i+4+cpos]); + } + copyQualityTrace("Found Application Segment (APP%u) Marker \"%s\"", type, (const char*) comment); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] >= 0xF0 && buf[i+1] <= 0xFD) { + u_short type = buf[i+1] - 0xF0; + copyQualityTrace("Found JPEG Extension (JPG%u) Marker", type); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] == 0xFE) { + fsize = 256*buf[i+2]; + fsize += buf[i+3]; + fxStr comment = ""; + for (u_long cpos = 0; i+4+cpos < cc && cpos < fsize && cpos < 256; cpos++) { + if (!isprint(buf[i+4+cpos])) break; // only printables + comment.append(buf[i+4+cpos]); + } + copyQualityTrace("Found Comment (COM) Marker \"%s\"", (const char*) comment); + i += (fsize + 1); + } else if (buf[i] == 0xFF && buf[i+1] == 0x01) { + copyQualityTrace("Found Temporary Private Use (TEM) Marker"); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] >= 0x02 && buf[i+1] <= 0xBF) { + copyQualityTrace("Found Reserved (RES) Marker 0x%X", buf[i+1]); + i += 1; + } else if (buf[i] == 0xFF && buf[i+1] != 0x00 && buf[i+1] != 0xFF) { + copyQualityTrace("Found Unknown Marker 0x%X", buf[i+1]); + i += 1; } } } diff --git a/faxd/DestInfo.h b/faxd/DestInfo.h index f6fe477..bf4c238 100644 --- a/faxd/DestInfo.h +++ b/faxd/DestInfo.h @@ -60,6 +60,7 @@ public: ~DestInfo(); u_int getActive() const; // return count of active jobs + u_int getBlocked() const; // return count of blocked jobs u_int getCount() const; // return count of active+blocked jobs bool isEmpty() const; // true if any jobs referenced u_int getCalls() const; // return count of active calls @@ -79,6 +80,7 @@ public: }; inline u_int DestInfo::getActive() const { return activeCount; } +inline u_int DestInfo::getBlocked() const { return blockedCount; } inline u_int DestInfo::getCount() const { return activeCount + blockedCount; } inline bool DestInfo::isEmpty() const { return getCount() == 0; } diff --git a/faxd/FaxAcctInfo.c++ b/faxd/FaxAcctInfo.c++ index c958ea5..13af829 100644 --- a/faxd/FaxAcctInfo.c++ +++ b/faxd/FaxAcctInfo.c++ @@ -40,28 +40,43 @@ FaxAcctInfo::record(const char* cmd) { bool ok = false; int fd = Sys::open(FAX_XFERLOG, O_RDWR|O_CREAT|O_APPEND, 0644); + + char timebuf[80]; + strftime(timebuf, sizeof (timebuf), "%D %H:%M", localtime(&start)); + + char jobtagbuf[80]; + u_int i = 0; + char c; + for (const char* cp = jobtag; (c = *cp); cp++) { + if (i == sizeof (jobtagbuf)-2) // truncate string + break; + if (c == '\t') // tabs are field delimiters + c = ' '; + else if (c == '"') // escape quote marks + jobtagbuf[i++] = '\\'; + jobtagbuf[i++] = c; + } + jobtagbuf[i] = '\0'; + + fxStr paramsbuf = fxStr::format("%u", params); + fxStr npagesbuf = fxStr::format("%d", npages); + fxStr durationbuf = fxStr::format("%s", fmtTime(duration)); + fxStr conntimebuf = fxStr::format("%s", fmtTime(conntime)); + + fxStr callid_formatted = ""; + for (i = 2; i < callid.size(); i++) { + if (i > 2) callid_formatted.append("::"); + callid_formatted.append(callid[i]); + } + if (fd >= 0) { fxStackBuffer record; - char buf[80]; - strftime(buf, sizeof (buf), "%D %H:%M", localtime(&start)); - record.put(buf); // $ 1 = time + record.put(timebuf); // $ 1 = time record.fput("\t%s", cmd); // $ 2 = SEND|RECV|POLL|PAGE record.fput("\t%s", commid); // $ 3 = commid record.fput("\t%s", device); // $ 4 = device record.fput("\t%s", jobid); // $ 5 = jobid - u_int i = 0; - char c; - for (const char* cp = jobtag; (c = *cp); cp++) { - if (i == sizeof (buf)-2) // truncate string - break; - if (c == '\t') // tabs are field delimiters - c = ' '; - else if (c == '"') // escape quote marks - buf[i++] = '\\'; - buf[i++] = c; - } - buf[i] = '\0'; - record.fput("\t\"%s\"", buf); // $ 6 = jobtag + record.fput("\t\"%s\"", jobtagbuf); // $ 6 = jobtag record.fput("\t%s", user); // $ 7 = sender record.fput("\t\"%s\"", dest); // $ 8 = dest record.fput("\t\"%s\"", csi); // $ 9 = csi @@ -72,18 +87,54 @@ FaxAcctInfo::record(const char* cmd) record.fput("\t\"%s\"", status); // $14 = status record.fput("\t\"%s\"", callid.size() > CallID::NAME ? (const char*) callid[1] : ""); // $15 = CallID2/CIDName record.fput("\t\"%s\"", callid.size() > CallID::NUMBER ? (const char*) callid[0] : ""); // $16 = CallID1/CIDNumber - fxStr callid_formatted = ""; - for (i = 2; i < callid.size(); i++) { - if (i > 2) callid_formatted.append("::"); - callid_formatted.append(callid[i]); - } record.fput("\t\"%s\"", (const char*) callid_formatted); // $17 = CallID3 -> CallIDn record.fput("\t\"%s\"", owner); // $18 = owner record.fput("\t\"%s\"", (const char*) faxdcs); // $19 = DCS + record.fput("\t%s", (const char*) jobinfo); // $20 = jobinfo record.put('\n'); flock(fd, LOCK_EX); ok = (Sys::write(fd, record, record.getLength()) == (ssize_t)record.getLength()); Sys::close(fd); // implicit unlock } + + /* + * Here we provide a hook for an external accounting + * facility, such as a database. + */ + const char* argv[21]; + argv[0] = "FaxAccounting"; + argv[1] = timebuf; + argv[2] = cmd; + argv[3] = commid; + argv[4] = device; + argv[5] = jobid; + argv[6] = jobtagbuf; + argv[7] = user; + argv[8] = dest; + argv[9] = csi; + argv[10] = (const char*) paramsbuf; + argv[11] = (const char*) npagesbuf; + argv[12] = (const char*) durationbuf; + argv[13] = (const char*) conntimebuf; + argv[14] = status; + argv[15] = callid.size() > CallID::NAME ? (const char*) callid[1] : ""; + argv[16] = callid.size() > CallID::NUMBER ? (const char*) callid[0] : ""; + argv[17] = (const char*) callid_formatted; + argv[18] = owner; + argv[19] = (const char*) faxdcs; + argv[20] = (const char*) jobinfo; + argv[21] = NULL; + pid_t pid = fork(); // signal handling in some apps seems to require a fork here + switch (pid) { + case 0: + Sys::execv("etc/FaxAccounting", (char* const*) argv); + sleep(1); // XXX give parent time + _exit(127); + case -1: + break; + default: + Sys::waitpid(pid); + break; + } return (ok); } diff --git a/faxd/FaxAcctInfo.h b/faxd/FaxAcctInfo.h index 0732a3a..dc3b250 100644 --- a/faxd/FaxAcctInfo.h +++ b/faxd/FaxAcctInfo.h @@ -47,6 +47,7 @@ struct FaxAcctInfo { CallID callid; // call identification const char* owner; // job owner (uid) fxStr faxdcs; // negotiated DCS parameters + fxStr jobinfo; // totpages, ntries, ndials, totdials, maxdials, tottries, maxtries bool record(const char* cmd); }; diff --git a/faxd/FaxMachineInfo.c++ b/faxd/FaxMachineInfo.c++ index b918520..a9f5ce6 100644 --- a/faxd/FaxMachineInfo.c++ +++ b/faxd/FaxMachineInfo.c++ @@ -344,7 +344,7 @@ FaxMachineInfo::writeConfig() Sys::close(fd); return; } - ftruncate(fd, cc); + (void) ftruncate(fd, cc); Sys::close(fd); } else error("open: %m"); diff --git a/faxd/FaxMachineLog.c++ b/faxd/FaxMachineLog.c++ index dc28cc5..9d8f3c9 100644 --- a/faxd/FaxMachineLog.c++ +++ b/faxd/FaxMachineLog.c++ @@ -39,13 +39,9 @@ extern void logError(const char* fmt ...); FaxMachineLog::FaxMachineLog(int f, const fxStr& number, const fxStr& commid) { - fxStr canon(number); - for (int i = canon.length()-1; i >= 0; i--) - if (!isdigit(canon[i])) - canon.remove(i,1); fd = f; pid = getpid(); - log("SESSION BEGIN %s %s", (const char*) commid, (const char*) canon); + log("SESSION BEGIN %s %s", (const char*) commid, (const char*) number); log("%s", HYLAFAX_VERSION); } diff --git a/faxd/FaxModem.c++ b/faxd/FaxModem.c++ index b4b8029..642f233 100644 --- a/faxd/FaxModem.c++ +++ b/faxd/FaxModem.c++ @@ -73,6 +73,32 @@ FaxModem::sendSetup(FaxRequest& req, const Class2Params&, fxStr&) else setupTagLine(req, conf.tagLineFmt); curreq = &req; + if (conf.setOriginCmd != "") { + fxStr origincmd = conf.setOriginCmd; + u_int numpos = origincmd.find(0, "%d"); + u_int nampos = origincmd.find(0, "%s"); + if (numpos == origincmd.length() && nampos == origincmd.length()) { + // neither %d nor %s appear in the cmd + if (!atCmd(origincmd)) return (false); + } else if (numpos == origincmd.length()) { + // just %s appears in the cmd + if (!atCmd(fxStr::format((const char*) origincmd, (const char*) req.faxname))) return (false); + } else if (nampos == origincmd.length()) { + // just %d appears in the cmd + origincmd[numpos+1] = 's'; // change %d to %s + if (!atCmd(fxStr::format((const char*) origincmd, (const char*) req.faxnumber))) return (false); + } else { + // both %d and %s appear in the cmd + origincmd[numpos+1] = 's'; // change %d to %s + if (numpos < nampos) { + // %d appears before %s + if (!atCmd(fxStr::format((const char*) origincmd, (const char*) req.faxnumber, (const char*) req.faxname))) return (false); + } else { + // %s appears before %d + if (!atCmd(fxStr::format((const char*) origincmd, (const char*) req.faxname, (const char*) req.faxnumber))) return (false); + } + } + } return (true); } /* @@ -395,7 +421,7 @@ FaxModem::getBestPageLength() const u_int FaxModem::getBestDataFormat() const { - return bestBit(modemParams.df, DF_2DMMR, DF_1DMH); + return bestBit(modemParams.df, DF_JBIG, DF_1DMH); } /* @@ -580,73 +606,93 @@ FaxModem::traceModemParams() } void -FaxModem::tracePPM(const char* dir, u_int ppm) +FaxModem::traceFCF(const char* dir, u_int fcf) { - if ((ppm & 0x7F) == FCF_DCS) { - protoTrace("%s DCS (command signal)", dir); - return; - } - if ((ppm & 0x7F) == FCF_TSI) { - protoTrace("%s TSI (sender id)", dir); - return; - } - if ((ppm & 0x7F) == FCF_CRP) { - protoTrace("%s CRP (command repeat)", dir); - return; - } - static const char* ppmNames[16] = { - "NULL (more blocks, same page)", // PPS-NULL - "EOM (more documents)", // FCF_EOM - "MPS (more pages, same document)", // FCF_MPS - "EOR (end of retransmission)", // FCF_EOR - "EOP (no more pages or documents)", // FCF_EOP - "unknown PPM 0x05", - "RR (receive ready)", // FCF_RR - "unknown PPM 0x07", - "CTC (continue to correct)", // FCF_CTC - "PRI-EOM (more documents after interrupt)", // FCF_PRI_EOM - "PRI-MPS (more pages after interrupt)", // FCF_PRI_MPS - "unknown PPM 0x0B", - "PRI-EOP (no more pages after interrupt)", // FCF_PRI_EOP - "PPS (partial page signal)", // FCF_PPS - "unknown PPM 0x0E", - "DCN (disconnect)", // FCF_DCN - }; - protoTrace("%s %s", dir, ppmNames[ppm&0xf]); -} - -void -FaxModem::tracePPR(const char* dir, u_int ppr) -{ - if ((ppr & 0x7f) == 0x58) // avoid CRP-ERR collision - protoTrace("%s %s", dir, "CRP (command repeat)"); - else if ((ppr & 0x7f) == 0x23) // avoid CTR-RTP collision - protoTrace("%s %s", dir, "CTR (confirm continue to correct)"); - else if ((ppr & 0x7f) == FCF_CFR) // avoid CFR-MPS collision - protoTrace("%s %s", dir, "CFR (confirmation to receive)"); - else if ((ppr & 0x7f) == FCF_NSF) // avoid NSF-PIN collision - protoTrace("%s %s", dir, "NSF (non-standard facilities)"); - else { - static const char* pprNames[16] = { - "unknown PPR 0x00", - "MCF (message confirmation)", // FCF_MCF/PPR_MCF - "RTN (retrain negative)", // FCF_RTN/PPR_RTN - "RTP (retrain positive)", // FCF_RTP/PPR_RTP - "PIN (procedural interrupt negative)", // FCF_PIN/PPR_PIN - "PIP (procedural interrupt positive)", // FCF_PIP/PPR_PIP - "unknown PPR 0x06", - "RNR (receive not ready)", // FCF_RNR - "ERR (confirm end of retransmission)", // FCF_ERR - "unknown PPR 0x09", - "unknown PPR 0x0A", - "unknown PPR 0x0B", - "unknown PPR 0x0C", - "PPR (partial page request)", // FCF_PPR - "unknown PPR 0x0E", - "DCN (disconnect)", // FCF_DCN - }; - protoTrace("%s %s", dir, pprNames[ppr&0xf]); + char* fcfname; + switch (fcf & 0x7F) { + case 0x00: + fcfname = "NULL (more blocks, same page)"; + break; + case FCF_DCS: + fcfname = "DCS (command signal)"; + break; + case FCF_TSI: + fcfname = "TSI (sender id)"; + break; + case FCF_CFR: + fcfname = "CFR (confirmation to receive)"; + break; + case FCF_CRP: + fcfname = "CRP (command repeat)"; + break; + case FCF_EOM: + fcfname = "EOM (more documents)"; + break; + case FCF_MPS: + fcfname = "MPS (more pages, same document)"; + break; + case FCF_EOR: + fcfname = "EOR (end of retransmission)"; + break; + case FCF_EOP: + fcfname = "EOP (no more pages or documents)"; + break; + case FCF_RR: + fcfname = "RR (receive ready)"; + break; + case FCF_CTC: + fcfname = "CTC (continue to correct)"; + break; + case FCF_PRI_EOM: + fcfname = "PRI-EOM (more documents after interrupt)"; + break; + case FCF_PRI_MPS: + fcfname = "PRI-MPS (more pages after interrupt)"; + break; + case FCF_PRI_EOP: + fcfname = "PRI-EOP (no more pages after interrupt)"; + break; + case FCF_PPS: + fcfname = "PPS (partial page signal)"; + break; + case FCF_DCN: + fcfname = "DCN (disconnect)"; + break; + case FCF_CTR: + fcfname = "CTR (confirm continue to correct)"; + break; + case FCF_NSF: + fcfname = "NSF (non-standard facilities)"; + break; + case FCF_MCF: + fcfname = "MCF (message confirmation)"; + break; + case FCF_RTN: + fcfname = "RTN (retrain negative)"; + break; + case FCF_RTP: + fcfname = "RTP (retrain positive)"; + break; + case FCF_PIN: + fcfname = "PIN (procedural interrupt negative)"; + break; + case FCF_PIP: + fcfname = "PIP (procedural interrupt positive)"; + break; + case FCF_RNR: + fcfname = "RNR (receive not ready)"; + break; + case FCF_ERR: + fcfname = "ERR (confirm end of retransmisison)"; + break; + case FCF_PPR: + fcfname = "PPR (partial page request)"; + break; + default: + protoTrace("unknown FCF 0x%X", fcf); + return; } + protoTrace("%s %s", dir, (const char*) fcfname); } /* @@ -748,7 +794,7 @@ FaxModem::correctPhaseCData(u_char* buf, u_long* pBufSize, */ MemoryDecoder dec2(buf, params.pageWidth(), *pBufSize, fillorder, params.is2D(), false); endOfData = dec2.cutExtraRTC(); - rows = dec2.getRows(); + // we don't update rows because we don't decode the entire image } if( endOfData ) *pBufSize = endOfData - buf; diff --git a/faxd/FaxModem.h b/faxd/FaxModem.h index 3119f21..b5664fe 100644 --- a/faxd/FaxModem.h +++ b/faxd/FaxModem.h @@ -44,6 +44,7 @@ class FaxServer; // NB: these would be enums in the FaxModem class // if there were a portable way to refer to them! typedef unsigned int RTNHandling; // RTN signal handling method +typedef unsigned int BadPageHandling; // bad page (received) handling method typedef unsigned int JBIGSupport; // JBIG support available /* @@ -71,6 +72,7 @@ private: // phase c data receive & copy quality checking u_int cblc; // current count of consecutive bad lines bool lastRowBad; // last decoded row was bad + u_long linesWereA4Width;// count of lines measuring 1728 pel u_long recvEOLCount; // EOL count for received page u_long recvBadLineCount; @@ -119,8 +121,7 @@ protected: void recvTrace(const char* fmt, ...); void copyQualityTrace(const char* fmt, ...); void traceModemParams(); - void tracePPR(const char* dir, u_int ppr); - void tracePPM(const char* dir, u_int ppm); + void traceFCF(const char* dir, u_int fcf); // server-related stuff bool getHDLCTracing(); bool getECMTracing(); @@ -175,6 +176,11 @@ public: RTN_IGNORE = 2, // ignore error and send next page RTN_RETRANSMITIGNORE = 3 // retransmit but ignore error instead of hanging up }; + enum { // FaxModem::BadPageHandling + BADPAGE_RTN = 0, // send RTN, expect a retransmission + BADPAGE_DCN = 1, // send DCN, expect a disconnection + BADPAGE_RTNSAVE = 2 // send RTN, but save the page + }; enum { // FaxModem::JBIGSupport JBIG_NONE = 0, // no JBIG support JBIG_RECV = 1, // receive-only JBIG support @@ -216,7 +222,7 @@ public: /* * Fax send protocol. The expected sequence is: * - * if (faxService() && sendSetup(req, params, emsg) && dial(number, emsg) == OK) { + * if (faxService() && sendSetup(req, params, emsg) && dial(number, origin, emsg) == OK) { * sendBegin(); * if (getPrologue() == send_ok and parameters acceptable) { * select send parameters diff --git a/faxd/FaxRecv.c++ b/faxd/FaxRecv.c++ index a90fd71..3dee12e 100644 --- a/faxd/FaxRecv.c++ +++ b/faxd/FaxRecv.c++ @@ -64,18 +64,19 @@ FaxServer::recvFax(const CallID& callid, fxStr& emsg) if (tif) { recvPages = 0; // total count of received pages fileStart = pageStart = Sys::now(); + if (faxRecognized = modem->recvBegin(emsg)) { /* * If the system is busy then notifyRecvBegun may not return * quickly. Thus we run it in a child process and move on. */ - waitNotifyPid = fork(); + waitNotifyPid = fork(); // waitNotifyPid keeps the notifies ordered switch (waitNotifyPid) { case 0: // NB: partially fill in info for notification call notifyRecvBegun(info); sleep(1); // XXX give parent time - exit(0); + _exit(0); case -1: logError("Can not fork for non-priority processing."); notifyRecvBegun(info); @@ -136,7 +137,6 @@ FaxServer::getRecvFile(fxStr& qfile, fxStr& emsg) emsg = "Failed to find unused filename"; (void) flock(ftmp, LOCK_EX); - return ftmp; } @@ -211,7 +211,16 @@ FaxServer::recvDocuments(TIFF* tif, FaxRecvInfo& info, FaxRecvInfoArray& docs, f TIFFClose(tif); return (false); } - setServerStatus("Receiving from \"%s\"", (const char*) info.sender); + fxStr statusmsg = fxStr::format("Receiving from \"%s\"", (const char*) info.sender); + for (u_int i = 0; i < info.callid.size(); i++) { + if (info.callid[i].length() && modem->doCallIDDisplay(i)) { + statusmsg.append(", "); + statusmsg.append(modem->getCallIDLabel(i)); + statusmsg.append(":"); + statusmsg.append(info.callid[i]); + } + } + setServerStatus((const char*) statusmsg); recvOK = recvFaxPhaseD(tif, info, ppm, emsg); TIFFClose(tif); info.time = (u_int) getFileTransferTime(); @@ -221,14 +230,13 @@ FaxServer::recvDocuments(TIFF* tif, FaxRecvInfo& info, FaxRecvInfoArray& docs, f * If syslog is busy then notifyDocumentRecvd may not return * quickly. Thus we run it in a child process and move on. */ - pid_t pid = waitNotifyPid; + if (waitNotifyPid > 0) (void) Sys::waitpid(waitNotifyPid); // keep the notifies ordered waitNotifyPid = fork(); switch (waitNotifyPid) { case 0: - if (pid > 0) (void) Sys::waitpid(pid); notifyDocumentRecvd(info); sleep(1); // XXX give parent time - exit(0); + _exit(0); case -1: logError("Can not fork for non-priority logging."); notifyDocumentRecvd(info); @@ -254,8 +262,12 @@ FaxServer::recvDocuments(TIFF* tif, FaxRecvInfo& info, FaxRecvInfoArray& docs, f if (tif == NULL) return (false); fileStart = pageStart = Sys::now(); - if (!modem->recvEOMBegin(emsg)) + if (!modem->recvEOMBegin(emsg)) { + info.reason = emsg; + docs[docs.length()-1] = info; + TIFFClose(tif); return (false); + } } /*NOTREACHED*/ } @@ -268,8 +280,14 @@ FaxServer::recvFaxPhaseD(TIFF* tif, FaxRecvInfo& info, u_int& ppm, fxStr& emsg) { fxStr id = info.sender; for (u_int i = 0; i < info.callid.size(); i++) { - id.append('\n'); - id.append(info.callid[i]); + if (modem->doCallIDRecord(i)) { + id.append('\n'); + if (modem->getCallIDLabel(i).length()) { + id.append(modem->getCallIDLabel(i)); + id.append('\t'); + } + id.append(info.callid[i]); + } } do { if (++recvPages > maxRecvPages) { @@ -286,14 +304,13 @@ FaxServer::recvFaxPhaseD(TIFF* tif, FaxRecvInfo& info, u_int& ppm, fxStr& emsg) * Thus we run it in a child process and move on. Timestamps * in syslog cannot be expected to have exact precision anyway. */ - pid_t pid = waitNotifyPid; + if (waitNotifyPid > 0) (void) Sys::waitpid(waitNotifyPid); // keep the notifies ordered waitNotifyPid = fork(); switch (waitNotifyPid) { case 0: - if (pid > 0) (void) Sys::waitpid(pid); notifyPageRecvd(tif, info, ppm); sleep(1); // XXX give parent time - exit(0); + _exit(0); case -1: logError("Can not fork for non-priority logging."); notifyPageRecvd(tif, info, ppm); @@ -340,7 +357,7 @@ FaxServer::notifyPageRecvd(TIFF*, FaxRecvInfo& ri, int) void FaxServer::notifyDocumentRecvd(FaxRecvInfo& ri) { - traceServer("RECV FAX (%s): %s from %s, route to %s, %u pages in %s" + traceServer("RECV FAX (%s): %s from %s, subaddress %s, %u pages in %s" , (const char*) ri.commid , (const char*) ri.qfile , (const char*) ri.sender diff --git a/faxd/FaxRequest.c++ b/faxd/FaxRequest.c++ index 9ae072c..c0e7ebd 100644 --- a/faxd/FaxRequest.c++ +++ b/faxd/FaxRequest.c++ @@ -96,6 +96,7 @@ FaxRequest::stringval FaxRequest::strvals[] = { { "pagehandling", &FaxRequest::pagehandling }, { "modem", &FaxRequest::modem }, { "faxnumber", &FaxRequest::faxnumber }, + { "faxname", &FaxRequest::faxname }, { "tsi", &FaxRequest::tsi }, { "receiver", &FaxRequest::receiver }, { "company", &FaxRequest::company }, @@ -268,6 +269,7 @@ FaxRequest::readQFile(bool& rejectJob) case H_PAGEHANDLING: pagehandling = tag; break; case H_MODEM: modem = tag; break; case H_FAXNUMBER: faxnumber = tag; break; + case H_FAXNAME: faxname = tag; break; case H_TSI: // NB: tsi csi collide if (cmd[0] == 't') tsi = tag; @@ -524,7 +526,7 @@ FaxRequest::writeQFile() } lseek(fd, 0L, SEEK_SET); Sys::write(fd, sb, sb.getLength()); - ftruncate(fd, sb.getLength()); + (void) ftruncate(fd, sb.getLength()); // XXX maybe should fsync, but not especially portable } diff --git a/faxd/FaxRequest.h b/faxd/FaxRequest.h index 5f529e2..5886454 100644 --- a/faxd/FaxRequest.h +++ b/faxd/FaxRequest.h @@ -108,7 +108,7 @@ public: u_short state; // job scheduling state u_short lineno; // line number when reading queue file FaxSendStatus status; // request status indicator - u_short totpages; // total cummulative pages in documents + u_short totpages; // total cumulative pages in documents u_short npages; // total pages sent/received u_short ntries; // # tries to send current page u_short ndials; // # consecutive failed tries to call dest @@ -145,6 +145,7 @@ public: fxStr notice; // message to send for notification fxStr modem; // outgoing modem to use fxStr faxnumber; // Sender's number to advertise to phone company + fxStr faxname; // Sender's name to advertise to phone company fxStr tsi; // TSI to use instead of LocalIdentifier fxStr pagehandling; // page analysis information fxStr receiver; // receiver's identity for cover page generation diff --git a/faxd/FaxSend.c++ b/faxd/FaxSend.c++ index a0e0905..3295d99 100644 --- a/faxd/FaxSend.c++ +++ b/faxd/FaxSend.c++ @@ -43,8 +43,9 @@ * FAX Server Transmission Protocol. */ void -FaxServer::sendFax(FaxRequest& fax, FaxMachineInfo& clientInfo, FaxAcctInfo& ai, u_int& batched) +FaxServer::sendFax(FaxRequest& fax, FaxMachineInfo& clientInfo, FaxAcctInfo& ai, u_int& batched, bool usedf) { + useDF = usedf; u_int prevPages = fax.npages; if (!(batched & BATCH_FIRST) || lockModem()) { if (batched & BATCH_FIRST) @@ -163,7 +164,7 @@ FaxServer::sendFax(FaxRequest& fax, FaxMachineInfo& clientInfo, const fxStr& num */ clientParams.decodePage(fax.pagehandling); /* - * So we want to restrict DF sellection to those masked in fax.desireddf + * So we want to restrict DF selection to those masked in fax.desireddf */ clientParams.df = fxmin(modem->getBestDataFormat(), (u_int)fax.desireddf); clientParams.br = fxmin(modem->getBestSignallingRate(), (u_int) fax.desiredbr); @@ -200,7 +201,7 @@ FaxServer::sendFax(FaxRequest& fax, FaxMachineInfo& clientInfo, const fxStr& num notifyCallPlaced(fax); CallStatus callstat; if (batched & BATCH_FIRST) - callstat = modem->dial(number, notice); + callstat = modem->dial(number, fax.faxnumber, notice); else callstat = ClassModem::OK; if (callstat == ClassModem::OK) @@ -521,13 +522,25 @@ FaxServer::sendClientCapabilitiesOK(FaxRequest& fax, FaxMachineInfo& clientInfo, * peer implements and our modem is also capable. */ if ((clientCapabilities.ec != EC_DISABLE) && modem->supportsECM() && fax.desiredec) { - // Technically, if the remote reports either type of T.30-A ECM, then they - // must therefore support the other (so we could pick), but we should follow - // the advice in T.30-C to honor the remote's bytes/frame request. - if (modem->supportsECM(EC_ENABLE256) && clientCapabilities.ec == EC_ENABLE256) - clientParams.ec = EC_ENABLE256; - else + /* + * 1) The receiver expresses a preference one type of T.30-A ECM. In nearly + * all circumstances 256-bit is the default. + * 2) We allow the fax job also to express a preference for one type or the + * other to be used. Our default is also 256-bit. + * 3) We allow the Class 1 modem to specify a preference between them. Again, + * the default it 256-bit. + * 4) Although T.30-C advises us to honor remote preferences (although + * discussing a different ECM type than these) the receiver is required to + * support both if either are supported at all. + * + * So, to respond to all of these preferences we basically say that if any + * of these preferences indicate 64-bit then use 64-bit. Otherwise, use + * 256-bit. + */ + if (!modem->supportsECM(EC_ENABLE256) || clientCapabilities.ec != EC_ENABLE256 || fax.desiredec != EC_ENABLE256) clientParams.ec = EC_ENABLE64; + else + clientParams.ec = EC_ENABLE256; } else clientParams.ec = EC_DISABLE; clientParams.bf = BF_DISABLE; @@ -606,20 +619,30 @@ FaxServer::sendSetupParams1(TIFF* tif, * Ignore what faxq did and send with the "highest" (monochrome) * compression that both the modem and the remote supports. */ - params.df = 0; - u_int bits = clientCapabilities.df; - bits &= BIT(DF_JBIG+1)-1; // cap at JBIG, only deal with monochrome - while (bits) { - bits >>= 1; - if (bits) params.df++; + if (useDF) { + /* + * The job has been restricted to a specific data format per + * JobControls or something similar - that value is now + * found in clientParams.df. + */ + params.df == fxmin(clientParams.df, params.df); + } else { + params.df = 0; + u_int bits = clientCapabilities.df; + bits &= BIT(DF_JBIG+1)-1; // cap at JBIG, only deal with monochrome + while (bits) { // puts params.df to the "best" support by the remote + bits >>= 1; + if (bits) params.df++; + } } - if (params.df == DF_JBIG && (!modem->supportsJBIG() || (params.ec == EC_DISABLE))) + // Class 2 RTFCC doesn't support JBIG + if (params.df == DF_JBIG && (!modem->supportsJBIG() || (params.ec == EC_DISABLE) || class2RTFCC)) params.df = DF_2DMMR; // even if RTFCC supported uncompressed mode (and it doesn't) // it's likely that the remote was incorrect in telling us it does if (params.df == DF_2DMRUNCOMP) params.df = DF_2DMR; // don't let RTFCC cause problems with restricted modems... - if (params.df == DF_2DMMR && (!modem->supportsMMR() || (params.ec == EC_DISABLE))) + if (params.df == DF_2DMMR && (!modem->supportsMMR() || (params.ec == EC_DISABLE) || !(clientCapabilities.df & BIT(DF_2DMMR)))) params.df = DF_2DMR; if (params.df == DF_2DMR && (!modem->supports2D() || !(clientCapabilities.df & BIT(DF_2DMR)))) params.df = DF_1DMH; @@ -763,7 +786,7 @@ FaxServer::sendSetupParams1(TIFF* tif, double rf = (params.vr == VR_R16 ? 2 : params.vr == VR_300X300 ? 1.5 : 1); if (w > (clientInfo.getMaxPageWidthInPixels()*rf)) { emsg = fxStr::format("Client does not support document page width" - ", max remote page width %u pixels, image width %lu pixels", + ", max remote page width %g pixels, image width %lu pixels", (uint32) (clientInfo.getMaxPageWidthInPixels()*rf), w); return (send_reformat); } @@ -779,7 +802,7 @@ FaxServer::sendSetupParams1(TIFF* tif, 0, }; emsg = fxStr::format("Modem does not support document page width" - ", max page width %u pixels, image width %lu pixels", + ", max page width %g pixels, image width %lu pixels", widths[modem->getBestPageWidth()&7]*rf, w); return (send_reformat); } @@ -794,7 +817,7 @@ FaxServer::sendSetupParams1(TIFF* tif, * to take into account sloppy coding practice (e.g. * using 200 dpi for high-res facsimile. */ - if (clientInfo.getMaxPageLengthInMM() != (u_short)-1) { + if (clientInfo.getMaxPageLengthInMM() != (u_short) -1 || !modem->supportsPageLength((u_int) -1)) { u_long h = 0; (void) TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); float len = h / yres; // page length in mm diff --git a/faxd/FaxServer.h b/faxd/FaxServer.h index 2b04780..ef37819 100644 --- a/faxd/FaxServer.h +++ b/faxd/FaxServer.h @@ -63,6 +63,8 @@ private: friend class FaxModem; + bool useDF; // limits application of RTFCC + // FAX transmission protocol support void sendFax(FaxRequest& fax, FaxMachineInfo&, const fxStr& number, u_int&); bool sendClientCapabilitiesOK(FaxRequest&, FaxMachineInfo&, fxStr&); @@ -93,7 +95,7 @@ protected: void readConfig(const fxStr& filename); void setLocalIdentifier(const fxStr& lid); - void sendFax(FaxRequest&, FaxMachineInfo&, FaxAcctInfo&, u_int&); + void sendFax(FaxRequest&, FaxMachineInfo&, FaxAcctInfo&, u_int&, bool); bool recvFax(const CallID& callid, fxStr& emsg); time_t getFileTransferTime() const; diff --git a/faxd/G3Encoder.c++ b/faxd/G3Encoder.c++ index 4c8160c..252ee84 100644 --- a/faxd/G3Encoder.c++ +++ b/faxd/G3Encoder.c++ @@ -162,7 +162,7 @@ G3Encoder::find0span(const u_char* bp, int bs, int be) bp++; } else span = 0; - if (bits >= 2*8*sizeof (long)) { + if ((uint32) bits >= 2*8*sizeof (long)) { long* lp; /* * Align to longword boundary and check longwords. @@ -174,7 +174,7 @@ G3Encoder::find0span(const u_char* bp, int bs, int be) bp++; } lp = (long*) bp; - while (bits >= 8*sizeof (long) && *lp == 0) { + while ((uint32) bits >= 8*sizeof (long) && *lp == 0) { span += 8*sizeof (long), bits -= 8*sizeof (long); lp++; } @@ -221,7 +221,7 @@ G3Encoder::find1span(const u_char* bp, int bs, int be) bp++; } else span = 0; - if (bits >= 2*8*sizeof (long)) { + if ((uint32) bits >= 2*8*sizeof (long)) { long* lp; /* * Align to longword boundary and check longwords. @@ -233,7 +233,7 @@ G3Encoder::find1span(const u_char* bp, int bs, int be) bp++; } lp = (long*) bp; - while (bits >= 8*sizeof (long) && *lp == ~0) { + while ((uint32) bits >= 8*sizeof (long) && *lp == ~0) { span += 8*sizeof (long), bits -= 8*sizeof (long); lp++; } @@ -352,12 +352,12 @@ G3Encoder::encode(const void* vp, u_int w, u_int h, u_char* rp) span = findspan(&bp, bs, w, zeroruns); // white span putspan(span, TIFFFaxWhiteCodes); bs += span; - if (bs >= w) + if ((u_int) bs >= w) break; span = findspan(&bp, bs, w, oneruns); // black span putspan(span, TIFFFaxBlackCodes); bs += span; - if (bs >= w) + if ((u_int) bs >= w) break; } } @@ -408,7 +408,7 @@ G3Encoder::putBits(u_int bits, u_int length) static const u_int mask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; - while (length > bit) { + while (length > (u_short) bit) { data |= bits >> (length - bit); length -= bit; flushBits(); diff --git a/faxd/Getty.c++ b/faxd/Getty.c++ index 7002a23..2f0f88c 100644 --- a/faxd/Getty.c++ +++ b/faxd/Getty.c++ @@ -207,7 +207,7 @@ Getty::setupSession(int fd) struct stat sb; Sys::fstat(fd, sb); #if HAS_FCHOWN - fchown(fd, 0, sb.st_gid); + (void) fchown(fd, 0, sb.st_gid); #else Sys::chown(getLine(), 0, sb.st_gid); #endif diff --git a/faxd/Job.c++ b/faxd/Job.c++ index 59fc736..788c3ee 100644 --- a/faxd/Job.c++ +++ b/faxd/Job.c++ @@ -50,38 +50,6 @@ JobSendHandler::~JobSendHandler() {} void JobSendHandler::childStatus(pid_t, int status) { faxQueueApp::instance().sendJobDone(job, status); } -JobCtrlHandler::JobCtrlHandler(Job& j) : job(j) {} -JobCtrlHandler::~JobCtrlHandler() {} - -int -JobCtrlHandler::inputReady (int f) -{ - char data[1024]; - fxAssert(f == fd, "Reading from a FD which is not our own"); - int n; - while ((n = Sys::read(fd, data, sizeof(data))) > 0) - { - buf.append(data, n); - } - return 0; -} - -void -JobCtrlHandler::childStatus(pid_t, int status) -{ - /* - * Dispatcher sometimes tells us child has exited before we - * get a chance to read it's pipe (even though it is ready) - */ - inputReady(fd); - Dispatcher::instance().unlink(fd); - close(fd); - job.jci = new JobControlInfo(buf); - buf.resize(0); - faxQueueApp::instance().ctrlJobDone(job, status); -} - - fxIMPLEMENT_StrKeyPtrValueDictionary(JobDict, Job*) JobDict Job::registry; JobControlInfo Job::defJCI; @@ -91,7 +59,6 @@ Job::Job(const FaxRequest& req) , ttsHandler(*this) , prepareHandler(*this) , sendHandler(*this) - , ctrlHandler(*this) , file(req.qfile) , jobid(req.jobid) { @@ -203,27 +170,6 @@ Job::startSend(pid_t p) Dispatcher::instance().startChild(pid = p, &sendHandler); } -void -Job::startControl(pid_t p, int fd) -{ - // Order is important here. - // 1) We need to guaranted that jci is NULL and not needed, incase - // our SIGCHLD comes before we get to delete it. - // 2) We want our child pid noted as soon as possible (once we're sure - // jci is taken care of) - // And then we can worry about deleting and linking fd... - JobControlInfo *tmp_jci = jci; - jci = NULL; - Dispatcher::instance().startChild(pid = p, &ctrlHandler); - - if (tmp_jci) - delete tmp_jci; - - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - ctrlHandler.fd = fd; - Dispatcher::instance().link(fd, Dispatcher::ReadMask, &ctrlHandler); -} - fxStr Job::jobStatusName(const JobStatus status) { diff --git a/faxd/Job.h b/faxd/Job.h index 154159b..afe397a 100644 --- a/faxd/Job.h +++ b/faxd/Job.h @@ -77,23 +77,6 @@ public: void childStatus(pid_t, int); }; -/* - * This does sligltly more than the other PID handlers. - * It also collects on a fd and stores it for JobControl. - */ -class JobCtrlHandler : public IOHandler { -private: - Job& job; - fxStr buf; - int fd; -public: - JobCtrlHandler(Job&); - ~JobCtrlHandler(); - int inputReady (int fd); - void childStatus(pid_t, int); - friend class Job; -}; - fxDECLARE_StrKeyDictionary(JobDict, Job*) /* @@ -105,7 +88,6 @@ private: JobTTSHandler ttsHandler; // Dispatcher handler for tts timeout JobPrepareHandler prepareHandler; // Dispatcher handler for job prep work JobSendHandler sendHandler; // Dispatcher handler for job send work - JobCtrlHandler ctrlHandler; // Dispatcher handler for job control work static JobDict registry; static JobControlInfo defJCI; diff --git a/faxd/JobControl.c++ b/faxd/JobControl.c++ index e23a73e..139d80f 100644 --- a/faxd/JobControl.c++ +++ b/faxd/JobControl.c++ @@ -38,6 +38,8 @@ #define DCI_MAXTRIES 0x0010 #define DCI_USEXVRES 0x0020 #define DCI_VRES 0x0040 +#define DCI_PRIORITY 0x0080 +#define DCI_DESIREDDF 0x0100 #define isDefined(b) (defined & b) #define setDefined(b) (defined |= b) @@ -56,6 +58,8 @@ JobControlInfo::JobControlInfo(const JobControlInfo& other) maxTries = other.maxTries; usexvres = other.usexvres; vres = other.vres; + priority = other.priority; + desireddf = other.desireddf; } JobControlInfo::JobControlInfo (const fxStr& buffer) @@ -82,8 +86,6 @@ JobControlInfo::~JobControlInfo() {} // XXX can't sort int JobControlInfo::compare(const JobControlInfo*) const { return (0); } -static int getNumber(const char* s) { return ((int) strtol(s, NULL, 0)); } - void JobControlInfo::configError (const char* fmt, ...) { @@ -130,11 +132,18 @@ JobControlInfo::setConfigItem (const char* tag, const char* value) } else if (streq(tag, "vres")) { vres = getNumber(value); setDefined(DCI_VRES); + } else if (streq(tag, "priority")) { + priority = getNumber(value); + setDefined(DCI_PRIORITY); } else { + if (streq(tag, "desireddf")) { // need to pass desireddf to faxsend, also + desireddf = getNumber(value); + setDefined(DCI_DESIREDDF); + } if( args != "" ) args.append('\0'); args.append(fxStr::format("-c%c%s:\"%s\"", - '\0', tag, value)); + '\0', tag, (const char*) value)); } return true; } @@ -213,3 +222,21 @@ JobControlInfo::getVRes() const else return 0; } + +int +JobControlInfo::getPriority() const +{ + if (isDefined(DCI_PRIORITY)) + return priority; + else + return -1; +} + +int +JobControlInfo::getDesiredDF() const +{ + if (isDefined(DCI_DESIREDDF)) + return desireddf; + else + return -1; +} diff --git a/faxd/JobControl.h b/faxd/JobControl.h index aebea90..993e19d 100644 --- a/faxd/JobControl.h +++ b/faxd/JobControl.h @@ -54,6 +54,8 @@ private: int usexvres; // use extended resolution u_int vres; // use extended resolution fxStr args; // arguments for subprocesses + int priority; // override submission priority with this + int desireddf; // if set, desireddf value // default returned on no match static const JobControlInfo defControlInfo; @@ -66,7 +68,6 @@ public: ~JobControlInfo(); int compare(const JobControlInfo*) const; - void parseEntry(const char* tag, const char* value, bool quoted); u_int getMaxConcurrentCalls() const; u_int getMaxSendPages() const; @@ -77,6 +78,8 @@ public: time_t nextTimeToSend(time_t) const; int getUseXVRes() const; u_int getVRes() const; + int getPriority() const; + int getDesiredDF() const; const fxStr& getArgs() const; virtual bool setConfigItem(const char*, const char*); diff --git a/faxd/MemoryDecoder.c++ b/faxd/MemoryDecoder.c++ index 25785c4..1e06af1 100644 --- a/faxd/MemoryDecoder.c++ +++ b/faxd/MemoryDecoder.c++ @@ -231,8 +231,6 @@ void MemoryDecoder::fixFirstEOL() */ u_char* MemoryDecoder::cutExtraRTC() { - u_char* start = current(); - /* * We expect RTC near the end of data and thus * do not check all image to save processing time. @@ -340,7 +338,6 @@ u_char* MemoryDecoder::encodeTagLine(u_long* raster, u_int th, u_int slop) G3Encoder enc(result); enc.setupEncoder(fillorder, is2D, isG4); - u_char* start = current(); decode(NULL, width, th); // discard decoded data if (!isG4) { /* @@ -540,5 +537,8 @@ u_char* MemoryDecoder::convertDataFormat(const Class2Params& params) printf("Attempt to convert Phase C data to JBIG without JBIG support. This should not happen.\n"); return (NULL); #endif /* HAVE_JBIG */ + } else { + printf("Attempt to convert Phase C data to an unsupported format. This should not happen.\n"); + return (NULL); } } diff --git a/faxd/Modem.c++ b/faxd/Modem.c++ index 7ced6cd..0ef9d87 100644 --- a/faxd/Modem.c++ +++ b/faxd/Modem.c++ @@ -91,12 +91,14 @@ Modem::~Modem() } Modem* -Modem::modemExists(const fxStr& id) +Modem::modemExists(const fxStr& id, bool notexempt) { for (ModemIter iter(list); iter.notDone(); iter++) { Modem& modem = iter; - if (modem.devID == id) + if (modem.devID == id) { + if (notexempt && modem.getState() == Modem::EXEMPT) return (NULL); return (&modem); + } } return (NULL); } @@ -207,11 +209,14 @@ Modem::findModem(const Job& job) /* * Job is assigned to an explicit modem or to an * invalid class or modem. Look for the modem - * in the list of known modems. + * in the list of known modems. + * + * Here we deliberately return a modem that is EXEMPT + * so that the caller can process accordingly. */ for (ModemIter iter(list); iter.notDone(); iter++) { Modem& modem = iter; - if (modem.getState() != Modem::READY) + if (modem.getState() != Modem::READY && modem.getState() != Modem::EXEMPT) continue; if (job.device != modem.devID) continue; diff --git a/faxd/Modem.h b/faxd/Modem.h index c9e9fb8..6eb0f6b 100644 --- a/faxd/Modem.h +++ b/faxd/Modem.h @@ -75,7 +75,8 @@ public: enum { DOWN = 0, // modem identified, but offline READY = 1, // modem ready for use - BUSY = 2 // modem in use + BUSY = 2, // modem in use + EXEMPT = 3 // modem exempt from sending use }; private: int fd; // cached open FIFO file @@ -106,7 +107,7 @@ public: virtual ~Modem(); static Modem& getModemByID(const fxStr& id); - static Modem* modemExists(const fxStr& id); + static Modem* modemExists(const fxStr& id, bool notexempt = false); static Modem* findModem(const Job& job); bool isInGroup(const fxStr& mgroup); diff --git a/faxd/ModemConfig.c++ b/faxd/ModemConfig.c++ index 9b722bc..0d200f5 100644 --- a/faxd/ModemConfig.c++ +++ b/faxd/ModemConfig.c++ @@ -86,6 +86,7 @@ static struct { { "modemresetcmds", &ModemConfig::resetCmds }, { "modemreadycmds", &ModemConfig::readyCmds }, { "modemdialcmd", &ModemConfig::dialCmd, "ATDT%s" }, +{ "modemsetorigincmd", &ModemConfig::setOriginCmd }, { "modemnoflowcmd", &ModemConfig::noFlowCmd }, { "modemsoftflowcmd", &ModemConfig::softFlowCmd }, { "modemhardflowcmd", &ModemConfig::hardFlowCmd }, @@ -200,7 +201,6 @@ static struct { { "modemringsbeforeresponse", &ModemConfig::ringsBeforeResponse, 0 }, { "modemsoftresetcmddelay", &ModemConfig::softResetCmdDelay, 3000 }, { "class1tcfrecvtimeout", &ModemConfig::class1TCFRecvTimeout, 4500 }, -{ "class1trainingrecovery", &ModemConfig::class1TrainingRecovery,1500 }, { "class1recvabortok", &ModemConfig::class1RecvAbortOK, 200 }, { "class1rmpersistence", &ModemConfig::class1RMPersistence, 2 }, { "class1frameoverhead", &ModemConfig::class1FrameOverhead, 4 }, @@ -209,6 +209,8 @@ static struct { { "class1tcfminrun", &ModemConfig::class1TCFMinRun, (2*TCF_DURATION)/3 }, { "class1tmconnectdelay", &ModemConfig::class1TMConnectDelay, 0 }, { "class1ecmframesize", &ModemConfig::class1ECMFrameSize, 256 }, +{ "class1pagelengthsupport", &ModemConfig::class1PageLengthSupport, LN_ALL }, +{ "class1pagewidthsupport", &ModemConfig::class1PageWidthSupport, WD_ALL }, }; static struct { const char* name; @@ -275,6 +277,7 @@ ModemConfig::setupConfig() setVolumeCmds("ATM0 ATL0M1 ATL1M1 ATL2M1 ATL3M1"); recvDataFormat = DF_ALL; // default to no transcoding rtnHandling = FaxModem::RTN_RETRANSMITIGNORE; // retransmit until MCF/MPS + badPageHandling = FaxModem::BADPAGE_RTNSAVE; // send RTN but save the page saveUnconfirmedPages = true; // keep unconfirmed pages softRTFCC = true; // real-time fax comp. conv. (software) noAnswerVoice = false; // answer voice calls @@ -473,6 +476,9 @@ ModemConfig::parseATCmd(const char* cp) } ecode[0] = ESC_WAITFOR; ecode[1] = (u_char) resp; + } else if (esc.length() > 5 && strneq(esc, "play:", 5)) { + ecode[0] = ESC_PLAY; + ecode[1] = (u_char) esc[5]; } else { configError("Unknown AT escape code \"%s\"", (const char*) esc); pos = epos; @@ -582,6 +588,20 @@ ModemConfig::getRTNHandling(const char* cp) } u_int +ModemConfig::getBadPageHandling(const char* cp) +{ + BadPageHandling bph; + if (valeq(cp, "RTN")) { + bph = FaxModem::BADPAGE_RTN; + } else if (valeq(cp, "DCN")) { + bph = FaxModem::BADPAGE_DCN; + } else { + bph = FaxModem::BADPAGE_RTNSAVE; + } + return (bph); +} + +u_int ModemConfig::getJBIGSupport(const char* cp) { JBIGSupport js; @@ -736,6 +756,8 @@ ModemConfig::setConfigItem(const char* tag, const char* value) recvDataFormat = getDataFormat(value); else if (streq(tag, "rtnhandlingmethod")) rtnHandling = getRTNHandling(value); + else if (streq(tag, "badpagehandlingmethod")) + badPageHandling = getBadPageHandling(value); else if (streq(tag, "class2ecmtype")) class2ECMType = getECMType(value); else if (streq(tag, "class2usehex")) @@ -754,22 +776,45 @@ ModemConfig::setConfigItem(const char* tag, const char* value) saveUnconfirmedPages = getBoolean(value); else if (streq(tag, "distinctiverings")) parseDR(value); - else if (streq(tag, "callidpattern") || streq(tag, "callidanswerlength") ) { + else if (streq(tag, "callidpattern") || streq(tag, "callidanswerlength") || streq(tag, "calliddisplay") + || streq(tag, "callidlabel") || streq(tag, "callidrecord")) { if (tag[6] == 'p') callidIndex++; // only increment on instances of "Pattern" if (idConfig.length() < callidIndex+1 && callidIndex != (u_int) -1) idConfig.resize(callidIndex+1); if (tag[6] == 'p') { idConfig[callidIndex].answerlength = 0; // we must initialize this + idConfig[callidIndex].display = false; // we must initialize this + idConfig[callidIndex].record = true; // we must initialize this + idConfig[callidIndex].label = ""; // we must initialize this idConfig[callidIndex].pattern = value; configTrace("CallID[%d].pattern = \"%s\"", callidIndex, (const char*)idConfig[callidIndex].pattern); } else { if (callidIndex != (u_int) -1) { - idConfig[callidIndex].answerlength = atoi(value); - configTrace("CallID[%d].answerlength = %d", callidIndex, - idConfig[callidIndex].answerlength); + switch (tag[6]) { + case 'a': + idConfig[callidIndex].answerlength = atoi(value); + configTrace("CallID[%d].answerlength = %d", callidIndex, + idConfig[callidIndex].answerlength); + break; + case 'd': + idConfig[callidIndex].display = getBoolean(value); + configTrace("CallID[%d].display = %s", callidIndex, + idConfig[callidIndex].display ? "true" : "false"); + break; + case 'r': + idConfig[callidIndex].record = getBoolean(value); + configTrace("CallID[%d].record = %s", callidIndex, + idConfig[callidIndex].record ? "true" : "false"); + break; + case 'l': + idConfig[callidIndex].label = value; + configTrace("CallID[%d].label = \"%s\"", callidIndex, + (const char*) idConfig[callidIndex].label); + break; + } } else - configError("No index for Call ID Answer Length"); + configError("No index for Call ID attribute"); } } else if (streq(tag, "cidnumber")) { if (idConfig.length() < CallID::NUMBER+1) diff --git a/faxd/ModemConfig.h b/faxd/ModemConfig.h index 8f563d7..e41f7e0 100644 --- a/faxd/ModemConfig.h +++ b/faxd/ModemConfig.h @@ -38,6 +38,9 @@ class id_config public: fxStr pattern; int answerlength; + bool display; + fxStr label; + bool record; int compare (const id_config* n) const { @@ -57,6 +60,7 @@ private: u_int getSpeed(const char* value); u_int getDataFormat(const char* value); u_int getRTNHandling(const char* cp); + u_int getBadPageHandling(const char* cp); u_int getJBIGSupport(const char* cp); ECMType getECMType(const char* cp); @@ -82,6 +86,7 @@ public: fxStr resetCmds; // extra modem reset commands for start of initialization fxStr readyCmds; // extra modem reset commands for end of initialization fxStr dialCmd; // cmd for dialing (%s for number) + fxStr setOriginCmd; // cmd for setting the origination id (%s for name, %d for number) fxStr answerAnyCmd; // cmd for answering unknown call type fxStr answerDataCmd; // cmd for answering data call fxStr answerFaxCmd; // cmd for answering fax call @@ -151,7 +156,6 @@ public: fxStr class1SwitchingCmd; // after recv HDLC and before sending fxStr class1MsgRecvHackCmd; // cmd to avoid +FCERROR before image u_int class1TCFRecvTimeout; // timeout receiving TCF - u_int class1TrainingRecovery; // delay (ms) after failed training u_int class1RecvAbortOK; // if non-zero, OK sent after recv abort u_int class1RMPersistence; // how many times to persist through +FCERROR u_int class1Resolutions; // resolutions support @@ -161,6 +165,8 @@ public: u_int class1TCFMinRun; // min length of zero run for TCF check u_int class1TMConnectDelay; // delay (ms) after +FTM CONNECT u_int class1ECMFrameSize; // ECM frame size for transmission + u_int class1PageLengthSupport;// page length support + u_int class1PageWidthSupport; // page width support bool class1GreyJPEGSupport; // Greyscale JPEG support bool class1ColorJPEGSupport; // Full-color JPEG support bool class1ECMSupport; // support T.30-A ECM @@ -229,6 +235,7 @@ public: bool useJobTagLine; // Use Job tagline or use conf taglineformat RTNHandling rtnHandling; // RTN signal handling method + BadPageHandling badPageHandling; // bad page (received) handling method JBIGSupport class1JBIGSupport; // monochrome JBIG support bool saveUnconfirmedPages; // don't delete unconfirmed pages diff --git a/faxd/ModemServer.c++ b/faxd/ModemServer.c++ index ce9cc2f..18b8d33 100644 --- a/faxd/ModemServer.c++ +++ b/faxd/ModemServer.c++ @@ -97,6 +97,7 @@ ModemServer::ModemServer(const fxStr& devName, const fxStr& devID) sawBlockEnd = false; timeout = false; log = NULL; + readyStateMsg = NULL; } ModemServer::~ModemServer() @@ -201,7 +202,7 @@ const char* ModemServer::stateStatus[9] = { * start a timer running for timeout seconds. */ void -ModemServer::changeState(ModemServerState s, long timeout) +ModemServer::changeState(ModemServerState s, long timeout, const char* msg) { if (s != state) { if (timeout) @@ -217,7 +218,13 @@ ModemServer::changeState(ModemServerState s, long timeout) if (modemFd >= 0) setInputBuffering(state != RUNNING && state != SENDING && state != ANSWERING && state != RECEIVING && state != LISTENING); - setServerStatus(stateStatus[state]); + if (state == RUNNING) { + fxStr statusmsg = msg ? fxStr(msg) : fxStr(stateStatus[state]); + if (readyStateMsg) statusmsg.append(readyStateMsg); + setServerStatus(statusmsg); + } else { + setServerStatus(msg ? msg : stateStatus[state]); + } switch (state) { case RUNNING: notifyModemReady(); // notify surrogate @@ -225,6 +232,8 @@ ModemServer::changeState(ModemServerState s, long timeout) case MODEMWAIT: setupAttempts = 0; break; + default: + break; } } else if (s == MODEMWAIT && ++setupAttempts >= maxSetupAttempts) { traceStatus(FAXTRACE_SERVER, @@ -430,7 +439,7 @@ ModemServer::setServerStatus(const char* fmt, ...) va_end(ap); fprintf(statusFile, "\n"); fflush(statusFile); - ftruncate(fileno(statusFile), ftell(statusFile)); + (void) ftruncate(fileno(statusFile), ftell(statusFile)); flock(fileno(statusFile), LOCK_UN); } @@ -474,13 +483,34 @@ ModemServer::setupModem(bool isSend) | modem->getModel() | "/" | modem->getRevision()); } - } else + } else { /* * Reset the modem in case some other program * went in and messed with the configuration. + * + * Sometimes a modem may get interrupted while in a + * "transmit" state such as AT+VTX (voice mode) or + * AT+FTM=146 (fax mode) or similar. Now, the modem + * should be smart enough to return to command-mode + * after a short period of inactivity, but it's + * conceivable that some don't (and we've seen some + * that are this way). Furthermore, it is likely + * possible to configure a modem in such a way so as + * to never provide for that short period of inactivity. + * Further complicating matters, some modems are not + * sensitive to DTR. + * + * So if our first reset attempt fails we send DLE+ETX + * to the modem just in case we happen to have a modem + * in this kind of state, waiting for DLE+ETX before + * returning to command mode. Then we retry the reset. */ - if( !(modem->reset() || modem->reset()) ) // try twice - return (false); + if (!(modem->reset())) { + sendDLEETX(); + if (!(modem->reset())) + return (false); + } + } /* * Most modem-related parameters are dealt with * in the modem driver. The speaker volume is @@ -1167,9 +1197,11 @@ ModemServer::setParity(Parity parity) if (!tcgetattr("setParity", term)) return (false); setParity(term, parity); - curParity = parity; // used above flushModemInput(); - return (tcsetattr(TCSANOW, term)); + if (!tcsetattr(TCSANOW, term)) + return (false); + curParity = parity; // used above + return (true); } /* @@ -1379,6 +1411,15 @@ ModemServer::stopTimeout(const char* whichdir) traceModemOp("TIMEOUT: %s", whichdir); } +void +ModemServer::sendDLEETX() +{ + u_char buf[2]; + buf[0] = DLE; + buf[1] = ETX; + (void) putModem(buf, 2); +} + int ModemServer::getModemLine(char rbuf[], u_int bufSize, long ms) { diff --git a/faxd/ModemServer.h b/faxd/ModemServer.h index ca06b49..40921a7 100644 --- a/faxd/ModemServer.h +++ b/faxd/ModemServer.h @@ -137,6 +137,7 @@ protected: virtual fxStr getModemCapabilities() const; // modem i/o support void timerExpired(long, long); + void sendDLEETX(); int getModemLine(char buf[], u_int bufSize, long ms = 0); int getModemChar(long ms = 0); int getModemBit(long ms = 0); @@ -165,7 +166,7 @@ protected: void modemFlushInput(); void modemHangup(); // server state and related control interfaces - void changeState(ModemServerState, long timeout = 0); + void changeState(ModemServerState, long timeout = 0, const char* msg = NULL); void setServerStatus(const char* fmt, ...); void setProcessPriority(ModemServerState s); // system logging interfaces @@ -194,6 +195,8 @@ protected: virtual void abortSession(); bool abortRequested(); public: + const char* readyStateMsg; + virtual ~ModemServer(); virtual void open(); diff --git a/faxd/ServerConfig.c++ b/faxd/ServerConfig.c++ index f4f7b8a..e42963b 100644 --- a/faxd/ServerConfig.c++ +++ b/faxd/ServerConfig.c++ @@ -96,7 +96,7 @@ ServerConfig::S_stringtag ServerConfig::strings[] = { ServerConfig::S_numbertag ServerConfig::numbers[] = { { "tracingmask", &ServerConfig::tracingMask, // NB: must be first FAXTRACE_MODEMIO|FAXTRACE_TIMEOUTS }, -{ "sessiontracing", &ServerConfig::logTracingLevel, FAXTRACE_SERVER }, +{ "sessiontracing", &ServerConfig::logTracingLevel, 0xFFF }, { "servertracing", &ServerConfig::tracingLevel, FAXTRACE_SERVER }, { "uucplocktimeout", &ServerConfig::uucpLockTimeout, 0 }, { "jobreqproto", &ServerConfig::requeueProto, FAX_REQPROTO }, diff --git a/faxd/TagLine.c++ b/faxd/TagLine.c++ index 806b3b5..aaf2a4d 100644 --- a/faxd/TagLine.c++ +++ b/faxd/TagLine.c++ @@ -159,7 +159,7 @@ FaxModem::imageTagLine(u_char* buf, u_int fillorder, const Class2Params& params, */ u_int w = params.pageWidth(); u_int h = (tagLineFont->fontHeight()*2)+MARGIN_TOP+MARGIN_BOT; // max height - double VR_FINE - u_int th; // actual tagline height + u_int th = 0; // actual tagline height switch(params.vr) { case VR_NORMAL: case VR_200X100: @@ -304,7 +304,7 @@ FaxModem::imageTagLine(u_char* buf, u_int fillorder, const Class2Params& params, u_int bpl = sizeof(u_long) * 8; // bits per u_long for (u_int nl = lpr/2 - 1; nl ; nl--) { // make 2 longs out of 1 (ABCD -> AABB CCDD) - int pos; + int pos = 0; for (u_int i = 0; i < (bpl/8); i++) { if (i == 0 || i == bpl/8/2) { *l2 = (u_long) 0; diff --git a/faxd/Trigger.h b/faxd/Trigger.h index 3c3132d..bd6c3ac 100644 --- a/faxd/Trigger.h +++ b/faxd/Trigger.h @@ -120,7 +120,8 @@ public: MODEM_DATA_END = MODEM_BASE+8, // inbound data call finished MODEM_VOICE_BEGIN= MODEM_BASE+9,// inbound voice call begun MODEM_VOICE_END = MODEM_BASE+10,// inbound voice call finished - MODEM_CID = MODEM_BASE+11 // inbound caller-ID information + MODEM_CID = MODEM_BASE+11,// inbound caller-ID information + MODEM_EXEMPT = MODEM_BASE+12 // modem marked exempt }; #define TRIGGER_MAXEVENT (MODEM_BASE+16) private: diff --git a/faxd/UUCPLock.c++ b/faxd/UUCPLock.c++ index 61b2017..c94a5f4 100644 --- a/faxd/UUCPLock.c++ +++ b/faxd/UUCPLock.c++ @@ -178,7 +178,7 @@ UUCPLock::create() Sys::chmod(buff, mode); #endif #if HAS_FCHOWN - fchown(fd, UUCPuid, UUCPgid); + (void) fchown(fd, UUCPuid, UUCPgid); #else Sys::chown(buff, UUCPuid, UUCPgid); #endif diff --git a/faxd/choptest.c++ b/faxd/choptest.c++ index dc940d2..db16d63 100644 --- a/faxd/choptest.c++ +++ b/faxd/choptest.c++ @@ -32,6 +32,7 @@ #include "Class2.h" #include "MemoryDecoder.h" #include "tiffio.h" +#include "Sys.h" const char* appName; @@ -57,7 +58,7 @@ fatal(const char* fmt ...) int main(int argc, char* argv[]) { - extern int optind, opterr; + extern int optind; extern char* optarg; float minChop = 3.0; // chop if >= 3" white space at bottom u_int minRows; @@ -65,7 +66,7 @@ main(int argc, char* argv[]) int c; appName = argv[0]; - while ((c = getopt(argc, argv, "t:a")) != -1) + while ((c = Sys::getopt(argc, argv, "t:a")) != -1) switch (c) { case 'a': doAll = true; @@ -129,8 +130,8 @@ main(int argc, char* argv[]) printf( "Chop %u rows, strip was %lu bytes, need only %lu\n" , dec.getLastBlanks() - , totbytes - , dec.getEndOfPage() - data + , (u_long) totbytes + , (u_long) (dec.getEndOfPage() - data) ); } else { printf("Don't chop, found %u rows, need %u rows\n" diff --git a/faxd/cqtest.c++ b/faxd/cqtest.c++ index f99ba1c..a3d19fb 100644 --- a/faxd/cqtest.c++ +++ b/faxd/cqtest.c++ @@ -503,13 +503,13 @@ int main(int argc, char* argv[]) { const char* outFile = "cq.tif"; - extern int optind, opterr; + extern int optind; extern char* optarg; int c; CQDecoder cq; appName = argv[0]; - while ((c = getopt(argc, argv, "m:o:p:")) != -1) + while ((c = Sys::getopt(argc, argv, "m:o:p:")) != -1) switch (c) { case 'm': cq.maxConsecutiveBadLines = (u_int) strtoul(optarg, NULL, 0); diff --git a/faxd/faxApp.c++ b/faxd/faxApp.c++ index 87e161e..ffe226f 100644 --- a/faxd/faxApp.c++ +++ b/faxd/faxApp.c++ @@ -367,8 +367,19 @@ detachIO(void) (void) Sys::close(fd); } -const fxStr faxApp::quote = " \""; -const fxStr faxApp::enquote = "\""; +const fxStr faxApp::quote = " \'"; +const fxStr faxApp::enquote = "\'"; + +fxStr +faxApp::quoted(const fxStr& s) +{ + fxStr q; + for (u_int i = 0; i < s.length(); i++) { + if (s[i] == '\'') q.append("\'\\\'"); + q.append(s[i]); + } + return (q); +} /* * Run the specified shell command. If changeIDs is diff --git a/faxd/faxApp.h b/faxd/faxApp.h index 47973d0..0b7f1a5 100644 --- a/faxd/faxApp.h +++ b/faxd/faxApp.h @@ -79,6 +79,7 @@ public: static const fxStr quote; static const fxStr enquote; + fxStr quoted(const fxStr& s); bool runCmd(const char* cmd, bool changeIDs = false, IOHandler* waiter = NULL); }; diff --git a/faxd/faxGettyApp.c++ b/faxd/faxGettyApp.c++ index 9021e8b..0832fcc 100644 --- a/faxd/faxGettyApp.c++ +++ b/faxd/faxGettyApp.c++ @@ -238,7 +238,8 @@ faxGettyApp::listenForRing() /* DID modems may only signal a call with DID data - no RING */ bool done = false; for (u_int i = 0; i < callid.size(); i++) { - if (idConfig[i].answerlength > 0 && callid[i].length() >= idConfig[i].answerlength) { + if ((u_int) idConfig[i].answerlength > 0 && + callid[i].length() >= (u_int) idConfig[i].answerlength) { done = true; break; } @@ -297,9 +298,19 @@ faxGettyApp::answerPhoneCmd(AnswerType atype, const char* dialnumber) * user (sending an "ANSWER" command through the FIFO). */ void -faxGettyApp::answerPhone(AnswerType atype, CallType ctype, const CallID& callid, const char* dialnumber) +faxGettyApp::answerPhone(AnswerType atype, CallType ctype, CallID& callid, const char* dialnumber) { - changeState(ANSWERING); + FaxModem* modem = (FaxModem*) ModemServer::getModem(); + fxStr statusmsg = "Answering the phone"; + for (u_int i = 0; i < callid.size(); i++) { + if (callid[i].length() && modem->doCallIDDisplay(i)) { + statusmsg.append(", "); + statusmsg.append(modem->getCallIDLabel(i)); + statusmsg.append(":"); + statusmsg.append(callid[i]); + } + } + changeState(ANSWERING, 0, (const char*) statusmsg); beginSession(FAXNumber); sendModemStatus("I" | getCommID()); @@ -336,53 +347,56 @@ faxGettyApp::answerPhone(AnswerType atype, CallType ctype, const CallID& callid, rejectCall = false; for (u_int i = 0; i < callid.size(); i++) - callid_formatted.append(quote | callid.id(i) | enquote); + callid_formatted.append(quote | quoted(callid.id(i)) | enquote); if (callid_formatted.length()) traceProtocol("CallID:%s", (const char*) callid_formatted); if (dynamicConfig.length()) { - fxStr cmd(dynamicConfig | quote | getModemDevice() | enquote | callid_formatted); + fxStr cmd(dynamicConfig | quote | quoted(getModemDevice()) | enquote | callid_formatted); traceServer("DynamicConfig: %s", (const char*)cmd); fxStr localid = ""; int pipefd[2], status; char line[1024]; - pipe(pipefd); - pid_t pid = fork(); - switch (pid) { - case -1: - emsg = "Could not fork for local ID."; - logError("%s", (const char*)emsg); - Sys::close(pipefd[0]); - Sys::close(pipefd[1]); - break; - case 0: - dup2(pipefd[1], STDOUT_FILENO); - Sys::close(pipefd[0]); - Sys::close(pipefd[1]); - execl("/bin/sh", "sh", "-c", (const char*) cmd, (char*) NULL); - sleep(1); - exit(1); - default: - Sys::close(pipefd[1]); - { - FILE* fd = fdopen(pipefd[0], "r"); - while (fgets(line, sizeof (line)-1, fd)){ - line[strlen(line)-1]='\0'; // Nuke \n at end of line - (void) readConfigItem(line); - } - Sys::waitpid(pid, status); - if (status != 0) + if (pipe(pipefd) == 0) { + pid_t pid = fork(); + switch (pid) { + case -1: + emsg = "Could not fork for local ID."; + logError("%s", (const char*)emsg); + Sys::close(pipefd[0]); + Sys::close(pipefd[1]); + break; + case 0: + dup2(pipefd[1], STDOUT_FILENO); + Sys::close(pipefd[0]); + Sys::close(pipefd[1]); + execl("/bin/sh", "sh", "-c", (const char*) cmd, (char*) NULL); + sleep(1); + _exit(1); + default: + Sys::close(pipefd[1]); { - emsg = fxStr::format("Bad exit status %#o for \'%s\'", status, (const char*) cmd); - logError("%s", (const char*)emsg); + FILE* fd = fdopen(pipefd[0], "r"); + while (fgets(line, sizeof (line)-1, fd)) { + line[strlen(line)-1]='\0'; // Nuke \n at end of line + (void) readConfigItem(line); + } + Sys::waitpid(pid, status); + if (status != 0) { + emsg = fxStr::format("Bad exit status %#o for \'%s\'", status, (const char*) cmd); + logError("%s", (const char*)emsg); + } + // modem settings may have changed... + FaxModem* modem = (FaxModem*) ModemServer::getModem(); + modem->pokeConfig(false); } - // modem settings may have changed... - FaxModem* modem = (FaxModem*) ModemServer::getModem(); - modem->pokeConfig(false); - } - Sys::close(pipefd[0]); - break; + Sys::close(pipefd[0]); + break; + } + } else { + emsg = "Could not open a pipe for local ID."; + logError("%s", (const char*) emsg); } } @@ -459,6 +473,7 @@ faxGettyApp::answerPhone(AnswerType atype, CallType ctype, const CallID& callid, ai.status = emsg; ai.duration = Sys::now() - ai.start; ai.conntime = ai.duration; + ai.jobinfo = ""; if (logCalls && !ai.record("CALL")) logError("Error writing CALL accounting record, dest=%s", (const char*) ai.dest); @@ -514,7 +529,7 @@ faxGettyApp::answerCleanup() * the modem layer arrives at as the call type. */ bool -faxGettyApp::answerCall(AnswerType atype, CallType& ctype, fxStr& emsg, const CallID& callid, const char* dialnumber) +faxGettyApp::answerCall(AnswerType atype, CallType& ctype, fxStr& emsg, CallID& callid, const char* dialnumber) { bool callResolved; if (atype == ClassModem::ANSTYPE_EXTERN) { @@ -554,7 +569,7 @@ faxGettyApp::answerCall(AnswerType atype, CallType& ctype, fxStr& emsg, const Ca * to recondition the modem for incoming calls (if configured). */ bool -faxGettyApp::processCall(CallType ctype, fxStr& emsg, const CallID& callid) +faxGettyApp::processCall(CallType ctype, fxStr& emsg, CallID& callid) { bool callHandled = false; @@ -568,13 +583,24 @@ faxGettyApp::processCall(CallType ctype, fxStr& emsg, const CallID& callid) } switch (ctype) { case ClassModem::CALLTYPE_FAX: - traceServer("ANSWER: FAX CONNECTION DEVICE '%s'" - , (const char*) getModemDevice() - ); - changeState(RECEIVING); - sendRecvStatus(getModemDeviceID(), "B"); - callHandled = recvFax(callid, emsg); - sendRecvStatus(getModemDeviceID(), "E"); + { + traceServer("ANSWER: FAX CONNECTION DEVICE '%s'" + , (const char*) getModemDevice()); + FaxModem* modem = (FaxModem*) ModemServer::getModem(); + fxStr statusmsg = "Receiving facsimile"; + for (u_int i = 0; i < callid.size(); i++) { + if (callid[i].length() && modem->doCallIDDisplay(i)) { + statusmsg.append(", "); + statusmsg.append(modem->getCallIDLabel(i)); + statusmsg.append(":"); + statusmsg.append(callid[i]); + } + } + changeState(RECEIVING, 0, (const char*) statusmsg); + sendRecvStatus(getModemDeviceID(), "B"); + callHandled = recvFax(callid, emsg); + sendRecvStatus(getModemDeviceID(), "E"); + } break; case ClassModem::CALLTYPE_DATA: traceServer("ANSWER: DATA CONNECTION"); @@ -827,6 +853,7 @@ faxGettyApp::notifyDocumentRecvd(FaxRecvInfo& ri) ai.jobtag = ""; ai.callid = ri.callid; ai.owner = ""; + ai.jobinfo = ""; ri.params.asciiEncode(ai.faxdcs); if (!ai.record("RECV")) logError("Error writing RECV accounting record, dest=%s", @@ -843,14 +870,14 @@ faxGettyApp::notifyRecvDone(FaxRecvInfo& ri) fxStr callid_formatted; for (u_int i = 0; i < ri.callid.size(); i++) { - callid_formatted.append(quote | ri.callid.id(i) | enquote); + callid_formatted.append(quote | quoted(ri.callid.id(i)) | enquote); } // hand to delivery/notification command fxStr cmd(faxRcvdCmd - | quote | ri.qfile | enquote - | quote | getModemDeviceID() | enquote - | quote | ri.commid | enquote - | quote | ri.reason | enquote + | quote | quoted(ri.qfile) | enquote + | quote | quoted(getModemDeviceID()) | enquote + | quote | quoted(ri.commid) | enquote + | quote | quoted(ri.reason) | enquote | callid_formatted); traceServer("RECV FAX: %s", (const char*) cmd); setProcessPriority(BASE); // lower priority @@ -1054,6 +1081,14 @@ faxGettyApp::setConfigItem(const char* tag, const char* value) if (state == FaxServer::RUNNING) notifyModemReady(); } + if (readyState == "B") + ModemServer::readyStateMsg = " (busy)"; + else if (readyState == "D") + ModemServer::readyStateMsg = " (down)"; + else if (readyState == "E") + ModemServer::readyStateMsg = " (exempt)"; + else + ModemServer::readyStateMsg = ""; } else return (FaxServer::setConfigItem(tag, value)); return (true); diff --git a/faxd/faxGettyApp.h b/faxd/faxGettyApp.h index c083e57..42b912f 100644 --- a/faxd/faxGettyApp.h +++ b/faxd/faxGettyApp.h @@ -106,7 +106,7 @@ private: bool setupModem(bool isSend); void discardModem(bool dropDTR); // inbound call handling - bool processCall(CallType ctype, fxStr& emsg, const CallID& callid); + bool processCall(CallType ctype, fxStr& emsg, CallID& callid); CallType runGetty(const char* what, Getty* (*newgetty)(const fxStr&, const fxStr&), const char* args, fxStr &emsg, @@ -116,9 +116,9 @@ private: void listenBegin(); void listenForRing(); void answerPhoneCmd(AnswerType, const char* dialnumber = NULL); - void answerPhone(AnswerType, CallType, const CallID& callid, const char* dialnumber = NULL); + void answerPhone(AnswerType, CallType, CallID& callid, const char* dialnumber = NULL); void answerCleanup(); - bool answerCall(AnswerType atype, CallType& ctype, fxStr& emsg, const CallID& callid, const char* dialnumber = NULL); + bool answerCall(AnswerType atype, CallType& ctype, fxStr& emsg, CallID& callid, const char* dialnumber = NULL); friend void AnswerTimeoutHandler::timerExpired(long, long); // miscellaneous stuff diff --git a/faxd/faxQCleanApp.c++ b/faxd/faxQCleanApp.c++ index 1947260..725df9a 100644 --- a/faxd/faxQCleanApp.c++ +++ b/faxd/faxQCleanApp.c++ @@ -57,11 +57,13 @@ private: static const fxStr archDir; static const fxStr doneDir; static const fxStr docDir; + static const fxStr tmpDir; void scanDirectory(void); void collectRefs(const FaxRequest&); void archiveJob(const FaxRequest& req); void expungeCruft(void); + bool findTmpInode(ino_t inode); public: faxQCleanApp(); ~faxQCleanApp(); @@ -80,6 +82,7 @@ public: const fxStr faxQCleanApp::archDir = FAX_ARCHDIR; const fxStr faxQCleanApp::doneDir = FAX_DONEDIR; const fxStr faxQCleanApp::docDir = FAX_DOCDIR; +const fxStr faxQCleanApp::tmpDir = FAX_TMPDIR; faxQCleanApp::faxQCleanApp() { @@ -248,12 +251,49 @@ faxQCleanApp::archiveJob(const FaxRequest& req) { // hand the archiving task off to the archiving command fxStr cmd("bin/archive" - | quote | req.jobid | enquote + | quote | quoted(req.jobid) | enquote ); runCmd(cmd, true); } /* + * Scan the tmp directory and look for a specific + * inode being used. + */ +bool +faxQCleanApp::findTmpInode(ino_t inode) +{ + if (trace) + printf("Scan %s directory for links to inode %d.\n", + (const char*) tmpDir, (int) inode); + + DIR* dir = Sys::opendir(tmpDir); + if (dir == NULL) { + printf("%s: Could not scan directory for links.\n", + (const char*) tmpDir); + return (false); + } + for (dirent* dp = readdir(dir); dp; dp = readdir(dir)) { + fxStr file(tmpDir | "/" | dp->d_name); + struct stat sb; + if (Sys::stat(file, sb) < 0 || !S_ISREG(sb.st_mode)) { + if (trace) + printf("%s: ignored, cannot stat or not a regular file\n", + (const char*) file); + continue; + } + if (sb.st_ino == inode) { // found our match + if (trace) + printf("%s: inode %d match found\n", + (const char*) file, (int) inode); + return (true); + } + } + if (trace) printf("inode %d match not found\n", (int) inode); + return (false); +} + +/* * Scan the document directory and look for stuff * that has no references in the sendq or doneq. * These documents are removed if they are older @@ -295,19 +335,21 @@ faxQCleanApp::expungeCruft(void) continue; } /* - * Document files should only have one link to them except - * during the job preparation stage (i.e. when hfaxd has - * original in the tmp directory and a link in the docq - * directory). Therefore if file has multiple links then - * it's not a candidate for removal. Note that this assumes - * the hard links are not created by any imaging work such - * as done by tiff2fax. + * During job preparation stage (i.e. when hfaxd has the original + * in the tmp directory and a link in the docq directory) document + * files will have multiple hard links to them, and we don't want + * to remove those links as the job may not even have an associated + * sendq file yet. So if the file has multiple links then we check + * do see if the original is in the tmp directory, and if not, then + * we ignore the fact that there are multiple hard links - as they + * get used in job grouping. */ if (sb.st_nlink > 1) { // can't be orphaned yet if (trace) - printf("%s: ignored, file has %u links\n", + printf("%s: file has %u links, check for associated tmp file\n", (const char*) file, sb.st_nlink); - continue; + if (findTmpInode(sb.st_ino)) + continue; } if (docrefs.find(file)) { // referenced from doneq if (trace) @@ -372,7 +414,7 @@ faxQCleanApp::expungeCruft(void) // suffix, when the PS.jobid version of the file still // exists. char *base; - int sl=k-docDir.length()-1-1; // removing docDir,'/' and trailing ';' + u_int sl=k-docDir.length()-1-1; // removing docDir,'/' and trailing ';' base=(char *)malloc(sl+1); strncpy(base, &file[docDir.length()+1], sl); base[sl]=0; diff --git a/faxd/faxQueueApp.c++ b/faxd/faxQueueApp.c++ index edbf13e..89d3a80 100644 --- a/faxd/faxQueueApp.c++ +++ b/faxd/faxQueueApp.c++ @@ -75,8 +75,9 @@ faxQueueApp::SchedTimeout::~SchedTimeout() {} void faxQueueApp::SchedTimeout::timerExpired(long, long) { + if (pending && lastRun <= Sys::now()) pending = false; if (faxQueueApp::instance().scheduling() ) { - start(0); + start(0); return; } faxQueueApp::instance().runScheduler(); @@ -92,7 +93,7 @@ faxQueueApp::SchedTimeout::start(u_short s) * So we keep the scheduler from running more than * once per second. */ - if (!started && Sys::now() > lastRun) { + if (!started && Sys::now() != lastRun) { started = true; pending = false; Dispatcher::instance().startTimer(s, 1, this); @@ -159,6 +160,24 @@ faxQueueApp::open() pokeScheduler(); } +void +faxQueueApp::blockSignals() +{ + sigset_t block; + sigemptyset(&block); + sigaddset(&block, SIGCHLD); + sigprocmask(SIG_BLOCK, &block, NULL); +} + +void +faxQueueApp::releaseSignals() +{ + sigset_t release; + sigemptyset(&release); + sigaddset(&release, SIGCHLD); + sigprocmask (SIG_UNBLOCK, &release, NULL); +} + /* * Scan the spool area for modems. We can't be certain the * modems are actively working without probing them; this @@ -202,8 +221,7 @@ faxQueueApp::scanQueueDirectory() { DIR* dir = Sys::opendir(sendDir); if (dir == NULL) { - logError("Could not scan %s directory for outbound jobs", - (const char*)sendDir); + logError("Could not scan " | sendDir | " directory for outbound jobs"); return; } for (dirent* dp = readdir(dir); dp; dp = readdir(dir)) { @@ -225,8 +243,7 @@ faxQueueApp::scanClientDirectory() { DIR* dir = Sys::opendir(clientDir); if (dir == NULL) { - logError("Could not scan %s directory for clients", - (const char*) clientDir); + logError("Could not scan " | clientDir | " directory for clients"); return; } for (dirent* dp = readdir(dir); dp; dp = readdir(dir)) { @@ -371,7 +388,7 @@ faxQueueApp::prepareJobStart(Job& job, FaxRequest* req, _exit(prepareJob(job, *req, info)); /*NOTREACHED*/ case -1: // fork failed, sleep and retry - job.remove(); // Remove from active queue + if (job.isOnList()) job.remove(); // Remove from active queue delayJob(job, *req, "Could not fork to prepare job for transmission", Sys::now() + random() % requeueInterval); delete req; @@ -437,7 +454,7 @@ faxQueueApp::prepareJobDone(Job& job, int status) processnext = true; } if (status == Job::requeued) { - job.remove(); + if (job.isOnList()) job.remove(); delayJob(job, *req, "Cannot fork to prepare job for transmission", Sys::now() + random() % requeueInterval); delete req; @@ -458,6 +475,7 @@ faxQueueApp::prepareJobDone(Job& job, int status) removeDestInfoJob(job); // release destination block DestInfo& di = destJobs[job.dest]; unblockDestJobs(di); // release any blocked jobs + pokeScheduler(); } } } @@ -662,6 +680,8 @@ faxQueueApp::prepareJob(Job& job, FaxRequest& req, * o the remote side is known to be capable of it, and * o the user hasn't specified a desire to send 1D data. */ + int jcdf = job.getJCI().getDesiredDF(); + if (jcdf != -1) req.desireddf = jcdf; if (req.desireddf == DF_2DMMR && (req.desiredec != EC_DISABLE) && use2D && job.modem->supportsMMR() && (! info.getCalledBefore() || info.getSupportsMMR()) ) @@ -946,7 +966,7 @@ faxQueueApp::preparePageChop(const FaxRequest& req, float threshold = req.chopthreshold; if (threshold == -1) threshold = pageChopThreshold; - u_int minRows; + u_int minRows = 0; switch(params.vr) { case VR_NORMAL: case VR_200X100: @@ -1136,7 +1156,7 @@ faxQueueApp::runConverter(Job& job, const char* app, char* const* argv, fxStr& e fxStr cmdline(argv[0]); for (u_int i = 1; argv[i] != NULL; i++) cmdline.append(fxStr::format(" %s", argv[i])); - traceQueue(job, "CONVERT DOCUMENT: %s", (const char*)cmdline); + traceQueue(job, "CONVERT DOCUMENT: " | cmdline); JobStatus status; int pfd[2]; if (pipe(pfd) >= 0) { @@ -1242,7 +1262,7 @@ faxQueueApp::makeCoverPage(Job& job, FaxRequest& req, const Class2Params& params | " " | contCoverPageTemplate | " " | fitem.item ); - traceQueue(job, "COVER PAGE: %s", (const char*)cmd); + traceQueue(job, "COVER PAGE: " | cmd); if (runCmd(cmd, true)) { fxStr emsg; fxStr tmp = fitem.item | ";" | params.encodePage(); @@ -1390,11 +1410,11 @@ faxQueueApp::sendJobStart(Job& job, FaxRequest* req) } break; default: // parent, setup handler to wait - // joinargs puts a leading space so this looks funny here - traceQueue(job, "CMD START%s -m %s %s (PID %lu)" - , (const char*) joinargs(cmd, dargs) - , (const char*) job.modem->getDeviceID() - , (const char*) files + traceQueue(job, "CMD START" + | joinargs(cmd, dargs) + | " -m " | job.modem->getDeviceID() + | " " | files + | " (PID %lu)" , pid ); job.startSend(pid); @@ -1425,7 +1445,7 @@ faxQueueApp::sendJobDone(Job& job, int status) if (req && req->status == send_retry) { // prevent turnaround-redialing, delay any blocked jobs time_t newtts = req->tts; - while (cjob = di.nextBlocked()) { + while ((cjob = di.nextBlocked())) { FaxRequest* blockedreq = readRequest(*cjob); if (blockedreq) { delayJob(*cjob, *blockedreq, "Delayed by prior call", newtts); @@ -1435,7 +1455,7 @@ faxQueueApp::sendJobDone(Job& job, int status) } else { unblockDestJobs(di); } - + removeDestInfoJob(job); for (cjob = &job; cjob != NULL; cjob = njob) { njob = cjob->bnext; if (cjob != &job) req = readRequest(*cjob); // the first was already read @@ -1449,6 +1469,7 @@ faxQueueApp::sendJobDone(Job& job, int status) } sendJobDone(*cjob, req); } + pokeScheduler(); } void @@ -1464,8 +1485,7 @@ faxQueueApp::sendJobDone(Job& job, FaxRequest* req) req->notice = "Send program terminated abnormally; unable to exec " | pickCmd(*req); req->status = send_failed; - logError("JOB %s: %s", - (const char*)job.jobid, (const char*)req->notice); + logError("JOB " | job.jobid | ": " | req->notice); } if (req->status == send_reformat) { /* @@ -1551,6 +1571,7 @@ faxQueueApp::sendJobDone(Job& job, FaxRequest* req) req->tts = now + (req->retrytime != 0 ? req->retrytime : (requeueInterval>>1) + (random()%requeueInterval)); + job.tts = req->tts; } /* * Bump the job priority if is not bulk-style in which case @@ -1573,18 +1594,19 @@ faxQueueApp::sendJobDone(Job& job, FaxRequest* req) FaxRequest::state_sleeping : FaxRequest::state_ready; updateRequest(*req, job); // update on-disk status if (!job.suspendPending) { - job.remove(); // remove from active list + if (job.isOnList()) job.remove(); // remove from active list if (req->tts > now) { - traceQueue(job, "SEND INCOMPLETE: requeue for %s; %s", - (const char*)strTime(req->tts - now), (const char*)req->notice); + traceQueue(job, "SEND INCOMPLETE: requeue for " + | strTime(req->tts - now) + | "; " | req->notice); setSleep(job, req->tts); Trigger::post(Trigger::SEND_REQUEUE, job); if (req->isNotify(FaxRequest::when_requeued)) notifySender(job, Job::requeued); } else { - traceQueue(job, "SEND INCOMPLETE: retry immediately; %s", - (const char*)req->notice); - setReadyToRun(job, jobCtrlWait); // NB: job.tts will be <= now + traceQueue(job, "SEND INCOMPLETE: retry immediately; " | + req->notice); + setReadyToRun(job, false); // NB: job.tts will be <= now } } else // signal waiting co-thread job.suspendPending = false; @@ -1598,7 +1620,7 @@ faxQueueApp::sendJobDone(Job& job, FaxRequest* req) job.state = FaxRequest::state_done; deleteRequest(job, req, Job::done, false, fmtTime(duration)); } - traceQueue(job, "SEND DONE: %s", (const char*)strTime(duration)); + traceQueue(job, "SEND DONE: " | strTime(duration)); Trigger::post(Trigger::SEND_DONE, job); setDead(job); } @@ -1610,22 +1632,12 @@ faxQueueApp::sendJobDone(Job& job, FaxRequest* req) /* * Begin the process to insert a job in the queue - * of ready-to-run jobs. We run JobControl, and when it's done, it's - * plased on the ready-to-run queue. - * JobControl is done running + * of ready-to-run jobs. We run JobControl, and when it's done + * the job is placed on the ready-to-run queue. */ void faxQueueApp::setReadyToRun(Job& job, bool wait) { - if (job.state == FaxRequest::state_blocked) { - /* - * If the job was "blocked", then jobcontrol previously ran - * just prior to it becoming blocked. Don't run it again right - * now - */ - setReady(job); - return; - } if (jobCtrlCmd.length()) { const char *app[3]; app[0] = jobCtrlCmd; @@ -1639,9 +1651,9 @@ faxQueueApp::setReadyToRun(Job& job, bool wait) case -1: // error - continue with no JCI jobError(job, "JOB CONTROL: fork: %m"); Sys::close(pfd[1]); - // When fork fails we need to run jobCtrlDone, since there - // will be no child signal to start it. - ctrlJobDone(job, -1); + // When fork fails we need to run ctrlJobDone, since there + // will be no child signal to start it. + ctrlJobDone(job, -1); break; case 0: // child, exec command if (pfd[1] != STDOUT_FILENO) @@ -1650,28 +1662,45 @@ faxQueueApp::setReadyToRun(Job& job, bool wait) traceQueue(job, "JOB CONTROL: %s %s", app[0], app[1]); dup2(STDOUT_FILENO, STDERR_FILENO); Sys::execv(app[0], (char * const*)app); - sleep(3); // XXX give parent time to catch signal + sleep(1); // XXX give parent time to catch signal traceQueue(job, "JOB CONTROL: failed to exec: %m"); _exit(255); /*NOTREACHED*/ default: // parent, read from pipe and wait - job.startControl(pid, pfd[0]); // First, get our child PID handled - Sys::close(pfd[1]); + { + Sys::close(pfd[1]); + int estat = -1; + char data[1024]; + int n; + fxStr buf; + while ((n = Sys::read(pfd[0], data, sizeof(data))) > 0) { + buf.append(data, n); + } + Sys::close(pfd[0]); + job.jci = new JobControlInfo(buf); + (void) Sys::waitpid(pid, estat); + + /* + * JobControl modification of job priority must be + * handled before ctrlJobDone, as that's where the + * job is placed into runq based on the priority. + */ + if (job.getJCI().getPriority() != -1) { + job.pri = job.getJCI().getPriority(); + } + + ctrlJobDone(job, estat); + } + break; } - } else - { + } else { // If our pipe fails, we can't run the child, but we still - // Need jobCtrlDone to be called to proceed this job + // Need ctrlJobDone to be called to proceed this job ctrlJobDone(job, -1); } - if (wait) - { - logError("WAITING FOR JobControl to finish"); - while (job.isEmpty() ) - Dispatcher::instance().dispatch(); - } - } else - setReady(job); + } else { + ctrlJobDone(job, 0); + } } /* @@ -1681,41 +1710,21 @@ faxQueueApp::setReadyToRun(Job& job, bool wait) void faxQueueApp::ctrlJobDone(Job& job, int status) { - traceQueue(job, "CMD DONE: exit status %#x", status); if (status) { logError("JOB %s: bad exit status %#x from sub-fork", (const char*) job.jobid, status); } - setReady(job); - FaxRequest* req = readRequest(job); - if (req) { - updateRequest(*req, job); - delete req; - } -} - -/* - * set a job as really ready - */ -void -faxQueueApp::setReady(Job& job) -{ - job.state = FaxRequest::state_ready; - traceJob(job, "READY"); - Trigger::post(Trigger::JOB_READY, job); + blockSignals(); JobIter iter(runqs[JOBHASH(job.pri)]); for (; iter.notDone() && (iter.job().pri < job.pri || (iter.job().pri == job.pri && iter.job().tts <= job.tts)); iter++) ; + job.state = FaxRequest::state_ready; job.insert(iter.job()); - /* - * In order to deliberately batch jobs by using a common - * time-to-send we need to give time for the other jobs' - * timers to expire and to enter the run queue before - * running the scheduler. Thus the scheduler is poked - * with a delay. - */ - pokeScheduler(1); + job.pid = 0; + releaseSignals(); + traceJob(job, "READY"); + Trigger::post(Trigger::JOB_READY, job); } /* @@ -1725,13 +1734,15 @@ faxQueueApp::setReady(Job& job) void faxQueueApp::setSleep(Job& job, time_t tts) { - traceJob(job, "SLEEP FOR %s", (const char*)strTime(tts - Sys::now())); - Trigger::post(Trigger::JOB_SLEEP, job); + blockSignals(); JobIter iter(sleepq); for (; iter.notDone() && iter.job().tts <= tts; iter++) ; job.insert(iter.job()); job.startTTSTimer(tts); + releaseSignals(); + traceJob(job, "SLEEP FOR " | strTime(tts - Sys::now())); + Trigger::post(Trigger::JOB_SLEEP, job); } /* @@ -1819,7 +1830,7 @@ faxQueueApp::submitJob(Job& job, FaxRequest& req, bool checkState) timeoutJob(job, req); return (false); } - if (!Modem::modemExists(req.modem) && !ModemGroup::find(req.modem)) { + if (!Modem::modemExists(req.modem, true) && !ModemGroup::find(req.modem)) { rejectSubmission(job, req, "REJECT: Requested modem " | req.modem | " is not registered"); return (false); @@ -1887,7 +1898,8 @@ faxQueueApp::submitJob(Job& job, FaxRequest& req, bool checkState) setSleep(job, job.tts); } else { // ready to go now job.startKillTimer(req.killtime); - setReadyToRun(job, false); // We never wait on submit + setReadyToRun(job, true); + pokeScheduler(); } updateRequest(req, job); return (true); @@ -1902,7 +1914,7 @@ faxQueueApp::rejectSubmission(Job& job, FaxRequest& req, const fxStr& reason) Trigger::post(Trigger::JOB_REJECT, job); req.status = send_failed; req.notice = reason; - traceServer("JOB %s: ", (const char*)job.jobid, (const char*)reason); + traceServer("JOB " | job.jobid | ": " | reason); deleteRequest(job, req, Job::rejected, true); setDead(job); // dispose of job } @@ -1970,7 +1982,7 @@ faxQueueApp::suspendJob(Job& job, bool abortActive) * will be the same */ removeDestInfoJob(job); - job.remove(); // remove from old queue + if (job.isOnList()) job.remove(); // remove from old queue job.stopKillTimer(); // clear kill timer return (true); } @@ -2033,8 +2045,7 @@ faxQueueApp::rejectJob(Job& job, FaxRequest& req, const fxStr& reason) { req.status = send_failed; req.notice = reason; - traceServer("JOB %s: %s", - (const char*)job.jobid, (const char*)reason); + traceServer("JOB " | job.jobid | ": " | reason); job.state = FaxRequest::state_failed; Trigger::post(Trigger::JOB_REJECT, job); setDead(job); // dispose of job @@ -2049,7 +2060,7 @@ faxQueueApp::blockJob(Job& job, FaxRequest& req, const char* mesg) job.state = FaxRequest::state_blocked; req.notice = mesg; updateRequest(req, job); - traceQueue(job, "%s", mesg); + traceQueue(job, mesg); if (req.isNotify(FaxRequest::when_requeued)) notifySender(job, Job::blocked); Trigger::post(Trigger::JOB_BLOCKED, job); @@ -2063,6 +2074,7 @@ faxQueueApp::delayJob(Job& job, FaxRequest& req, const char* mesg, time_t tts) { job.state = FaxRequest::state_sleeping; fxStr reason(mesg); + job.tts = tts; req.tts = tts; time_t delay = tts - Sys::now(); // adjust kill time so job isn't removed before it runs @@ -2071,8 +2083,7 @@ faxQueueApp::delayJob(Job& job, FaxRequest& req, const char* mesg, time_t tts) job.startKillTimer(req.killtime); req.notice = reason; updateRequest(req, job); - traceQueue(job, "%s: requeue for %s", - (const char*)mesg, (const char*)strTime(delay)); + traceQueue(job, reason | ": requeue for " | strTime(delay)); if (req.isNotify(FaxRequest::when_requeued)) notifySender(job, Job::requeued); Trigger::post(Trigger::JOB_DELAYED, job); @@ -2082,7 +2093,7 @@ faxQueueApp::delayJob(Job& job, FaxRequest& req, const char* mesg, time_t tts) } void -faxQueueApp::timeoutAccounting(Job& job, FaxRequest& req) +faxQueueApp::queueAccounting(Job& job, FaxRequest& req, const char* type) { FaxAcctInfo ai; ai.jobid = (const char*) req.jobid; @@ -2095,24 +2106,34 @@ faxQueueApp::timeoutAccounting(Job& job, FaxRequest& req) ai.device = ""; ai.dest = (const char*) req.external; ai.csi = ""; - ai.npages = 0; + ai.npages = req.npages; ai.params = 0; - ai.status = "Kill time expired"; + if (req.status == send_done) + ai.status = ""; + else { + ai.status = req.notice; + } + if (strstr(type, "UNSENT")) + ai.status = "Kill time expired"; + else if (strstr(type, "SUBMIT")); + ai.status = "Submitted"; CallID empty_callid; ai.callid = empty_callid; ai.owner = (const char*) req.owner; ai.faxdcs = ""; + ai.jobinfo = fxStr::format("%u/%u/%u/%u/%u/%u/%u", + req.totpages, req.ntries, req.ndials, req.totdials, req.maxdials, req.tottries, req.maxtries); pid_t pid = fork(); switch (pid) { case -1: // error - if (!ai.record("UNSENT")) - logError("Error writing UNSENT accounting record, dest=%s", - (const char*) ai.dest); + if (!ai.record(type)) + logError("Error writing %s accounting record, dest=%s", + type, (const char*) ai.dest); break; case 0: // child - if (!ai.record("UNSENT")) + if (!ai.record(type)) logError("Error writing UNSENT accounting record, dest=%s", - (const char*) ai.dest); + type, (const char*) ai.dest); _exit(255); /*NOTREACHED*/ default: // parent @@ -2133,11 +2154,12 @@ faxQueueApp::timeoutJob(Job& job) traceQueue(job, "KILL TIME EXPIRED"); Trigger::post(Trigger::JOB_TIMEDOUT, job); if (job.state != FaxRequest::state_active) { - job.remove(); // remove from sleep queue + if (job.isOnList()) + job.remove(); // i.e. remove from sleep queue job.state = FaxRequest::state_failed; FaxRequest* req = readRequest(job); if (req) { - timeoutAccounting(job, *req); + queueAccounting(job, *req, "UNSENT"); req->notice = "Kill time expired"; deleteRequest(job, req, Job::timedout, true); } @@ -2162,7 +2184,7 @@ faxQueueApp::timeoutJob(Job& job, FaxRequest& req) job.state = FaxRequest::state_failed; traceQueue(job, "KILL TIME EXPIRED"); Trigger::post(Trigger::JOB_TIMEDOUT, job); - timeoutAccounting(job, req); + queueAccounting(job, req, "UNSENT"); req.notice = "Kill time expired"; deleteRequest(job, req, Job::timedout, true); setDead(job); @@ -2173,7 +2195,7 @@ faxQueueApp::timeoutJob(Job& job, FaxRequest& req) * using the specified job description file. */ bool -faxQueueApp::submitJob(const fxStr& jobid, bool checkState) +faxQueueApp::submitJob(const fxStr& jobid, bool checkState, bool nascent) { Job* job = Job::getJobByID(jobid); if (job) { @@ -2227,12 +2249,13 @@ faxQueueApp::submitJob(const fxStr& jobid, bool checkState) req.state != FaxRequest::state_done && req.state != FaxRequest::state_failed) { status = submitJob(req, checkState); + if (nascent) queueAccounting(*job, req, "SUBMIT"); } else if (reject) { Job job(req); job.state = FaxRequest::state_failed; req.status = send_failed; req.notice = "Invalid or corrupted job description file"; - traceServer("JOB %s : %s", (const char*)jobid, (const char*) req.notice); + traceServer("JOB " | jobid | ": %s", (const char*) req.notice); // NB: this may not work, but we try... deleteRequest(job, req, Job::rejected, true); } else if (req.state == FaxRequest::state_done || @@ -2240,8 +2263,7 @@ faxQueueApp::submitJob(const fxStr& jobid, bool checkState) logError("JOB %s: Cannot resubmit a completed job", (const char*) jobid); } else - traceServer("%s: Unable to purge job, ignoring it", - (const char*)filename); + traceServer(filename | ": Unable to purge job, ignoring it"); } else logError("JOB %s: Could not lock job file; %m.", (const char*) jobid); @@ -2259,13 +2281,21 @@ faxQueueApp::submitJob(const fxStr& jobid, bool checkState) void faxQueueApp::runJob(Job& job) { - job.remove(); - setReadyToRun(job, jobCtrlWait); + if (job.isOnList()) job.remove(); + setReadyToRun(job, true); FaxRequest* req = readRequest(job); if (req) { - updateRequest(*req, job); - delete req; + updateRequest(*req, job); + delete req; } + /* + * In order to deliberately batch jobs by using a common + * time-to-send we need to give time for the other jobs' + * timers to expire and to enter the run queue before + * running the scheduler. Thus the scheduler is poked + * with a delay. + */ + pokeScheduler(1); } /* @@ -2287,24 +2317,40 @@ faxQueueApp::unblockDestJobs(DestInfo& di) */ Job* jb; u_int n = 1; - while ( (jb = di.nextBlocked()) ) { - if ( isOKToCall(di, jb->getJCI(), n) ) - { - if (!di.supportsBatching()) n++; + while ((jb = di.nextBlocked())) { + if (isOKToCall(di, jb->getJCI(), n)) { FaxRequest* req = readRequest(*jb); - if (! req) { + if (!req) { setDead(*jb); continue; } + setReadyToRun(*jb, false); + if (!di.supportsBatching()) n++; req->notice = ""; updateRequest(*req, *jb); delete req; - setReadyToRun(*jb, jobCtrlWait); - } else - { - traceJob(*jb, "Continue BLOCK, current calls: %d, max concurrent calls: %d", - di.getCalls(), jb->getJCI().getMaxConcurrentCalls()); + /* + * We check isOKToCall again here now to avoid di.nextBlocked + * which would pull jb from the blocked list and then possibly + * require us to re-block it, and restoring the blocked order + * would be troublesome. + */ + if (di.getBlocked() && !isOKToCall(di, jb->getJCI(), n)) { + traceQueue("Continue BLOCK on %d job(s) to %s, current calls: %d, max concurrent calls: %d", + di.getBlocked(), (const char*) jb->dest, di.getCalls()+n-1, jb->getJCI().getMaxConcurrentCalls()); + break; + } + } else { + /* + * unblockDestJobs was called, but a new + * call cannot be placed. This would be + * unusual, but because di.nextBlocked + * removed jb from the di list, we need + * to put it back. + */ di.block(*jb); + traceQueue("Continue BLOCK on %d job(s) to %s, current calls: %d, max concurrent calls: %d", + di.getBlocked(), (const char*) jb->dest, di.getCalls()+n-1, jb->getJCI().getMaxConcurrentCalls()); break; } } @@ -2330,28 +2376,14 @@ faxQueueApp::removeDestInfoJob(Job& job) * job to see if they can be batched together. */ bool -faxQueueApp::areBatchable(Job& job, Job& nextjob, FaxRequest& nextreq) +faxQueueApp::areBatchable(FaxRequest& reqa, FaxRequest& reqb, Job& job) { // make sure the job's modem is in the requested ModemGroup - if (!job.modem->isInGroup(nextreq.modem)) - return(false); + if (!job.modem->isInGroup(reqb.modem)) return(false); return(true); } /* - * Add new job "cjob" to the batched job "bjob" - */ -void -faxQueueApp::batchJob(Job* bjob, Job* cjob, FaxRequest* creq) -{ - cjob->remove(); - cjob->modem = bjob->modem; - cjob->breq = creq; - cjob->bprev = bjob; - bjob->bnext = cjob; -} - -/* * Scan the list of jobs and process those that are ready * to go. Note that the scheduler should only ever be * invoked from the dispatcher via a timeout. This way we @@ -2376,10 +2408,8 @@ faxQueueApp::runScheduler() close(); return; } - fxAssert(inSchedule == false, "Scheduler running twice"); inSchedule = true; - /* * Reread the configuration file if it has been * changed. We do this before each scheduler run @@ -2394,6 +2424,7 @@ faxQueueApp::runScheduler() * insures the highest priority job is always processed * first. */ + blockSignals(); if (! quit) { for (u_int i = 0; i < NQHASH; i++) { for (JobIter iter(runqs[i]); iter.notDone(); iter++) { @@ -2406,7 +2437,6 @@ faxQueueApp::runScheduler() pokeScheduler(); break; } - fxAssert(job.tts <= Sys::now(), "Sleeping job on run queue"); fxAssert(job.modem == NULL, "Job on run queue holding modem"); /* @@ -2425,6 +2455,23 @@ faxQueueApp::runScheduler() setDead(job); continue; } + + time_t tts; + time_t now = Sys::now(); + /* + * A computer's clock can jump backwards. For example, if + * the system runs ntp and regularly syncs the system clock + * with some outside source it is possible that the local + * clock will move backwards. We cannot die, then, simply + * because we find a job on the run queue that has a future + * tts. The possibility exists that it is due to some + * adjustment in the system clock. + */ + if (job.tts > now) { + traceJob(job, "WARNING: Job tts is %d seconds in the future. Proceeding anyway.", job.tts - now); + job.tts = now; + } + /* * Do per-destination processing and checking. */ @@ -2468,8 +2515,6 @@ faxQueueApp::runScheduler() deleteRequest(job, req, Job::rejected, true); continue; } - time_t now = Sys::now(); - time_t tts; if (!isOKToCall(di, job.getJCI(), 1)) { /* * This job would exceed the max number of concurrent @@ -2479,7 +2524,7 @@ faxQueueApp::runScheduler() * jobs terminates. */ blockJob(job, *req, "Blocked by concurrent calls"); - job.remove(); // remove from run queue + if (job.isOnList()) job.remove(); // remove from run queue di.block(job); // place at tail of di queue delete req; } else if ((tts = job.getJCI().nextTimeToSend(now)) != now) { @@ -2487,11 +2532,21 @@ faxQueueApp::runScheduler() * This job may not be started now because of time-of-day * restrictions. Reschedule it for the next possible time. */ - job.remove(); // remove from run queue + if (job.isOnList()) job.remove(); // remove from run queue delayJob(job, *req, "Delayed by time-of-day restrictions", tts); delete req; + } else if (staggerCalls && lastCall + staggerCalls > now) { + /* + * This job may not be started now because we last started + * another job too recently and we're staggering jobs. + * Reschedule it for the time when next okay. + */ + if (job.isOnList()) job.remove(); + delayJob(job, *req, "Delayed by outbound call staggering", lastCall + staggerCalls); + delete req; } else if (assignModem(job)) { - job.remove(); // remove from run queue + lastCall = now; + if (job.isOnList()) job.remove(); // remove from run queue job.breq = req; /* * We have a modem and have assigned it to the @@ -2524,68 +2579,83 @@ faxQueueApp::runScheduler() * parameters. 64 should be a portable number. */ if (maxBatchJobs > 64) maxBatchJobs = 64; - + Job* bjob = &job; // Last batched Job Job* cjob = &job; // current Job u_int batchedjobs = 1; for (u_int j = 0; batchedjobs < maxBatchJobs && j < NQHASH; j++) { + blockSignals(); for (JobIter joblist(runqs[j]); batchedjobs < maxBatchJobs && joblist.notDone(); joblist++) { + if (joblist.job().dest != cjob->dest) + continue; cjob = joblist; if (job.jobid == cjob->jobid) continue; // Skip the current job - if (job.dest != cjob->dest) - continue; fxAssert(cjob->tts <= Sys::now(), "Sleeping job on run queue"); fxAssert(cjob->modem == NULL, "Job on run queue holding modem"); - FaxRequest* creq = readRequest(*cjob); - if (!areBatchable(job, *cjob, *creq)) { + if (!areBatchable(*req, *creq, job)) { delete creq; continue; } - if (iter.notDone() && &iter.job() == bjob) iter++; - traceJob(job, "ADDING JOB %s TO BATCH", (const char*)cjob->jobid); - batchJob(bjob, cjob, creq); + traceJob(job, "ADDING JOB " | cjob->jobid | " TO BATCH"); + cjob->modem = job.modem; + cjob->remove(); + bjob->bnext = cjob; + cjob->bprev = bjob; bjob = cjob; + cjob->breq = creq; batchedjobs++; } + releaseSignals(); } - /* * Jobs that are on the sleep queue with state_sleeping * can be batched because the tts that the submitter requested * is known to have passed already. So we pull these jobs out * of the sleep queue and batch them directly. */ + blockSignals(); for (JobIter sleepiter(sleepq); batchedjobs < maxBatchJobs && sleepiter.notDone(); sleepiter++) { cjob = sleepiter; if (cjob->dest != job.dest || cjob->state != FaxRequest::state_sleeping) continue; - FaxRequest* creq = readRequest(*cjob); if (! (req && areBatchable(job, *cjob, *req) ) ) { + FaxRequest* creq = readRequest(*cjob); + if (!(req && areBatchable(*req, *creq, job))) { delete creq; continue; } - traceJob(job, "ADDING JOB %s TO BATCH", (const char*)cjob->jobid); + + traceJob(job, "ADDING JOB " | cjob->jobid | " TO BATCH"); cjob->stopTTSTimer(); - // This job was batched from sleeping, things have - // changed; Update the queue file for onlookers. cjob->tts = now; cjob->state = FaxRequest::state_ready; + cjob->remove(); + cjob->modem = job.modem; + bjob->bnext = cjob; + cjob->bprev = bjob; + cjob->breq = creq; + bjob = cjob; + // This job was batched from sleeping, things have + // changed; Update the queue file for onlookers. creq->tts = now; updateRequest(*creq, *cjob); - batchJob(bjob, cjob, creq); - bjob = cjob; batchedjobs++; } bjob->bnext = NULL; + releaseSignals(); } else job.bnext = NULL; di.call(); // mark as called to correctly block other jobs processJob(job, req, di); + } else if (job.state == FaxRequest::state_failed) { + rejectJob(job, *req, fxStr::format("REJECT: Modem is configured as exempt from accepting jobs")); + deleteRequest(job, req, Job::rejected, true); + continue; } else // leave job on run queue delete req; } @@ -2601,16 +2671,18 @@ faxQueueApp::runScheduler() Trigger::post(Trigger::JOB_REAP, *job); delete job; } + releaseSignals(); /* * Reclaim resources associated with clients * that terminated without telling us. */ HylaClient::purge(); // XXX maybe do this less often + inSchedule = false; } bool -faxQueueApp::scheduling (void) +faxQueueApp::scheduling(void) { return inSchedule; } @@ -2634,6 +2706,10 @@ faxQueueApp::assignModem(Job& job) retryModemLookup = false; Modem* modem = Modem::findModem(job); if (modem) { + if (modem->getState() == Modem::EXEMPT) { + job.state = FaxRequest::state_failed; + return (false); + } if (modem->assign(job)) { Trigger::post(Trigger::MODEM_ASSIGN, *modem); return (true); @@ -2796,6 +2872,7 @@ faxQueueApp::deleteRequest(Job& job, FaxRequest& req, JobStatus why, req.state = FaxRequest::state_failed;// job is definitely done req.pri = job.pri; // just in case someone cares req.tts = Sys::now(); // mark job termination time + job.tts = req.tts; req.writeQFile(); if (force || req.isNotify(FaxRequest::notify_any)) notifySender(job, why, duration); @@ -2924,7 +3001,7 @@ faxQueueApp::FIFOMessage(char cmd, const fxStr& id, const char* args) break; case 'S': // submit an outbound job traceServer("SUBMIT JOB %s", args); - if (status = submitJob(args)) + if (status = submitJob(args, false, true)) pokeScheduler(); break; case 'U': // unreference file @@ -2964,10 +3041,10 @@ void faxQueueApp::notifyModemWedged(Modem& modem) { fxStr dev(idToDev(modem.getDeviceID())); - logError("MODEM %s appears to be wedged", (const char*)dev); + logError("MODEM " | dev | " appears to be wedged"); fxStr cmd(wedgedCmd - | quote | modem.getDeviceID() | enquote - | quote | dev | enquote + | quote | quoted(modem.getDeviceID()) | enquote + | quote | quoted(dev) | enquote ); traceServer("MODEM WEDGED: %s", (const char*) cmd); runCmd(cmd, true, this); @@ -3002,6 +3079,14 @@ faxQueueApp::FIFOModemMessage(const fxStr& devid, const char* msg) modem.setState(Modem::DOWN); Trigger::post(Trigger::MODEM_DOWN, modem); break; + case 'E': // modem exempt from sending use + modem.stopLockPolling(); + traceModem(modem, "EXEMPT"); + modem.setState(Modem::EXEMPT); + Trigger::post(Trigger::MODEM_EXEMPT, modem); + // clear any pending jobs for this modem + pokeScheduler(); + break; case 'N': // modem phone number updated traceModem(modem, "NUMBER %s", msg+1); modem.setNumber(msg+1); @@ -3037,8 +3122,7 @@ faxQueueApp::FIFOModemMessage(const fxStr& devid, const char* msg) Trigger::post(Trigger::MODEM_VOICE_END, modem); break; default: - traceServer("FIFO: Bad modem message \"%s\" for modem %s", - msg, (const char*)devid); + traceServer("FIFO: Bad modem message \"%s\" for modem " | devid, msg); break; } } @@ -3073,8 +3157,7 @@ faxQueueApp::FIFOJobMessage(const fxStr& jobid, const char* msg) Trigger::post(Trigger::SEND_POLLDONE, *jp, msg+1); break; default: - traceServer("FIFO: Unknown job message \"%s\" for job %s", - msg, (const char*)jobid); + traceServer("FIFO: Unknown job message \"%s\" for job " | jobid, msg); break; } } @@ -3100,8 +3183,7 @@ faxQueueApp::FIFORecvMessage(const fxStr& devid, const char* msg) Trigger::post(Trigger::RECV_DOC, modem, msg+1); break; default: - traceServer("FIFO: Unknown recv message \"%s\" for modem %s", - msg, (const char*)devid); + traceServer("FIFO: Unknown recv message \"%s\" for modem " | devid,msg); break; } } @@ -3158,10 +3240,7 @@ faxQueueApp::numbertag faxQueueApp::numbers[] = { { "maxdials", &faxQueueApp::maxDials, (u_int) FAX_REDIALS }, { "jobreqother", &faxQueueApp::requeueInterval, FAX_REQUEUE }, { "polllockwait", &faxQueueApp::pollLockWait, 30 }, -}; - -faxQueueApp::booltag faxQueueApp::booleans[] = { -{ "jobcontrolwait", &faxQueueApp::jobCtrlWait, true }, +{ "staggercalls", &faxQueueApp::staggerCalls, 0 }, }; void @@ -3173,8 +3252,6 @@ faxQueueApp::setupConfig() (*this).*strings[i].p = (strings[i].def ? strings[i].def : ""); for (i = N(numbers)-1; i >= 0; i--) (*this).*numbers[i].p = numbers[i].def; - for (i = N(booleans)-1; i >= 0; i--) - (*this).*booleans[i].p = booleans[i].def; tod.reset(); // any day, any time use2D = true; // ok to use 2D data useUnlimitedLN = true; // ok to use LN_INF @@ -3184,6 +3261,7 @@ faxQueueApp::setupConfig() ModemGroup::set(MODEM_ANY, new RE(".*")); pageChop = FaxRequest::chop_last; pageChopThreshold = 3.0; // minimum of 3" of white space + lastCall = Sys::now() - 3600; } void @@ -3270,8 +3348,6 @@ faxQueueApp::setConfigItem(const char* tag, const char* value) break; case 2: UUCPLock::setLockTimeout(uucpLockTimeout); break; } - } else if (findTag(tag, (const tags*) booleans, N(booleans), ix)) { - (*this).*booleans[ix].p = getBoolean(value); } else if (streq(tag, "dialstringrules")) setDialRules(value); else if (streq(tag, "timeofday")) @@ -3427,9 +3503,9 @@ void faxQueueApp::notifySender(Job& job, JobStatus why, const char* duration) { fxStr cmd(notifyCmd - | quote | job.file | enquote - | quote | Job::jobStatusName(why) | enquote - | quote | duration | enquote + | quote | quoted(job.file) | enquote + | quote | quoted(Job::jobStatusName(why)) | enquote + | quote | quoted(duration) | enquote ); if (why == Job::requeued) { /* diff --git a/faxd/faxQueueApp.h b/faxd/faxQueueApp.h index 2fc4886..6c428a1 100644 --- a/faxd/faxQueueApp.h +++ b/faxd/faxQueueApp.h @@ -65,12 +65,9 @@ public: u_int faxQueueApp::*p; u_int def; }; - struct booltag { - const char* name; - bool faxQueueApp::*p; - bool def; - }; private: + time_t lastCall; // time of last call + class SchedTimeout : public IOHandler { private: bool started; @@ -103,6 +100,7 @@ private: mode_t uucpLockMode; // UUCP lock file creation mode u_int uucpLockTimeout; // UUCP stale lock file timeout u_int pollLockWait; // polling interval in lock wait state + u_int staggerCalls; // time to wait between initiating calls u_int tracingLevel; // tracing level w/o session u_int tracingMask; // tracing level control mask fxStr logFacility; // syslog facility to direct trace msgs @@ -122,11 +120,9 @@ private: fxStr sendUUCPCmd; // external command for UUCP calls fxStr wedgedCmd; // external command for wedged modems fxStr jobCtrlCmd; // external command for JobControl - bool jobCtrlWait; // Wait syncronously for JobControl to finish static stringtag strings[]; static numbertag numbers[]; - static booltag booleans[]; // runtime state bool timeout; // timeout occurred bool abortPrepare; // job preparation should be aborted @@ -141,7 +137,7 @@ private: SchedTimeout schedTimeout; // timeout for running scheduler DestInfoDict destJobs; // jobs organized by destination fxStrDict pendingDocs; // documents waiting for removal - bool inSchedule; + bool inSchedule; // indicates processing of runScheduler static faxQueueApp* _instance; @@ -183,6 +179,8 @@ private: void startTimeout(long ms); void stopTimeout(const char* whichdir); const fxStr& pickCmd(const FaxRequest& req); + void blockSignals(); + void releaseSignals(); // FIFO-related stuff void childStatus(pid_t, int); // Dispatcher hook int inputReady(int); // Dispatcher hook @@ -212,14 +210,12 @@ private: void blockJob(Job&, FaxRequest&, const char*); void delayJob(Job&, FaxRequest&, const char*, time_t); void rejectJob(Job& job, FaxRequest& req, const fxStr& reason); - bool submitJob(const fxStr& jobid, bool checkState = false); + bool submitJob(const fxStr& jobid, bool checkState = false, bool nascent = false); bool suspendJob(const fxStr& jobid, bool abortActive); void rejectSubmission(Job&, FaxRequest&, const fxStr& reason); - bool areBatchable(Job& job, Job& nextjob, FaxRequest& nextreq); - void batchJob(Job* prevjob, Job* nextjob, FaxRequest* nextreq); + bool areBatchable(FaxRequest& reqa, FaxRequest& reqb, Job& job); void setReadyToRun(Job& job, bool wait); - void setReady(Job& job); void setSleep(Job& job, time_t tts); void setDead(Job& job); void setActive(Job& job); @@ -228,7 +224,7 @@ private: bool submitJob(Job& job, FaxRequest& req, bool checkState = false); bool suspendJob(Job& job, bool abortActive); bool terminateJob(const fxStr& filename, JobStatus why); - void timeoutAccounting(Job& job, FaxRequest&); + void queueAccounting(Job& job, FaxRequest&, const char* type); void timeoutJob(Job& job); void timeoutJob(Job& job, FaxRequest&); void runJob(Job& job); diff --git a/faxd/faxSendApp.c++ b/faxd/faxSendApp.c++ index 01041d8..a49f290 100644 --- a/faxd/faxSendApp.c++ +++ b/faxd/faxSendApp.c++ @@ -121,7 +121,7 @@ faxSendApp::send(const char** filenames, int num) u_int batched = BATCH_FIRST; FaxSendStatus status = send_done; fxStr batchcommid, notice; - time_t retrybatchtts; + time_t retrybatchtts = 0; for (int i = 0; i < num; i++) { @@ -161,8 +161,11 @@ faxSendApp::send(const char** filenames, int num) * user-specification. */ - if (desiredDF != (u_int) -1) + bool usedf = false; // to limit RTFCC application + if (desiredDF != (u_int) -1) { req->desireddf = desiredDF; + usedf = true; + } if (desiredBR != (u_int) -1) req->desiredbr = desiredBR; if (desiredEC != (u_int) -1) @@ -175,7 +178,7 @@ faxSendApp::send(const char** filenames, int num) if (useJobTSI && req->tsi != "") FaxServer::setLocalIdentifier(req->tsi); - FaxServer::sendFax(*req, info, ai, batched); + FaxServer::sendFax(*req, info, ai, batched, usedf); batchcommid = req->commid; // ... to all batched jobs @@ -198,6 +201,8 @@ faxSendApp::send(const char** filenames, int num) ai.status = req->notice; retrybatchtts = req->tts; } + ai.jobinfo = fxStr::format("%u/%u/%u/%u/%u/%u/%u", + req->totpages, req->ntries, req->ndials, req->totdials, req->maxdials, req->tottries, req->maxtries); if (!ai.record("SEND")) logError("Error writing SEND accounting record, dest=%s", (const char*) ai.dest); @@ -329,7 +334,7 @@ faxSendApp::notifyPageSent(FaxRequest& req, const char* filename) case 0: sendJobStatus(req.jobid, "d%s", (const char*) si.encode()); sleep(1); // XXX give parent time - exit(0); + _exit(0); case -1: logError("Can not fork for non-priority logging."); sendJobStatus(req.jobid, "d%s", (const char*) si.encode()); @@ -387,17 +392,19 @@ faxSendApp::notifyPollRecvd(FaxRequest& req, FaxRecvInfo& ri) CallID empty_callid; ai.callid = empty_callid; ri.params.asciiEncode(ai.faxdcs); + ai.jobinfo = fxStr::format("%u/%u/%u/%u/%u/%u/%u", + req.totpages, req.ntries, req.ndials, req.totdials, req.maxdials, req.tottries, req.maxtries); if (!ai.record("POLL")) logError("Error writing POLL accounting record, dest=%s", (const char*) ai.dest); // hand to delivery/notification command fxStr cmd(pollRcvdCmd - | quote | req.mailaddr | enquote - | quote | ri.qfile | enquote - | quote | getModemDeviceID() | enquote - | quote | ai.commid | enquote - | quote | ri.reason | enquote + | quote | quoted(req.mailaddr) | enquote + | quote | quoted(ri.qfile) | enquote + | quote | quoted(getModemDeviceID()) | enquote + | quote | quoted(ai.commid) | enquote + | quote | quoted(ri.reason) | enquote ); traceServer("RECV POLL: %s", (const char*) cmd); setProcessPriority(BASE); // lower priority diff --git a/faxd/mkhash.c b/faxd/mkhash.c index 6cc88d5..cd7fd25 100644 --- a/faxd/mkhash.c +++ b/faxd/mkhash.c @@ -79,6 +79,7 @@ main() hash("pagehandling"); hash("modem"); hash("faxnumber"); + hash("faxname"); hash("tsi"); hash("receiver"); hash("company"); diff --git a/faxd/pageSendApp.c++ b/faxd/pageSendApp.c++ index ebbf82f..4fa5b6f 100644 --- a/faxd/pageSendApp.c++ +++ b/faxd/pageSendApp.c++ @@ -120,7 +120,7 @@ pageSendApp::send(const char** filenames, int num) u_int batched = BATCH_FIRST; FaxSendStatus status = send_done; fxStr batchcommid, notice; - time_t retrybatchtts; + time_t retrybatchtts = 0; for (int i = 0; i < num; i++) { if (i+1 == num) @@ -165,6 +165,8 @@ pageSendApp::send(const char** filenames, int num) ai.status = ""; else ai.status = req->notice; + ai.jobinfo = fxStr::format("%u/%u/%u/%u/%u/%u/%u", + req->totpages, req->ntries, req->ndials, req->totdials, req->maxdials, req->tottries, req->maxtries); if (!ai.record("PAGE")) logError("Error writing %s accounting record, dest=%s", "PAGE", (const char*) ai.dest); @@ -245,7 +247,7 @@ pageSendApp::sendPage(FaxRequest& req, FaxMachineInfo& info, u_int& batched) if (info.getPagerTTYParity() != "") pagerTTYParity = info.getPagerTTYParity(); // NB: may need to set tty baud rate here XXX - if (!(batched & BATCH_FIRST) || setupModem(true)) { + if (!(batched & BATCH_FIRST) || setupModem(true)) { changeState(SENDING); setServerStatus("Sending page " | req.jobid); /* @@ -254,7 +256,7 @@ pageSendApp::sendPage(FaxRequest& req, FaxMachineInfo& info, u_int& batched) */ fxStr msg; if (prepareMsg(req, info, msg)) - sendPage(req, info, prepareDialString(req.number), msg, batched); + sendPage(req, info, prepareDialString(req.number), msg, batched); } else sendFailed(req, send_retry, "Can not setup modem", 4*pollModemWait); @@ -340,7 +342,7 @@ pageSendApp::sendPage(FaxRequest& req, FaxMachineInfo& info, const fxStr& number CallStatus callstat; if (batched & BATCH_FIRST) - callstat = getModem()->dial(number, notice); + callstat = getModem()->dial(number, req.faxnumber, notice); else callstat = ClassModem::OK; if (callstat == ClassModem::OK) @@ -1174,7 +1176,7 @@ pageSendApp::sendUcpMsg(FaxRequest& req, FaxItem& preq, const fxStr& msg, fxStr& u_int unknown = 0; // count of unknown responses time_t start = Sys::now(); do { - u_int len = getResponse(resp, ixoXmitTimeout); + (void) getResponse(resp, ixoXmitTimeout); const fxStr str = (const char*)resp; //readUcpResponse(str); fxStr tmp; diff --git a/faxd/tagtest.c++ b/faxd/tagtest.c++ index 517ca10..12f1374 100644 --- a/faxd/tagtest.c++ +++ b/faxd/tagtest.c++ @@ -164,7 +164,7 @@ imageTagLine(u_char* buf, u_int fillorder, const Class2Params& params, u_long& t */ u_int w = params.pageWidth(); u_int h = (tagLineFont->fontHeight()*2)+MARGIN_TOP+MARGIN_BOT; // max height - double VR_FINE - u_int th; // actual tagline height + u_int th = 0; // actual tagline height switch(params.vr) { case VR_NORMAL: case VR_200X100: @@ -304,7 +304,7 @@ imageTagLine(u_char* buf, u_int fillorder, const Class2Params& params, u_long& t u_int bpl = sizeof(u_long) * 8; // bits per u_long for (u_int nl = lpr/2 - 1; nl ; nl--) { // make 2 longs out of 1 (ABCD -> AABB CCDD) - int pos; + int pos = 0; for (u_int i = 0; i < (bpl/8); i++) { if (i == 0 || i == bpl/8/2) { *l2 = (u_long) 0; @@ -401,7 +401,7 @@ fatal(const char* fmt ...) int main(int argc, char* argv[]) { - extern int optind, opterr; + extern int optind; extern char* optarg; int c; const char* output = "t.tif"; @@ -413,7 +413,7 @@ main(int argc, char* argv[]) setlocale(LC_TIME, ""); // for strftime calls #endif appName = argv[0]; - while ((c = getopt(argc, argv, "f:m:o:")) != -1) + while ((c = Sys::getopt(argc, argv, "f:m:o:")) != -1) switch (c) { case 'f': tagLineFontFile = optarg; diff --git a/faxd/tif_fax3.h b/faxd/tif_fax3.h index ece58ea..f652fb6 100644 --- a/faxd/tif_fax3.h +++ b/faxd/tif_fax3.h @@ -291,17 +291,17 @@ static const char* StateNames[] = { #define CLEANUP_RUNS() do { \ if (RunLength) \ SETVAL(0); \ - if (a0 != lastx) { \ - badlength(a0, lastx); \ - while (a0 > lastx && pa > thisrun) \ + if ((tiff_runlen_t)a0 != lastx) { \ + badlength((tiff_runlen_t)a0, lastx); \ + while ((tiff_runlen_t)a0 > lastx && pa > thisrun) \ a0 -= *--pa; \ - if (a0 < lastx) { \ + if ((tiff_runlen_t)a0 < lastx) { \ if (a0 < 0) \ a0 = 0; \ if ((pa-thisrun)&1) \ SETVAL(0); \ SETVAL(lastx - a0); \ - } else if (a0 > lastx) { \ + } else if ((tiff_runlen_t)a0 > lastx) { \ SETVAL(lastx); \ SETVAL(0); \ } \ @@ -342,7 +342,7 @@ static const char* StateNames[] = { } \ } \ doneWhite1d: \ - if (a0 >= lastx) \ + if ((tiff_runlen_t)a0 >= lastx) \ goto done1d; \ for (;;) { \ LOOKUP16(13, TIFFFaxBlackTable, eof1d); \ @@ -364,7 +364,7 @@ static const char* StateNames[] = { } \ } \ doneBlack1d: \ - if (a0 >= lastx) \ + if ((tiff_runlen_t)a0 >= lastx) \ goto done1d; \ if( *(pa-1) == 0 && *(pa-2) == 0 ) \ pa -= 2; \ @@ -382,7 +382,7 @@ done1d: \ * of runs for the reference line. */ #define CHECK_b1 do { \ - if (pa != thisrun) while (b1 <= a0 && b1 < lastx) { \ + if (pa != thisrun) while (b1 <= a0 && (tiff_runlen_t)b1 < lastx) { \ b1 += pb[0] + pb[1]; \ pb += 2; \ } \ @@ -392,7 +392,7 @@ done1d: \ * Expand a row of 2D-encoded data. */ #define EXPAND2D(eoflab) do { \ - while (a0 < lastx) { \ + while ((tiff_runlen_t)a0 < lastx) { \ LOOKUP8(7, TIFFFaxMainTable, eof2d); \ switch (TabEnt->State) { \ case S_Pass: \ @@ -516,7 +516,7 @@ done1d: \ } \ } \ if (RunLength) { \ - if (RunLength + a0 < lastx) { \ + if ((tiff_runlen_t)(RunLength + a0) < lastx) { \ /* expect a final V0 */ \ NeedBits8(1,eof2d); \ if (!GetBits(1)) \ diff --git a/faxd/tsitest.c++ b/faxd/tsitest.c++ index 63c0813..cbf5992 100644 --- a/faxd/tsitest.c++ +++ b/faxd/tsitest.c++ @@ -39,6 +39,7 @@ #include "REArray.h" #include "BoolArray.h" #include "Str.h" +#include "Sys.h" fxStr qualifyTSI; REArray* tsiPats = NULL; // recv tsi patterns @@ -139,12 +140,11 @@ int main(int argc, char* argv[]) { bool verbose = true; - extern int optind, opterr; - extern char* optarg; + extern int optind; int c; appName = argv[0]; - while ((c = getopt(argc, argv, ":q")) != -1) + while ((c = Sys::getopt(argc, argv, ":q")) != -1) switch (c) { case 'q': verbose = false;