/* * "fwin.c" Copyright A C Norman 2003-2007 * * * Window interface for old-fashioned C applications. Intended to * be better than just running them within rxvt/xterm, but some people will * always believe that running them under emacs is best! * * Note that although the graphical bits of fwin and coded in C++ the * parts needed for a text-only interface are in just C. This is so that * on limited platforms where graphics are not relevant the C++ libraries * do not have to be used. */ /****************************************************************************** * Copyright (C) 2003-7 by Arthur Norman, Codemist Ltd. All Rights Reserved. * ******************************************************************************* * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; * * version 2.1 of the License. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * * * See also the FOX Toolkit addendum to the LGPL, which also applies to this * * code. This addedum gives, in addition to the rights granted by the LGPL, * * permission to distribute this code statically linked against other code * * without any need for that other code to have its source released. * ******************************************************************************/ /* Signature: 115225e8 12-Apr-2008 */ #ifdef HAVE_CONFIG_H #include "config.h" #else #error "config.h" needed for this file. Run "configure" #endif #include "fwin.h" /* Even without FOX if I am building on Windows I need this header file * for (eg) GetModuleFileName(). */ #ifdef WIN32 /* Indicate that I expect to be using a RECENT version of Windows */ #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif #include #include #endif #include #include #include #include #include #include #include #if HAVE_UNISTD_H #include #else #ifndef _MSC_VER extern char *getcwd(char *s, size_t n); #endif #endif #include #include #include #ifdef HAVE_DIRENT_H #include #else #ifndef WIN32 #include #else #include #endif #endif #if defined MACINTOSH && defined MAC_FRAMEWORK /* * The extent to which any code in CSL pays attention to Mac-specific * features is and will probably remain minimal! However some may be * used here. */ #include #include #endif #ifdef _MSC_VER #define MS_CDECL __cdecl #else #define MS_CDECL #endif #include "termed.h" /* * The next few are not exactly useful if FOX is not available * and hence this code will run in line-mode only. However it is * convenient to leave them available. * * Note that FOX licensing would quite like credit given there, so I * stick a line in with the other information that I might or might not * be able to display. */ char about_box_title[32] = "About XXX"; char about_box_description[32] = "XXX version 1.1"; // char about_box_rights_1[32] = "Copyright info"; char about_box_rights_2[32] = "Date or whatever"; #ifdef HAVE_LIBFOX char about_box_rights_3[32] = "See also www.fox-toolkit.org"; #else char about_box_rights_3[32] = " "; #endif /* * The value LONGEST_LEGAL_FILENAME should be seen as a problem wrt * buffer overflow! I will just blandly assume throughout all my code that * no string that denotes a full file-name (including its path) is ever * longer than this. */ #ifndef LONGEST_LEGAL_FILENAME #define LONGEST_LEGAL_FILENAME 1024 #endif const char *colour_spec = "-"; char fwin_prompt_string[MAX_PROMPT_LENGTH] = "> "; int fwin_linelength = 80; delay_callback_t *delay_callback; interrupt_callback_t *interrupt_callback; extern const char *my_getenv(const char *s); static int find_program_directory(char *argv0); #ifdef WIN32 static int program_name_dot_com; #endif int windowed = 0; int texmacs_mode = 0; extern int windowed_worker(int argc, char *argv[]); #ifdef HAVE_XFT int fwin_use_xft = 1; #else int fwin_use_xft = 0; #endif int fwin_pause_at_end = 0; #ifdef MINIMAL_SUPPORT #undef CSL #else /* Here is where things get started... */ int main(int argc, char *argv[]) { int i; #ifndef WIN32 const char *disp; #endif /* I want to know the path to the directory from which this * code was launched. */ if (argc == 0) { fprintf(stderr, "argc == 0. You tried to launch the code in a funny way?\n"); return 1; } if (find_program_directory(argv[0])) { fprintf(stderr, "Unable to identify program name and directory\n"); return 1; } texmacs_mode = 0; /* * An option "--my-path" just prints the path to the executable * and stops. */ for (i=1; i yyy run as console application * My hope is that the detection of redirected stdin/stdout will help * when the application is used in a script. There may remain a dodgy case! * if xxx is run under a debugger at least some debuggers intercept standard * input & output so debugging the windowed mode may be harded here. But I * will defer that worry since the ".exe" not the ".com" file is the version * with windowed use its prime interface. */ h = GetStdHandle(STD_INPUT_HANDLE); if (GetFileType(h) != FILE_TYPE_CHAR) windowed = 0; else if (!GetConsoleMode(h, &w)) windowed = 0; h = GetStdHandle(STD_OUTPUT_HANDLE); if (GetFileType(h) != FILE_TYPE_CHAR) windowed = 0; else if (!GetConsoleScreenBufferInfo(h, &csb)) windowed = 0; } else { /* The program was named "xxx.exe". I am going to suppose that this has NOT * been launched from a normal Windows command prompt (since xxx.com would * have been preferred). I am left with two scenarios. One is that the * program was launched by double-clicking, and in that case it detached * from its console as it started. The other is that it was launched from * a cygwin prompt (which looks for xxx.exe but not xxx.com when you type * just xxx). */ HANDLE h = GetStdHandle(STD_INPUT_HANDLE); /* The discrimination I make here is based on an empirical check of what * seems to happen under Windows XP with the version of cygwin current as * of September 2004. What I find is that when stdin has been redirected by * a shell (either the Windows command shell or cygwin, and in the cygwin * case either with " "); r = fwin_main(argc, argv); input_history_end(); term_close(); return r; } #define INPUT_BUFFER_SIZE 100 static const char *current_line; static char input_buffer[INPUT_BUFFER_SIZE]; static int chars_left = 0; static int prompt_needed = 1; int fwin_plain_getchar() { int ch; if (using_termed) { while (chars_left == 0) { term_setprompt(fwin_prompt_string); current_line = term_getline(); if (current_line == NULL) return EOF; // failed or EOF chars_left = strlen(current_line); input_history_add(current_line); } } else if (chars_left == 0) { if (prompt_needed) { printf("%s", fwin_prompt_string); prompt_needed = 0; } fflush(stdout); for (chars_left=0; chars_left 4 && argv0[len-4] == '.' && ((tolower(argv0[len-3]) == 'e' && tolower(argv0[len-2]) == 'x' && tolower(argv0[len-1]) == 'e') || (tolower(argv0[len-3]) == 'c' && tolower(argv0[len-2]) == 'o' && tolower(argv0[len-1]) == 'm'))) { program_name_dot_com = (tolower(argv0[len-3]) == 'c'); len -= 4; } for (npgm=0; npgm= sizeof(pgmname) || pgmname[0] == 0) return 2; /* Current dir unavailable or full name too long */ else { pgmname[n] = '/'; strcpy(&pgmname[n+1], argv0); fwin_full_program_name = pgmname; } } else { const char *path = my_getenv("PATH"); /* * I omit checks for names of shell built-in functions, since my code is * actually being executed by here. So I get my search path and look * for an executable file somewhere on it. I note that the shells back this * up with hash tables, and so in cases where "rehash" might be needed this * code may become confused. */ struct stat buf; uid_t myuid = geteuid(), hisuid; gid_t mygid = getegid(), hisgid; int protection; int ok = 0; /* I expect $PATH to be a sequence of directories with ":" characters to * separate them. I suppose it COULD be that somebody used directory names * that had embedded colons, and quote marks or escapes in $PATH to allow * for that. In such case this code will just fail to cope. */ if (path != NULL) { while (*path != 0) { while (*path == ':') path++; /* skip over ":" */ n = 0; while (*path != 0 && *path != ':') { pgmname[n++] = *path++; if (n > (int)(sizeof(pgmname)-3-strlen(argv0))) return 3; /* fail! 3=$PATH element overlong */ } /* Here I have separated off the next segment of my $PATH and put it at * the start of pgmname. Observe that to avoid buffer overflow I * exit abruptly if the entry on $PATH is itself too big for my buffer. */ pgmname[n++] = '/'; strcpy(&pgmname[n], argv0); /* see if the file whose name I have just built up exists at all. */ if (stat(pgmname, &buf) == -1) continue; hisuid = buf.st_uid; hisgid = buf.st_gid; protection = buf.st_mode; /* info about the file found */ /* * I now want to check if there is a file of the right name that is * executable by the current (effective) user. */ if (protection & S_IXOTH || (mygid == hisgid && protection & S_IXGRP) || (myuid == hisuid && protection & S_IXUSR)) { ok = 1; /* Haha - I have found the one we ... */ break; /* are presumably executing! */ } } } if (!ok) return 4; /* executable not found via $PATH */ /* Life is not yet quite easy! $PATH may contain some items that do not * start with "/", ie that are still local paths relative to the * current directory. I want to be able to return an absolute fully * rooted path name! So unless the item we have at present starts with "/" * I will stick the current directory's location in front. */ if (pgmname[0] != '/') { char temp[LONGEST_LEGAL_FILENAME]; strcpy(temp, pgmname); n = get_current_directory(pgmname, sizeof(pgmname)); if (n < 0) return 1; /* fail! 1=current directory failure */ if ((n + strlen(temp) + 1) >= sizeof(pgmname)) return 9; pgmname[n++] = '/'; strcpy(&pgmname[n], temp); } fwin_full_program_name = pgmname; } } /* * Now if I have a program name I will try to see if it is a symbolic link * and if so I will follow it. */ { struct stat buf; char temp[LONGEST_LEGAL_FILENAME]; if (lstat(fwin_full_program_name, &buf) != -1 && S_ISLNK(buf.st_mode) && (n1 = readlink(fwin_full_program_name, temp, sizeof(temp)-1)) > 0) { temp[n1] = 0; strcpy(pgmname, temp); fwin_full_program_name = pgmname; } } /* Now fwin_full_program_name is set up, but may refer to an array that * is stack allocated. I need to make it proper! */ w = (char *)malloc(1+strlen(fwin_full_program_name)); if (w == NULL) return 5; /* 5 = malloc fails */ strcpy(w, fwin_full_program_name); fwin_full_program_name = w; /* OK now I have the full name, which is of the form * abc/def/fgi/xyz * and I need to split it at the final "/" (and by now I very fully expect * there to be at least one "/". */ for (n=strlen(fwin_full_program_name)-1; n>=0; n--) if (fwin_full_program_name[n] == '/') break; if (n < 0) return 6; /* 6 = no "/" in full file path */ w = (char *)malloc(1+n); if (w == NULL) return 7; /* 7 = malloc fails */ strncpy(w, fwin_full_program_name, n); w[n] = 0; /* Note that if the executable was "/foo" then programDir will end up as "" * so that programDir + "/" + programName works out properly. */ programDir = w; n1 = strlen(fwin_full_program_name) - n; w = (char *)malloc(n1); if (w == NULL) return 8; /* 8 = malloc fails */ strncpy(w, fwin_full_program_name+n+1, n1-1); w[n1-1] = 0; programName = w; return 0; /* whew! */ } #endif /* end of Unix/Linux name unpicking */ #ifndef S_IRUSR #ifdef __S_IRUSR #define S_IRUSR __S_IRUSR #endif #endif #ifndef S_IWUSR #ifdef __S_IWUSR #define S_IWUSR __S_IWUSR #endif #endif #ifndef S_IXUSR #ifdef __S_IXUSR #define S_IXUSR __S_IXUSR #endif #endif extern int get_home_directory(char *b, int len); extern int get_users_home_directory(char *b, int len); #ifdef CSL extern char *look_in_lisp_variable(char *b, int prefix); #endif static void process_file_name(char *filename, char *old, size_t n) /* * This procedure maps filenames by expanding some environment * variables. It is very thoroughly system specific, which is why it * is in this file. See also LONGEST_LEGAL_FILENAME in "tags.h" for a * limit on the permitted size of an expanded filename. * The input (old) is not necessarily properly terminated as a C string, * so n says how many characters to inspect. Build a converted name * in filename. * At present the expansions I allow are: * * $xxx (terminated by '.', '/' or '\' with at least one char x) * ${xxx} (self-terminating) * First check for a Lisp variable @xxx. If this is set (and is * a string or a symbol) then its value is used. If not then * next inspect the environment variable xxx and dump its * value into the output. If the variable is unset then a check * is made for the value of a global lisp variable called $xxx, * and if that exists and is a string or symbol it is used. * If $xxx is undefined a null string is inserted. * If one of the variables is defined but has an improper value * then the whole file-translation fails. * The use of two Lisp variables makes it possible to control * precedence between these and shell variables. * At one stage I make the search order $xxx, env, @xxx, but then * in shell scripts it is easier to go "-D@xxx=..." because * "-D$xxx=..." tends to get subject to shell expansion. So now * I give priority to the version I use most, to avoid being bitten * when somebody has a stray shell variable set. * * ~ ) followed by '.', '/' or '\' * ~xxx ) * On Unix these try to find home directories using * getpwuid(getuid()) for '~' and getpwnam() for ~xxx. * If that fails ~ expands into nothing at all. * This syntax is only recognised at the very start of a file-name. * For systems other than Unix this syntax will not be useful and * should be avoided, however as an experimental place-holder I * may do things with environment variables called HOME etc. * * * I convert file-names of the form aaa/bbb/ccc.ddd into something * acceptable to the system being used, even though this may result in * some native file titles that include '/' characters becoming unavailable. * The reasoning here is that scripts and programs can then use Unix-like * names and non-Unix hosts will treat them forgivingly. * * */ { int i; int c; char *o; if (n == 0) { *filename = 0; return; /* deem zero-length name to be illegal */ } o = filename; c = *old; /* * First I deal with a leading "~" */ if (c == '~') { old++; n--; while (n != 0) { c = *old; if (c == '.' || c == '/' || c == '\\') break; old++; n--; *o++ = (char)c; } *o = 0; /* * actually deciding what the home directory is is passed down to a * system-specific call, but it is not to be relied upon especially * on personal computers. */ if (o == filename) /* '~' on its own */ { get_home_directory(filename, LONGEST_LEGAL_FILENAME); o = filename + strlen(filename); } else { get_users_home_directory(filename, LONGEST_LEGAL_FILENAME); o = filename + strlen(filename); } } /* * Having copies a user-name across (if there was one) I now copy the * rest of the file-name, expanding $xxx and ${xxx} as necessary. */ while (n != 0) { c = *old++; n--; /* * If I find a "$" that is either at the end of the file-name or that is * immediately followed by ".", "/" or "\" then I will not use it for * parameter expansion. This at least gives me some help with the RISCOS * file-name $.abc.def where the "$" is used to indicate the root of the * current disc. Well RISCOS is no longer supported here so this does * not worry me a lot! */ if (c == '$' && n != 0 && (c = *old) != '.' && c != '/' && c != '\\') { char *p = o; const char *w; /* * I collect the name of the parameter at the end of my file-name buffer, * but will over-write it later on when I actually do the expansion. */ if (c == '{') { old++; n--; while (n != 0) { c = *old++; n--; if (c == '}') break; *p++ = (char)c; } } else { while (n != 0) { c = *old; if (c == '.' || c == '/' || c == '\\') break; old++; n--; *p++ = (char)c; } } *p = 0; i = strlen(o) + 2; while (i-- != 0) o[i] = o[i-1]; #ifdef CSL if ((p = look_in_lisp_variable(o, '@')) != NULL && p != o) o = p; else #endif if ((w = my_getenv(o+1)) != NULL) /* Shell variable? */ { strcpy(o, w); o = o + strlen(o); } #ifdef CSL else if ((p = look_in_lisp_variable(o, '$')) != NULL) o = p; #endif else { *filename = 0; /* return reporting failure */ return; } } else *o++ = (char)c; } *o = 0; #ifdef WIN32 /* * Now the filename has had $ and ~ prefix things expanded - I "just" * need to deal with sub-directory representation issues. Specifically I need * to map "/" separators into "\" so that if a user presents a file * name such as aaa/bbb/ccc.d it gets passed to the operating system * as aaa\bbb\ccc.d * Note that I enable this code under the heading MS_DOS but really it * means any file-system (eg Windows too) that uses "\" as its main * directory separator character. * As of September 2004 I will also map an intial sequence * /cygdrive/x/ * onto x:\ */ if (strncmp(filename, "/cygdrive/", 10) == 0 && filename[11] == '/') { char *p = filename+2, *tail = filename+11; filename[0] = filename[10]; filename[1] = ':'; while (*tail != 0) *p++ = *tail++; *p = 0; } /* * I map "/" characters in MSDOS filenames into "\" so that users * can give file names with Unix-like slashes as separators if they want. * People who WANT to use filenames with '/' in them will be hurt. */ { int j; char *tail = filename; while ((j = *tail) != 0) { if (j == '/') *tail = '\\'; tail++; } /* * stat and friends do not like directories referred to as "\foo\", so check * for a trailing slash, being careful to respect directories with names * like "\" and "a:\". */ j = strlen(filename); if (j > 0 && j != 1 && !(j == 3 && *(filename+1) == ':')) { if ( (*(tail - 1) == '\\')) *(tail - 1) = 0; } } #endif /* WIN32 */ #if defined MACINTOSH && defined MAC_FRAMEWORK /* * For MacOS the issue of "aliases" arises. The "preferred" file system * is HFS+ and that supports both links and aliases, but at the very least * some old users and legacy applications will certainly continue to use * links. However the Posix-style APIs do not provide any way to deal with * them! So here I use some Carbon calls to map a path to an alias into * a path to the file it refers to. Thise code was requested by Thomas * Sturm who provided a skeleton chunk of code showing what APIs needed to be * used and references to the documentation to them, so thanks are due. */ { char alias[LONGEST_LEGAL_FILENAME]; FSRef ref; Boolean is_folder, is_alias; /* * This works by converting from a path to an FSRef object, which is the Mac * internal handle. It can then resolve the alias. I use the option that * will chain through sequences of aliases if necessary until a genuine * regular file is found. If no aliases has been involved I do nothing. * If any of the Mac system calls report errors of any sort I do * nothing. In the end if all works I convert from an FSRef back to a path and * copy it to where I want it to be. */ if (FSPathMakeRef((UInt8 *)filename, &ref, NULL) == noErr && FSResolveAliasFile(&ref, TRUE, &is_folder, &is_alias) == noErr && is_alias && FSRefMakePath(&ref, (UInt8 *)alias, (UInt32)sizeof(alias)) == noErr) { strcpy(filename, alias); } } #endif } /* * datestamps that I store away have given me significant * trouble with regard to portability - so now I deal with times by * talking to the system in terms of broken down local time (struct tm). * I then pack things up for myself to get 32-bit timestamps. The * encoding I use aims at simplicity - it treats all months as 31 days * and thus does not have to worry about leap years etc. The effect will be * rather as if dates were stored as strings. And MAYBE I thereby avoid * some of the oddities that arise when data files containing packed dates * are transported across time-zones. * * NOTE: dates here are based from 1970, and this will lead to overflow * beyond 32-bit offsets in around 2099. At the time of writing that is around * 100 years ahead, and I intend not to worry. Note it is important here to * us an unsigned number or else the overflow is sooner and might even cause * genuine pain! * * ANOTHER NOTE: I only allow the "seconds" field to run from 0 to 59. * In consequence I am quite possibly going to mess up when there are * leap seconds, and this confusion could make times processed here * disagree across systems by up to the number of leap seconds that * have been used to date. Well I have quite severe doubts about time * agreement closer than a few seconds anyway and so again I am going to * ignore this oddity! But those who keep systems synchronised at a * millisecond or microsecond resolution (GPS anybody?) might need to * know I have been sloppy. */ void unpack_date(unsigned long int r, int *year, int *mon, int *day, int *hour, int *min, int *sec) { *sec = r%60; r = r/60; *min = r%60; r = r/60; *hour = r%24; r = r/24; *day = r%32; r = r/32; *mon = r%12; r = r/12; /* * Please note that the Standard C "struct tm" structure specifies dates * in terms of years since 1900. Thus from the year 2000 on the year will * be a value of at least 100, but that is not supposed to be any special * cause of disaster. In particular the calculation involving "+70" * rather than "+1970" is NOT a bug here! */ *year = 70+r; } unsigned long int pack_date(int year, int mon, int day, int hour, int min, int sec) { unsigned long int r = (year-70)*12 + mon; r = r*32 + day; r = r*24 + hour; r = r*60 + min; return r*60 + sec; } typedef struct date_and_type { unsigned long int date; unsigned long int type; } date_and_type; #ifdef WIN32 /* * This version is for Windows NT 3.1 with Microsoft VC++, Windows 95, 98, * NT 3.5, 4.0, 2000, XP etc etc, also with Watcom C, mingw32 and so on. * Note it uses the native Windows capabilities and so it is not intended * for use with cygwin and its Unix-portability layer. */ #include "windows.h" int Cmkdir(char *name) { SECURITY_ATTRIBUTES s; s.nLength = sizeof(s); s.lpSecurityDescriptor = NULL; s.bInheritHandle = FALSE; return CreateDirectory(name, &s); } int truncate_file(FILE *f, long int where) { if (fflush(f) != 0) return 1; #ifdef _MSC_VER return _chsize(_fileno(f), where); /* Returns zero if success */ #else #ifdef __CYGWIN__ if (fflush(f) != 0) return 1; return ftruncate(fileno(f), where); /* Returns zero if success */ #else return chsize(fileno(f), where); /* Returns zero if success */ #endif #endif } void set_filedate(char *name, unsigned long int datestamp, unsigned long int filetype) { HANDLE h = CreateFile(name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); SYSTEMTIME st; FILETIME ft; int yr, mon, day, hr, min, sec; /* * Here datestamp is a time expressed (sort of) in seconds since the start * of 1970. * I need to convert it into a broken-down SYSTEMTIME so that I * can then re-pack it as a Windows-NT FILETIME.... */ unpack_date(datestamp, &yr, &mon, &day, &hr, &min, &sec); st.wMilliseconds = 0; st.wYear = yr + 1900; /* Windows NT uses full dates since the year 0 */ st.wMonth = mon + 1; st.wDay = day; st.wHour = hr; st.wMinute = min; st.wSecond = sec; SystemTimeToFileTime(&st, &ft); SetFileTime(h, NULL, NULL, &ft); CloseHandle(h); } void put_fileinfo(date_and_type *p, char *name) { unsigned long int datestamp, filetype; #ifdef _MSC_VER struct _stat file_info; #else struct stat file_info; #endif struct tm *st; /* * Read file parameters... Maybe I should use a Windows-style not a Unix-style * call here? */ #ifdef _MSC_VER _stat(name, &file_info); #else stat(name, &file_info); #endif st = localtime(&(file_info.st_mtime)); datestamp = pack_date(st->tm_year, st->tm_mon, st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec); filetype = 0xfff; p->date = datestamp; p->type = filetype; } #else /* WIN32 */ /* * On some Unix variants I may want this declaration inserted and on others * it would clash with a system-provided header file. Ugh! With luck the C * compiler will invent a suitable calling convention even if a declaration * is not present. * extern ftruncate(int, int); */ int truncate_file(FILE *f, long int where) { if (fflush(f) != 0) return 1; return ftruncate(fileno(f), where); /* Returns zero if success */ } /* extern void mkdir(const char *, unsigned short int); */ int Cmkdir(char *s) { mkdir(s, 0775); return 1; } #include void set_filedate(char *name, unsigned long int datestamp, unsigned long int filetype) { #ifdef UTIME_TIME_T time_t tt[2]; #else struct utimbuf tt; #endif time_t t0; struct tm st; unpack_date(datestamp, &st.tm_year, &st.tm_mon, &st.tm_mday, &st.tm_hour, &st.tm_min, &st.tm_sec); st.tm_isdst = -1; t0 = mktime(&st); #ifdef UTIME_TIME_T tt[0] = tt[1] = t0; #else tt.actime = tt.modtime = t0; #endif utime(name, &tt); } void put_fileinfo(date_and_type *p, char *name) { unsigned long int datestamp, filetype; struct stat file_info; struct tm *st; /* * Read file parameters... */ stat(name, &file_info); st = localtime(&(file_info.st_mtime)); datestamp = pack_date(st->tm_year, st->tm_mon, st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec); filetype = 0xfff; /* should get access status here? */ p->date = datestamp; p->type = filetype; } #endif /* WIN32 */ /* * If I am to process directories I need a set of routines that will * scan sub-directories for me. This is necessarily dependent on * the operating system I am running under, hence the conditional compilation * here. The specification I want is: * void scan_directory(const char *dir, * void (*proc)(char *name, int why, long int size)); * * This is called with a file- or directory-name as its first argument * and a function as its second. * It calls the function for every directory and every file that can be found * rooted from the given place. If the file to scan is specified as NULL * the current directory is processed. I also arrange that an input string * "." (on Windows, DOS and Unix) or "@" (Archimedes) is treated as a request * to scan the whole of the current directory. * When a simple file is found the procedure is called with the name of the * file, why=0, and the length (in bytes) of the file. For a directory * the function is called with why=1, then the contents of the directory are * processed. For directories the size information will be 0. There is no * guarantee of useful behaviour if some of the files to be scanned are * flagged as "invisible" or "not readable" or if they are otherwise special. * * I also provide a similar function scan_files() with the same arguments that * does just the same except that it does not recurse into sub-directories, * but if the name originally passed is that of a directory then all the * files in it will be scanned. */ /* * When scan_directory calls the procedure it has been passed, it will have * set scan_leafstart to the offset in the passed filename where the * original directory ended and the new information starts. */ int scan_leafstart = 0; /* * For CSL's purposes the following 3 are in syscsl.h, but in general I do not * want to use that header with random fwin applications... */ #define SCAN_FILE 0 #define SCAN_STARTDIR 1 #define SCAN_ENDDIR 2 /* * I use a (static) flag to indicate how sub-directories should be * handled, and what to do about case. By default I fold to lower case * on windows. setting hostcase non-zero causes case to be preserved. */ static int recursive_scan, hostcase = 0; void set_hostcase(int fg) { hostcase = fg; } #ifdef WIN32 /* Hmm - buffer overflow worry with the next line! */ static char filename[LONGEST_LEGAL_FILENAME]; static WIN32_FIND_DATA *found_files = NULL; static int n_found_files = 0, max_found_files = 0; #define TABLE_INCREMENT 50 static int more_files(void) { if (n_found_files > max_found_files - 5) { WIN32_FIND_DATA *fnew = (WIN32_FIND_DATA *) realloc((void *)found_files, sizeof(WIN32_FIND_DATA)* (max_found_files + TABLE_INCREMENT)); if (fnew == NULL) return 1; /* failure flag */ found_files = fnew; max_found_files += TABLE_INCREMENT; } return 0; } /* * Anybody compiling using Microsoft Visual C++ had better note that * the type declared in the Microsoft header files for qsort insists * on a __cdecl here. Ugh. */ int MS_CDECL alphasort_files(const void *a, const void *b) { const WIN32_FIND_DATA *fa = (const WIN32_FIND_DATA *)a, *fb = (const WIN32_FIND_DATA *)b; return strncmp(fb->cFileName, fa->cFileName, sizeof(fa->cFileName)); } static void exall(int namelength, void (*proc)(const char *name, int why, long int size)) /* * This procedure scans a directory-full of files, calling the given procedure * to process each one it finds. */ { WIN32_FIND_DATA found; int rootlen = namelength, first = n_found_files; HANDLE hSearch = FindFirstFile(filename, &found); if (hSearch == INVALID_HANDLE_VALUE) return; /* No files found at all */ for (;;) { if (more_files()) break; found_files[n_found_files++] = found; if (!FindNextFile(hSearch, &found)) break; } FindClose(hSearch); qsort((void *)&found_files[first], n_found_files-first, sizeof(WIN32_FIND_DATA), alphasort_files); while (rootlen>=0 && filename[rootlen]!='\\') rootlen--; while (n_found_files != first) { char *p = (char *)&found_files[--n_found_files].cFileName; int c; /* * Fill out filename with the actual name I grabbed, i.e. with * wild-cards expanded. */ namelength = rootlen+1; /* * I fold DOS filenames into lower case because it does not matter much * to DOS and I think it looks better - furthermore it helps when I move * archives to other systems. So I do the same on NT. */ while ((c = *p++) != 0) { if (!hostcase) if (isupper(c)) c = tolower(c); filename[namelength++] = (char)c; } filename[namelength] = 0; if (found_files[n_found_files].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (found_files[n_found_files].cFileName[0] != '.') /* * I filter out directory names that start with '.'. * This is to avoid calamity with recursion though chains such as .\.\.\..... */ { proc(filename, SCAN_STARTDIR, 0); if (!recursive_scan) continue; strcpy(&filename[namelength], "\\*.*"); /* * Append "\*.*" to the directory-name and try again, thereby scanning * its contents. */ exall(namelength+4, proc); filename[namelength] = 0; proc(filename, SCAN_ENDDIR, 0); } } else proc(filename, SCAN_FILE, found_files[n_found_files].nFileSizeLow); } return; } void scan_directory(const char *dir, void (*proc)(const char *name, int why, long int size)) { recursive_scan = 1; if (dir==NULL || strcmp(dir,".")==0) { dir = "*.*"; scan_leafstart = 0; } else scan_leafstart = strlen(dir)+1; strcpy(filename, dir); exall(strlen(filename), proc); } void scan_files(const char *dir, void (*proc)(const char *name, int why, long int size)) { recursive_scan = 0; if (dir==NULL || strcmp(dir,".")==0) { strcpy(filename, "*.*"); scan_leafstart = 0; } else { scan_leafstart = strlen(dir); strcpy(filename, dir); if (filename[scan_leafstart-1] == '\\') { /* Root directory */ strcpy(filename+scan_leafstart, "*.*"); --scan_leafstart; } else strcpy(filename+scan_leafstart, "\\*.*"); scan_leafstart++; } exall(strlen(filename), proc); } #else /* WIN32 */ static char filename[LONGEST_LEGAL_FILENAME]; /* * The code here uses opendir, readdir and closedir and as such ought to * be Posix compatible. The macro USE_DIRECT_H can cause an older variant * on this idea to be used. BUt it may need adjustment for different * systems. */ static char **found_files = NULL; int n_found_files = 0, max_found_files = 0; #define TABLE_INCREMENT 50 static int more_files(void) { if (n_found_files > max_found_files - 5) { char **fnew = (char **) realloc((void *)found_files, sizeof(char *) * (max_found_files + TABLE_INCREMENT)); if (fnew == NULL) return 1; /* failure flag */ found_files = fnew; max_found_files += TABLE_INCREMENT; } return 0; } int alphasort_files(const void *a, const void *b) { const char *fa = *(const char **)a, *fb = *(const char **)b; return strcmp(fb, fa); } static void scan_file(int namelength, void (*proc)(const char *name, int why, long int size)); static void exall(int namelength, void (*proc)(const char *name, int why, long int size)) { DIR *d; #ifdef USE_DIRECT_H struct direct *dd; #else struct dirent *dd; #endif int rootlen = namelength, first = n_found_files; proc(filename, SCAN_STARTDIR, 0); d = opendir(filename); if (d != NULL) { while ((dd = readdir(d)) != NULL) { char *leafname = dd->d_name; char *copyname; /* * readdir hands back both "." and ".." but I had better not recurse * into either! */ if (strcmp(leafname, ".") == 0 || strcmp(leafname, "..") == 0) continue; if (more_files()) break; copyname = (char *)malloc(1+strlen(leafname)); if (copyname == NULL) break; strcpy(copyname, leafname); found_files[n_found_files++] = copyname; } closedir(d); } qsort((void *)&found_files[first], n_found_files-first, sizeof(char *), alphasort_files); filename[rootlen] = '/'; while (n_found_files != first) { char *p = found_files[--n_found_files]; int c; namelength = rootlen+1; while ((c = *p++) != 0) filename[namelength++] = (char)c; free((void *)found_files[n_found_files]); filename[namelength] = 0; scan_file(namelength, proc); } filename[rootlen] = 0; proc(filename, SCAN_ENDDIR, 0); } #ifndef S_IFMT # ifdef __S_IFMT # define S_IFMT __S_IFMT # endif #endif #ifndef S_IFDIR # ifdef __S_IFDIR # define S_IFDIR __S_IFDIR # endif #endif #ifndef S_IFREG # ifdef __S_IFREG # define S_IFREG __S_IFREG # endif #endif static void scan_file(int namelength, void (*proc)(const char *name, int why, long int size)) { struct stat buf; stat(filename, &buf); if ((buf.st_mode & S_IFMT) == S_IFDIR) { if (!recursive_scan) proc(filename, SCAN_STARTDIR, 0); else exall(namelength, proc); } else if ((buf.st_mode & S_IFMT) == S_IFREG) proc(filename, SCAN_FILE, buf.st_size); /* else fprintf(stderr, "Mode of %s is %o\n", filename, buf.st_mode); */ } void scan_directory(const char *dir, void (*proc)(const char *name, int why, long int size)) { recursive_scan = 1; if (dir==NULL || strcmp(dir, ".")==0) dir = "."; scan_leafstart = strlen(dir)+1; strcpy(filename, dir); scan_file(scan_leafstart-1, proc); } void scan_files(const char *dir, void (*proc)(const char *name, int why, long int size)) { recursive_scan = 0; if (dir==NULL || strcmp(dir, ".")==0) dir = "."; scan_leafstart = strlen(dir)+1; strcpy(filename, dir); exall(scan_leafstart-1, proc); } #endif /* WIN32 */ FILE *open_file(char *filename, char *old, size_t n, char *mode, FILE *old_file) { /* * mode is something like "r" or "w" or "rb", as needed by fopen(), * and old_file is NULL normally, but can be a (FILE *) to indicate * the use of freopen rather than fopen. */ FILE *ff; process_file_name(filename, old, n); if (*filename == 0) return NULL; if (old_file == NULL) ff = fopen(filename, mode); else ff = freopen(filename, mode, old_file); /* * In suitable cases when the first attempt to open the file fails I * will try creating any necessary directories and then try again. */ if (ff==NULL && *mode=='w') { char *p = filename; while (*p != 0) { int ch = *p; if (ch == '/' || ch == '\\') { *p = 0; Cmkdir(filename); *p = ch; } p++; } if (old_file == NULL) ff = fopen(filename, mode); else ff = freopen(filename, mode, old_file); } return ff; } static char err_buf[LONGEST_LEGAL_FILENAME+100]; char *change_directory(char *filename, char *old, size_t n) { process_file_name(filename, old, n); if (*filename == 0) { sprintf(err_buf, "Filename \"%s\" invalid.", old); return err_buf; } if (chdir(filename)) { const char *msg; switch (errno) { case ENOTDIR: msg = "A component of %s is not a directory."; break; case ENOENT: msg = "The directory %s does not exist."; break; case EACCES: msg = "Insufficient permission for %s."; break; case ENAMETOOLONG: msg = "The pathname %s is too long."; break; default: msg = "Cannot change directory to %s."; break; } sprintf(err_buf, msg, filename); return err_buf; } else return NULL; } int create_directory(char *filename, char *old, size_t n) { process_file_name(filename, old, n); if (*filename == 0) return 1; return Cmkdir(filename); } static void remove_files(const char *name, int dirp, long int size) /* Remove a file, or a directory and all its contents */ { switch (dirp) { case 0: /* SCAN_FILE */ remove(name); return; case 2: /* SCAN_ENDDIR */ rmdir(name); return; default: /* 1 == SCAN_STARTDIR */ return; } } int delete_file(char *filename, char *old, size_t n) { process_file_name(filename, old, n); if (*filename == 0) return 0; /* * We cannot simply use remove here, since this will not * work with directories and their contents. Hence the * use of scan_directory. */ scan_directory(filename, remove_files); return 0; } long file_length(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return -1; return (long)(buf.st_size); } #ifdef NAG_VERSION int list_directory_members(char *filename, char *old, char **filelist[], size_t n) { struct dirent **namelist; int number_of_entries, i; char **files; process_file_name(filename, old, n); /* scandir expects "." for the current directory */ if (*filename == 0) number_of_entries = scandir(".",&namelist,NULL,NULL); else number_of_entries = scandir(filename,&namelist,NULL,NULL); /* * If the scandir failed then return now, since we make an assumption later * that we found at least two entries: "." and "..". */ if (number_of_entries == -1) return -1; files=(char **)malloc(number_of_entries*sizeof(char *)); for (i=0;id_name); free(namelist[i]); } free(namelist); *filelist = files; /* * When we return we will prepend the directory name to the files, so we * must make sure it is suitable for that. This is done here since it is * platform dependent (i.e. in DOS we would need to ensure the last * character was "\"). */ /* i=strlen(filename); if (i > 0 && filename[i-1] != '/') { filename[i]='/'; filename[i+1]='\0'; } */ return number_of_entries; } #else void list_directory_members(char *filename, char *old, size_t n, void (*fn)(const char *name, int why, long int size)) { process_file_name(filename, old, n); scan_files(filename, fn); } #endif int file_exists(char *filename, char *old, size_t n, char *tt) /* * This returns YES if the file exists, and as a side-effect copies a * textual form of the last-changed-time of the file into the buffer tt. */ { struct stat statbuff; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename, &statbuff) != 0) return 0; strcpy(tt, ctime(&(statbuff.st_mtime))); return 1; } int directoryp(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; return ((buf.st_mode & S_IFMT) == S_IFDIR); } char *get_truename(char *filename, char *old, size_t n) { struct stat buf; char *temp, *fn, *dir; char pwd[LONGEST_LEGAL_FILENAME]; process_file_name(filename, old, n); if (*filename == 0) { strcpy(filename, "truename"); return NULL; } /* Find out whether we have a file or a directory */ if (stat(filename,&buf) == -1) { strcpy(filename, "truename: cannot stat file"); return NULL; } /* Store current directory */ if (get_current_directory(pwd, LONGEST_LEGAL_FILENAME) < 0) { strcpy(filename, "truename: cannot get current working directory"); return NULL; } if ((buf.st_mode & S_IFMT) == S_IFDIR) { /* We have a directory */ char *dir = (char*)malloc(LONGEST_LEGAL_FILENAME); if (chdir(filename) != 0) { strcpy(filename, "truename: cannot change directory"); return NULL; } if (getcwd(dir,LONGEST_LEGAL_FILENAME) == NULL) { strcpy(filename, "truename: cannot get current working directory"); return NULL; } if (chdir(pwd) != 0) { strcpy(filename, "truename: cannot change directory"); return NULL; } /* * Axiom-specific hack: truename preserves '/' at the end of * a path */ if (old[n-1] == '/' && dir[strlen(dir)-1] != '/') { n = strlen(dir); dir[n] = '/'; dir[n+1] = '\0'; } return dir; } else { /* Assume we have some kind of file */ temp = strrchr(filename,'/'); if (temp) { /* Found a directory component */ char theDir[LONGEST_LEGAL_FILENAME]; fn = (char *)malloc(1+strlen(temp)); strcpy(fn, temp); *temp = '\0'; /* fn is now "/file" and filename is the directory */ if (chdir(filename) != 0) { strcpy(filename, "truename: cannot change directory"); return NULL; } if (get_current_directory(theDir, LONGEST_LEGAL_FILENAME) < 0) { strcpy(filename, "truename: cannot get current working directory"); return NULL; } temp = theDir; if (chdir(pwd) != 0) { strcpy(filename, "truename: cannot change directory"); return NULL; } dir = (char *)malloc((strlen(temp) + strlen(fn) + 1)*sizeof(char)); if (dir == NULL) { strcpy(filename, "truename: run out of memory"); return NULL; } strcpy(dir, temp); free(temp); strcat(dir, fn); free(fn); return dir; } else { dir = (char *)malloc((strlen(pwd) + strlen(filename) + 2)*sizeof(char)); if (dir == NULL) { strcpy(filename, "truename: run out of memory"); return NULL; } strcpy(dir, pwd); strcat(dir, "/"); strcat(dir, filename); return dir; } } } /* * The tests here are probably rather WRONG_MINDED in that they check the * status of the file and report whether its OWNER could read, write or * execute it, rather than whether the current user could. However what * I do here will hold the fort for now. */ int file_readable(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; /* File probably does not exist */ /* * The #ifdef here is a cop-out and has surfaced while trying to build * using the Microsoft C compiler, where there will be a different API I * could use to get this information... */ #ifndef S_IRUSR return 1; #else return (buf.st_mode & S_IRUSR); #endif } int file_writeable(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; /* Should we check to see if the directory is writeable? */ #ifndef S_IWUSR return 1; #else return (buf.st_mode & S_IWUSR); #endif } int file_executable(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; /* Should we check to see if the directory is writeable? */ #ifndef S_IXUSR return 1; #else return (buf.st_mode & S_IXUSR); #endif } int rename_file(char *from_name, char *from_old, size_t from_size, char *to_name, char *to_old, size_t to_size) { process_file_name(from_name, from_old, from_size); process_file_name(to_name, to_old, to_size); if (*from_name == 0 || *to_name == 0) return 0; return rename(from_name,to_name); } /* * getenv() is a mild pain: Windows seems * to have a strong preference for upper case names. To allow for * all this I do not call getenv() directly but go via the following * code that can patch things up. */ const char *my_getenv(const char *s) { #ifdef WIN32 char uppercasename[LONGEST_LEGAL_FILENAME]; char *p = uppercasename; int c; while ((c = *s++) != 0) *p++ = toupper(c); *p = 0; return getenv(uppercasename); #else return getenv(s); #endif } int my_system(char *s) { return system(s); } #define DO_NOT_USE_GETUID 1 /* For MinGW */ #ifndef DO_NOT_USE_GETUID /* * "machine.h" should set DO_NOT_USE_GETUID if that function is not * properly available. Not having it will make the treatment of * (eg) "~xxx/..." in filenames less satisfactory. */ #include int get_home_directory(char *b, int len) { int i; struct passwd *pw = getpwuid(getuid()); strcpy(b, pw->pw_dir); i = strlen(b); /* Here the directory handed back has "/" forced in as its final character */ if ( b[i-1] != '/') { b[i++] = '/'; b[i] = 0; } return i; } int get_users_home_directory(char *b, int len) { struct passwd *pw = getpwnam(b); if (pw != NULL) strcpy(b, pw->pw_dir); else strcpy(b, "."); /* use current directory if getpwnam() fails */ return strlen(b); } #else /* USE_GETUID */ int get_home_directory(char *b, int len) { int i; strcpy(b, getenv("HOME")); /* Probably works with most shells */ i = strlen(b); if ( b[i-1] != '/') { b[i++] = '/'; b[i] = 0; } return i; } int get_users_home_directory(char *b, int len) { strcpy(b, "."); /* use current directory if getpwnam() no available */ return 1; } #endif /* USE_GETUID */ /* end of fwin.c */