/*
* "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 <windows.h>
#include <io.h>
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#else
#ifndef _MSC_VER
extern char *getcwd(char *s, size_t n);
#endif
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#else
#ifndef WIN32
#include <sys/dir.h>
#else
#include <direct.h>
#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 <Carbon/Carbon.h>
#include <CoreServices/CoreServices.h>
#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";
// <icon appears here>
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<argc; i++)
{ if (strcmp(argv[i], "--my-path") == 0)
{ printf("%s\n", programDir);
exit(0);
}
}
#ifdef HAVE_LIBFOX
/*
* As the very first thing I will do, I will seek an argument
* that is just "-w", and if it is present record that I will want to
* run in text mode, not windowed mode. I also detected "--", "-f"
* and "-f" and use them to flag up a request to run minimised.
* Note that "-w" takes precedence over "--" here...
*
* I run as a minimise window (by default) in the "--" case since I can use
* the window title-bar to report progress even when all output is directed to
* file.
*/
windowed = 1;
#ifdef WIN32
/* I have tried various messy Windows API cals here to get this right.
* But so far I find that the cases that apply to me are
* (a) windows command prompt : normal case
* (b) windows command prompt : stdin redirected via "<" on command line
* (c) windows, but launched by a double-click, .com version
* (d) windows, but launched by a double-click, .exe version
* (e) cygwin shell : normal case
* (f) cygwin shell : stdin redirected via "<"
* leave me in a state
* (a) stdin exists and is a tty, a char device and a Console
* (b) stdin exists and is a pipe or a file not a tty
* (c) as (a)
* (d) stdin seems to exist but is not a tty
* (e) stdin exists and is a pipe
* (f) as (e)
* I want (b), (c) and (f) to force a non-windowed treatment. But you may see
* that various cases are not readily properly distinguished...
*
* So for now I will leave the code not doing ANYTHING special so that the
* user must go "-w" to specify windowed mode.
*/
if (program_name_dot_com)
{
/* The program was named "xxx.com". I will assume that that means it was
* a console-mode application and it is being launched directly from a
* Windows console. Why do I feel these are plausible:
* . The Makefile.in & configure.ac stuff arranges to build xxx.com as
* console mode and xxx.exe as subsystem:windows
* . A Windows command prompt will launch xxx.com in preference to xxx.exe
* if both are present
* . xxx.com is not given an icon, while xxx.exe is - people should not
* double-click on the .com version (please)
* Obviously users can subvert this by copying xxx.exe to yyy.com, by
* double clicking where I did not want or by specifying an explicit
* extension when they launch a command from a console prompt. But in such
* cases I will take the view that they will get what they deserve!
*/
HANDLE h;
DWORD w;
CONSOLE_SCREEN_BUFFER_INFO csb;
/* If either standard input or output has been redirected I will force use
* of console rather than windowed mode. Thus
* xxx launch in a window
* xxx -w run as console application
* xxx < yyy run as console application
* xxx > 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 "<file" or "<<TAG") my standard input handle exists
* and identifies itself as type DISK. The the case of launching the code
* by double-clicking on the .exe file the handle is probably invalid, but
* GetFileType returns FILE_TYPE_UNKNOWN. The end effect is that I can
* detect cases where input has bene redirected in a way that appears to
* work in both cases. Note that if a user wants to launch an application
* via a pipe then they should EITHER launch the ".com" version or (better)
* explictly provide a "-w" flag to indicate that the application should
* work in stream/console mode.
*/
if (GetFileType(h) == FILE_TYPE_DISK) windowed = 0;
}
#else /* WIN32 */
/* If stdin or stdout is not from a "tty" I will run in non-windowed mode.
* This may help when the system is used in scripts. I worry a bit about
* what the status of stdin/stdout are when launched not from a command line
* but by clicking on an icon...
*/
if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) windowed = 0;
/* On Unix-like systems I will check the DISPLAY variable, and if it is not
* set I will suppose that I can not create a window. That case will normally
* arise when you have gained remote access to the system eg via telnet or
* ssh without X forwarding. I will also insist that if set it has a ":" in
* its value... that is to avoid trouble with it getting set to an empty
* string.
*/
disp = my_getenv("DISPLAY");
if (disp == NULL || strchr(disp, ':')==NULL) windowed = 0;
#endif /* WIN32 */
/*
* REGARDLESS of any decisions about windowing made so for things can be
* forced by command line options.
* -w+ forces an attempt to run in a window even if it looks as if that
* would not make sense or would fail. It is mainly for debugging.
* -w. forces use of a window, but starts it minimised.
* -w forces command-line rather than windowed use (can also write
* "-w-" for this case).
*/
for (i=1; i<argc; i++)
{ if (strcmp(argv[i], "--texmacs") == 0) texmacs_mode = 1;
else if (strncmp(argv[i], "-w", 2) == 0)
{ if (argv[i][2] == '+') windowed = 1;
else if (argv[i][2] == '.') windowed = -1;
else windowed = 0;
break;
}
else if (strcmp(argv[i], "-h") == 0 ||
strcmp(argv[i], "-H") == 0)
#ifdef HAVE_XFT
fwin_use_xft = 0;
#else
; /* Ignore "-h" option if Xft not available */
#endif
/*
* Note well that I detect just "--" as an entire argument here, so that
* extended options "--option" do not interfere.
*/
else if ((strcmp(argv[i], "--") == 0 ||
strcmp(argv[i], "-f") == 0 ||
strcmp(argv[i], "-F") == 0) &&
windowed != 0) windowed = -1;
}
if (texmacs_mode) windowed = 0;
#else /* HAVE_LIBFOX */
/* If the FOX toolkit is not available there is no point in
* looking for a command-line option that controls whether to use it!
*/
#endif /* HAVE_LIBFOX */
/* Windowed or not, if there is an argument "-b" or "-bxxxx" then the
* string xxx will do something about screen colours. An empty string
* will suggest no colouring, the string "-" (as in -b-) whatever default
* I choose.
*/
colour_spec = "-";
for (i=1; i<argc; i++)
{ if (strncmp(argv[i], "-b", 2) == 0)
{ colour_spec = argv[i]+2;
break;
}
}
/* If I am using a text-only interface everything is now set up! */
#ifndef HAVE_LIBFOX
return plain_worker(argc, argv);
#else
if (windowed==0) return plain_worker(argc, argv);
return windowed_worker(argc, argv);
#endif
}
void MS_CDECL sigint_handler(int code)
{
#ifdef TEST
fprintf(stderr, "sigint_handler called %d %#x\n", code, code);
fflush(stderr);
#endif
signal(SIGINT, sigint_handler);
if (interrupt_callback != NULL) (*interrupt_callback)(QUIET_INTERRUPT);
return;
}
#ifdef SIGBREAK
void MS_CDECL sigbreak_handler(int code)
{
#ifdef TEST
fprintf(stderr, "sigbreak_handler called %d %#x\n", code, code);
fflush(stderr);
#endif
signal(SIGBREAK, sigbreak_handler);
if (interrupt_callback != NULL) (*interrupt_callback)(NOISY_INTERRUPT);
return;
}
#endif
/*
* I will only try to use my own local editing and history package
* if both stdin and stdout are routed directly to a "tty" or "console".
* The test I apply can probably never be 100% satisfactory, but if I
* catch all the most common cases I will feel reasonably relaxed!
*/
int using_termed = 0;
static int direct_to_terminal(int argc, char *argv[])
{
#ifdef WIN32
HANDLE h;
DWORD w;
CONSOLE_SCREEN_BUFFER_INFO csb;
/*
* Standard input must be from a character device and must be accepted
* by the GetConsoleMode function
*/
h = GetStdHandle(STD_INPUT_HANDLE);
if (GetFileType(h) != FILE_TYPE_CHAR) return 0;
if (!GetConsoleMode(h, &w)) return 0;
/*
* Standard output must be a character device and a ConsoleScreenBuffer
*/
h = GetStdHandle(STD_OUTPUT_HANDLE);
if (GetFileType(h) != FILE_TYPE_CHAR) return 0;
if (!GetConsoleScreenBufferInfo(h, &csb)) return 0;
/*
* Note that I will allow stderr to have been redirected as much
* as you like without that having an effect here.
*/
return 1;
#else
return isatty(fileno(stdin)) && isatty(fileno(stdout));
#endif
}
int plain_worker(int argc, char **argv)
{
int r;
signal(SIGINT, sigint_handler);
#ifdef SIGBREAK
signal(SIGBREAK, sigbreak_handler);
#endif
#ifdef TEST
fprintf(stderr, "handlers for sigint and sigbreak set up\n");
fflush(stderr);
#endif
if (!texmacs_mode && direct_to_terminal(argc, argv))
{ input_history_init();
term_setup(1, colour_spec);
atexit(term_close);
using_termed = 1;
}
else using_termed = 0;
strcpy(fwin_prompt_string, "> ");
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<INPUT_BUFFER_SIZE;)
{ int c = getchar();
if (c == EOF) c = (0x1f & 'D');
input_buffer[chars_left++] = c;
if (c == '\n' || c == (0x1f & 'D'))
{ prompt_needed = 1;
break;
}
}
if (chars_left == 0) return EOF;
current_line = input_buffer;
}
chars_left--;
ch = *current_line++;
if (ch == (0x1f & 'D')) ch = EOF;
return ch;
}
#ifndef HAVE_LIBFOX
void fwin_restore()
{
}
void fwin_putchar(int c)
{
/*
* Despite using termed during keyboard input I will just use the
* ordinary C stream functions for normal output. Provided I do an
* fflush(stdout) before requesting input I should be OK.
*/
#ifdef RAW_CYGWIN
/*
* If I have built the system under Cygwin then we are running under
* Windows. To keep files tidy I will (mostly) insert CRs at line-end
* in case Cygwin does not...
*/
if (c == '\n') putchar('\r');
#endif
putchar(c);
}
void fwin_puts(const char *s)
{
/*
* See comment above where putchar() is used...
*/
#ifdef RAW_CYGWIN
while (*s != 0) fwin_putchar(*s++);
#else
puts(s);
#endif
}
void MS_CDECL fwin_printf(const char *fmt, ...)
{
va_list a;
va_start(a, fmt);
/*
* See comment above where putchar() is used...
*/
#ifdef RAW_CYGWIN
/* NOT reconstructed yet @@@ */
vfprintf(stdout, fmt, a);
#else
vfprintf(stdout, fmt, a);
#endif
va_end(a);
}
void fwin_vfprintf(const char *fmt, va_list a)
{
/*
* See comment above where putchar() is used...
*/
#ifdef RAW_CYGWIN
/* Not reconstructed yet @@@ */
vfprintf(stdout, fmt, a);
#else
vfprintf(stdout, fmt, a);
#endif
}
void fwin_ensure_screen()
{
fflush(stdout);
}
void fwin_report_left(const char *s)
{
}
void fwin_report_mid(const char *s)
{
}
void fwin_report_right(const char *s)
{
}
int fwin_getchar()
{
return fwin_plain_getchar();
}
void fwin_set_prompt(const char *s)
{
strncpy(fwin_prompt_string, s, sizeof(fwin_prompt_string));
fwin_prompt_string[sizeof(fwin_prompt_string)-1] = 0;
}
void fwin_menus(char **modules, char **switches)
{
}
void fwin_refresh_switches(char **switches, char **packages)
{
}
void fwin_set_help_file(const char *key, const char *path)
{
}
void fwin_acknowledge_tick()
{
}
int fwin_windowmode()
{
return 0;
}
#endif /* !HAVE_LIBFOX */
#endif /* MINIMAL_SUPPORT */
int get_current_directory(char *s, int n)
{
if (getcwd(s, n) == 0)
{ switch(errno)
{
case ERANGE: return -2; /* negative return value flags an error. */
case EACCES: return -3;
default: return -4;
}
}
else return strlen(s);
}
/*
* The next procedure is responsible for establishing information about
* both the "short-form" name of the program launched and the directory
* it was found in. This latter directory may be a good place to keep
* associated resources.
*
* The way of finding the information concerned differs between Windows and
* Unix/Linux, as one might expect.
*
* return non-zero value if failure.
*/
const char *fwin_full_program_name = "./fwin.exe";
const char *programName = "fwin.exe";
const char *programDir = ".";
#ifdef WIN32
static char this_executable[LONGEST_LEGAL_FILENAME];
int find_program_directory(char *argv0)
{
char *w;
int len, ndir, npgm, j;
/* In older code I believed that I could rely on Windows giving me
* the full path of my executable in argv[0]. With bits of mingw/cygwin
* anywhere near me that may not be so, so I grab the information directly
* from the Windows APIs.
*/
GetModuleFileName(NULL, this_executable, LONGEST_LEGAL_FILENAME-2);
argv0 = this_executable;
program_name_dot_com = 0;
if (argv0[0] == 0) /* should never happen - name is empty string! */
{ programDir = ".";
programName = "fwin"; /* nothing really known! */
fwin_full_program_name = ".\\fwin.exe";
return 0;
}
fwin_full_program_name = argv0;
len = strlen(argv0);
/*
* If the current program is called c:\aaa\xxx.exe, then the directory
* is just c:\aaa and the simplified program name is just xxx
*/
j = len-1;
if (len > 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<len; npgm++)
{ int c = argv0[len-npgm-1];
if (c == '\\') break;
}
ndir = len - npgm - 1;
if (ndir < 0) programDir = "."; /* none really visible */
else
{ if ((w = (char *)malloc(ndir+1)) == NULL) return 1;
strncpy(w, argv0, ndir);
w[ndir] = 0;
programDir = w;
}
if ((w = (char *)malloc(npgm+1)) == NULL) return 1;
strncpy(w, argv0 + len - npgm, npgm);
w[npgm] = 0;
programName = w;
return 0;
}
#else /* now the Unix and Linux version */
/* Different systems put or do not put underscores in front of these
* names. My adaptation here should give me a chabce to work whichever
* way round it goes.
*/
#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
#ifndef S_ISLNK
# ifdef S_IFLNK
# ifdef S_IFMT
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
# endif
# endif
#endif
/*
* the length set here is at least the longest length that I
* am prepared to worry about.
*/
int find_program_directory(char *argv0)
{
char pgmname[LONGEST_LEGAL_FILENAME];
char *w;
int n, n1;
/*
* If the main reduce executable is has a full path-name /xxx/yyy/zzz then
* I will use /xxx/yyy as its directory To find this I need to find the full
* path for the executable. I ATTEMPT to follow the behaviour of "sh",
* "bash" and "csh". But NOTE WELL that if anybody launches this code in
* an unusual manner (eg using an "exec" style function) that could confuse
* me substantially. What comes in via argv[0] is typically just the final
* component of the program name - what I am doing here is scanning to
* see what path it might have corresponded to.
*
*
* If the name of the executable starts with a "/" it is already an
* absolute path name. I believe that if the user types (to the shell)
* something like $DIR/bin/$PGMNAME or ~user/subdir/pgmname then the
* environment variables and user-name get expanded out by the shell before
* the command is actually launched.
*/
if (argv0 == NULL || argv0[0] == 0) /* Information not there - return */
{ programDir = (const char *)"."; /* some sort of default. */
programName = (const char *)"fwin";
fwin_full_program_name = (const char *)"./fwin";
return 0;
}
/*
* I will treat 3 cases here
* (a) /abc/def/ghi fully rooted: already an absolute name;
* (b) abc/def/ghi treat as ./abc/def/ghi;
* (c) ghi scan $PATH to see where it may have come from.
*/
else if (argv0[0] == '/') fwin_full_program_name = argv0;
else
{ for (w=argv0; *w!=0 && *w!='/'; w++); /* seek a "/" */
if (*w == '/') /* treat as if relative to current dir */
{ /* If the thing is actually written as "./abc/..." then */
/* strip of the initial "./" here just to be tidy. */
if (argv0[0] == '.' && argv0[1] == '/') argv0 += 2;
n = get_current_directory(pgmname, sizeof(pgmname));
if (n < 0) return 1; /* fail! 1=current directory failure */
if (n + strlen(argv0) + 2 >= 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 <utime.h>
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;i<number_of_entries;++i)
{ files[i] = strdup(namelist[i]->d_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 <pwd.h>
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 */