// // 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; status = rfsv::E_PSI_FILE_DISC; skt = _skt; reset(); } Enum 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 E_PSI_GEN_NONE; } Enum rfsv16:: fopen(long attr, const char *name, long &handle) { bufferStore a; char realName[200]; Enum rv = convertName(name, realName); if (rv) return 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; Enum res = getResponse(a); if (res == 0) { handle = (long)a.getWord(0); return E_PSI_GEN_NONE; } return res; } // internal Enum rfsv16:: mktemp(long &handle, char * const 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; Enum res = getResponse(a); if (res == E_PSI_GEN_NONE) { handle = a.getWord(0); if (tmpname) strcpy(tmpname, a.getString(2)); return res; } return res; } // internal and external Enum 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 Enum rfsv16:: freplacefile(long attr, const char *name, long &handle) { return fopen(attr | P_FREPLACE, name, handle); } Enum rfsv16:: fopendir(const char * const name, long &handle) { return fopen(P_FDIR, name, handle); } Enum rfsv16:: fclose(long fileHandle) { bufferStore a; a.addWord(fileHandle & 0xFFFF); if (!sendCommand(FCLOSE, a)) return E_PSI_FILE_DISC; return getResponse(a); } Enum rfsv16:: dir(const char * const dirName, bufferArray &files) { long fileHandle; Enum res = fopendir(dirName, fileHandle); if (res != E_PSI_GEN_NONE) return res; while (1) { bufferStore a; a.addWord(fileHandle & 0xFFFF); if (!sendCommand(FDIRREAD, a)) return E_PSI_FILE_DISC; res = getResponse(a); if (res != E_PSI_GEN_NONE) 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; fclose(fileHandle); return E_PSI_GEN_FAIL; } long attr = attr2std((long)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(attr); temp.addStringT(s); files += temp; } } if (res == E_PSI_FILE_EOF) res = E_PSI_GEN_NONE; fclose(fileHandle); return res; } 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; } Enum rfsv16:: fgetmtime(const char * const name, PsiTime &mtime) { cerr << "rfsv16::fgetmtime" << endl; // NB: fgetattr, fgeteattr is almost identical... bufferStore a; char realName[200]; Enum 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; Enum res = getResponse(a); if (res != E_PSI_GEN_NONE) { cerr << "fgetmtime: Error " << res << " on file " << name << endl; return res; } else if (a.getLen() == 16) { // struct timeval tv; // tv.tv_sec = a.getDWord(8); // tv.tv_usec = 0; // mtime.setUnixTime(&tv); mtime.setUnixTime(a.getDWord(8)); return res; } cerr << "fgetmtime: Unknown response (" << name << ") " << a < rfsv16:: fsetmtime(const char *name, PsiTime mtime) { cerr << "rfsv16::fsetmtime ***" << endl; // I don't think there's a protocol frame that allows us to set the // modification time. SFDATE allows setting of creation time... return E_PSI_NOT_SIBO; } Enum rfsv16:: fgetattr(const char * const name, long &attr) { // NB: fgetmtime, fgeteattr are almost identical... bufferStore a; char realName[200]; Enum 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; Enum res = getResponse(a); if (res != 0) { cerr << "fgetattr: Error " << res << " on file " << name << endl; return res; } else if (a.getLen() == 16) { attr = attr2std((long)a.getWord(2)); return res; } cerr << "fgetattr: Unknown response (" << name << ") " << a < rfsv16:: fgeteattr(const char * const name, long &attr, long &size, PsiTime &time) { bufferStore a; char realName[200]; Enum 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; Enum res = getResponse(a); if (res != 0) { cerr << "fgeteattr: Error " << res << " on file " << name << endl; return res; } else if (a.getLen() == 16) { attr = a.getWord(2); size = a.getDWord(4); time.setUnixTime(a.getDWord(8)); //time = a.getDWord(8); return res; } cerr << "fgeteattr: Unknown response (" << name << ") " << a < rfsv16:: fsetattr(const char *name, long seta, long unseta) { cerr << "rfsv16::fsetattr" << endl; // seta are attributes to set; unseta are attributes to unset. Need to // turn this into attributes to change state and a bit mask. // 210000 // 008421 // a shr long statusword = std2attr(seta) & (~ std2attr(unseta)); statusword ^= 0x0000001; // r bit is inverted long bitmask = std2attr(seta) | std2attr(unseta); // cerr << "seta is " << hex << setw(2) << setfill('0') << seta << endl; // cerr << "unseta is " << hex << setw(2) << setfill('0') << unseta << endl; // cerr << "statusword is " << hex << setw(2) << setfill('0') << statusword << endl; // cerr << "bitmask is " << hex << setw(2) << setfill('0') << bitmask << endl; bufferStore a; a.addWord(statusword & 0xFFFF); a.addWord(bitmask & 0xFFFF); a.addString(name); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. if (!sendCommand(SFSTAT, a)) return E_PSI_FILE_DISC; return getResponse(a); } Enum rfsv16:: dircount(const char * const name, long &count) { long fileHandle; Enum res; count = 0; res = fopen(P_FDIR, name, fileHandle); if (res != E_PSI_GEN_NONE) return res; while (1) { bufferStore a; a.addWord(fileHandle & 0xFFFF); if (!sendCommand(FDIRREAD, a)) return E_PSI_FILE_DISC; res = getResponse(a); if (res != E_PSI_GEN_NONE) 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; fclose(fileHandle); return E_PSI_GEN_FAIL; } // 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 (res == E_PSI_FILE_EOF) res = E_PSI_GEN_NONE; fclose(fileHandle); return res; } Enum rfsv16:: devlist(long &devbits) { Enum 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 != E_PSI_GEN_NONE) 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 res = fopen(P_FDEVICE, name, fileHandle); if (res != E_PSI_GEN_NONE) 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; fclose(fileHandle); return E_PSI_GEN_FAIL; // 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 (res == E_PSI_FILE_EOF) res = E_PSI_GEN_NONE; fclose(fileHandle); return res; } Enum rfsv16:: devinfo(const int devnum, long &free, long &size, long &attr, long &uniqueid, char * const name) { bufferStore a; Enum res; // long fileHandle; // Again, this is taken from an exchange 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 E_PSI_FILE_DISC; if ((res = getResponse(a)) != E_PSI_GEN_NONE) { // cerr << "devinfo PARSE res is " << dec << (signed short int) res << endl; return res; } a.init(); a.addByte((char) (devnum + 'A')); // Name 1 a.addByte(':'); a.addByte('\\'); a.addByte(0x00); if (!sendCommand(STATUSDEVICE, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != E_PSI_GEN_NONE) { // cerr << "devinfo STATUSDEVICE res is " << dec << (signed short int) res << endl; return res; } attr = a.getWord(2); // int changeable = a.getWord(4); size = a.getDWord(6); free = a.getDWord(10); // const char *volume = a.getString(14); // int battery = a.getWord(30); // const char *devicename = a.getString(62); uniqueid = 0; if (name) { name[0] = (char) (devnum + 'A'); name[1] = '\0'; } return res; } 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; } Enum 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) { Enum ret = (enum errs)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; } Enum rfsv16:: fread(const long handle, unsigned char * const buf, const long len, long &count) { Enum res; unsigned char *p = buf; 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)); if (!sendCommand(FREAD, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != E_PSI_GEN_NONE) { if (res == E_PSI_FILE_EOF) return E_PSI_GEN_NONE; return res; } long l = a.getLen(); memcpy(buf, a.getString(), l); count += l; p += l; } return res; } Enum rfsv16:: fwrite(const long handle, const unsigned char * const buf, const long len, long &count) { Enum res; const unsigned char *p = buf; 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(p, nbytes); if (!sendCommand(FWRITE, a)) return E_PSI_FILE_DISC; if ((res = getResponse(a)) != E_PSI_GEN_NONE) return res; count += nbytes; p += nbytes; } return res; } Enum rfsv16:: copyFromPsion(const char *from, const char *to, cpCallback_t cb) { long handle; Enum res; long len; long total = 0; if ((res = fopen(P_FSHARE | P_FSTREAM, from, handle)) != E_PSI_GEN_NONE) return res; ofstream op(to); if (!op) { fclose(handle); return E_PSI_GEN_FAIL; } do { unsigned char buf[RFSV_SENDLEN]; if ((res = fread(handle, buf, sizeof(buf), len)) == E_PSI_GEN_NONE) { if (len > 0) op.write(buf, len); total += len; if (cb && !cb(total)) res = E_PSI_FILE_CANCEL; } } while (len > 0 && (res == E_PSI_GEN_NONE)); fclose(handle); op.close(); if (res == E_PSI_FILE_EOF) res = E_PSI_GEN_NONE; return res; } Enum rfsv16:: copyToPsion(const char *from, const char *to, cpCallback_t cb) { long handle; long len = 0; long total = 0; Enum res; ifstream ip(from); if (!ip) return E_PSI_FILE_NXIST; res = fcreatefile(P_FSTREAM | P_FUPDATE, to, handle); if (res != E_PSI_GEN_NONE) { res = freplacefile(P_FSTREAM | P_FUPDATE, to, handle); if (res != E_PSI_GEN_NONE) return res; } unsigned char *buff = new unsigned char[RFSV_SENDLEN]; while (res == E_PSI_GEN_NONE && ip && !ip.eof()) { ip.read(buff, RFSV_SENDLEN); if ((res = fwrite(handle, buff, ip.gcount(), len)) == E_PSI_GEN_NONE) { total += len; if (cb && !cb(total)) res = E_PSI_FILE_CANCEL; } } delete[]buff; fclose(handle); ip.close(); return res; } Enum 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. */ Enum rfsv16:: fseek(const long handle, const long pos, const long mode, long &resultpos) { bufferStore a; Enum 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)) != E_PSI_GEN_NONE) return res; savpos = a.getDWord(0); if (pos == 0) { resultpos = savpos; return res; } } 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)) != E_PSI_GEN_NONE) return res; savpos = a.getDWord(0); if (pos == 0) { resultpos = savpos; return res; } } /* 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: resultpos = realpos; return res; break; } if (calcpos > realpos) { /* Beyond end of file */ res = fsetsize(handle, calcpos); if (res != E_PSI_GEN_NONE) 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); } resultpos = realpos; return res; } Enum rfsv16:: mkdir(const char* dirName) { char realName[200]; Enum res = convertName(dirName, realName); if (res != E_PSI_GEN_NONE) return res; bufferStore a; a.addString(realName); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. sendCommand(MKDIR, a); res = getResponse(a); if (res == E_PSI_GEN_NONE) { // Correct response return res; } cerr << "Unknown response from mkdir "<< res < rfsv16:: rmdir(const char *dirName) { // There doesn't seem to be an RMDIR command, but DELETE works. We // should probably check to see if the file is a directory first! return remove(dirName); } Enum rfsv16:: rename(const char *oldName, const char *newName) { cerr << "rfsv16::rename ***" << endl; char realOldName[200]; Enum res = convertName(oldName, realOldName); if (res != E_PSI_GEN_NONE) return res; char realNewName[200]; res = convertName(newName, realNewName); if (res != E_PSI_GEN_NONE) return res; bufferStore a; a.addString(realOldName); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. a.addString(realNewName); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. sendCommand(RENAME, a); res = getResponse(a); if (res == E_PSI_GEN_NONE) { // Correct response return res; } cerr << "Unknown response from rename "<< res < rfsv16:: remove(const char* psionName) { char realName[200]; Enum res = convertName(psionName, realName); if (res != E_PSI_GEN_NONE) return res; bufferStore a; a.addString(realName); a.addByte(0x00); // needs to be null-terminated, // and this needs sending in the length word. sendCommand(DELETE, a); res = getResponse(a); if (res == E_PSI_GEN_NONE) { // Correct response return res; } cerr << "Unknown response from delete "<< res <