// // RFSV16 - An implementation of the PSION SIBO RFSV Client protocol // // Copyright (C) 1999 Philip Proudman // Modifications for plptools: // Copyright (C) 1999 Matt J. Gumbley // Sources: rfsv32.cc by Fritz Elfert, and rfsv16.cc by Philip Proudman // Descriptions of the RFSV16 protocol by Michael Pieper, Olaf Flebbe & Me. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // e-mail philip.proudman@btinternet.com #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "bool.h" #include "rfsv16.h" #include "bufferstore.h" #include "ppsocket.h" #include "bufferarray.h" #define RFSV16_MAXDATALEN 852 // 640 rfsv16::rfsv16(ppsocket *_skt) : serNum(0) { skt = _skt; reset(); } // move to base class? rfsv16::~rfsv16() { bufferStore a; a.addStringT("Close"); if (status == E_PSI_GEN_NONE) skt->sendBufferStore(a); skt->closeSocket(); } // move to base class? void rfsv16:: reconnect() { skt->closeSocket(); skt->reconnect(); serNum = 0; reset(); } // move to base class? void rfsv16:: reset() { bufferStore a; status = E_PSI_FILE_DISC; a.addStringT(getConnectName()); if (skt->sendBufferStore(a)) { if (skt->getBufferStore(a) == 1) { if (!strcmp(a.getString(0), "Ok")) status = E_PSI_GEN_NONE; } } } // move to base class? long rfsv16:: getStatus() { return status; } // move to base class? const char *rfsv16:: getConnectName() { return "SYS$RFSV"; } int rfsv16:: convertName(const char* orig, char *retVal) { int len = strlen(orig); char *temp = new char [len+1]; // FIXME: need to return 1 if OOM? for (int i=0; i <= len; i++) { if (orig[i] == '/') temp[i] = '\\'; else temp[i] = orig[i]; } if (len == 0 || temp[1] != ':') { // We can automatically supply a drive letter strcpy(retVal, DDRIVE); if (len == 0 || temp[0] != '\\') { strcat(retVal, DBASEDIR); } else { retVal[strlen(retVal)-1] = 0; } strcat(retVal, temp); } else { strcpy(retVal, temp); } delete [] temp; cout << retVal << endl; return 0; } long rfsv16:: fopen(long attr, const char *name, long &handle) { bufferStore a; char realName[200]; int rv = convertName(name, realName); if (rv) return (long)rv; // FIXME: anything that calls fopen should NOT do the name // conversion - it's just done here. a.addWord(attr & 0xFFFF); a.addString(realName); a.addByte(0x00); // Needs to be manually Null-Terminated. if (!sendCommand(FOPEN, a)) return E_PSI_FILE_DISC; long res = getResponse(a); if (res == 0) { handle = (long)a.getWord(0); return 0; } return res; } // internal long rfsv16:: mktemp(long *handle, char *tmpname) { bufferStore a; // FIXME: anything that calls fopen should NOT do the name // conversion - it's just done here. a.addWord(P_FUNIQUE); a.addString("TMP"); a.addByte(0x00); // Needs to be manually Null-Terminated. if (!sendCommand(OPENUNIQUE, a)) return E_PSI_FILE_DISC; long res = getResponse(a); if (res == 0) { *handle = a.getWord(0); strcpy(tmpname, a.getString(2)); return 0; } return res; } // internal and external long rfsv16:: fcreatefile(long attr, const char *name, long &handle) { return fopen(attr | P_FCREATE, name, handle); } // this is internal - not used by plpnfsd, unlike fcreatefile long rfsv16:: freplacefile(long attr, const char *name, long &handle) { return fopen(attr | P_FREPLACE, name, handle); } // internal long rfsv16:: fopendir(long attr, const char *name, long &handle) { cerr << "rfsv16::fopendir ***" << endl; return 0; } long rfsv16:: fclose(long fileHandle) { bufferStore a; a.addWord(fileHandle & 0xFFFF); if (!sendCommand(FCLOSE, a)) return E_PSI_FILE_DISC; return getResponse(a); } long rfsv16:: dir(const char *dirName, bufferArray * files) { long fileHandle; long res; long status = fopen(P_FDIR, dirName, fileHandle); if (status != 0) { return status; } while (1) { bufferStore a; a.addWord(fileHandle & 0xFFFF); if (!sendCommand(FDIRREAD, a)) return E_PSI_FILE_DISC; res = getResponse(a); if (res) break; a.discardFirstBytes(2); // Don't know what these mean! while (a.getLen() > 16) { int version = a.getWord(0); if (version != 2) { cerr << "dir: not version 2" << endl; return 1; } int status = a.getWord(2); long size = a.getDWord(4); long date = a.getDWord(8); const char *s = a.getString(16); a.discardFirstBytes(17+strlen(s)); bufferStore temp; temp.addDWord(date); temp.addDWord(size); temp.addDWord((long)status); temp.addStringT(s); files->append(temp); } } if ((short int)res == E_PSI_FILE_EOF) res = 0; fclose(fileHandle); return res; } char * rfsv16:: opAttr(long attr) { static char buf[11]; buf[0] = ((attr & rfsv16::P_FAWRITE) ? 'w' : '-'); buf[1] = ((attr & rfsv16::P_FAHIDDEN) ? 'h' : '-'); buf[2] = ((attr & rfsv16::P_FASYSTEM) ? 's' : '-'); buf[3] = ((attr & rfsv16::P_FAVOLUME) ? 'v' : '-'); buf[4] = ((attr & rfsv16::P_FADIR) ? 'd' : '-'); buf[5] = ((attr & rfsv16::P_FAMOD) ? 'm' : '-'); buf[6] = ((attr & rfsv16::P_FAREAD) ? 'r' : '-'); buf[7] = ((attr & rfsv16::P_FAEXEC) ? 'x' : '-'); buf[8] = ((attr & rfsv16::P_FASTREAM) ? 'b' : '-'); buf[9] = ((attr & rfsv16::P_FATEXT) ? 't' : '-'); buf[10] = '\0'; return (char *) (&buf); } long rfsv16:: opMode(long mode) { long ret = 0; ret |= ((mode & 03) == PSI_O_RDONLY) ? 0 : P_FUPDATE; ret |= (mode & PSI_O_TRUNC) ? P_FREPLACE : 0; ret |= (mode & PSI_O_CREAT) ? P_FCREATE : 0; ret |= (mode & PSI_O_APPEND) ? P_FAPPEND : 0; if ((mode & 03) == PSI_O_RDONLY) ret |= (mode & PSI_O_EXCL) ? 0 : P_FSHARE; return ret; } long rfsv16:: fgetmtime(const char *name, long *mtime) { cerr << "rfsv16::fgetmtime" << endl; // NB: fgetattr, fgeteattr is almost identical... bufferStore a; char realName[200]; int rv = convertName(name, realName); if (rv) return rv; a.addString(realName); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. if (!sendCommand(FINFO, a)) return E_PSI_FILE_DISC; long res = getResponse(a); if (res != 0) { cerr << "fgetmtime: Error " << res << " on file " << name << endl; return res; } else if (a.getLen() == 16) { *mtime = a.getDWord(8); return res; } cerr << "fgetmtime: Unknown response (" << name << ") " << a < 16) { int version = a.getWord(0); if (version != 2) { cerr << "dir: not version 2" << endl; return 1; } // int status = a.getWord(2); // long size = a.getDWord(4); // long date = a.getDWord(8); const char *s = a.getString(16); a.discardFirstBytes(17+strlen(s)); (*count)++; } } if ((short int)res == E_PSI_FILE_EOF) res = 0; fclose(fileHandle); return res; } long rfsv16:: devlist(long *devbits) { long res; long fileHandle; *devbits = 0; // The following is taken from a trace between a Series 3c and PsiWin. // Hope it works! We PARSE to find the correct node, then FOPEN // (P_FDEVICE) this, FDEVICEREAD each entry, setting the appropriate // drive-letter-bit in devbits, then FCLOSE. bufferStore a; a.init(); a.addByte(0x00); // no Name 1 a.addByte(0x00); // no Name 2 a.addByte(0x00); // no Name 3 if (!sendCommand(PARSE, a)) return E_PSI_FILE_DISC; res = getResponse(a); if (res) return res; // Find the drive to FOPEN char name[4] = { 'x', ':', '\\', '\0' } ; a.discardFirstBytes(6); // Result, fsys, dev, path, file, file, ending, flag /* This leaves R E M : : M : \ */ name[0] = (char) a.getByte(5); // the M long status = fopen(P_FDEVICE, name, fileHandle); if (status != 0) { return status; } while (1) { bufferStore a; a.init(); a.addWord(fileHandle & 0xFFFF); if (!sendCommand(FDEVICEREAD, a)) return E_PSI_FILE_DISC; res = getResponse(a); if (res) break; int version = a.getWord(0); if (version != 2) { cerr << "devlist: not version 2" << endl; return 1; // FIXME } char drive = a.getByte(64); if (drive >= 'A' && drive <= 'Z') { int shift = (drive - 'A'); (*devbits) |= (long) ( 1 << shift ); } else { cerr << "devlist: non-alphabetic drive letter (" << drive << ")" << endl; } } if ((short int)res == E_PSI_FILE_EOF) res = 0; fclose(fileHandle); return res; } char *rfsv16:: devinfo(int devnum, long *vfree, long *vtotal, long *vattr, long *vuniqueid) { bufferStore a; long res; long fileHandle; // Again, this is taken from an excahnge between PsiWin and a 3c. // For each drive, we PARSE with its drive letter to get a response // (which we ignore), then do a STATUSDEVICE to get the info. a.init(); a.addByte((char) (devnum + 'A')); // Name 1 a.addByte(':'); a.addByte(0x00); a.addByte(0x00); // No name 2 a.addByte(0x00); // No name 3 if (!sendCommand(PARSE, a)) return NULL; res = getResponse(a); if (res) { // cerr << "devinfo PARSE res is " << dec << (signed short int) res << endl; return NULL; } a.init(); a.addByte((char) (devnum + 'A')); // Name 1 a.addByte(':'); a.addByte('\\'); a.addByte(0x00); if (!sendCommand(STATUSDEVICE, a)) return NULL; res = getResponse(a); if (res) { // cerr << "devinfo STATUSDEVICE res is " << dec << (signed short int) res << endl; return NULL; } int type = a.getWord(2); int changeable = a.getWord(4); long size = a.getDWord(6); long free = a.getDWord(10); const char *volume = a.getString(14); int battery = a.getWord(30); const char *devicename = a.getString(62); *vfree = free; *vtotal = size; *vattr = type; *vuniqueid = 0; static char name[2] = { 'x', '\0' }; name[0] = (char) (devnum + 'A'); return strdup(name); } bool rfsv16:: sendCommand(enum commands cc, bufferStore & data) { if (status == E_PSI_FILE_DISC) { reconnect(); if (status == E_PSI_FILE_DISC) return FALSE; } bool result; bufferStore a; a.addWord(cc); a.addWord(data.getLen()); a.addBuff(data); result = skt->sendBufferStore(a); if (!result) { reconnect(); result = skt->sendBufferStore(a); if (!result) status = E_PSI_FILE_DISC; } return result; } long rfsv16:: getResponse(bufferStore & data) { // getWord(2) is the size field // which is the body of the response not counting the command (002a) and // the size word. if (skt->getBufferStore(data) != 1) { cerr << "rfsv16::getResponse: duff response. " "getBufferStore failed." << endl; } else if (data.getWord(0) == 0x2a && data.getWord(2) == data.getLen()-4) { long ret = (short)data.getWord(4); data.discardFirstBytes(6); return ret; } else { cerr << "rfsv16::getResponse: duff response. Size field:" << data.getWord(2) << " Frame size:" << data.getLen()-4 << " Result field:" << data.getWord(4) << endl; } status = E_PSI_FILE_DISC; return status; } char * rfsv16:: opErr(long status) { cerr << "rfsv16::opErr 0x" << hex << setfill('0') << setw(4) << status << " (" << dec << (signed short int)status << ")" << endl; return rfsv::opErr(status); } long rfsv16:: fread(long handle, unsigned char *buf, long len) { long res; long count = 0; while (count < len) { bufferStore a; // Read in blocks of 291 bytes; the maximum payload for // an RFSV frame. ( As seen in traces ) - this isn't optimal: // RFSV can handle fragmentation of frames, where only the // first FREAD RESPONSE frame has a RESPONSE (00 2A), SIZE // and RESULT field. Every subsequent frame // just has data, 297 bytes (or less) of it. a.addWord(handle); a.addWord((len - count) > RFSV16_MAXDATALEN ? RFSV16_MAXDATALEN : (len - count)); sendCommand(FREAD, a); res = getResponse(a); // The rest of the code treats a 0 return from here // as meaning EOF, so we'll arrange for that to happen. if (res == E_PSI_FILE_EOF) return count; else if (res < 0) return res; res = a.getLen(); memcpy(buf, a.getString(), res); count += res; buf += res; } return count; } long rfsv16:: fwrite(long handle, unsigned char *buf, long len) { long res; long count = 0; while (count < len) { bufferStore a; int nbytes; // Write in blocks of 291 bytes; the maximum payload for // an RFSV frame. ( As seen in traces ) - this isn't optimal: // RFSV can handle fragmentation of frames, where only the // first FREAD RESPONSE frame has a RESPONSE (00 2A), SIZE // and RESULT field. Every subsequent frame // just has data, 297 bytes (or less) of it. nbytes = (len - count) > RFSV16_MAXDATALEN ? RFSV16_MAXDATALEN : (len - count); a.addWord(handle); a.addBytes(buf, nbytes); sendCommand(FWRITE, a); res = getResponse(a); if (res != 0) return res; count += nbytes; buf += nbytes; } return count; } long rfsv16:: copyFromPsion(const char *from, const char *to, cpCallback_t cb) { long handle; long res; long len; if ((res = fopen(P_FSHARE | P_FSTREAM, from, handle)) != 0) return res; ofstream op(to); if (!op) { fclose(handle); return -1; } do { unsigned char buf[RFSV_SENDLEN]; if ((len = fread(handle, buf, sizeof(buf))) > 0) op.write(buf, len); if (cb) { if (!cb(len)) { len = E_PSI_FILE_CANCEL; break; } } } while (len > 0); fclose(handle); op.close(); return len == E_PSI_FILE_EOF ? 0 : len; } long rfsv16:: copyToPsion(const char *from, const char *to, cpCallback_t cb) { long handle; long res; ifstream ip(from); if (!ip) return E_PSI_FILE_NXIST; res = fcreatefile(P_FSTREAM | P_FUPDATE, to, handle); if (res != 0) { res = freplacefile(P_FSTREAM | P_FUPDATE, to, handle); if (res != 0) return res; } unsigned char *buff = new unsigned char[RFSV_SENDLEN]; while (res >= 0 && ip && !ip.eof()) { ip.read(buff, RFSV_SENDLEN); res = fwrite(handle, buff, ip.gcount()); if (cb) if (!cb(res)) { res = E_PSI_FILE_CANCEL; } } delete[]buff; fclose(handle); ip.close(); return 0; } long rfsv16:: fsetsize(long handle, long size) { bufferStore a; a.addWord(handle); a.addDWord(size); if (!sendCommand(FSETEOF, a)) return E_PSI_FILE_DISC; return getResponse(a); } /* * Unix-like implementation off fseek with one * exception: If seeking beyond eof, the gap * contains garbage instead of zeroes. */ long rfsv16:: fseek(long handle, long pos, long mode) { bufferStore a; long res; long savpos = 0; long realpos; long calcpos = 0; /* seek-parameter for psion: dword position dword handle dword mode 1 = from start 2 = from current pos 3 = from end ??no more?? 4 = sense recpos ??no more?? 5 = set recpos ??no more?? 6 = text-rewind */ if ((mode < PSI_SEEK_SET) || (mode > PSI_SEEK_END)) return E_PSI_GEN_ARG; if ((mode == PSI_SEEK_CUR) && (pos >= 0)) { /* get and save current position */ a.init(); a.addWord(handle); a.addDWord(0); a.addWord(PSI_SEEK_CUR); if (!sendCommand(FSEEK, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != 0) return res; savpos = a.getDWord(0); if (pos == 0) return savpos; } if ((mode == PSI_SEEK_END) && (pos >= 0)) { /* get and save end position */ a.init(); a.addWord(handle); a.addDWord(0); a.addWord(PSI_SEEK_END); if (!sendCommand(FSEEK, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != 0) return res; savpos = a.getDWord(0); if (pos == 0) return savpos; } /* Now the real seek */ a.addWord(handle); a.addDWord(pos); a.addWord(mode); if (!sendCommand(FSEEK, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != 0) return res; realpos = a.getDWord(0); switch (mode) { case PSI_SEEK_SET: calcpos = pos; break; case PSI_SEEK_CUR: calcpos = savpos + pos; break; case PSI_SEEK_END: return realpos; break; } if (calcpos > realpos) { /* Beyond end of file */ res = fsetsize(handle, calcpos); if (res != 0) return res; a.init(); a.addWord(handle); a.addDWord(calcpos); a.addWord(PSI_SEEK_SET); if (!sendCommand(FSEEK, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != 0) return res; realpos = a.getDWord(0); } return realpos; } long rfsv16:: mkdir(const char* dirName) { char realName[200]; int rv = convertName(dirName, realName); if (rv) return rv; bufferStore a; a.addString(realName); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. sendCommand(MKDIR, a); long res = getResponse(a); if (!res) { // Correct response return res; } cerr << "Unknown response from mkdir "<< res <