X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/16bb7578f27c14e3668ad5e978d7f871b0f0ff5d..59b7fa6569f8b865b6ef688a8531d745f1cc67d4:/src/w32.c diff --git a/src/w32.c b/src/w32.c index 2cdccbe389..718869978e 100644 --- a/src/w32.c +++ b/src/w32.c @@ -1,5 +1,5 @@ /* Utility and Unix shadow routines for GNU Emacs on the Microsoft W32 API. - Copyright (C) 1994, 1995 Free Software Foundation, Inc. + Copyright (C) 1994, 1995, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -30,11 +30,16 @@ Boston, MA 02111-1307, USA. #include #include #include +#include #include #include /* must include CRT headers *before* config.h */ -#include "config.h" + +#ifdef HAVE_CONFIG_H +#include +#endif + #undef access #undef chdir #undef chmod @@ -56,10 +61,16 @@ Boston, MA 02111-1307, USA. #undef read #undef write +#undef strerror + #include "lisp.h" #include +#ifdef __GNUC__ +#define _ANONYMOUS_UNION +#define _ANONYMOUS_STRUCT +#endif #include #ifdef HAVE_SOCKETS /* TCP connection support, if kernel can do it */ @@ -74,20 +85,42 @@ Boston, MA 02111-1307, USA. #undef gethostbyname #undef getservbyname #undef shutdown +#undef setsockopt +#undef listen +#undef getsockname +#undef accept +#undef recvfrom +#undef sendto #endif #include "w32.h" #include "ndir.h" #include "w32heap.h" - -#undef min -#undef max -#define min(x, y) (((x) < (y)) ? (x) : (y)) -#define max(x, y) (((x) > (y)) ? (x) : (y)) +#include "systime.h" extern Lisp_Object Vw32_downcase_file_names; extern Lisp_Object Vw32_generate_fake_inodes; extern Lisp_Object Vw32_get_true_file_attributes; +extern Lisp_Object Vw32_num_mouse_buttons; + + +/* Equivalent of strerror for W32 error codes. */ +char * +w32_strerror (int error_no) +{ + static char buf[500]; + + if (error_no == 0) + error_no = GetLastError (); + + buf[0] = '\0'; + if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, + error_no, + 0, /* choose most suitable language */ + buf, sizeof (buf), NULL)) + sprintf (buf, "w32 error %u", error_no); + return buf; +} static char startup_dir[MAXPATHLEN]; @@ -285,9 +318,9 @@ init_user_info () /* Ensure HOME and SHELL are defined. */ if (getenv ("HOME") == NULL) - putenv ("HOME=c:/"); + abort (); if (getenv ("SHELL") == NULL) - putenv (os_subtype == OS_WIN95 ? "SHELL=command" : "SHELL=cmd"); + abort (); /* Set dir and shell from environment variables. */ strcpy (the_passwd.pw_dir, getenv ("HOME")); @@ -464,6 +497,10 @@ get_long_basename (char * name, char * buf, int size) HANDLE dir_handle; int len = 0; + /* must be valid filename, no wild cards or other invalid characters */ + if (strpbrk (name, "*?|<>\"")) + return 0; + dir_handle = FindFirstFile (name, &find_data); if (dir_handle != INVALID_HANDLE_VALUE) { @@ -498,9 +535,10 @@ w32_get_long_filename (char * name, char * buf, int size) len = parse_root (full, &p); memcpy (o, full, len); o += len; + *o = '\0'; size -= len; - do + while (p != NULL && *p) { q = p; p = strchr (q, '\\'); @@ -523,11 +561,23 @@ w32_get_long_filename (char * name, char * buf, int size) else return FALSE; } - while (p != NULL && *p); return TRUE; } +int +is_unc_volume (const char *filename) +{ + const char *ptr = filename; + + if (!IS_DIRECTORY_SEP (ptr[0]) || !IS_DIRECTORY_SEP (ptr[1]) || !ptr[2]) + return 0; + + if (strpbrk (ptr + 2, "*?|<>\"\\/")) + return 0; + + return 1; +} /* Routines that are no-ops on NT but are defined to get Emacs to compile. */ @@ -537,12 +587,24 @@ sigsetmask (int signal_mask) return 0; } +int +sigmask (int sig) +{ + return 0; +} + int sigblock (int sig) { return 0; } +int +sigunblock (int sig) +{ + return 0; +} + int setpgrp (int pid, int gid) { @@ -621,54 +683,150 @@ char *get_emacs_configuration (void); extern Lisp_Object Vsystem_configuration; void -init_environment () +init_environment (char ** argv) { - /* Check for environment variables and use registry if they don't exist */ + static const char * const tempdirs[] = { + "$TMPDIR", "$TEMP", "$TMP", "c:/" + }; + int i; + const int imax = sizeof (tempdirs) / sizeof (tempdirs[0]); + + /* Make sure they have a usable $TMPDIR. Many Emacs functions use + temporary files and assume "/tmp" if $TMPDIR is unset, which + will break on DOS/Windows. Refuse to work if we cannot find + a directory, not even "c:/", usable for that purpose. */ + for (i = 0; i < imax ; i++) + { + const char *tmp = tempdirs[i]; + + if (*tmp == '$') + tmp = getenv (tmp + 1); + /* Note that `access' can lie to us if the directory resides on a + read-only filesystem, like CD-ROM or a write-protected floppy. + The only way to be really sure is to actually create a file and + see if it succeeds. But I think that's too much to ask. */ + if (tmp && _access (tmp, D_OK) == 0) + { + char * var = alloca (strlen (tmp) + 8); + sprintf (var, "TMPDIR=%s", tmp); + _putenv (strdup (var)); + break; + } + } + if (i >= imax) + cmd_error_internal + (Fcons (Qerror, + Fcons (build_string ("no usable temporary directories found!!"), + Qnil)), + "While setting TMPDIR: "); + + /* Check for environment variables and use registry settings if they + don't exist. Fallback on default values where applicable. */ { int i; LPBYTE lpval; DWORD dwType; - - static char * env_vars[] = - { - "HOME", - "PRELOAD_WINSOCK", - "emacs_dir", - "EMACSLOADPATH", - "SHELL", - "CMDPROXY", - "EMACSDATA", - "EMACSPATH", - "EMACSLOCKDIR", + char locale_name[32]; + + static struct env_entry + { + char * name; + char * def_value; + } env_vars[] = + { + {"HOME", "C:/"}, + {"PRELOAD_WINSOCK", NULL}, + {"emacs_dir", "C:/emacs"}, + {"EMACSLOADPATH", "%emacs_dir%/site-lisp;%emacs_dir%/../site-lisp;%emacs_dir%/lisp;%emacs_dir%/leim"}, + {"SHELL", "%emacs_dir%/bin/cmdproxy.exe"}, + {"EMACSDATA", "%emacs_dir%/etc"}, + {"EMACSPATH", "%emacs_dir%/bin"}, + {"EMACSLOCKDIR", "%emacs_dir%/lock"}, /* We no longer set INFOPATH because Info-default-directory-list - is then ignored. We use a hook in winnt.el instead. */ - /* "INFOPATH", */ - "EMACSDOC", - "TERM", + is then ignored. */ + /* {"INFOPATH", "%emacs_dir%/info"}, */ + {"EMACSDOC", "%emacs_dir%/etc"}, + {"TERM", "cmd"}, + {"LANG", NULL}, }; - for (i = 0; i < (sizeof (env_vars) / sizeof (env_vars[0])); i++) + /* Get default locale info and use it for LANG. */ + if (GetLocaleInfo (LOCALE_USER_DEFAULT, + LOCALE_SABBREVLANGNAME | LOCALE_USE_CP_ACP, + locale_name, sizeof (locale_name))) + { + for (i = 0; i < (sizeof (env_vars) / sizeof (env_vars[0])); i++) + { + if (strcmp (env_vars[i].name, "LANG") == 0) + { + env_vars[i].def_value = locale_name; + break; + } + } + } + +#define SET_ENV_BUF_SIZE (4 * MAX_PATH) /* to cover EMACSLOADPATH */ + + /* Treat emacs_dir specially: set it unconditionally based on our + location, if it appears that we are running from the bin subdir + of a standard installation. */ + { + char *p; + char modname[MAX_PATH]; + + if (!GetModuleFileName (NULL, modname, MAX_PATH)) + abort (); + if ((p = strrchr (modname, '\\')) == NULL) + abort (); + *p = 0; + + if ((p = strrchr (modname, '\\')) && stricmp (p, "\\bin") == 0) + { + char buf[SET_ENV_BUF_SIZE]; + + *p = 0; + for (p = modname; *p; p++) + if (*p == '\\') *p = '/'; + + _snprintf (buf, sizeof(buf)-1, "emacs_dir=%s", modname); + _putenv (strdup (buf)); + } + } + + for (i = 0; i < (sizeof (env_vars) / sizeof (env_vars[0])); i++) { - if (!getenv (env_vars[i]) - && (lpval = w32_get_resource (env_vars[i], &dwType)) != NULL) + if (!getenv (env_vars[i].name)) { - if (dwType == REG_EXPAND_SZ) - { - char buf1[500], buf2[500]; + int dont_free = 0; - ExpandEnvironmentStrings ((LPSTR) lpval, buf1, 500); - _snprintf (buf2, 499, "%s=%s", env_vars[i], buf1); - putenv (strdup (buf2)); + if ((lpval = w32_get_resource (env_vars[i].name, &dwType)) == NULL) + { + lpval = env_vars[i].def_value; + dwType = REG_EXPAND_SZ; + dont_free = 1; } - else if (dwType == REG_SZ) + + if (lpval) { - char buf[500]; + if (dwType == REG_EXPAND_SZ) + { + char buf1[SET_ENV_BUF_SIZE], buf2[SET_ENV_BUF_SIZE]; + + ExpandEnvironmentStrings ((LPSTR) lpval, buf1, sizeof(buf1)); + _snprintf (buf2, sizeof(buf2)-1, "%s=%s", env_vars[i].name, buf1); + _putenv (strdup (buf2)); + } + else if (dwType == REG_SZ) + { + char buf[SET_ENV_BUF_SIZE]; - _snprintf (buf, 499, "%s=%s", env_vars[i], lpval); - putenv (strdup (buf)); - } + _snprintf (buf, sizeof(buf)-1, "%s=%s", env_vars[i].name, lpval); + _putenv (strdup (buf)); + } - xfree (lpval); + if (!dont_free) + xfree (lpval); + } } } } @@ -704,7 +862,7 @@ init_environment () { char *p; - char modname[MAX_PATH]; + static char modname[MAX_PATH]; if (!GetModuleFileName (NULL, modname, MAX_PATH)) abort (); @@ -713,22 +871,46 @@ init_environment () *p = 0; SetCurrentDirectory (modname); + + /* Ensure argv[0] has the full path to Emacs. */ + *p = '\\'; + argv[0] = modname; } + /* Determine if there is a middle mouse button, to allow parse_button + to decide whether right mouse events should be mouse-2 or + mouse-3. */ + XSETINT (Vw32_num_mouse_buttons, GetSystemMetrics (SM_CMOUSEBUTTONS)); + init_user_info (); } +char * +emacs_root_dir (void) +{ + static char root_dir[FILENAME_MAX]; + const char *p; + + p = getenv ("emacs_dir"); + if (p == NULL) + abort (); + strcpy (root_dir, p); + root_dir[parse_root (root_dir, NULL)] = '\0'; + dostounix_filename (root_dir); + return root_dir; +} + /* We don't have scripts to automatically determine the system configuration for Emacs before it's compiled, and we don't want to have to make the user enter it, so we define EMACS_CONFIGURATION to invoke this runtime routine. */ -static char configuration_buffer[32]; - char * get_emacs_configuration (void) { char *arch, *oem, *os; + int build_num; + static char configuration_buffer[32]; /* Determine the processor type. */ switch (get_processor_type ()) @@ -767,23 +949,91 @@ get_emacs_configuration (void) break; } - /* Let oem be "*" until we figure out how to decode the OEM field. */ - oem = "*"; + /* Use the OEM field to reflect the compiler/library combination. */ +#ifdef _MSC_VER +#define COMPILER_NAME "msvc" +#else +#ifdef __GNUC__ +#define COMPILER_NAME "mingw" +#else +#define COMPILER_NAME "unknown" +#endif +#endif + oem = COMPILER_NAME; + + switch (osinfo_cache.dwPlatformId) { + case VER_PLATFORM_WIN32_NT: + os = "nt"; + build_num = osinfo_cache.dwBuildNumber; + break; + case VER_PLATFORM_WIN32_WINDOWS: + if (osinfo_cache.dwMinorVersion == 0) { + os = "windows95"; + } else { + os = "windows98"; + } + build_num = LOWORD (osinfo_cache.dwBuildNumber); + break; + case VER_PLATFORM_WIN32s: + /* Not supported, should not happen. */ + os = "windows32s"; + build_num = LOWORD (osinfo_cache.dwBuildNumber); + break; + default: + os = "unknown"; + build_num = 0; + break; + } - os = (GetVersion () & OS_WIN95) ? "windows95" : "nt"; + if (osinfo_cache.dwPlatformId == VER_PLATFORM_WIN32_NT) { + sprintf (configuration_buffer, "%s-%s-%s%d.%d.%d", arch, oem, os, + get_w32_major_version (), get_w32_minor_version (), build_num); + } else { + sprintf (configuration_buffer, "%s-%s-%s.%d", arch, oem, os, build_num); + } - sprintf (configuration_buffer, "%s-%s-%s%d.%d", arch, oem, os, - get_w32_major_version (), get_w32_minor_version ()); return configuration_buffer; } +char * +get_emacs_configuration_options (void) +{ + static char options_buffer[256]; + +/* Work out the effective configure options for this build. */ +#ifdef _MSC_VER +#define COMPILER_VERSION "--with-msvc (%d.%02d)", _MSC_VER / 100, _MSC_VER % 100 +#else +#ifdef __GNUC__ +#define COMPILER_VERSION "--with-gcc (%d.%d)", __GNUC__, __GNUC_MINOR__ +#else +#define COMPILER_VERSION "" +#endif +#endif + + sprintf (options_buffer, COMPILER_VERSION); +#ifdef EMACSDEBUG + strcat (options_buffer, " --no-opt"); +#endif +#ifdef USER_CFLAGS + strcat (options_buffer, " --cflags"); + strcat (options_buffer, USER_CFLAGS); +#endif +#ifdef USER_LDFLAGS + strcat (options_buffer, " --ldflags"); + strcat (options_buffer, USER_LDFLAGS); +#endif + return options_buffer; +} + + #include /* Emulate gettimeofday (Ulrich Leodolter, 1/11/95). */ void gettimeofday (struct timeval *tv, struct timezone *tz) { - struct _timeb tb; + struct timeb tb; _ftime (&tb); tv->tv_sec = tb.time; @@ -874,7 +1124,7 @@ lookup_volume_info (char * root_dir) static void add_volume_info (char * root_dir, volume_info_data * info) { - info->root_dir = strdup (root_dir); + info->root_dir = xstrdup (root_dir); info->next = volume_cache; volume_cache = info; } @@ -957,15 +1207,15 @@ GetCachedVolumeInformation (char * root_dir) } else { - free (info->name); - free (info->type); + xfree (info->name); + xfree (info->type); } - info->name = strdup (name); + info->name = xstrdup (name); info->serialnum = serialnum; info->maxcomp = maxcomp; info->flags = flags; - info->type = strdup (type); + info->type = xstrdup (type); info->timestamp = GetTickCount (); } @@ -1043,7 +1293,14 @@ map_w32_filename (const char * name, const char ** pPath) char * path; const char * save_name = name; - if (is_fat_volume (name, &path)) /* truncate to 8.3 */ + if (strlen (name) >= MAX_PATH) + { + /* Return a filename which will cause callers to fail. */ + strcpy (shortname, "?"); + return shortname; + } + + if (is_fat_volume (name, (const char **)&path)) /* truncate to 8.3 */ { register int left = 8; /* maximum number of chars in part */ register int extn = 0; /* extension added? */ @@ -1131,6 +1388,18 @@ map_w32_filename (const char * name, const char ** pPath) return shortname; } +static int +is_exec (const char * name) +{ + char * p = strrchr (name, '.'); + return + (p != NULL + && (stricmp (p, ".exe") == 0 || + stricmp (p, ".com") == 0 || + stricmp (p, ".bat") == 0 || + stricmp (p, ".cmd") == 0)); +} + /* Emulate the Unix directory procedures opendir, closedir, and readdir. We can't use the procedures supplied in sysdep.c, so we provide them here. */ @@ -1141,6 +1410,13 @@ static int dir_is_fat; static char dir_pathname[MAXPATHLEN+1]; static WIN32_FIND_DATA dir_find_data; +/* Support shares on a network resource as subdirectories of a read-only + root directory. */ +static HANDLE wnet_enum_handle = INVALID_HANDLE_VALUE; +HANDLE open_unc_volume (char *); +char *read_unc_volume (HANDLE, char *, int); +void close_unc_volume (HANDLE); + DIR * opendir (char *filename) { @@ -1149,10 +1425,20 @@ opendir (char *filename) /* Opening is done by FindFirstFile. However, a read is inherent to this operation, so we defer the open until read time. */ - if (!(dirp = (DIR *) malloc (sizeof (DIR)))) - return NULL; if (dir_find_handle != INVALID_HANDLE_VALUE) return NULL; + if (wnet_enum_handle != INVALID_HANDLE_VALUE) + return NULL; + + if (is_unc_volume (filename)) + { + wnet_enum_handle = open_unc_volume (filename); + if (wnet_enum_handle == INVALID_HANDLE_VALUE) + return NULL; + } + + if (!(dirp = (DIR *) malloc (sizeof (DIR)))) + return NULL; dirp->dd_fd = 0; dirp->dd_loc = 0; @@ -1174,14 +1460,26 @@ closedir (DIR *dirp) FindClose (dir_find_handle); dir_find_handle = INVALID_HANDLE_VALUE; } + else if (wnet_enum_handle != INVALID_HANDLE_VALUE) + { + close_unc_volume (wnet_enum_handle); + wnet_enum_handle = INVALID_HANDLE_VALUE; + } xfree ((char *) dirp); } struct direct * readdir (DIR *dirp) { + if (wnet_enum_handle != INVALID_HANDLE_VALUE) + { + if (!read_unc_volume (wnet_enum_handle, + dir_find_data.cFileName, + MAX_PATH)) + return NULL; + } /* If we aren't dir_finding, do a find-first, otherwise do a find-next. */ - if (dir_find_handle == INVALID_HANDLE_VALUE) + else if (dir_find_handle == INVALID_HANDLE_VALUE) { char filename[MAXNAMLEN + 3]; int ln; @@ -1227,6 +1525,80 @@ readdir (DIR *dirp) return &dir_static; } +HANDLE +open_unc_volume (char *path) +{ + NETRESOURCE nr; + HANDLE henum; + int result; + + nr.dwScope = RESOURCE_GLOBALNET; + nr.dwType = RESOURCETYPE_DISK; + nr.dwDisplayType = RESOURCEDISPLAYTYPE_SERVER; + nr.dwUsage = RESOURCEUSAGE_CONTAINER; + nr.lpLocalName = NULL; + nr.lpRemoteName = map_w32_filename (path, NULL); + nr.lpComment = NULL; + nr.lpProvider = NULL; + + result = WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_DISK, + RESOURCEUSAGE_CONNECTABLE, &nr, &henum); + + if (result == NO_ERROR) + return henum; + else + return INVALID_HANDLE_VALUE; +} + +char * +read_unc_volume (HANDLE henum, char *readbuf, int size) +{ + DWORD count; + int result; + DWORD bufsize = 512; + char *buffer; + char *ptr; + + count = 1; + buffer = alloca (bufsize); + result = WNetEnumResource (wnet_enum_handle, &count, buffer, &bufsize); + if (result != NO_ERROR) + return NULL; + + /* WNetEnumResource returns \\resource\share...skip forward to "share". */ + ptr = ((LPNETRESOURCE) buffer)->lpRemoteName; + ptr += 2; + while (*ptr && !IS_DIRECTORY_SEP (*ptr)) ptr++; + ptr++; + + strncpy (readbuf, ptr, size); + return readbuf; +} + +void +close_unc_volume (HANDLE henum) +{ + if (henum != INVALID_HANDLE_VALUE) + WNetCloseEnum (henum); +} + +DWORD +unc_volume_file_attributes (char *path) +{ + HANDLE henum; + DWORD attrs; + + henum = open_unc_volume (path); + if (henum == INVALID_HANDLE_VALUE) + return -1; + + attrs = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY; + + close_unc_volume (henum); + + return attrs; +} + /* Shadow some MSVC runtime functions to map requests for long filenames to reasonable short names if necessary. This was originally added to @@ -1236,7 +1608,41 @@ readdir (DIR *dirp) int sys_access (const char * path, int mode) { - return _access (map_w32_filename (path, NULL), mode); + DWORD attributes; + + /* MSVC implementation doesn't recognize D_OK. */ + path = map_w32_filename (path, NULL); + if (is_unc_volume (path)) + { + attributes = unc_volume_file_attributes (path); + if (attributes == -1) { + errno = EACCES; + return -1; + } + } + else if ((attributes = GetFileAttributes (path)) == -1) + { + /* Should try mapping GetLastError to errno; for now just indicate + that path doesn't exist. */ + errno = EACCES; + return -1; + } + if ((mode & X_OK) != 0 && !is_exec (path)) + { + errno = EACCES; + return -1; + } + if ((mode & W_OK) != 0 && (attributes & FILE_ATTRIBUTE_READONLY) != 0) + { + errno = EACCES; + return -1; + } + if ((mode & D_OK) != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + { + errno = EACCES; + return -1; + } + return 0; } int @@ -1433,15 +1839,21 @@ sys_mktemp (char * template) int sys_open (const char * path, int oflag, int mode) { - /* Force all file handles to be non-inheritable. */ - return _open (map_w32_filename (path, NULL), oflag | _O_NOINHERIT, mode); + const char* mpath = map_w32_filename (path, NULL); + /* Try to open file without _O_CREAT, to be able to write to hidden + and system files. Force all file handles to be + non-inheritable. */ + int res = _open (mpath, (oflag & ~_O_CREAT) | _O_NOINHERIT, mode); + if (res >= 0) + return res; + return _open (mpath, oflag | _O_NOINHERIT, mode); } int sys_rename (const char * oldname, const char * newname) { + BOOL result; char temp[MAX_PATH]; - DWORD attr; /* MoveFile on Windows 95 doesn't correctly change the short file name alias in a number of circumstances (it is not easy to predict when @@ -1460,37 +1872,56 @@ sys_rename (const char * oldname, const char * newname) if (os_subtype == OS_WIN95) { + char * o; char * p; + int i = 0; + + oldname = map_w32_filename (oldname, NULL); + if (o = strrchr (oldname, '\\')) + o++; + else + o = (char *) oldname; if (p = strrchr (temp, '\\')) p++; else p = temp; - /* Force temp name to require a manufactured 8.3 alias - this - seems to make the second rename work properly. */ - strcpy (p, "_rename_temp.XXXXXX"); - sys_mktemp (temp); - if (rename (map_w32_filename (oldname, NULL), temp) < 0) + + do + { + /* Force temp name to require a manufactured 8.3 alias - this + seems to make the second rename work properly. */ + sprintf (p, "_.%s.%u", o, i); + i++; + result = rename (oldname, temp); + } + /* This loop must surely terminate! */ + while (result < 0 && errno == EEXIST); + if (result < 0) return -1; } /* Emulate Unix behaviour - newname is deleted if it already exists (at least if it is a file; don't do this for directories). - However, don't do this if we are just changing the case of the file - name - we will end up deleting the file we are trying to rename! */ + + Since we mustn't do this if we are just changing the case of the + file name (we would end up deleting the file we are trying to + rename!), we let rename detect if the destination file already + exists - that way we avoid the possible pitfalls of trying to + determine ourselves whether two names really refer to the same + file, which is not always possible in the general case. (Consider + all the permutations of shared or subst'd drives, etc.) */ + newname = map_w32_filename (newname, NULL); + result = rename (temp, newname); - /* TODO: Use GetInformationByHandle (on NT) to ensure newname and temp - do not refer to the same file, eg. through share aliases. */ - if (stricmp (newname, temp) != 0 - && (attr = GetFileAttributes (newname)) != -1 - && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) - { - _chmod (newname, 0666); - _unlink (newname); - } + if (result < 0 + && errno == EEXIST + && _chmod (newname, 0666) == 0 + && _unlink (newname) == 0) + result = rename (temp, newname); - return rename (temp, newname); + return result; } int @@ -1624,7 +2055,7 @@ generate_inode_val (const char * name) int stat (const char * path, struct stat * buf) { - char * name; + char *name, *r; WIN32_FIND_DATA wfd; HANDLE fh; DWORD fake_inode; @@ -1639,13 +2070,20 @@ stat (const char * path, struct stat * buf) } name = (char *) map_w32_filename (path, &path); - /* must be valid filename, no wild cards */ - if (strchr (name, '*') || strchr (name, '?')) + /* must be valid filename, no wild cards or other invalid characters */ + if (strpbrk (name, "*?|<>\"")) { errno = ENOENT; return -1; } + /* If name is "c:/.." or "/.." then stat "c:/" or "/". */ + r = IS_DEVICE_SEP (name[1]) ? &name[2] : name; + if (IS_DIRECTORY_SEP (r[0]) && r[1] == '.' && r[2] == '.' && r[3] == '\0') + { + r[1] = r[2] = '\0'; + } + /* Remove trailing directory separator, unless name is the root directory of a drive or UNC volume in which case ensure there is a trailing separator. */ @@ -1654,7 +2092,21 @@ stat (const char * path, struct stat * buf) && (IS_DIRECTORY_SEP (*path) || *path == 0)); name = strcpy (alloca (len + 2), name); - if (rootdir) + if (is_unc_volume (name)) + { + DWORD attrs = unc_volume_file_attributes (name); + + if (attrs == -1) + return -1; + + memset (&wfd, 0, sizeof (wfd)); + wfd.dwFileAttributes = attrs; + wfd.ftCreationTime = utc_base_ft; + wfd.ftLastAccessTime = utc_base_ft; + wfd.ftLastWriteTime = utc_base_ft; + strcpy (wfd.cFileName, name); + } + else if (rootdir) { if (!IS_DIRECTORY_SEP (name[len-1])) strcat (name, "\\"); @@ -1678,9 +2130,11 @@ stat (const char * path, struct stat * buf) /* (This is hacky, but helps when doing file completions on network drives.) Optimize by using information available from active readdir if possible. */ + len = strlen (dir_pathname); + if (IS_DIRECTORY_SEP (dir_pathname[len-1])) + len--; if (dir_find_handle != INVALID_HANDLE_VALUE - && (len = strlen (dir_pathname)), - strnicmp (name, dir_pathname, len) == 0 + && strnicmp (name, dir_pathname, len) == 0 && IS_DIRECTORY_SEP (name[len]) && stricmp (name + len + 1, dir_static.d_name) == 0) { @@ -1705,31 +2159,18 @@ stat (const char * path, struct stat * buf) buf->st_nlink = 2; /* doesn't really matter */ fake_inode = 0; /* this doesn't either I think */ } - else if (!NILP (Vw32_get_true_file_attributes)) + else if (!NILP (Vw32_get_true_file_attributes) + /* No access rights required to get info. */ + && (fh = CreateFile (name, 0, 0, NULL, OPEN_EXISTING, 0, NULL)) + != INVALID_HANDLE_VALUE) { /* This is more accurate in terms of gettting the correct number of links, but is quite slow (it is noticable when Emacs is making a list of file name completions). */ BY_HANDLE_FILE_INFORMATION info; - /* No access rights required to get info. */ - fh = CreateFile (name, 0, 0, NULL, OPEN_EXISTING, 0, NULL); - if (GetFileInformationByHandle (fh, &info)) { - switch (GetFileType (fh)) - { - case FILE_TYPE_DISK: - buf->st_mode = _S_IFREG; - break; - case FILE_TYPE_PIPE: - buf->st_mode = _S_IFIFO; - break; - case FILE_TYPE_CHAR: - case FILE_TYPE_UNKNOWN: - default: - buf->st_mode = _S_IFCHR; - } buf->st_nlink = info.nNumberOfLinks; /* Might as well use file index to fake inode values, but this is not guaranteed to be unique unless we keep a handle open @@ -1737,13 +2178,27 @@ stat (const char * path, struct stat * buf) not unique). Reputedly, there are at most 48 bits of info (on NTFS, presumably less on FAT). */ fake_inode = info.nFileIndexLow ^ info.nFileIndexHigh; - CloseHandle (fh); } else { - errno = EACCES; - return -1; + buf->st_nlink = 1; + fake_inode = 0; } + + switch (GetFileType (fh)) + { + case FILE_TYPE_DISK: + buf->st_mode = _S_IFREG; + break; + case FILE_TYPE_PIPE: + buf->st_mode = _S_IFIFO; + break; + case FILE_TYPE_CHAR: + case FILE_TYPE_UNKNOWN: + default: + buf->st_mode = _S_IFCHR; + } + CloseHandle (fh); } else { @@ -1797,16 +2252,8 @@ stat (const char * path, struct stat * buf) if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) permission |= _S_IEXEC; - else - { - char * p = strrchr (name, '.'); - if (p != NULL - && (stricmp (p, ".exe") == 0 || - stricmp (p, ".com") == 0 || - stricmp (p, ".bat") == 0 || - stricmp (p, ".cmd") == 0)) - permission |= _S_IEXEC; - } + else if (is_exec (name)) + permission |= _S_IEXEC; buf->st_mode |= permission | (permission >> 3) | (permission >> 6); @@ -1980,7 +2427,18 @@ unsigned long (PASCAL *pfn_inet_addr) (const char * cp); int (PASCAL *pfn_gethostname) (char * name, int namelen); struct hostent * (PASCAL *pfn_gethostbyname) (const char * name); struct servent * (PASCAL *pfn_getservbyname) (const char * name, const char * proto); - + +int (PASCAL *pfn_setsockopt) (SOCKET s, int level, int optname, + const char * optval, int optlen); +int (PASCAL *pfn_listen) (SOCKET s, int backlog); +int (PASCAL *pfn_getsockname) (SOCKET s, struct sockaddr * name, + int * namelen); +SOCKET (PASCAL *pfn_accept) (SOCKET s, struct sockaddr * addr, int * addrlen); +int (PASCAL *pfn_recvfrom) (SOCKET s, char * buf, int len, int flags, + struct sockaddr * from, int * fromlen); +int (PASCAL *pfn_sendto) (SOCKET s, const char * buf, int len, int flags, + const struct sockaddr * to, int tolen); + /* SetHandleInformation is only needed to make sockets non-inheritable. */ BOOL (WINAPI *pfn_SetHandleInformation) (HANDLE object, DWORD mask, DWORD flags); #ifndef HANDLE_FLAG_INHERIT @@ -2050,7 +2508,12 @@ init_winsock (int load_now) LOAD_PROC( gethostbyname ); LOAD_PROC( getservbyname ); LOAD_PROC( WSACleanup ); - + LOAD_PROC( setsockopt ); + LOAD_PROC( listen ); + LOAD_PROC( getsockname ); + LOAD_PROC( accept ); + LOAD_PROC( recvfrom ); + LOAD_PROC( sendto ); #undef LOAD_PROC /* specify version 1.1 of winsock */ @@ -2116,6 +2579,99 @@ static void check_errno () pfn_WSASetLastError (0); } +/* Extend strerror to handle the winsock-specific error codes. */ +struct { + int errnum; + char * msg; +} _wsa_errlist[] = { + WSAEINTR , "Interrupted function call", + WSAEBADF , "Bad file descriptor", + WSAEACCES , "Permission denied", + WSAEFAULT , "Bad address", + WSAEINVAL , "Invalid argument", + WSAEMFILE , "Too many open files", + + WSAEWOULDBLOCK , "Resource temporarily unavailable", + WSAEINPROGRESS , "Operation now in progress", + WSAEALREADY , "Operation already in progress", + WSAENOTSOCK , "Socket operation on non-socket", + WSAEDESTADDRREQ , "Destination address required", + WSAEMSGSIZE , "Message too long", + WSAEPROTOTYPE , "Protocol wrong type for socket", + WSAENOPROTOOPT , "Bad protocol option", + WSAEPROTONOSUPPORT , "Protocol not supported", + WSAESOCKTNOSUPPORT , "Socket type not supported", + WSAEOPNOTSUPP , "Operation not supported", + WSAEPFNOSUPPORT , "Protocol family not supported", + WSAEAFNOSUPPORT , "Address family not supported by protocol family", + WSAEADDRINUSE , "Address already in use", + WSAEADDRNOTAVAIL , "Cannot assign requested address", + WSAENETDOWN , "Network is down", + WSAENETUNREACH , "Network is unreachable", + WSAENETRESET , "Network dropped connection on reset", + WSAECONNABORTED , "Software caused connection abort", + WSAECONNRESET , "Connection reset by peer", + WSAENOBUFS , "No buffer space available", + WSAEISCONN , "Socket is already connected", + WSAENOTCONN , "Socket is not connected", + WSAESHUTDOWN , "Cannot send after socket shutdown", + WSAETOOMANYREFS , "Too many references", /* not sure */ + WSAETIMEDOUT , "Connection timed out", + WSAECONNREFUSED , "Connection refused", + WSAELOOP , "Network loop", /* not sure */ + WSAENAMETOOLONG , "Name is too long", + WSAEHOSTDOWN , "Host is down", + WSAEHOSTUNREACH , "No route to host", + WSAENOTEMPTY , "Buffer not empty", /* not sure */ + WSAEPROCLIM , "Too many processes", + WSAEUSERS , "Too many users", /* not sure */ + WSAEDQUOT , "Double quote in host name", /* really not sure */ + WSAESTALE , "Data is stale", /* not sure */ + WSAEREMOTE , "Remote error", /* not sure */ + + WSASYSNOTREADY , "Network subsystem is unavailable", + WSAVERNOTSUPPORTED , "WINSOCK.DLL version out of range", + WSANOTINITIALISED , "Winsock not initialized successfully", + WSAEDISCON , "Graceful shutdown in progress", +#ifdef WSAENOMORE + WSAENOMORE , "No more operations allowed", /* not sure */ + WSAECANCELLED , "Operation cancelled", /* not sure */ + WSAEINVALIDPROCTABLE , "Invalid procedure table from service provider", + WSAEINVALIDPROVIDER , "Invalid service provider version number", + WSAEPROVIDERFAILEDINIT , "Unable to initialize a service provider", + WSASYSCALLFAILURE , "System call failured", + WSASERVICE_NOT_FOUND , "Service not found", /* not sure */ + WSATYPE_NOT_FOUND , "Class type not found", + WSA_E_NO_MORE , "No more resources available", /* really not sure */ + WSA_E_CANCELLED , "Operation already cancelled", /* really not sure */ + WSAEREFUSED , "Operation refused", /* not sure */ +#endif + + WSAHOST_NOT_FOUND , "Host not found", + WSATRY_AGAIN , "Authoritative host not found during name lookup", + WSANO_RECOVERY , "Non-recoverable error during name lookup", + WSANO_DATA , "Valid name, no data record of requested type", + + -1, NULL +}; + +char * +sys_strerror(int error_no) +{ + int i; + static char unknown_msg[40]; + + if (error_no >= 0 && error_no < sys_nerr) + return sys_errlist[error_no]; + + for (i = 0; _wsa_errlist[i].errnum >= 0; i++) + if (_wsa_errlist[i].errnum == error_no) + return _wsa_errlist[i].msg; + + sprintf(unknown_msg, "Unidentified error: %d", error_no); + return unknown_msg; +} + /* [andrewi 3-May-96] I've had conflicting results using both methods, but I believe the method of keeping the socket handle separate (and insuring it is not inheritable) is the correct one. */ @@ -2128,12 +2684,12 @@ static void check_errno () #define SOCK_HANDLE(fd) ((SOCKET) fd_info[fd].hnd) #endif +int socket_to_fd (SOCKET s); + int sys_socket(int af, int type, int protocol) { - int fd; - long s; - child_process * cp; + SOCKET s; if (winsock_lib == NULL) { @@ -2144,89 +2700,114 @@ sys_socket(int af, int type, int protocol) check_errno (); /* call the real socket function */ - s = (long) pfn_socket (af, type, protocol); + s = pfn_socket (af, type, protocol); if (s != INVALID_SOCKET) - { - /* Although under NT 3.5 _open_osfhandle will accept a socket - handle, if opened with SO_OPENTYPE == SO_SYNCHRONOUS_NONALERT, - that does not work under NT 3.1. However, we can get the same - effect by using a backdoor function to replace an existing - descriptor handle with the one we want. */ + return socket_to_fd (s); - /* allocate a file descriptor (with appropriate flags) */ - fd = _open ("NUL:", _O_RDWR); - if (fd >= 0) - { + set_errno (); + return -1; +} + +/* Convert a SOCKET to a file descriptor. */ +int +socket_to_fd (SOCKET s) +{ + int fd; + child_process * cp; + + /* Although under NT 3.5 _open_osfhandle will accept a socket + handle, if opened with SO_OPENTYPE == SO_SYNCHRONOUS_NONALERT, + that does not work under NT 3.1. However, we can get the same + effect by using a backdoor function to replace an existing + descriptor handle with the one we want. */ + + /* allocate a file descriptor (with appropriate flags) */ + fd = _open ("NUL:", _O_RDWR); + if (fd >= 0) + { #ifdef SOCK_REPLACE_HANDLE - /* now replace handle to NUL with our socket handle */ - CloseHandle ((HANDLE) _get_osfhandle (fd)); - _free_osfhnd (fd); - _set_osfhnd (fd, s); - /* setmode (fd, _O_BINARY); */ + /* now replace handle to NUL with our socket handle */ + CloseHandle ((HANDLE) _get_osfhandle (fd)); + _free_osfhnd (fd); + _set_osfhnd (fd, s); + /* setmode (fd, _O_BINARY); */ #else - /* Make a non-inheritable copy of the socket handle. */ + /* Make a non-inheritable copy of the socket handle. Note + that it is possible that sockets aren't actually kernel + handles, which appears to be the case on Windows 9x when + the MS Proxy winsock client is installed. */ + { + /* Apparently there is a bug in NT 3.51 with some service + packs, which prevents using DuplicateHandle to make a + socket handle non-inheritable (causes WSACleanup to + hang). The work-around is to use SetHandleInformation + instead if it is available and implemented. */ + if (pfn_SetHandleInformation) { - HANDLE parent; + pfn_SetHandleInformation ((HANDLE) s, HANDLE_FLAG_INHERIT, 0); + } + else + { + HANDLE parent = GetCurrentProcess (); HANDLE new_s = INVALID_HANDLE_VALUE; - parent = GetCurrentProcess (); - - /* Apparently there is a bug in NT 3.51 with some service - packs, which prevents using DuplicateHandle to make a - socket handle non-inheritable (causes WSACleanup to - hang). The work-around is to use SetHandleInformation - instead if it is available and implemented. */ - if (!pfn_SetHandleInformation - || !pfn_SetHandleInformation ((HANDLE) s, - HANDLE_FLAG_INHERIT, - HANDLE_FLAG_INHERIT)) - { - DuplicateHandle (parent, + if (DuplicateHandle (parent, (HANDLE) s, parent, &new_s, 0, FALSE, - DUPLICATE_SAME_ACCESS); - pfn_closesocket (s); - s = (SOCKET) new_s; - } - fd_info[fd].hnd = (HANDLE) s; + DUPLICATE_SAME_ACCESS)) + { + /* It is possible that DuplicateHandle succeeds even + though the socket wasn't really a kernel handle, + because a real handle has the same value. So + test whether the new handle really is a socket. */ + long nonblocking = 0; + if (pfn_ioctlsocket ((SOCKET) new_s, FIONBIO, &nonblocking) == 0) + { + pfn_closesocket (s); + s = (SOCKET) new_s; + } + else + { + CloseHandle (new_s); + } + } } + } + fd_info[fd].hnd = (HANDLE) s; #endif - /* set our own internal flags */ - fd_info[fd].flags = FILE_SOCKET | FILE_BINARY | FILE_READ | FILE_WRITE; + /* set our own internal flags */ + fd_info[fd].flags = FILE_SOCKET | FILE_BINARY | FILE_READ | FILE_WRITE; - cp = new_child (); - if (cp) - { - cp->fd = fd; - cp->status = STATUS_READ_ACKNOWLEDGED; - - /* attach child_process to fd_info */ - if (fd_info[ fd ].cp != NULL) - { - DebPrint (("sys_socket: fd_info[%d] apparently in use!\n", fd)); - abort (); - } - - fd_info[ fd ].cp = cp; + cp = new_child (); + if (cp) + { + cp->fd = fd; + cp->status = STATUS_READ_ACKNOWLEDGED; - /* success! */ - winsock_inuse++; /* count open sockets */ - return fd; + /* attach child_process to fd_info */ + if (fd_info[ fd ].cp != NULL) + { + DebPrint (("sys_socket: fd_info[%d] apparently in use!\n", fd)); + abort (); } - /* clean up */ - _close (fd); + fd_info[ fd ].cp = cp; + + /* success! */ + winsock_inuse++; /* count open sockets */ + return fd; } - pfn_closesocket (s); - h_errno = EMFILE; - } - set_errno (); + /* clean up */ + _close (fd); + } + pfn_closesocket (s); + h_errno = EMFILE; return -1; } @@ -2302,7 +2883,7 @@ sys_gethostname (char * name, int namelen) return pfn_gethostname (name, namelen); if (namelen > MAX_COMPUTERNAME_LENGTH) - return !GetComputerName (name, &namelen); + return !GetComputerName (name, (DWORD *)&namelen); h_errno = EFAULT; return SOCKET_ERROR; @@ -2347,8 +2928,6 @@ sys_getservbyname(const char * name, const char * proto) int sys_shutdown (int s, int how) { - int rc; - if (winsock_lib == NULL) { h_errno = ENETDOWN; @@ -2367,6 +2946,137 @@ sys_shutdown (int s, int how) return SOCKET_ERROR; } +int +sys_setsockopt (int s, int level, int optname, const char * optval, int optlen) +{ + if (winsock_lib == NULL) + { + h_errno = ENETDOWN; + return SOCKET_ERROR; + } + + check_errno (); + if (fd_info[s].flags & FILE_SOCKET) + { + int rc = pfn_setsockopt (SOCK_HANDLE (s), level, optname, + optval, optlen); + if (rc == SOCKET_ERROR) + set_errno (); + return rc; + } + h_errno = ENOTSOCK; + return SOCKET_ERROR; +} + +int +sys_listen (int s, int backlog) +{ + if (winsock_lib == NULL) + { + h_errno = ENETDOWN; + return SOCKET_ERROR; + } + + check_errno (); + if (fd_info[s].flags & FILE_SOCKET) + { + int rc = pfn_listen (SOCK_HANDLE (s), backlog); + if (rc == SOCKET_ERROR) + set_errno (); + return rc; + } + h_errno = ENOTSOCK; + return SOCKET_ERROR; +} + +int +sys_getsockname (int s, struct sockaddr * name, int * namelen) +{ + if (winsock_lib == NULL) + { + h_errno = ENETDOWN; + return SOCKET_ERROR; + } + + check_errno (); + if (fd_info[s].flags & FILE_SOCKET) + { + int rc = pfn_getsockname (SOCK_HANDLE (s), name, namelen); + if (rc == SOCKET_ERROR) + set_errno (); + return rc; + } + h_errno = ENOTSOCK; + return SOCKET_ERROR; +} + +int +sys_accept (int s, struct sockaddr * addr, int * addrlen) +{ + if (winsock_lib == NULL) + { + h_errno = ENETDOWN; + return -1; + } + + check_errno (); + if (fd_info[s].flags & FILE_SOCKET) + { + SOCKET t = pfn_accept (SOCK_HANDLE (s), addr, addrlen); + if (t != INVALID_SOCKET) + return socket_to_fd (t); + + set_errno (); + return -1; + } + h_errno = ENOTSOCK; + return -1; +} + +int +sys_recvfrom (int s, char * buf, int len, int flags, + struct sockaddr * from, int * fromlen) +{ + if (winsock_lib == NULL) + { + h_errno = ENETDOWN; + return SOCKET_ERROR; + } + + check_errno (); + if (fd_info[s].flags & FILE_SOCKET) + { + int rc = pfn_recvfrom (SOCK_HANDLE (s), buf, len, flags, from, fromlen); + if (rc == SOCKET_ERROR) + set_errno (); + return rc; + } + h_errno = ENOTSOCK; + return SOCKET_ERROR; +} + +int +sys_sendto (int s, const char * buf, int len, int flags, + const struct sockaddr * to, int tolen) +{ + if (winsock_lib == NULL) + { + h_errno = ENETDOWN; + return SOCKET_ERROR; + } + + check_errno (); + if (fd_info[s].flags & FILE_SOCKET) + { + int rc = pfn_sendto (SOCK_HANDLE (s), buf, len, flags, to, tolen); + if (rc == SOCKET_ERROR) + set_errno (); + return rc; + } + h_errno = ENOTSOCK; + return SOCKET_ERROR; +} + #endif /* HAVE_SOCKETS */ @@ -2477,7 +3187,6 @@ sys_pipe (int * phandles) { int rc; unsigned flags; - child_process * cp; /* make pipe handles non-inheritable; when we spawn a child, we replace the relevant handle with an inheritable one. Also put @@ -2789,40 +3498,49 @@ check_windows_init_file () it cannot find the Windows installation file. If this file does not exist in the expected place, tell the user. */ - if (!noninteractive && !inhibit_window_system) { - extern Lisp_Object Vwindow_system, Vload_path; - Lisp_Object init_file; - int fd; - - init_file = build_string ("term/w32-win"); - fd = openp (Vload_path, init_file, ".el:.elc", NULL, 0); - if (fd < 0) { - Lisp_Object load_path_print = Fprin1_to_string (Vload_path, Qnil); - char *init_file_name = XSTRING (init_file)->data; - char *load_path = XSTRING (load_path_print)->data; - char *buffer = alloca (1024); - - sprintf (buffer, - "The Emacs Windows initialization file \"%s.el\" " - "could not be found in your Emacs installation. " - "Emacs checked the following directories for this file:\n" - "\n%s\n\n" - "When Emacs cannot find this file, it usually means that it " - "was not installed properly, or its distribution file was " - "not unpacked properly.\nSee the README.W32 file in the " - "top-level Emacs directory for more information.", - init_file_name, load_path); - MessageBox (NULL, - buffer, - "Emacs Abort Dialog", - MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); - close (fd); + if (!noninteractive && !inhibit_window_system) + { + extern Lisp_Object Vwindow_system, Vload_path, Qfile_exists_p; + Lisp_Object objs[2]; + Lisp_Object full_load_path; + Lisp_Object init_file; + int fd; + objs[0] = Vload_path; + objs[1] = decode_env_path (0, (getenv ("EMACSLOADPATH"))); + full_load_path = Fappend (2, objs); + init_file = build_string ("term/w32-win"); + fd = openp (full_load_path, init_file, Vload_suffixes, NULL, 0); + if (fd < 0) + { + Lisp_Object load_path_print = Fprin1_to_string (full_load_path, Qnil); + char *init_file_name = XSTRING (init_file)->data; + char *load_path = XSTRING (load_path_print)->data; + char *buffer = alloca (1024); + + sprintf (buffer, + "The Emacs Windows initialization file \"%s.el\" " + "could not be found in your Emacs installation. " + "Emacs checked the following directories for this file:\n" + "\n%s\n\n" + "When Emacs cannot find this file, it usually means that it " + "was not installed properly, or its distribution file was " + "not unpacked properly.\nSee the README.W32 file in the " + "top-level Emacs directory for more information.", + init_file_name, load_path); + MessageBox (NULL, + buffer, + "Emacs Abort Dialog", + MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); /* Use the low-level Emacs abort. */ #undef abort - abort (); + abort (); + } + else + { + _close (fd); + } } - } } void @@ -2832,12 +3550,6 @@ term_ntproc () /* shutdown the socket interface if necessary */ term_winsock (); #endif - - /* Check whether we are shutting down because we cannot find the - Windows initialization file. Do this during shutdown so that - Emacs is initialized as possible, and so that it is out of the - critical startup path. */ - check_windows_init_file (); } void @@ -2936,7 +3648,13 @@ init_ntproc () (*drive)++; } + + /* Reset the volume info cache. */ + volume_cache = NULL; } + + /* Check to see if Emacs has been installed correctly. */ + check_windows_init_file (); } /* end of nt.c */