#include #include #include #include #include #include #include /* Strcat "limited," meaning that the copy is done unless the result would overflow the buffer. The first two args are as for strcat, and the last is the size of the destination buffer. The copy is only done if the whole src can be copied; there are no partial copies. If the copy is not done, the return value is NULL. This implementation is somewhat inefficient in that it has to measure the length of the destination each time, so if you did N concatenations of 1 character each, the whole algorithm would become N^2, but that's already the case, since you have to find the end of the string anyhow. One extra inefficiency is having to measure the length of the src; this could be avoided with lower-level coding. */ char* strcatl(char* dest, const char* src, int destsize) { int destlen = strlen(dest); int srclen = strlen(src); if( destlen+srclen < destsize ) { return strcat(dest,src); } else { return NULL; } } char* twPathname(const char* filename) { struct stat stat_buf; char* loadpath; bool freeloadpath = false; // absolute pathnames and explicitly relative pathnames are not processed further if(filename[0] == '/' || filename[0] == '.') return (char*) filename; #ifdef PATHNAME_DEBUG fprintf(stderr,"filename is neither absolute nor explicitly relative\n"); #endif loadpath = getenv("TWLOADPATH"); if(NULL == loadpath) { // Construct a default value of TWLOADPATH. Note that it is // inefficient to concatenate these directories together just to // tokenize them later in this same function, but C doesn't have // easy lists like Lisp, and we're hoping not to have to do this // most of the time anyhow. fprintf(stderr,"no TWLOADPATH; constructing default value from TWHOMEDIR\n"); char* twhomedir = getenv("TWHOMEDIR"); if( NULL == twhomedir ) { fprintf(stderr,"No value of TWHOMEDIR\n"); return NULL; } if(stat(twhomedir,&stat_buf)) { perror("TWHOMEDIR does not seem to be a valid directory"); return NULL; } // We need our own, writeable buffer for loadpath. const int MAX_LOAD_PATH = 512; int twhomedirlen = strlen(twhomedir); if(twhomedirlen > MAX_LOAD_PATH) { fprintf(stderr,"twhomedir is too long (max is %d): %s\n",MAX_LOAD_PATH,twhomedir); return NULL; } // Here's where we malloc it; from now on, we should free it before returning. loadpath = (char*) malloc(MAX_LOAD_PATH); freeloadpath = true; char* newpath; // for return values from strcatl loadpath[0] = 0; // initialize it to the empty string. // Construct default value // First "." newpath = strcatl(loadpath,".:",MAX_LOAD_PATH); if(NULL==newpath) { free(loadpath); return NULL; } #ifdef PATHNAME_DEBUG fprintf(stderr,"1. twloadpath = %s\n",loadpath); #endif // Second, $TWHOMEDIR/textures newpath = strcatl(loadpath,twhomedir,MAX_LOAD_PATH); if(NULL==newpath) { free(loadpath); return NULL; } newpath = strcatl(loadpath,"/textures/:",MAX_LOAD_PATH); if(NULL==newpath) { free(loadpath); return NULL; } #ifdef PATHNAME_DEBUG fprintf(stderr,"2. twloadpath = %s\n",loadpath); #endif // Third, $TWHOMEDIR/objects newpath = strcatl(loadpath,twhomedir,MAX_LOAD_PATH); if(NULL==newpath) { free(loadpath); return NULL; } newpath = strcatl(loadpath,"/objects/:",MAX_LOAD_PATH); if(NULL==newpath) { free(loadpath); return NULL; } #ifdef PATHNAME_DEBUG fprintf(stderr,"3. twloadpath = %s\n",loadpath); #endif } #ifdef PATHNAME_DEBUG fprintf(stderr,"twloadpath = %s\n",loadpath); #endif // Now, tokenize the loadpath and look for the filename. Our only // delimiter is a colon. const char delimiters[] = ":"; // We also need to copy the tokens into a big buffer, so we can append // the filename onto them, and we need to check those concatenations. char* token; const int MAX_PATHNAME = 512; char pathname[MAX_PATHNAME]; // Since loadpath *might* come from the environment (hopefully it will), // we need to copy it, since strtok destroys it. Note that this will be // ineffient if it didn't come from the environment and thus didn't need // to be copied, but we hope that's rare. char* newstring = strdupa(loadpath); if(NULL==newstring) { fprintf(stderr,"Out of memory for path searching.\n"); if(freeloadpath) free(loadpath); return NULL; } // Look at each token in turn, appending the filename onto it and // checking if the file exists. Stop when we find a match. while(NULL != (token=strtok(newstring,delimiters))) { #ifdef PATHNAME_DEBUG fprintf(stderr,"load directory is %s\n",token); #endif int tokenlen = strlen(token); if(tokenlen > MAX_PATHNAME) { // as long as MAX_PATHNAME >= MAX_LOAD_PATH, this should never happen, // but if MAX_LOAD_PATH comes from the environment, it could in principle be ridiculously long. fprintf(stderr,"directory in TWLOADPATH is too long: %s\n",token); if(freeloadpath) free(loadpath); return NULL; } strcpy(pathname,token); // Always add slash char* newpath = strcatl(pathname,"/",MAX_PATHNAME); if(NULL==newpath) { fprintf(stderr,"pathname is too long: %s + %s\n",pathname,filename); // In this case, don't give up if the pathname is too long continue; } // Now, append the filename that the user gave us newpath = strcatl(pathname,filename,MAX_PATHNAME); if(NULL==newpath) { fprintf(stderr,"pathname is too long: %s + %s\n",pathname,filename); // In this case, don't give up if the pathname is too long continue; } // Finally, check if it exists if(!stat(pathname,&stat_buf)) { if(freeloadpath) free(loadpath); return strdupa(pathname); } newstring = NULL; // should only be a string for the first time around } // if we get here, nothing worked. *sigh* fprintf(stderr,"Couldn't find %s in TWLOADPATH = %s\n",filename,loadpath); if(freeloadpath) free(loadpath); return NULL; } /* This old version tests to see whether a slash needs to be added at the end of a directory name, but it seems that we can go ahead and just always add one, and it'll work. */ char* twPathnameOld(const char* filename) { struct stat stat_buf; char* loadpath; // absolute pathnames and explicitly relative pathnames are not processed further if(filename[0] == '/' || filename[0] == '.') return (char*) filename; loadpath = getenv("TWLOADPATH"); if(NULL == loadpath) { // Construct a default value of TWLOADPATH. Note that it is // inefficient to concatenate these directories together just to // tokenize them later in this same function, but C doesn't have // easy lists like Lisp, and we're hoping not to have to do this // most of the time anyhow. fprintf(stderr,"no TWLOADPATH; constructing default value from TWHOMEDIR\n"); // We need our own, writeable buffer for loadpath. const int MAX_LOAD_PATH = 512; char loadpath[MAX_LOAD_PATH]; char* newpath; // for return values from strcatl loadpath[0] = 0; char* twhomedir = getenv("TWHOMEDIR"); if( NULL == twhomedir ) { fprintf(stderr,"No value of TWHOMEDIR\n"); return NULL; } if(stat(twhomedir,&stat_buf)) { perror("TWHOMEDIR does not seem to be a valid directory"); return NULL; } int twhomedirlen = strlen(twhomedir); bool addslash = (twhomedir[twhomedirlen-1] != '/'); fprintf(stderr,"twhomedir = %s, addslash is %s\n",twhomedir,addslash?"true":"false"); if(twhomedirlen > MAX_LOAD_PATH) { fprintf(stderr,"twhomedir is too long (max is %d): %s\n",MAX_LOAD_PATH,twhomedir); return NULL; } // Construct default value // First "." newpath = strcatl(loadpath,".:",MAX_LOAD_PATH); if(NULL==newpath) return NULL; fprintf(stderr,"1. twloadpath = %s\n",loadpath); // Second, $TWHOMEDIR/textures newpath = strcatl(loadpath,twhomedir,MAX_LOAD_PATH); if(NULL==newpath) return NULL; newpath = strcatl(loadpath,addslash?"/textures/:":"textures/:",MAX_LOAD_PATH); if(NULL==newpath) return NULL; fprintf(stderr,"2. twloadpath = %s\n",loadpath); // Third, $TWHOMEDIR/objects newpath = strcatl(loadpath,twhomedir,MAX_LOAD_PATH); if(NULL==newpath) return NULL; newpath = strcatl(loadpath,addslash?"/objects/:":"objects/:",MAX_LOAD_PATH); if(NULL==newpath) return NULL; fprintf(stderr,"3. twloadpath = %s\n",loadpath); } fprintf(stderr,"twloadpath = %s\n",loadpath); // Now, tokenize the loadpath and look for the filename. Our only // delimiter is a colon. const char delimiters[] = ":"; // We also need to copy the tokens into a big buffer, so we can append // the filename onto them, and we need to check those concatenations. char* token; const int MAX_PATHNAME = 512; char pathname[MAX_PATHNAME]; // Since loadpath *might* come from the environment (hopefully it will), // we need to copy it, since strtok destroys it. Note that this will be // ineffient if it didn't come from the environment and thus didn't need // to be copied, but we hope that's rare. char* newstring = strdupa(loadpath); if(NULL==newstring) { fprintf(stderr,"Out of memory for path searching.\n"); return NULL; } // Look at each token in turn, appending the filename onto it and // checking if the file exists. Stop when we find a match. while(NULL != (token=strtok(newstring,delimiters))) { fprintf(stderr,"load directory is %s\n",token); int tokenlen = strlen(token); if(tokenlen > MAX_PATHNAME) { // as long as MAX_PATHNAME >= MAX_LOAD_PATH, this should never happen, // but if MAX_LOAD_PATH comes from the environment, it could in principle be ridiculously long. fprintf(stderr,"directory in TWLOADPATH is too long: %s\n",token); return NULL; } strcpy(pathname,token); // Maybe add slash if(token[tokenlen-1] != '/') { strcatl(pathname,"/",MAX_PATHNAME); } // Now, append the filename that the user gave us char* newpath = strcatl(pathname,filename,MAX_PATHNAME); if(NULL==newpath) { fprintf(stderr,"pathname is too long: %s + %s\n",pathname,filename); // In this case, don't give up if the pathname is too long continue; } // Finally, check if it exists if(!stat(pathname,&stat_buf)) return strdupa(pathname); newstring = NULL; // should only be a string for the first time around } // if we get here, nothing worked. *sigh* fprintf(stderr,"Couldn't find %s in TWLOADPATH = %s\n",filename,loadpath); return NULL; } int main(int argc, char* argv[]) { char filename[256]; for(;;) { printf("file: "); scanf("%s",filename); printf("file %s => ",filename); printf("%s\n",twPathname(filename)); } }