#include #include #include #ifndef bsdi #include #include #endif #include #define DIRECTORY_SEP '/' #define IS_DIRECTORY_SEP(x) ((x) == DIRECTORY_SEP) static char *file_name_as_directory(char* out, char* in) { int size = strlen(in) - 1; strcpy(out, in); /* For Unix syntax, Append a slash if necessary */ if(!IS_DIRECTORY_SEP(out[size])) { out[size + 1] = DIRECTORY_SEP; out[size + 2] = '\0'; } return out; } char* expand_file_name(char* name, char* default_directory) { unsigned char *nm, *newdir, *p, *o; int tlen; unsigned char *target; struct passwd *pw; int length; if(name == NULL) return NULL; o = (unsigned char *)default_directory; nm = (unsigned char *)name; /* Handle // and /~ in middle of file name by discarding everything through the first / of that sequence. */ p = nm; while(*p) { /* Since we are expecting the name to be absolute, we can assume that each element starts with a "/". */ if(IS_DIRECTORY_SEP(p[0]) && IS_DIRECTORY_SEP(p[1])) nm = p + 1; if(IS_DIRECTORY_SEP(p[0]) && p[1] == '~') nm = p + 1; p++; } /* If nm is absolute, look for /./ or /../ sequences; if none are found, we can probably return right away. We will avoid allocating a new string if name is already fully expanded. */ if(IS_DIRECTORY_SEP(nm[0])) { /* If it turns out that the filename we want to return is just a suffix of FILENAME, we don't need to go through and edit things; we just need to construct a new string using data starting at the middle of FILENAME. If we set lose to a non-zero value, that means we've discovered that we can't do that cool trick. */ int lose = 0; p = nm; while(*p) { /* Since we know the name is absolute, we can assume that each element starts with a "/". */ /* "." and ".." are hairy. */ if(IS_DIRECTORY_SEP(p[0]) && p[1] == '.' && (IS_DIRECTORY_SEP(p[2]) || p[2] == '\0' || (p[2] == '.' && (IS_DIRECTORY_SEP(p[3]) || p[3] == '\0')))) lose = 1; p++; } if(!lose) { if(nm == (unsigned char *)name) return strdup(name); return strdup(nm); } } /* At this point, nm might or might not be an absolute file name. We need to expand ~ or ~user if present, otherwise prefix nm with default_directory if nm is not absolute, and finally collapse /./ and /foo/../ sequences. We set newdir to be the appropriate prefix if one is needed: - the relevant user directory if nm starts with ~ or ~user - the specified drive's working dir (DOS/NT only) if nm does not start with / - the value of default_directory. Note that these prefixes are not guaranteed to be absolute (except for the working dir of a drive). Therefore, to ensure we always return an absolute name, if the final prefix is not absolute we append it to the current working directory. */ newdir = NULL; if(nm[0] == '~') /* prefix ~ */ { if(IS_DIRECTORY_SEP(nm[1]) || nm[1] == '\0') /* ~ by itself or ~/ */ { if(!(newdir = (unsigned char *) getenv ("HOME"))) newdir = (unsigned char *) ""; nm++; } else /* ~user/filename */ { for(p = nm; *p && !IS_DIRECTORY_SEP(*p); p++) ; o = (unsigned char *)alloca(p - nm + 1); memcpy(o, nm, p - nm); o[p - nm] = '\0'; pw = (struct passwd *)getpwnam(o + 1); if(pw) { newdir = (unsigned char *)pw->pw_dir; nm = p; } /* If we don't find a user of that name, leave the name unchanged; don't move nm forward to p. */ } } /* Finally, if no prefix has been specified and nm is not absolute, then it must be expanded relative to default_directory. */ if(!IS_DIRECTORY_SEP(nm[0]) && !newdir) /* /... alone is not absolute on DOS and Windows. */ { newdir = (unsigned char *)default_directory; } if(newdir) { /* Get rid of any slash at the end of newdir. */ length = strlen(newdir); if(IS_DIRECTORY_SEP(newdir[length - 1])) { unsigned char *temp = (unsigned char *)alloca(length); memcpy(temp, newdir, length - 1); temp[length - 1] = '\0'; newdir = temp; } tlen = length + 1; } else tlen = 0; /* Now concatenate the directory and name to new space in the stack frame */ tlen += strlen(nm) + 1; target = (unsigned char *)alloca(tlen); *target = '\0'; if(newdir) { if(nm[0] == '\0' || IS_DIRECTORY_SEP(nm[0])) strcpy(target, newdir); else file_name_as_directory((char *)target, (char *)newdir); } strcat(target, nm); /* ASSERT (IS_DIRECTORY_SEP(target[0])) if not VMS */ /* Now canonicalize by removing /. and /foo/.. if they appear. */ p = target; o = target; while(*p) { if(!IS_DIRECTORY_SEP(*p)) { *o++ = *p++; } else if(IS_DIRECTORY_SEP(p[0]) && IS_DIRECTORY_SEP(p[1])) { o = target; p++; } else if(IS_DIRECTORY_SEP(p[0]) && p[1] == '.' && (IS_DIRECTORY_SEP(p[2]) || p[2] == '\0')) { /* If "/." is the entire filename, keep the "/". Otherwise, just delete the whole "/.". */ if(o == target && p[2] == '\0') *o++ = *p; p += 2; } else if(IS_DIRECTORY_SEP(p[0]) && p[1] == '.' && p[2] == '.' /* `/../' is the "superroot" on certain file systems. */ && o != target && (IS_DIRECTORY_SEP(p[3]) || p[3] == '\0')) { while(o != target && (--o) && !IS_DIRECTORY_SEP(*o)) ; p += 3; } else { *o++ = *p++; } } target[o - target] = '\0'; return strdup(target); } int main(int argc, char** argv) { int i; char* name; char* en; int short_display; short_display = 0; i = 1; if(i < argc && !strcmp(argv[i], "-s")) { short_display = 1; i++; } for(; i < argc; i++) { name = argv[i]; en = expand_file_name(name, NULL); if(short_display) printf("%s\n", en); else printf("%s\t%s\n", name, en); free(en); } return 0; }