/*Copyright (c) Edward Rosten 2005 See file LICENSE for the license Set tab stops to 4, if you wish. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; //Default values #define CODA_DEV "/dev/cfs0" #define TMP_PERM 0600 #define FILE_PERM 0666 #define DIR_PERM 0777 #define CLOSED_FILE_TIMEOUT 600 //After 10 minutes, closed files become stale #define DEBUG #if defined DEBUG || defined DEBUG_OPTIONAL #ifdef DEBUG_OPTIONAL #define TDEBUG(X) do{if(config.debug)cerr << "Debug (" << __FUNCTION__ << "): " << X << endl;}while(0) #define TDEBUGVAR(X) do{if(config.debug)TDEBUG(#X << " = " << X);}while(0) #else #define TDEBUG(X) do{cerr << "Debug (" << __FUNCTION__ << "): " << X << endl;}while(0) #define TDEBUGVAR(X) do{TDEBUG(#X << " = " << X);}while(0) #endif #else #define TDEBUG(X) #define TDEBUGVAR(X) #endif /****************************************************************************** * * Function prototypes, etc * *******************************************************************************/ void rmtemp(); static void interrupted(int sig); string tempfile(); /* Compare function required for stroing in std::map */ struct fid_compare { bool operator()(const struct CodaFid& f1, const struct CodaFid& f2) { return f1.opaque[0] < f2.opaque[0]; } }; void do_options(int argc, char** argv); void check_error_f(int e, int line); #define check_error(X) check_error_f(X, __LINE__) char* access_flags(int flags); char* open_flags(int flags); int coda_flags_to_unix_flags(int c); struct CodaFid gimme_a_fid(); string convert_dir_listing_to_local_file(const vector& listing); //Gnokii I/O functions bool is_remote_file_a_file(string fullname); int get_attributes(CodaFid fid); int deletefile(string filename); int putfile(string phone, string real); int replacefile(string phone, string real); string fetch_a_file(string fullname); string convert_remote_dir_to_local_file(string fullname); static void busterminate(void); static void businit(void); /****************************************************************************** * * Global data * *******************************************************************************/ //Gnokii data static struct gn_statemachine state; static gn_data data; static char *lockfile = NULL; //Runtime configurable data struct { bool debug; long file_perm, dir_perm, closed_file_timeout; string coda_device; } config; map phone_files; //FID in, filename on telephone out map is_dir; //FID in, directoryp out map local; //FID in, local filename out map attributes; //FID in, attributes out map open_count; //FID in, number of times file is open map time_of_last_close; //When was I first opened? //Default attributes for an ordinary file static struct coda_vattr default_fatts= { /*.va_type = */C_VREG, /*.va_mode =*/ FILE_PERM, /*.va_nlink =*/ 1, /*.va_uid =*/ getuid(), //Root... /*.va_gid =*/ getgid(), /*.va_fileid =*/ 0, //request.coda_getattr.VFid.opaque[0], /*.va_size =*/ 0, /*.va_blocksize =*/ 1, /*.va_atime =*/ {0,0}, /*.va_mtime =*/ {0,0}, /*.va_ctime =*/ {0,0}, /*.va_gen =*/ 0 , /*.va_flags =*/ 0, /*.va_rdev =*/ 0, /*.va_bytes =*/ 0, /*.va_filerev =*/ 0 }; //Default attributes for a directory static struct coda_vattr default_vatts= { /*.va_type = */C_VDIR, /*.va_mode =*/ DIR_PERM, /*.va_nlink =*/ 1, /*.va_uid =*/ getuid(), //Root... /*.va_gid =*/ getgid(), /*.va_fileid =*/ 0, //request.coda_getattr.VFid.opaque[0], /*.va_size =*/ 0, /*.va_blocksize =*/ 1, /*.va_atime =*/ {0,0}, /*.va_mtime =*/ {0,0}, /*.va_ctime =*/ {0,0}, /*.va_gen =*/ 0 , /*.va_flags =*/ 0, /*.va_rdev =*/ 0, /*.va_bytes =*/ 0, /*.va_filerev =*/ 0 }; /********************************************************************************/ int main(int argc, char** argv) { do_options(argc, argv); int fd, e, size; bool bus_inited=0; char inbuf[VC_MAXMSGSIZE]; union inputArgs& request=*(union inputArgs*)inbuf; union outputArgs reply; fd = open(config.coda_device.c_str(), O_RDWR); check_error(fd); TDEBUG("sizeof(request) = " << sizeof(request)); TDEBUG("Starting loop"); while(1) { memset(&request, 0, sizeof(request)); memset(&reply, 0, sizeof(reply)); TDEBUG("Reading"); e = read(fd, &request, VC_MAXMSGSIZE); //-1? who knows why check_error(e); if(e==0) { TDEBUG("Zero size read!!!"); continue; } TDEBUG("read " << e << " bytes"); TDEBUG("opcode = " << request.ih.opcode); reply.oh.opcode = request.ih.opcode; reply.oh.unique = request.ih.unique; reply.oh.result = 0; struct CodaFid VFid; size = sizeof(reply.oh); switch(request.ih.opcode) { case CODA_ROOT: if(!bus_inited) { businit(); bus_inited=true; } TDEBUG("Command = root"); reply.coda_root.VFid = gimme_a_fid(); size = sizeof(reply.coda_root); VFid = reply.coda_root.VFid; //Build attributes bu hand, since A:\ does not exist. phone_files[VFid] = "A:"; is_dir[VFid] = true; attributes[VFid] = default_vatts; break; case CODA_GETATTR: //This is simple: getattr requires a VFID. //The only way to get a VFID is through lookup //Lookup fetches the attributes. VFid = request.coda_getattr.VFid; TDEBUG("Command = GetAttr"); TDEBUG("Attribute of: " << phone_files[VFid]); reply.coda_getattr.attr = attributes[VFid]; size = sizeof(coda_getattr_out); break; case CODA_SETATTR: TDEBUG("Command = Setattr"); TDEBUG("We can't honour this request, but we pretend that we do."); break; case CODA_ACCESS: //Test for accessability. The answer is yes. TDEBUG("Command = Access"); TDEBUG("Accessing: " << phone_files[request.coda_access.VFid]); TDEBUG("Flags: " << access_flags(request.coda_access.flags)); size = sizeof(reply.oh); reply.oh.result = 0; break; case CODA_OPEN_BY_FD: //We get a VFID (as usual) and reply with a standard file descriptor. //The kernel sorts out the rest. We no longer have to worry about closing it. VFid = request.coda_open_by_fd.VFid; TDEBUG("Command = Open by FD (whatever that means)"); TDEBUG("Opening file on phone: " << phone_files[VFid]); TDEBUG("File is " << (is_dir[VFid]?"": "not ") << "a directory."); TDEBUG("Flags: " << open_flags(request.coda_open_by_fd.flags)); size = sizeof(reply.coda_open_by_fd); if(is_dir[VFid]) { //Directories are simple. string name; name = convert_remote_dir_to_local_file(phone_files[VFid]); reply.coda_open_by_fd.fd = open(name.c_str(), O_RDONLY); if(reply.coda_open_by_fd.fd == -1) { reply.oh.result = errno; break; } } else { TDEBUG("File is already open " << open_count[VFid] << " time(s)"); if(open_count[VFid] == 0) { //If we're opening the file, then we need a local copy //first check to see if there is an old local copy //I do this because I like using xv. xv opens all the files //first, then closes them all and then opens them one //by one. Which is slow if you fetch the file every time. //This also speeds up image viewing (with your favourite //viewer) in general. if(local.find(VFid) != local.end()) { TDEBUG("An old local file exists"); TDEBUGVAR(time_of_last_close[VFid]); TDEBUG("Current time = " << time(0)); TDEBUG("Time since last close = " << time(0) - time_of_last_close[VFid]); TDEBUGVAR(config.closed_file_timeout); if(time(0) - time_of_last_close[VFid] < config.closed_file_timeout) { TDEBUG("Using old file."); goto use_old; } } if(! (request.coda_open_by_fd.flags & C_O_TRUNC)) { TDEBUG("Opening real file non destructively."); if(!is_remote_file_a_file(phone_files[VFid])) { //It can't be a directory because of the preceding logic :-) reply.oh.result = ENOENT; break; } local[VFid] = fetch_a_file(phone_files[VFid]); if(local[VFid] == "") { //I can't get lookup to respond with this properly yet. //So, assume any error is an ENOENT :-/ reply.oh.result = ENOENT; break; } } else { //Truncate the file on the phone TDEBUG("Destroying remote file, so we don't need to fetch it"); replacefile(phone_files[VFid], "/dev/null"); local[VFid] = tempfile(); } use_old:; } //Attempt to open, and reply with the file descriptor //We may need to create it if this is the first instance of it. reply.coda_open_by_fd.fd = open(local[VFid].c_str(), O_CREAT | coda_flags_to_unix_flags(request.coda_open_by_fd.flags), TMP_PERM); if(reply.coda_open_by_fd.fd == -1) { reply.oh.result = errno; break; } //Update the reference count open_count[VFid]++; } break; case CODA_STORE: //This is called when a process with write access on the file closes the file. //Here we upload it. I could do an optimization, whereby the last process on the //phone closing the file commits it to the phone (as opposed to all processes), but //it's a phone, not a high performance mass storage device. VFid = request.coda_store.VFid; TDEBUG("Command = store"); TDEBUG("Storing file to: " << phone_files[VFid]); TDEBUG("Storing data from: " << local[VFid]); //Overwrite the file on the phone: e = replacefile(phone_files[VFid], local[VFid]); if(e == -1) reply.oh.result = 1; break; case CODA_LOOKUP: //we're given a name. Now assign a FID { char* name = inbuf + request.coda_lookup.name; string fullname = phone_files[request.coda_lookup.VFid] + "\\" + name; TDEBUG("Command = Lookup"); TDEBUG("VFID translates to: " << phone_files[request.coda_lookup.VFid]); TDEBUG("Flags: 0x" << setbase(16) << request.coda_lookup.flags << setbase(10)); TDEBUG("Name: " << name); TDEBUG("Full remote name: " << fullname.c_str()); VFid = gimme_a_fid(); phone_files[VFid] = fullname; int e = get_attributes(VFid); TDEBUG("Attributes returned: " << e); if(e != -1) TDEBUG("File is " << (is_dir[VFid]?"": "not ") << "a directory."); else TDEBUG("File does not exist."); reply.coda_lookup.VFid = VFid; if(e == -1) { reply.coda_lookup.oh.result = ENOENT; break; } if(is_dir[VFid]) reply.coda_lookup.vtype = C_VDIR; else reply.coda_lookup.vtype = C_VREG; size = sizeof(reply.coda_lookup); } break; case CODA_REMOVE: { string name = inbuf + request.coda_remove.name; TDEBUG("Commend = remove"); TDEBUG("VFID translates to: " << phone_files[request.coda_remove.VFid]); TDEBUG("Name is given as:" << name); string fullname = phone_files[request.coda_remove.VFid] + "\\" + name; TDEBUG("Full file name:" << fullname); if(deletefile(fullname) == -1) reply.oh.result = EPERM; break; } case CODA_RELEASE: TDEBUG("Command = Release"); VFid = request.coda_open_by_fd.VFid; TDEBUG("Releasing " << phone_files[VFid]); if(!is_dir[VFid]) { open_count[VFid] --; TDEBUG("Reference count is now " << open_count[VFid]); TDEBUG("Current time = " << time(0)); time_of_last_close[VFid] = time(0); } else TDEBUG("Nothing to do for a directory."); reply.oh.result = 0; break; case CODA_CREATE: { TDEBUG("Command = create"); string name = inbuf + request.coda_create.name; TDEBUG("VFID translates to: " << phone_files[request.coda_create.VFid]); TDEBUG("Name is given as:" << name); TDEBUG("Name:" << request.coda_create.name); string fullname = phone_files[request.coda_create.VFid] + "\\" + name; TDEBUG("Full file name:" << fullname); //Create the remote file int e = putfile(fullname, "/dev/null"); reply.oh.result = e; if(!e) { TDEBUG("Creating remote file..."); VFid = gimme_a_fid(); //Fill in attributes by hand phone_files[VFid] = fullname; is_dir[VFid] = 0; attributes[VFid] = default_fatts; //Now attempt to get the attributes from the phone //Do we have the correct time/date, etc int e = get_attributes(VFid); if(e) cerr << "Warning: geting attributes from a new file failed!\n"; reply.coda_create.attr = attributes[VFid]; reply.coda_create.VFid = VFid; size = sizeof(reply.coda_create); } else TDEBUG("Failed to create remote file."); break; } default: TDEBUG("Erk!!!"); reply.oh.result = EPERM; } if(size > 0) { TDEBUG("Replying with a write of size " << size); TDEBUG("Reply code is: " << reply.oh.result << " (" << strerror(reply.oh.result) << ")"); e = write(fd, &reply, size); check_error(e); } TDEBUG("Processing completed\n\n\n"); } } /* GNOKII library deinitialisztion copied from gnokii.c */ static void busterminate(void) { TDEBUG("Terminating bus"); gn_sm_functions(GN_OP_Terminate, NULL, &state); if (lockfile) gn_device_unlock(lockfile); } /* Shutdown cleanly, shudtown functions registered with atexit */ static void interrupted(int sig) { signal(sig, SIG_IGN); //We could upload all currently open files. We won't, though. //If you want to keep your files, then close them first. exit(0); } /* GNOKII library initialisation copied from gnokii.c */ static void businit(void) { gn_error error; char *aux; TDEBUG("Read config file"); /* Read config file */ if (gn_cfg_read_default() < 0) { cerr << "Failed to read config.\n"; exit(1); } if (!gn_cfg_phone_load("", &state)) exit(1); signal(SIGINT, interrupted); gn_data_clear(&data); TDEBUG("Bus initialization"); aux = gn_cfg_get(gn_cfg_info, "global", "use_locking"); /* Defaults to 'no' */ if (aux && !strcmp(aux, "yes")) { lockfile = gn_device_lock(state.config.port_device); if (lockfile == NULL) { cerr << "Lock file error. Exiting.\n"; exit(1); } } /* register cleanup function */ atexit(busterminate); /* signal(SIGINT, bussignal); */ /* Initialise the code for the GSM interface. */ error = gn_gsm_initialise(&state); if (error != GN_ERR_NONE) { cerr << "Telephone interface init failed: " << gn_error_print(error) << endl; exit(2); } } /* Check standard UNIX errors */ void check_error_f(int e, int line) { if(e == -1) { cerr << "Error: " << strerror(errno) << " on line " << line << endl; exit(1); } } /* Convert CODA bitfields in to human readable strings */ char* access_flags(int flags) { static char buf[1024]; buf[1023] = 0; snprintf(buf, 1023, "%s %s %s %s %s", flags & C_A_C_OK ? "write_on_create": "", flags & C_A_R_OK ? "read": "", flags & C_A_W_OK ? "write": "", flags & C_A_X_OK ? "execute": "", flags & C_A_F_OK ? "existence": ""); return buf; } /* Convert CODA bitfields in to human readable strings */ char* open_flags(int flags) { static char buf[1024]; buf[1023] = 0; snprintf(buf, 1023, "%s %s %s %s %s", flags & C_O_READ ? "read" : "", flags & C_O_WRITE ? "write" : "", flags & C_O_TRUNC ? "truncate" : "", flags & C_O_EXCL ? "exclusive" : "", flags & C_O_CREAT ? "creat(e)" : ""); return buf; } int coda_flags_to_unix_flags(int c) { int o=0; if(c & C_O_READ && c & C_O_WRITE) o = O_RDWR; else if(c & C_O_READ) o = O_RDONLY; else if(c & C_O_WRITE) o = O_WRONLY; if(c & C_O_TRUNC) o |= O_TRUNC; return o; } /* Generate a unique FID :-) */ struct CodaFid gimme_a_fid() { static int id = 0; struct CodaFid a_fid = { {id++,0xfffffff,0x01234567,0xfffffff } }; return a_fid; } /******************************************************************************* Code for dealing with temp files (including cleanup) */ list tempfiles; string tempfile() { static string root; static int num=0; if(num == 0) { if(getenv("TMPDIR") == NULL) root = "/tmp/"; else root = getenv("TMPDIR") + string("/"); atexit(rmtemp); } ostringstream buf; buf << root << "tmp-" << getpid() << "-" << num++; tempfiles.push_back(buf.str()); return buf.str(); } void rmtemp() { TDEBUG("Removing temp files..."); for(list::iterator i=tempfiles.begin(); i != tempfiles.end(); i++) unlink(i->c_str()); } bool is_remote_file_a_file(string fullname) { //OK, how's this for a b0rk3d up hack? // getfilelist A:\an_empty_dir\* //returns a list of files, length 0. //But: // getfilelist A:\a_regular_file\* //returns a list of files with a single entry of length 0. // hee, hee, hee //This function returns 0 for a directory or ENOENT, 1 for a file. gn_file_list fi; gn_error error; fullname += "\\*"; TDEBUG("Attempting to get a directory listing for " << fullname); gn_data_clear(&data); data.file_list = &fi; memset(&fi, 0, sizeof(fi)); snprintf(fi.path, sizeof(fi.path)-1, "%s", fullname.c_str()); if ((error = gn_sm_functions(GN_OP_GetFileList, &data, &state)) != GN_ERR_NONE) { TDEBUG("Failed to get info for " << fullname << ":" << gn_error_print(error)); //An error means it is not there, so it is not a file. return false; } TDEBUGVAR(fi.file_count); if(fi.file_count != 1) return false; TDEBUGVAR(strlen(fi.files[0]->name)); if(strlen(fi.files[0]->name) == 0) return true; else return false; } // Given a CODA FID, get the file attributes. int get_attributes(CodaFid fid) { struct coda_vattr vatts; gn_file_list fi; gn_error error; memset(&fi, 0, sizeof(fi)); snprintf(fi.path, sizeof(fi.path)-1, "%s", phone_files[fid].c_str()); TDEBUG("Getting: " << phone_files[fid]); gn_data_clear(&data); data.file_list = &fi; TDEBUG("File count: " << fi.file_count); if ((error = gn_sm_functions(GN_OP_GetFileList, &data, &state)) != GN_ERR_NONE || fi.file_count ==0) { TDEBUG("Failed to get info: " << gn_error_print(error)); return -1; } TDEBUG("Got attributes."); if(is_remote_file_a_file(phone_files[fid])) { TDEBUG("Is not a directory."); vatts = default_fatts; is_dir[fid] = false; } else { TDEBUG("Is a directory."); vatts = default_vatts; is_dir[fid] = true; } vatts.va_size = fi.files[0]->file_length; //Figure out creation time struct tm time; memset(&time, 0, sizeof(time)); //Some extra fields on some systems? time.tm_sec = fi.files[0]->second; time.tm_min = fi.files[0]->minute; time.tm_hour = fi.files[0]->hour; time.tm_mday = fi.files[0]->day; time.tm_mon = fi.files[0]->month-1; //Monts start from 0 time.tm_year = fi.files[0]->year-1900; //Years start from 1900 time.tm_wday = 0; time.tm_yday = 0; time.tm_isdst = -1; //FIXME timezones? The phone can tell us what it thinks the time is //Mktime ignores the tm_wday, yday fields struct timespec ts= { //Second, useconds since the Epoch. mktime(&time), 0 }; vatts.va_ctime = vatts.va_mtime = vatts.va_atime = ts; TDEBUGVAR(mktime(&time)); TDEBUGVAR(fi.files[0]->name); TDEBUGVAR(fi.files[0]->year); TDEBUGVAR(fi.files[0]->month); TDEBUGVAR(fi.files[0]->day); TDEBUGVAR(fi.files[0]->hour); TDEBUGVAR(fi.files[0]->minute); TDEBUGVAR(fi.files[0]->second); TDEBUGVAR(fi.files[0]->file_length); TDEBUGVAR(fi.files[0]->togo); TDEBUGVAR(fi.files[0]->just_sent); TDEBUGVAR(fi.files[0]->name); free(fi.files[0]); attributes[fid] = vatts; return 0; } //This fetched a named file from the phone and places it in a temporary file. //The name of the temporary file is returned. string fetch_a_file(string fullname) { //Copied from gnokii.c gn_file fi; gn_error error; memset(&fi, 0, sizeof(fi)); snprintf(fi.name, 511, "%s", fullname.c_str()); TDEBUG("Getting file: " << fullname); gn_data_clear(&data); data.file = &fi; if ((error = gn_sm_functions(GN_OP_GetFile, &data, &state)) != GN_ERR_NONE) { cerr <<"Failed to get file:" << gn_error_print(error) << endl; return ""; } string name = tempfile(); int fd = open(name.c_str(), O_TRUNC | O_WRONLY | O_CREAT, TMP_PERM); write(fd, fi.file, fi.file_length); close(fd); free(fi.file); return name; } string convert_dir_listing_to_local_file(const vector& listing) { //Why? static int dirfileno = 0; string output_file=tempfile(); TDEBUG("Converting listing."); TDEBUGVAR(output_file); int fd = open(output_file.c_str(), O_TRUNC | O_WRONLY | O_CREAT, TMP_PERM); if(fd == -1) { cerr << "Failed to create tempfile for directory listing: " << strerror(errno) << endl; return ""; } struct venus_dirent d; int bytes = 0; memset(&d, 0xFF, sizeof(d)); //Prevent padding being filled up with zeros (end of dir marker) //First entry is ignored, but we have to have it, so write an empty entry d.d_fileno = dirfileno++; d.d_type = CDT_REG; //This is ignored d.d_namlen = 0; d.d_reclen=DIRSIZ(&d); write(fd, &d, d.d_reclen); bytes += d.d_reclen; for(unsigned int i=0;i CODA_MAXNAMLEN) len = CODA_MAXNAMLEN; d.d_namlen = len; memcpy(d.d_name, listing[i].c_str(), len); d.d_reclen=DIRSIZ(&d); write(fd, &d, d.d_reclen); bytes += d.d_reclen; TDEBUG("File name: " << listing[i]); } //End of dir d.d_type = 0; d.d_namlen = 0; //Magic 12? Why? write(fd, &d, 12); close(fd); return output_file; } //Fetch a remote directory listing and place it in a file that coda can //understand. The filename is returned. string convert_remote_dir_to_local_file(string fullname) { //Make data for a coda directory if necessary TDEBUG("Getting listing."); ////copy from gnokii.c gn_file_list fi; gn_error error; int i; fullname += "\\*"; memset(&fi, 0, sizeof(fi)); snprintf(fi.path, sizeof(fi.path)-1, "%s", fullname.c_str()); TDEBUG("Getting: " << fullname); gn_data_clear(&data); data.file_list = &fi; if ((error = gn_sm_functions(GN_OP_GetFileList, &data, &state)) != GN_ERR_NONE) { cerr << "Failed to get info for " << fullname << ":" << gn_error_print(error) << endl; return ""; } TDEBUG("File count in directory is: " << fi.file_count); vector listing; for(i=0;iname); listing.push_back(fi.files[i]->name); free(fi.files[i]); } return convert_dir_listing_to_local_file(listing); } //Delete a remote file int deletefile(string filename) { gn_file fi; gn_error error; TDEBUG("Deleteing: " << filename); memset(&fi, 0, sizeof(fi)); snprintf(fi.name, 511, "%s", filename.c_str()); gn_data_clear(&data); data.file = &fi; if ((error = gn_sm_functions(GN_OP_DeleteFile, &data, &state)) != GN_ERR_NONE) { cerr << "Failed to delete:" << gn_error_print(error) << endl;; return -1; } TDEBUG("Delete sucessful."); return 0; } int putfile(string phone, string real) { //NB this function only overwrites existing files. It does not //truncate then first. gn_file fi; gn_error error; TDEBUG("Putting file " << real << " to " << phone); memset(&fi, 0, sizeof(fi)); snprintf(fi.name, 511, "%s", phone.c_str()); gn_data_clear(&data); data.file = &fi; ifstream f; f.open(real.c_str()); if (!f.good()) { cerr << "Cannot open file: " << real; return errno; } TDEBUG("Reading file in to memory."); vector file_data; unsigned char c; for(;;) { c = f.get(); if(!f.good()) break; file_data.push_back(c); } TDEBUG("File size = " << file_data.size()); fi.file_length = file_data.size(); fi.file = file_data.size() == 0 ? NULL: &file_data[0]; if ((error = gn_sm_functions(GN_OP_PutFile, &data, &state)) != GN_ERR_NONE) { cerr << "Failed to put file:" << gn_error_print(error) << endl; return EAGAIN; } return 0; } int replacefile(string phone, string real) { TDEBUG("Replacing file."); deletefile(phone); if(putfile(phone, real) == -1) return -1; return 0; } void do_options(int argc, char** argv) { TDEBUG("Processing command line arguments."); opterr = 0; int opt; config.debug = 0; config.file_perm = FILE_PERM; config.dir_perm = DIR_PERM; config.coda_device = CODA_DEV; config.closed_file_timeout = CLOSED_FILE_TIMEOUT; char* opts = "f:d:t:c:hD"; char* prog = strrchr(argv[0], '/'); if(!prog) prog = argv[0]; else prog++; string help = " [-f X | -d X | -c X | -t X | -h | -d]\n" " -f X = file permission bits (use 0nnn for octal)\n" " -d X = directory permission bits\n" " -c X = coda device file\n" " -t X = Time after which closed files are flushed from the cache in seconds.\n" " -D print very verbose debug messages (only if compiled in)\n" " -h print this message\n\n"; while((opt = getopt(argc, argv, opts)) != -1) { switch(opt) { case 'f': config.file_perm = strtol(optarg, 0, 0); TDEBUG("File mode is now: " << setbase(8) << config.file_perm << setbase(10)); break; case 'd': config.dir_perm = strtol(optarg, 0, 0); TDEBUG("Directory mode is now: " << setbase(8) << config.dir_perm << setbase(10)); break; case 't': config.closed_file_timeout = strtol(optarg, 0, 0); TDEBUG("Old file flushing time is now: " << config.closed_file_timeout << "s"); break; case 'c': config.coda_device = optarg; TDEBUG("Coda device is now: " << config.coda_device); break; case 'D': config.debug = 1; TDEBUG("Debugging output now enabled."); break; case 'h': cout << prog << help; exit(0); case '?': default: if(strchr(opts, opt) == NULL) cerr << prog << ": Error: unknown option -" << (char)optopt << endl; else cerr << prog << ": Error: option -" << (char)optopt << " requires a parameter." << endl; cerr << prog << help; exit(1); } } if(optind != argc) { cerr << prog << ": Error: bad argument: " << argv[optind] << endl << prog << help; exit(1); } default_vatts.va_mode = config.dir_perm; default_fatts.va_mode = config.file_perm; }