I've been using the SharpSSH Library quite a lot, recently and I've been coming across quite a few unfortunate errors…
Today's was to do with permissions from an SFTP entry. When you execute this using the standard library you get access to a "longName" property which contains the UNIX style listing of the entry, such as "-rwxr-xr-x 1 root apache 58585 Mar 14 2009 wibble.exe".
What was throwing me was the date format; "Mar 14 2009" – we have a script at work which reports on vital files that appear to change. It's not complex, but it depends on the string basically, err, not changing. Problem is that when a file reaches its six month birthday, UNIX stops reporting it as "Mar 14 14:17" and starts reporting it as "Mar 14 2009". So all our textual comparisons start screaming that 19,000 files have changed. Except they haven't…
Easy, thought I… I will go to the comparitor method and force the ToString() method to always print an absolute date. That was, in fact, easy. What surprised me was that the permissions output from the SharpSSH library threw up. 0755 perms (rwxr-xr-x were being reported as swxswxs-x). So the comparitor still failed.
I spent ages staring at the debugger, the way you do when common sense is screaming at you that this cannot be right. Then I realised that the author had a whole load of pseudo-constants (pseudo cos they are static ints, not static const ints) that read like "04000". The author expected the C style evaluation where a leading 0 means this is an octal. But, no, C# does not recognise the leading zero. So 04000 (octal 04000, hex 0×800, decimal 2048) was actually interpreted as a decimal 4,000. In octal, this reads as 07640 which is a lot more bits than expected!
The solution was to throw the octal pseudo-consts away and rewrite them as hex (in jsch/SftpATTRS.cs);
/* Scott, 21/12/2011 – the original constants had leading zeroes which, in C, would
* make the octals. Not in C#, though, where they come through as decimals.
* Replaced the following with definitions in hex, which isn't quite as easy to read
* but does have the benefit of being accurate…
static int S_ISUID = 04000; // set user ID on execution
static int S_ISGID = 02000; // set group ID on execution
static int S_ISVTX = 01000; // sticky bit ****** NOT DOCUMENTED *****
static int S_IRUSR = 00400; // read by owner
static int S_IWUSR = 00200; // write by owner
static int S_IXUSR = 00100; // execute/search by owner
static int S_IREAD = 00400; // read by owner
static int S_IWRITE= 00200; // write by owner
static int S_IEXEC = 00100; // execute/search by owner
static int S_IRGRP = 00040; // read by group
static int S_IWGRP = 00020; // write by group
static int S_IXGRP = 00010; // execute/search by group
static int S_IROTH = 00004; // read by others
static int S_IWOTH = 00002; // write by others
static int S_IXOTH = 00001; // execute/search by others
*/
staticint S_ISUID = 0×800; // set user ID on execution
staticint S_ISGID = 0×400; // set group ID on execution
staticint S_ISVTX = 0×200; // sticky bit ****** NOT DOCUMENTED *****
staticint S_IRUSR = 0×100; // read by owner
staticint S_IWUSR = 0×080; // write by owner
staticint S_IXUSR = 0×040; // execute/search by owner
staticint S_IREAD = 0×100; // read by owner
staticint S_IWRITE =0×080; // write by owner
staticint S_IEXEC = 0×040; // execute/search by owner
staticint S_IRGRP = 0×020; // read by group
staticint S_IWGRP = 0×010; // write by group
staticint S_IXGRP = 0×008; // execute/search by group
staticint S_IROTH = 00004; // read by others
staticint S_IWOTH = 00002; // write by others
staticint S_IXOTH = 00001; // execut/search by others
privatestaticint pmask = 0xFFF;