/* This file contains filesystem utilities for the TW software. There are probably better ways to do all these functions; please let me know so I can improve it. Scott D. Anderson scott.anderson@acm.org Fall 2006 */ /* 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, bool verbose) { 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 = strdup(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))) { if(verbose) fprintf(stderr,"searching load directory %s for %s\n", token,filename); 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(verbose) printf("Found %s\n",pathname); if(freeloadpath) free(loadpath); return strdup(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; }