-/* Utility and Unix shadow routines for GNU Emacs on the Microsoft W32 API.
- Copyright (C) 1994-1995, 2000-2011 Free Software Foundation, Inc.
+/* Utility and Unix shadow routines for GNU Emacs on the Microsoft Windows API.
+ Copyright (C) 1994-1995, 2000-2012 Free Software Foundation, Inc.
This file is part of GNU Emacs.
} PROCESS_MEMORY_COUNTERS_EX,*PPROCESS_MEMORY_COUNTERS_EX;
#endif
+#include <winioctl.h>
+#include <aclapi.h>
+
+#ifdef _MSC_VER
+/* MSVC doesn't provide the definition of REPARSE_DATA_BUFFER, except
+ on ntifs.h, which cannot be included because it triggers conflicts
+ with other Windows API headers. So we define it here by hand. */
+
+typedef struct _REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ } DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+#endif
+
/* TCP connection support. */
#include <sys/socket.h>
#undef socket
void globals_of_w32 (void);
static DWORD get_rid (PSID);
+static int is_symlink (const char *);
+static char * chase_symlinks (const char *);
+static int enable_privilege (LPCTSTR, BOOL, TOKEN_PRIVILEGES *);
+static int restore_privilege (TOKEN_PRIVILEGES *);
+static BOOL WINAPI revert_to_self (void);
\f
/* Initialization states.
static BOOL g_b_init_lookup_account_sid;
static BOOL g_b_init_get_sid_sub_authority;
static BOOL g_b_init_get_sid_sub_authority_count;
+static BOOL g_b_init_get_security_info;
static BOOL g_b_init_get_file_security;
static BOOL g_b_init_get_security_descriptor_owner;
static BOOL g_b_init_get_security_descriptor_group;
static BOOL g_b_init_copy_sid;
static BOOL g_b_init_get_native_system_info;
static BOOL g_b_init_get_system_times;
+static BOOL g_b_init_create_symbolic_link;
/*
BEGIN: Wrapper functions around OpenProcessToken
DWORD n);
typedef PUCHAR (WINAPI * GetSidSubAuthorityCount_Proc) (
PSID pSid);
+typedef DWORD (WINAPI * GetSecurityInfo_Proc) (
+ HANDLE handle,
+ SE_OBJECT_TYPE ObjectType,
+ SECURITY_INFORMATION SecurityInfo,
+ PSID *ppsidOwner,
+ PSID *ppsidGroup,
+ PACL *ppDacl,
+ PACL *ppSacl,
+ PSECURITY_DESCRIPTOR *ppSecurityDescriptor);
typedef BOOL (WINAPI * GetFileSecurity_Proc) (
LPCTSTR lpFileName,
SECURITY_INFORMATION RequestedInformation,
LPFILETIME lpIdleTime,
LPFILETIME lpKernelTime,
LPFILETIME lpUserTime);
+typedef BOOLEAN (WINAPI *CreateSymbolicLink_Proc) (
+ LPTSTR lpSymlinkFileName,
+ LPTSTR lpTargetFileName,
+ DWORD dwFlags);
/* ** A utility function ** */
static BOOL
return s_b_ret;
}
+static Lisp_Object ltime (ULONGLONG);
+
/* Get total user and system times for get-internal-run-time.
- Returns a list of three integers if the times are provided by the OS
+ Returns a list of integers if the times are provided by the OS
(NT derivatives), otherwise it returns the result of current-time. */
Lisp_Object
w32_get_internal_run_time (void)
if ((*get_process_times_fn) (proc, &create, &exit, &kernel, &user))
{
LARGE_INTEGER user_int, kernel_int, total;
- int microseconds;
user_int.LowPart = user.dwLowDateTime;
user_int.HighPart = user.dwHighDateTime;
kernel_int.LowPart = kernel.dwLowDateTime;
kernel_int.HighPart = kernel.dwHighDateTime;
total.QuadPart = user_int.QuadPart + kernel_int.QuadPart;
- /* FILETIME is 100 nanosecond increments, Emacs only wants
- microsecond resolution. */
- total.QuadPart /= 10;
- microseconds = total.QuadPart % 1000000;
- total.QuadPart /= 1000000;
-
- /* Sanity check to make sure we can represent the result. */
- if (total.HighPart == 0)
- {
- int secs = total.LowPart;
-
- return list3 (make_number ((secs >> 16) & 0xffff),
- make_number (secs & 0xffff),
- make_number (microseconds));
- }
+ return ltime (total.QuadPart);
}
}
return (s_pfn_Get_Sid_Sub_Authority_Count (pSid));
}
+static DWORD WINAPI
+get_security_info (HANDLE handle,
+ SE_OBJECT_TYPE ObjectType,
+ SECURITY_INFORMATION SecurityInfo,
+ PSID *ppsidOwner,
+ PSID *ppsidGroup,
+ PACL *ppDacl,
+ PACL *ppSacl,
+ PSECURITY_DESCRIPTOR *ppSecurityDescriptor)
+{
+ static GetSecurityInfo_Proc s_pfn_Get_Security_Info = NULL;
+ HMODULE hm_advapi32 = NULL;
+ if (is_windows_9x () == TRUE)
+ {
+ return FALSE;
+ }
+ if (g_b_init_get_security_info == 0)
+ {
+ g_b_init_get_security_info = 1;
+ hm_advapi32 = LoadLibrary ("Advapi32.dll");
+ s_pfn_Get_Security_Info =
+ (GetSecurityInfo_Proc) GetProcAddress (
+ hm_advapi32, "GetSecurityInfo");
+ }
+ if (s_pfn_Get_Security_Info == NULL)
+ {
+ return FALSE;
+ }
+ return (s_pfn_Get_Security_Info (handle, ObjectType, SecurityInfo,
+ ppsidOwner, ppsidGroup, ppDacl, ppSacl,
+ ppSecurityDescriptor));
+}
+
static BOOL WINAPI
get_file_security (LPCTSTR lpFileName,
SECURITY_INFORMATION RequestedInformation,
return FALSE;
return (s_pfn_Get_System_times (lpIdleTime, lpKernelTime, lpUserTime));
}
+
+static BOOLEAN WINAPI
+create_symbolic_link (LPTSTR lpSymlinkFilename,
+ LPTSTR lpTargetFileName,
+ DWORD dwFlags)
+{
+ static CreateSymbolicLink_Proc s_pfn_Create_Symbolic_Link = NULL;
+ BOOLEAN retval;
+
+ if (is_windows_9x () == TRUE)
+ {
+ errno = ENOSYS;
+ return 0;
+ }
+ if (g_b_init_create_symbolic_link == 0)
+ {
+ g_b_init_create_symbolic_link = 1;
+#ifdef _UNICODE
+ s_pfn_Create_Symbolic_Link =
+ (CreateSymbolicLink_Proc)GetProcAddress (GetModuleHandle ("kernel32.dll"),
+ "CreateSymbolicLinkW");
+#else
+ s_pfn_Create_Symbolic_Link =
+ (CreateSymbolicLink_Proc)GetProcAddress (GetModuleHandle ("kernel32.dll"),
+ "CreateSymbolicLinkA");
+#endif
+ }
+ if (s_pfn_Create_Symbolic_Link == NULL)
+ {
+ errno = ENOSYS;
+ return 0;
+ }
+
+ retval = s_pfn_Create_Symbolic_Link (lpSymlinkFilename, lpTargetFileName,
+ dwFlags);
+ /* If we were denied creation of the symlink, try again after
+ enabling the SeCreateSymbolicLinkPrivilege for our process. */
+ if (!retval)
+ {
+ TOKEN_PRIVILEGES priv_current;
+
+ if (enable_privilege (SE_CREATE_SYMBOLIC_LINK_NAME, TRUE, &priv_current))
+ {
+ retval = s_pfn_Create_Symbolic_Link (lpSymlinkFilename, lpTargetFileName,
+ dwFlags);
+ restore_privilege (&priv_current);
+ revert_to_self ();
+ }
+ }
+ return retval;
+}
\f
/* Equivalent of strerror for W32 error codes. */
char *
return dir;
return NULL;
#else
- /* Emacs doesn't actually change directory itself, and we want to
- force our real wd to be where emacs.exe is to avoid unnecessary
- conflicts when trying to rename or delete directories. */
+ /* Emacs doesn't actually change directory itself, it stays in the
+ same directory where it was started. */
strcpy (dir, startup_dir);
return dir;
#endif
lpvalue = NULL;
if (RegQueryValueEx (hrootkey, key, NULL, NULL, NULL, &cbData) == ERROR_SUCCESS
- && (lpvalue = (LPBYTE) xmalloc (cbData)) != NULL
+ && (lpvalue = xmalloc (cbData)) != NULL
&& RegQueryValueEx (hrootkey, key, NULL, lpdwtype, lpvalue, &cbData) == ERROR_SUCCESS)
{
RegCloseKey (hrootkey);
lpvalue = NULL;
if (RegQueryValueEx (hrootkey, key, NULL, NULL, NULL, &cbData) == ERROR_SUCCESS
- && (lpvalue = (LPBYTE) xmalloc (cbData)) != NULL
+ && (lpvalue = xmalloc (cbData)) != NULL
&& RegQueryValueEx (hrootkey, key, NULL, lpdwtype, lpvalue, &cbData) == ERROR_SUCCESS)
{
RegCloseKey (hrootkey);
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. */
-#ifdef _MSC_VER
- /* MSVC's _access crashes with D_OK. */
+
+ /* MSVCRT's _access crashes with D_OK. */
if (tmp && sys_access (tmp, D_OK) == 0)
-#else
- if (tmp && _access (tmp, D_OK) == 0)
-#endif
{
char * var = alloca (strlen (tmp) + 8);
sprintf (var, "TMPDIR=%s", tmp);
char * def_value;
} dflt_envvars[] =
{
+ /* If the default value is NULL, we will use the value from the
+ outside environment or the Registry, but will not push the
+ variable into the Emacs environment if it is defined neither
+ in the Registry nor in the outside environment. */
{"HOME", "C:/"},
{"PRELOAD_WINSOCK", NULL},
{"emacs_dir", "C:/emacs"},
- {"EMACSLOADPATH", "%emacs_dir%/site-lisp;%emacs_dir%/../site-lisp;%emacs_dir%/lisp;%emacs_dir%/leim"},
+ {"EMACSLOADPATH", NULL},
{"SHELL", "%emacs_dir%/bin/cmdproxy.exe"},
- {"EMACSDATA", "%emacs_dir%/etc"},
- {"EMACSPATH", "%emacs_dir%/bin"},
- /* We no longer set INFOPATH because Info-default-directory-list
- is then ignored. */
- /* {"INFOPATH", "%emacs_dir%/info"}, */
- {"EMACSDOC", "%emacs_dir%/etc"},
+ {"EMACSDATA", NULL},
+ {"EMACSPATH", NULL},
+ {"INFOPATH", NULL},
+ {"EMACSDOC", NULL},
{"TERM", "cmd"},
{"LANG", NULL},
};
}
}
- /* When Emacs is invoked with --no-site-lisp, we must remove the
- site-lisp directories from the default value of EMACSLOADPATH.
- This assumes that the site-lisp entries are at the front, and
- that additional entries do exist. */
- if (no_site_lisp)
- {
- for (i = 0; i < N_ENV_VARS; i++)
- {
- if (strcmp (env_vars[i].name, "EMACSLOADPATH") == 0)
- {
- char *site;
- while ((site = strstr (env_vars[i].def_value, "site-lisp")))
- env_vars[i].def_value = strchr (site, ';') + 1;
- 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. */
+ location. */
{
char *p;
char modname[MAX_PATH];
dwType = REG_EXPAND_SZ;
dont_free = 1;
if (!strcmp (env_vars[i].name, "HOME") && !appdata)
- {
- Lisp_Object warning[2];
- warning[0] = intern ("initialization");
- warning[1] = build_string ("Setting HOME to C:\\ by default is deprecated");
- Vdelayed_warnings_list = Fcons (Flist (2, warning),
- Vdelayed_warnings_list);
- }
+ Vdelayed_warnings_list
+ = Fcons (listn (CONSTYPE_HEAP, 2,
+ intern ("initialization"),
+ build_string ("Setting HOME to C:\\ by default is deprecated")),
+ Vdelayed_warnings_list);
}
if (lpval)
memcpy (*envp, "COMSPEC=", 8);
}
- /* Remember the initial working directory for getwd, then make the
- real wd be the location of emacs.exe to avoid conflicts when
- renaming or deleting directories. (We also don't call chdir when
- running subprocesses for the same reason.) */
+ /* Remember the initial working directory for getwd. */
+ /* FIXME: Do we need to resolve possible symlinks in startup_dir?
+ Does it matter anywhere in Emacs? */
if (!GetCurrentDirectory (MAXPATHLEN, startup_dir))
abort ();
{
- char *p;
static char modname[MAX_PATH];
if (!GetModuleFileName (NULL, modname, MAX_PATH))
abort ();
- if ((p = strrchr (modname, '\\')) == NULL)
- abort ();
- *p = 0;
-
- SetCurrentDirectory (modname);
-
- /* Ensure argv[0] has the full path to Emacs. */
- *p = '\\';
argv[0] = modname;
}
init_user_info ();
}
+/* Called from expand-file-name when default-directory is not a string. */
+
char *
emacs_root_dir (void)
{
cv, /* To be filled later. */
#ifdef EMACSDEBUG
" --no-opt",
+#endif
+#ifdef ENABLE_CHECKING
+ " --enable-checking",
#endif
/* configure.bat already sets USER_CFLAGS and USER_LDFLAGS
with a starting space to save work here. */
changed. We could fix that by using GetSystemTime and
GetTimeZoneInformation, but that doesn't seem necessary, since
Emacs always calls gettimeofday with the 2nd argument NULL (see
- EMACS_GET_TIME). */
+ current_emacs_time). */
if (tz)
{
tz->tz_minuteswest = tb.timezone; /* minutes west of Greenwich */
}
}
+/* Emulate fdutimens. */
+
+/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
+ TIMESPEC[0] and TIMESPEC[1], respectively.
+ FD must be either negative -- in which case it is ignored --
+ or a file descriptor that is open on FILE.
+ If FD is nonnegative, then FILE can be NULL, which means
+ use just futimes instead of utimes.
+ If TIMESPEC is null, FAIL.
+ Return 0 on success, -1 (setting errno) on failure. */
+
+int
+fdutimens (int fd, char const *file, struct timespec const timespec[2])
+{
+ struct _utimbuf ut;
+
+ if (!timespec)
+ {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (fd < 0 && !file)
+ {
+ errno = EBADF;
+ return -1;
+ }
+ ut.actime = timespec[0].tv_sec;
+ ut.modtime = timespec[1].tv_sec;
+ if (fd >= 0)
+ return _futime (fd, &ut);
+ else
+ return _utime (file, &ut);
+}
+
+
/* ------------------------------------------------------------------------- */
-/* IO support and wrapper functions for W32 API. */
+/* IO support and wrapper functions for the Windows API. */
/* ------------------------------------------------------------------------- */
/* Place a wrapper around the MSVC version of ctime. It returns NULL
entry if present. */
if (info == NULL)
{
- info = (volume_info_data *) xmalloc (sizeof (volume_info_data));
+ info = xmalloc (sizeof (volume_info_data));
add_volume_info (root_dir, info);
}
else
return info;
}
-/* Get information on the volume where name is held; set path pointer to
- start of pathname in name (past UNC header\volume header if present). */
+/* Get information on the volume where NAME is held; set path pointer to
+ start of pathname in NAME (past UNC header\volume header if present),
+ if pPath is non-NULL.
+
+ Note: if NAME includes symlinks, the information is for the volume
+ of the symlink, not of its target. That's because, even though
+ GetVolumeInformation returns information about the symlink target
+ of its argument, we only pass the root directory to
+ GetVolumeInformation, not the full NAME. */
static int
get_volume_info (const char * name, const char ** pPath)
{
if (name == NULL)
return FALSE;
- /* find the root name of the volume if given */
+ /* Find the root name of the volume if given. */
if (isalpha (name[0]) && name[1] == ':')
{
rootname = temp;
}
/* Determine if volume is FAT format (ie. only supports short 8.3
- names); also set path pointer to start of pathname in name. */
+ names); also set path pointer to start of pathname in name, if
+ pPath is non-NULL. */
static int
is_fat_volume (const char * name, const char ** pPath)
{
return FALSE;
}
-/* Map filename to a valid 8.3 name if necessary. */
+/* Map filename to a valid 8.3 name if necessary.
+ The result is a pointer to a static buffer, so CAVEAT EMPTOR! */
const char *
map_w32_filename (const char * name, const char ** pPath)
{
{
switch ( c )
{
+ case ':':
case '\\':
case '/':
- *str++ = '\\';
- extn = 0; /* reset extension flags */
- dots = 2; /* max 2 dots */
- left = 8; /* max length 8 for main part */
- break;
- case ':':
- *str++ = ':';
+ *str++ = (c == ':' ? ':' : '\\');
extn = 0; /* reset extension flags */
dots = 2; /* max 2 dots */
left = 8; /* max length 8 for main part */
if (wnet_enum_handle != INVALID_HANDLE_VALUE)
return NULL;
+ /* Note: We don't support traversal of UNC volumes via symlinks.
+ Doing so would mean punishing 99.99% of use cases by resolving
+ all the possible symlinks in FILENAME, recursively. */
if (is_unc_volume (filename))
{
wnet_enum_handle = open_unc_volume (filename);
strncpy (dir_pathname, map_w32_filename (filename, NULL), MAXPATHLEN);
dir_pathname[MAXPATHLEN] = '\0';
+ /* Note: We don't support symlinks to file names on FAT volumes.
+ Doing so would mean punishing 99.99% of use cases by resolving
+ all the possible symlinks in FILENAME, recursively. */
dir_is_fat = is_fat_volume (filename, NULL);
return dirp;
strcat (filename, "\\");
strcat (filename, "*");
+ /* Note: No need to resolve symlinks in FILENAME, because
+ FindFirst opens the directory that is the target of a
+ symlink. */
dir_find_handle = FindFirstFile (filename, &dir_find_data);
if (dir_find_handle == INVALID_HANDLE_VALUE)
{
DWORD attributes;
- /* MSVC implementation doesn't recognize D_OK. */
+ /* MSVCRT implementation of 'access' doesn't recognize D_OK, and its
+ newer versions blow up when passed 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)
+ /* If the last element of PATH is a symlink, we need to resolve it
+ to get the attributes of its target file. Note: any symlinks in
+ PATH elements other than the last one are transparently resolved
+ by GetFileAttributes below. */
+ if ((volume_info.flags & FILE_SUPPORTS_REPARSE_POINTS) != 0)
+ path = chase_symlinks (path);
+
+ if ((attributes = GetFileAttributes (path)) == -1)
{
- /* Should try mapping GetLastError to errno; for now just indicate
- that path doesn't exist. */
- errno = EACCES;
+ DWORD w32err = GetLastError ();
+
+ switch (w32err)
+ {
+ case ERROR_INVALID_NAME:
+ case ERROR_BAD_PATHNAME:
+ if (is_unc_volume (path))
+ {
+ attributes = unc_volume_file_attributes (path);
+ if (attributes == -1)
+ {
+ errno = EACCES;
+ return -1;
+ }
+ break;
+ }
+ /* FALLTHROUGH */
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_BAD_NETPATH:
+ errno = ENOENT;
+ break;
+ default:
+ errno = EACCES;
+ break;
+ }
return -1;
}
if ((mode & X_OK) != 0 && !is_exec (path))
int
sys_chmod (const char * path, int mode)
{
- return _chmod (map_w32_filename (path, NULL), mode);
+ path = chase_symlinks (map_w32_filename (path, NULL));
+ return _chmod (path, mode);
}
int
{
BOOL result;
char temp[MAX_PATH];
+ int newname_dev;
+ int oldname_dev;
/* 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
strcpy (temp, map_w32_filename (oldname, NULL));
- if (os_subtype == OS_WIN95)
+ /* volume_info is set indirectly by map_w32_filename. */
+ oldname_dev = volume_info.serialnum;
+
+ if (os_subtype == OS_9X)
{
char * o;
char * p;
all the permutations of shared or subst'd drives, etc.) */
newname = map_w32_filename (newname, NULL);
+
+ /* volume_info is set indirectly by map_w32_filename. */
+ newname_dev = volume_info.serialnum;
+
result = rename (temp, newname);
- if (result < 0
- && errno == EEXIST
- && _chmod (newname, 0666) == 0
- && _unlink (newname) == 0)
- result = rename (temp, newname);
+ if (result < 0)
+ {
+ DWORD w32err = GetLastError ();
+
+ if (errno == EACCES
+ && newname_dev != oldname_dev)
+ {
+ /* The implementation of `rename' on Windows does not return
+ errno = EXDEV when you are moving a directory to a
+ different storage device (ex. logical disk). It returns
+ EACCES instead. So here we handle such situations and
+ return EXDEV. */
+ DWORD attributes;
+
+ if ((attributes = GetFileAttributes (temp)) != -1
+ && (attributes & FILE_ATTRIBUTE_DIRECTORY))
+ errno = EXDEV;
+ }
+ else if (errno == EEXIST)
+ {
+ if (_chmod (newname, 0666) != 0)
+ return result;
+ if (_unlink (newname) != 0)
+ return result;
+ result = rename (temp, newname);
+ }
+ else if (w32err == ERROR_PRIVILEGE_NOT_HELD
+ && is_symlink (temp))
+ {
+ /* This is Windows prohibiting the user from creating a
+ symlink in another place, since that requires
+ privileges. */
+ errno = EPERM;
+ }
+ }
return result;
}
#endif
static PSECURITY_DESCRIPTOR
-get_file_security_desc (const char *fname)
+get_file_security_desc_by_handle (HANDLE h)
+{
+ PSECURITY_DESCRIPTOR psd = NULL;
+ DWORD err;
+ SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION
+ | GROUP_SECURITY_INFORMATION /* | DACL_SECURITY_INFORMATION */ ;
+
+ err = get_security_info (h, SE_FILE_OBJECT, si,
+ NULL, NULL, NULL, NULL, &psd);
+ if (err != ERROR_SUCCESS)
+ return NULL;
+
+ return psd;
+}
+
+static PSECURITY_DESCRIPTOR
+get_file_security_desc_by_name (const char *fname)
{
PSECURITY_DESCRIPTOR psd = NULL;
DWORD sd_len, err;
/* MSVC stat function can't cope with UNC names and has other bugs, so
replace it with our own. This also allows us to calculate consistent
- inode values without hacks in the main Emacs code. */
-int
-stat (const char * path, struct stat * buf)
+ inode values and owner/group without hacks in the main Emacs code. */
+
+static int
+stat_worker (const char * path, struct stat * buf, int follow_symlinks)
{
- char *name, *r;
+ char *name, *save_name, *r;
WIN32_FIND_DATA wfd;
HANDLE fh;
- unsigned __int64 fake_inode;
+ unsigned __int64 fake_inode = 0;
int permission;
int len;
int rootdir = FALSE;
PSECURITY_DESCRIPTOR psd = NULL;
+ int is_a_symlink = 0;
+ DWORD file_flags = FILE_FLAG_BACKUP_SEMANTICS;
+ DWORD access_rights = 0;
+ DWORD fattrs = 0, serialnum = 0, fs_high = 0, fs_low = 0, nlinks = 1;
+ FILETIME ctime, atime, wtime;
if (path == NULL || buf == NULL)
{
return -1;
}
- name = (char *) map_w32_filename (path, &path);
+ save_name = name = (char *) map_w32_filename (path, &path);
/* Must be valid filename, no wild cards or other invalid
characters. We use _mbspbrk to support multibyte strings that
might look to strpbrk as if they included literal *, ?, and other
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. */
len = strlen (name);
- rootdir = (path >= name + len - 1
- && (IS_DIRECTORY_SEP (*path) || *path == 0));
name = strcpy (alloca (len + 2), name);
- 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, "\\");
- if (GetDriveType (name) < 2)
- {
- errno = ENOENT;
- return -1;
- }
- memset (&wfd, 0, sizeof (wfd));
- wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
- wfd.ftCreationTime = utc_base_ft;
- wfd.ftLastAccessTime = utc_base_ft;
- wfd.ftLastWriteTime = utc_base_ft;
- strcpy (wfd.cFileName, name);
- }
- else
- {
- if (IS_DIRECTORY_SEP (name[len-1]))
- name[len - 1] = 0;
-
- /* (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
- && strnicmp (name, dir_pathname, len) == 0
- && IS_DIRECTORY_SEP (name[len])
- && xstrcasecmp (name + len + 1, dir_static.d_name) == 0)
- {
- /* This was the last entry returned by readdir. */
- wfd = dir_find_data;
- }
- else
- {
- logon_network_drive (name);
-
- fh = FindFirstFile (name, &wfd);
- if (fh == INVALID_HANDLE_VALUE)
- {
- errno = ENOENT;
- return -1;
- }
- FindClose (fh);
- }
- }
-
+ /* Avoid a somewhat costly call to is_symlink if the filesystem
+ doesn't support symlinks. */
+ if ((volume_info.flags & FILE_SUPPORTS_REPARSE_POINTS) != 0)
+ is_a_symlink = is_symlink (name);
+
+ /* Plan A: Open the file and get all the necessary information via
+ the resulting handle. This solves several issues in one blow:
+
+ . retrieves attributes for the target of a symlink, if needed
+ . gets attributes of root directories and symlinks pointing to
+ root directories, thus avoiding the need for special-casing
+ these and detecting them by examining the file-name format
+ . retrieves more accurate attributes (e.g., non-zero size for
+ some directories, esp. directories that are junction points)
+ . correctly resolves "c:/..", "/.." and similar file names
+ . avoids run-time penalties for 99% of use cases
+
+ Plan A is always tried first, unless the user asked not to (but
+ if the file is a symlink and we need to follow links, we try Plan
+ A even if the user asked not to).
+
+ If Plan A fails, we go to Plan B (below), where various
+ potentially expensive techniques must be used to handle "special"
+ files such as UNC volumes etc. */
if (!(NILP (Vw32_get_true_file_attributes)
|| (EQ (Vw32_get_true_file_attributes, Qlocal) && is_slow_fs (name)))
- /* No access rights required to get info. */
- && (fh = CreateFile (name, 0, 0, NULL, OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS, NULL))
- != INVALID_HANDLE_VALUE)
+ /* Following symlinks requires getting the info by handle. */
+ || (is_a_symlink && follow_symlinks))
{
+ BY_HANDLE_FILE_INFORMATION info;
+
+ if (is_a_symlink && !follow_symlinks)
+ file_flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+ /* READ_CONTROL access rights are required to get security info
+ by handle. But if the OS doesn't support security in the
+ first place, we don't need to try. */
+ if (is_windows_9x () != TRUE)
+ access_rights |= READ_CONTROL;
+
+ fh = CreateFile (name, access_rights, 0, NULL, OPEN_EXISTING,
+ file_flags, NULL);
+ /* If CreateFile fails with READ_CONTROL, try again with zero as
+ access rights. */
+ if (fh == INVALID_HANDLE_VALUE && access_rights)
+ fh = CreateFile (name, 0, 0, NULL, OPEN_EXISTING,
+ file_flags, NULL);
+ if (fh == INVALID_HANDLE_VALUE)
+ goto no_true_file_attributes;
+
/* This is more accurate in terms of getting the correct number
of links, but is quite slow (it is noticeable when Emacs is
making a list of file name completions). */
- BY_HANDLE_FILE_INFORMATION info;
-
if (GetFileInformationByHandle (fh, &info))
{
- buf->st_nlink = info.nNumberOfLinks;
+ nlinks = 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
all the time (even then there are situations where it is
fake_inode = info.nFileIndexHigh;
fake_inode <<= 32;
fake_inode += info.nFileIndexLow;
+ serialnum = info.dwVolumeSerialNumber;
+ fs_high = info.nFileSizeHigh;
+ fs_low = info.nFileSizeLow;
+ ctime = info.ftCreationTime;
+ atime = info.ftLastAccessTime;
+ wtime = info.ftLastWriteTime;
+ fattrs = info.dwFileAttributes;
}
else
{
- buf->st_nlink = 1;
- fake_inode = 0;
+ /* We don't go to Plan B here, because it's not clear that
+ it's a good idea. The only known use case where
+ CreateFile succeeds, but GetFileInformationByHandle fails
+ (with ERROR_INVALID_FUNCTION) is for character devices
+ such as NUL, PRN, etc. For these, switching to Plan B is
+ a net loss, because we lose the character device
+ attribute returned by GetFileType below (FindFirstFile
+ doesn't set that bit in the attributes), and the other
+ fields don't make sense for character devices anyway.
+ Emacs doesn't really care for non-file entities in the
+ context of l?stat, so neither do we. */
+
+ /* w32err is assigned so one could put a breakpoint here and
+ examine its value, when GetFileInformationByHandle
+ fails. */
+ DWORD w32err = GetLastError ();
+
+ switch (w32err)
+ {
+ case ERROR_FILE_NOT_FOUND: /* can this ever happen? */
+ errno = ENOENT;
+ return -1;
+ }
}
- if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- {
- buf->st_mode = S_IFDIR;
- }
+ /* Test for a symlink before testing for a directory, since
+ symlinks to directories have the directory bit set, but we
+ don't want them to appear as directories. */
+ if (is_a_symlink && !follow_symlinks)
+ buf->st_mode = S_IFLNK;
+ else if (fattrs & FILE_ATTRIBUTE_DIRECTORY)
+ buf->st_mode = S_IFDIR;
else
{
- switch (GetFileType (fh))
+ DWORD ftype = GetFileType (fh);
+
+ switch (ftype)
{
case FILE_TYPE_DISK:
buf->st_mode = S_IFREG;
buf->st_mode = S_IFCHR;
}
}
+ /* We produce the fallback owner and group data, based on the
+ current user that runs Emacs, in the following cases:
+
+ . this is Windows 9X
+ . getting security by handle failed, and we need to produce
+ information for the target of a symlink (this is better
+ than producing a potentially misleading info about the
+ symlink itself)
+
+ If getting security by handle fails, and we don't need to
+ resolve symlinks, we try getting security by name. */
+ if (is_windows_9x () != TRUE)
+ psd = get_file_security_desc_by_handle (fh);
+ if (psd)
+ {
+ get_file_owner_and_group (psd, name, buf);
+ LocalFree (psd);
+ }
+ else if (is_windows_9x () == TRUE)
+ get_file_owner_and_group (NULL, name, buf);
+ else if (!(is_a_symlink && follow_symlinks))
+ {
+ psd = get_file_security_desc_by_name (name);
+ get_file_owner_and_group (psd, name, buf);
+ xfree (psd);
+ }
+ else
+ get_file_owner_and_group (NULL, name, buf);
CloseHandle (fh);
- psd = get_file_security_desc (name);
- get_file_owner_and_group (psd, name, buf);
}
else
{
- /* Don't bother to make this information more accurate. */
- buf->st_mode = (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
- S_IFDIR : S_IFREG;
- buf->st_nlink = 1;
- fake_inode = 0;
+ no_true_file_attributes:
+ /* Plan B: Either getting a handle on the file failed, or the
+ caller explicitly asked us to not bother making this
+ information more accurate.
+
+ Implementation note: In Plan B, we never bother to resolve
+ symlinks, even if we got here because we tried Plan A and
+ failed. That's because, even if the caller asked for extra
+ precision by setting Vw32_get_true_file_attributes to t,
+ resolving symlinks requires acquiring a file handle to the
+ symlink, which we already know will fail. And if the user
+ did not ask for extra precision, resolving symlinks will fly
+ in the face of that request, since the user then wants the
+ lightweight version of the code. */
+ rootdir = (path >= save_name + len - 1
+ && (IS_DIRECTORY_SEP (*path) || *path == 0));
+
+ /* 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';
+
+ /* Note: If NAME is a symlink to the root of a UNC volume
+ (i.e. "\\SERVER"), we will not detect that here, and we will
+ return data about the symlink as result of FindFirst below.
+ This is unfortunate, but that marginal use case does not
+ justify a call to chase_symlinks which would impose a penalty
+ on all the other use cases. (We get here for symlinks to
+ roots of UNC volumes because CreateFile above fails for them,
+ unlike with symlinks to root directories X:\ of drives.) */
+ if (is_unc_volume (name))
+ {
+ fattrs = unc_volume_file_attributes (name);
+ if (fattrs == -1)
+ return -1;
+
+ ctime = atime = wtime = utc_base_ft;
+ }
+ else if (rootdir)
+ {
+ if (!IS_DIRECTORY_SEP (name[len-1]))
+ strcat (name, "\\");
+ if (GetDriveType (name) < 2)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ fattrs = FILE_ATTRIBUTE_DIRECTORY;
+ ctime = atime = wtime = utc_base_ft;
+ }
+ else
+ {
+ if (IS_DIRECTORY_SEP (name[len-1]))
+ name[len - 1] = 0;
+
+ /* (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
+ && !(is_a_symlink && follow_symlinks)
+ && strnicmp (save_name, dir_pathname, len) == 0
+ && IS_DIRECTORY_SEP (name[len])
+ && xstrcasecmp (name + len + 1, dir_static.d_name) == 0)
+ {
+ /* This was the last entry returned by readdir. */
+ wfd = dir_find_data;
+ }
+ else
+ {
+ logon_network_drive (name);
+
+ fh = FindFirstFile (name, &wfd);
+ if (fh == INVALID_HANDLE_VALUE)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ FindClose (fh);
+ }
+ /* Note: if NAME is a symlink, the information we get from
+ FindFirstFile is for the symlink, not its target. */
+ fattrs = wfd.dwFileAttributes;
+ ctime = wfd.ftCreationTime;
+ atime = wfd.ftLastAccessTime;
+ wtime = wfd.ftLastWriteTime;
+ fs_high = wfd.nFileSizeHigh;
+ fs_low = wfd.nFileSizeLow;
+ fake_inode = 0;
+ nlinks = 1;
+ serialnum = volume_info.serialnum;
+ }
+ if (is_a_symlink && !follow_symlinks)
+ buf->st_mode = S_IFLNK;
+ else if (fattrs & FILE_ATTRIBUTE_DIRECTORY)
+ buf->st_mode = S_IFDIR;
+ else
+ buf->st_mode = S_IFREG;
get_file_owner_and_group (NULL, name, buf);
}
- xfree (psd);
#if 0
/* Not sure if there is any point in this. */
}
#endif
- /* MSVC defines _ino_t to be short; other libc's might not. */
- if (sizeof (buf->st_ino) == 2)
- buf->st_ino = fake_inode ^ (fake_inode >> 16);
- else
- buf->st_ino = fake_inode;
+ buf->st_ino = fake_inode;
- /* volume_info is set indirectly by map_w32_filename */
- buf->st_dev = volume_info.serialnum;
- buf->st_rdev = volume_info.serialnum;
+ buf->st_dev = serialnum;
+ buf->st_rdev = serialnum;
- buf->st_size = wfd.nFileSizeHigh;
+ buf->st_size = fs_high;
buf->st_size <<= 32;
- buf->st_size += wfd.nFileSizeLow;
+ buf->st_size += fs_low;
+ buf->st_nlink = nlinks;
/* Convert timestamps to Unix format. */
- buf->st_mtime = convert_time (wfd.ftLastWriteTime);
- buf->st_atime = convert_time (wfd.ftLastAccessTime);
+ buf->st_mtime = convert_time (wtime);
+ buf->st_atime = convert_time (atime);
if (buf->st_atime == 0) buf->st_atime = buf->st_mtime;
- buf->st_ctime = convert_time (wfd.ftCreationTime);
+ buf->st_ctime = convert_time (ctime);
if (buf->st_ctime == 0) buf->st_ctime = buf->st_mtime;
/* determine rwx permissions */
- if (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
- permission = S_IREAD;
+ if (is_a_symlink && !follow_symlinks)
+ permission = S_IREAD | S_IWRITE | S_IEXEC; /* Posix expectations */
else
- permission = S_IREAD | S_IWRITE;
+ {
+ if (fattrs & FILE_ATTRIBUTE_READONLY)
+ permission = S_IREAD;
+ else
+ permission = S_IREAD | S_IWRITE;
- if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- permission |= S_IEXEC;
- else if (is_exec (name))
- permission |= S_IEXEC;
+ if (fattrs & FILE_ATTRIBUTE_DIRECTORY)
+ permission |= S_IEXEC;
+ else if (is_exec (name))
+ permission |= S_IEXEC;
+ }
buf->st_mode |= permission | (permission >> 3) | (permission >> 6);
return 0;
}
+int
+stat (const char * path, struct stat * buf)
+{
+ return stat_worker (path, buf, 1);
+}
+
+int
+lstat (const char * path, struct stat * buf)
+{
+ return stat_worker (path, buf, 0);
+}
+
/* Provide fstat and utime as well as stat for consistent handling of
file timestamps. */
int
}
\f
-/* Symlink-related functions that always fail. Used in fileio.c and in
- sysdep.c to avoid #ifdef's. */
+/* Symlink-related functions. */
+#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
+#define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1
+#endif
+
int
-symlink (char const *dummy1, char const *dummy2)
+symlink (char const *filename, char const *linkname)
{
- errno = ENOSYS;
- return -1;
+ char linkfn[MAX_PATH], *tgtfn;
+ DWORD flags = 0;
+ int dir_access, filename_ends_in_slash;
+
+ /* Diagnostics follows Posix as much as possible. */
+ if (filename == NULL || linkname == NULL)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+ if (!*filename)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ if (strlen (filename) > MAX_PATH || strlen (linkname) > MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ strcpy (linkfn, map_w32_filename (linkname, NULL));
+ if ((volume_info.flags & FILE_SUPPORTS_REPARSE_POINTS) == 0)
+ {
+ errno = EPERM;
+ return -1;
+ }
+
+ /* Note: since empty FILENAME was already rejected, we can safely
+ refer to FILENAME[1]. */
+ if (!(IS_DIRECTORY_SEP (filename[0]) || IS_DEVICE_SEP (filename[1])))
+ {
+ /* Non-absolute FILENAME is understood as being relative to
+ LINKNAME's directory. We need to prepend that directory to
+ FILENAME to get correct results from sys_access below, since
+ otherwise it will interpret FILENAME relative to the
+ directory where the Emacs process runs. Note that
+ make-symbolic-link always makes sure LINKNAME is a fully
+ expanded file name. */
+ char tem[MAX_PATH];
+ char *p = linkfn + strlen (linkfn);
+
+ while (p > linkfn && !IS_ANY_SEP (p[-1]))
+ p--;
+ if (p > linkfn)
+ strncpy (tem, linkfn, p - linkfn);
+ tem[p - linkfn] = '\0';
+ strcat (tem, filename);
+ dir_access = sys_access (tem, D_OK);
+ }
+ else
+ dir_access = sys_access (filename, D_OK);
+
+ /* Since Windows distinguishes between symlinks to directories and
+ to files, we provide a kludgey feature: if FILENAME doesn't
+ exist, but ends in a slash, we create a symlink to directory. If
+ FILENAME exists and is a directory, we always create a symlink to
+ directory. */
+ filename_ends_in_slash = IS_DIRECTORY_SEP (filename[strlen (filename) - 1]);
+ if (dir_access == 0 || filename_ends_in_slash)
+ flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
+
+ tgtfn = (char *)map_w32_filename (filename, NULL);
+ if (filename_ends_in_slash)
+ tgtfn[strlen (tgtfn) - 1] = '\0';
+
+ errno = 0;
+ if (!create_symbolic_link (linkfn, tgtfn, flags))
+ {
+ /* ENOSYS is set by create_symbolic_link, when it detects that
+ the OS doesn't support the CreateSymbolicLink API. */
+ if (errno != ENOSYS)
+ {
+ DWORD w32err = GetLastError ();
+
+ switch (w32err)
+ {
+ /* ERROR_SUCCESS is sometimes returned when LINKFN and
+ TGTFN point to the same file name, go figure. */
+ case ERROR_SUCCESS:
+ case ERROR_FILE_EXISTS:
+ errno = EEXIST;
+ break;
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_BAD_NETPATH:
+ case ERROR_INVALID_REPARSE_DATA:
+ errno = ENOENT;
+ break;
+ case ERROR_DIRECTORY:
+ errno = EISDIR;
+ break;
+ case ERROR_PRIVILEGE_NOT_HELD:
+ case ERROR_NOT_ALL_ASSIGNED:
+ errno = EPERM;
+ break;
+ case ERROR_DISK_FULL:
+ errno = ENOSPC;
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+ }
+ return -1;
+ }
+ return 0;
}
+/* A quick inexpensive test of whether FILENAME identifies a file that
+ is a symlink. Returns non-zero if it is, zero otherwise. FILENAME
+ must already be in the normalized form returned by
+ map_w32_filename.
+
+ Note: for repeated operations on many files, it is best to test
+ whether the underlying volume actually supports symlinks, by
+ testing the FILE_SUPPORTS_REPARSE_POINTS bit in volume's flags, and
+ avoid the call to this function if it doesn't. That's because the
+ call to GetFileAttributes takes a non-negligible time, expecially
+ on non-local or removable filesystems. See stat_worker for an
+ example of how to do that. */
+static int
+is_symlink (const char *filename)
+{
+ DWORD attrs;
+ WIN32_FIND_DATA wfd;
+ HANDLE fh;
+
+ attrs = GetFileAttributes (filename);
+ if (attrs == -1)
+ {
+ DWORD w32err = GetLastError ();
+
+ switch (w32err)
+ {
+ case ERROR_BAD_NETPATH: /* network share, can't be a symlink */
+ break;
+ case ERROR_ACCESS_DENIED:
+ errno = EACCES;
+ break;
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ default:
+ errno = ENOENT;
+ break;
+ }
+ return 0;
+ }
+ if ((attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ return 0;
+ logon_network_drive (filename);
+ fh = FindFirstFile (filename, &wfd);
+ if (fh == INVALID_HANDLE_VALUE)
+ return 0;
+ FindClose (fh);
+ return (wfd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
+ && (wfd.dwReserved0 & IO_REPARSE_TAG_SYMLINK) == IO_REPARSE_TAG_SYMLINK;
+}
+
+/* If NAME identifies a symbolic link, copy into BUF the file name of
+ the symlink's target. Copy at most BUF_SIZE bytes, and do NOT
+ null-terminate the target name, even if it fits. Return the number
+ of bytes copied, or -1 if NAME is not a symlink or any error was
+ encountered while resolving it. The file name copied into BUF is
+ encoded in the current ANSI codepage. */
ssize_t
-readlink (const char *name, char *dummy1, size_t dummy2)
+readlink (const char *name, char *buf, size_t buf_size)
{
- /* `access' is much faster than `stat' on MS-Windows. */
- if (sys_access (name, 0) == 0)
- errno = EINVAL;
- return -1;
+ const char *path;
+ TOKEN_PRIVILEGES privs;
+ int restore_privs = 0;
+ HANDLE sh;
+ ssize_t retval;
+
+ if (name == NULL)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+ if (!*name)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ path = map_w32_filename (name, NULL);
+
+ if (strlen (path) > MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ errno = 0;
+ if (is_windows_9x () == TRUE
+ || (volume_info.flags & FILE_SUPPORTS_REPARSE_POINTS) == 0
+ || !is_symlink (path))
+ {
+ if (!errno)
+ errno = EINVAL; /* not a symlink */
+ return -1;
+ }
+
+ /* Done with simple tests, now we're in for some _real_ work. */
+ if (enable_privilege (SE_BACKUP_NAME, TRUE, &privs))
+ restore_privs = 1;
+ /* Implementation note: From here and onward, don't return early,
+ since that will fail to restore the original set of privileges of
+ the calling thread. */
+
+ retval = -1; /* not too optimistic, are we? */
+
+ /* Note: In the next call to CreateFile, we use zero as the 2nd
+ argument because, when the symlink is a hidden/system file,
+ e.g. 'C:\Users\All Users', GENERIC_READ fails with
+ ERROR_ACCESS_DENIED. Zero seems to work just fine, both for file
+ and directory symlinks. */
+ sh = CreateFile (path, 0, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (sh != INVALID_HANDLE_VALUE)
+ {
+ BYTE reparse_buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ REPARSE_DATA_BUFFER *reparse_data = (REPARSE_DATA_BUFFER *)&reparse_buf[0];
+ DWORD retbytes;
+
+ if (!DeviceIoControl (sh, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+ &retbytes, NULL))
+ errno = EIO;
+ else if (reparse_data->ReparseTag != IO_REPARSE_TAG_SYMLINK)
+ errno = EINVAL;
+ else
+ {
+ /* Copy the link target name, in wide characters, fro
+ reparse_data, then convert it to multibyte encoding in
+ the current locale's codepage. */
+ WCHAR *lwname;
+ BYTE lname[MAX_PATH];
+ USHORT lname_len;
+ USHORT lwname_len =
+ reparse_data->SymbolicLinkReparseBuffer.PrintNameLength;
+ WCHAR *lwname_src =
+ reparse_data->SymbolicLinkReparseBuffer.PathBuffer
+ + reparse_data->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR);
+
+ /* According to MSDN, PrintNameLength does not include the
+ terminating null character. */
+ lwname = alloca ((lwname_len + 1) * sizeof(WCHAR));
+ memcpy (lwname, lwname_src, lwname_len);
+ lwname[lwname_len/sizeof(WCHAR)] = 0; /* null-terminate */
+
+ /* FIXME: Should we use the current file-name coding system
+ instead of the fixed value of the ANSI codepage? */
+ lname_len = WideCharToMultiByte (w32_ansi_code_page, 0, lwname, -1,
+ lname, MAX_PATH, NULL, NULL);
+ if (!lname_len)
+ {
+ /* WideCharToMultiByte failed. */
+ DWORD w32err1 = GetLastError ();
+
+ switch (w32err1)
+ {
+ case ERROR_INSUFFICIENT_BUFFER:
+ errno = ENAMETOOLONG;
+ break;
+ case ERROR_INVALID_PARAMETER:
+ errno = EFAULT;
+ break;
+ case ERROR_NO_UNICODE_TRANSLATION:
+ errno = ENOENT;
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+ }
+ else
+ {
+ size_t size_to_copy = buf_size;
+ BYTE *p = lname;
+ BYTE *pend = p + lname_len;
+
+ /* Normalize like dostounix_filename does, but we don't
+ want to assume that lname is null-terminated. */
+ if (*p && p[1] == ':' && *p >= 'A' && *p <= 'Z')
+ *p += 'a' - 'A';
+ while (p <= pend)
+ {
+ if (*p == '\\')
+ *p = '/';
+ ++p;
+ }
+ /* Testing for null-terminated LNAME is paranoia:
+ WideCharToMultiByte should always return a
+ null-terminated string when its 4th argument is -1
+ and its 3rd argument is null-terminated (which they
+ are, see above). */
+ if (lname[lname_len - 1] == '\0')
+ lname_len--;
+ if (lname_len <= buf_size)
+ size_to_copy = lname_len;
+ strncpy (buf, lname, size_to_copy);
+ /* Success! */
+ retval = size_to_copy;
+ }
+ }
+ CloseHandle (sh);
+ }
+ else
+ {
+ /* CreateFile failed. */
+ DWORD w32err2 = GetLastError ();
+
+ switch (w32err2)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ errno = ENOENT;
+ break;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_TOO_MANY_OPEN_FILES:
+ errno = EACCES;
+ break;
+ default:
+ errno = EPERM;
+ break;
+ }
+ }
+ if (restore_privs)
+ {
+ restore_privilege (&privs);
+ revert_to_self ();
+ }
+
+ return retval;
}
+/* If FILE is a symlink, return its target (stored in a static
+ buffer); otherwise return FILE.
+
+ This function repeatedly resolves symlinks in the last component of
+ a chain of symlink file names, as in foo -> bar -> baz -> ...,
+ until it arrives at a file whose last component is not a symlink,
+ or some error occurs. It returns the target of the last
+ successfully resolved symlink in the chain. If it succeeds to
+ resolve even a single symlink, the value returned is an absolute
+ file name with backslashes (result of GetFullPathName). By
+ contrast, if the original FILE is returned, it is unaltered.
+
+ Note: This function can set errno even if it succeeds.
+
+ Implementation note: we only resolve the last portion ("basename")
+ of the argument FILE and of each following file in the chain,
+ disregarding any possible symlinks in its leading directories.
+ This is because Windows system calls and library functions
+ transparently resolve symlinks in leading directories and return
+ correct information, as long as the basename is not a symlink. */
+static char *
+chase_symlinks (const char *file)
+{
+ static char target[MAX_PATH];
+ char link[MAX_PATH];
+ ssize_t res, link_len;
+ int loop_count = 0;
+
+ if (is_windows_9x () == TRUE || !is_symlink (file))
+ return (char *)file;
+
+ if ((link_len = GetFullPathName (file, MAX_PATH, link, NULL)) == 0)
+ return (char *)file;
+
+ target[0] = '\0';
+ do {
+
+ /* Remove trailing slashes, as we want to resolve the last
+ non-trivial part of the link name. */
+ while (link_len > 3 && IS_DIRECTORY_SEP (link[link_len-1]))
+ link[link_len--] = '\0';
+
+ res = readlink (link, target, MAX_PATH);
+ if (res > 0)
+ {
+ target[res] = '\0';
+ if (!(IS_DEVICE_SEP (target[1])
+ || (IS_DIRECTORY_SEP (target[0]) && IS_DIRECTORY_SEP (target[1]))))
+ {
+ /* Target is relative. Append it to the directory part of
+ the symlink, then copy the result back to target. */
+ char *p = link + link_len;
+
+ while (p > link && !IS_ANY_SEP (p[-1]))
+ p--;
+ strcpy (p, target);
+ strcpy (target, link);
+ }
+ /* Resolve any "." and ".." to get a fully-qualified file name
+ in link[] again. */
+ link_len = GetFullPathName (target, MAX_PATH, link, NULL);
+ }
+ } while (res > 0 && link_len > 0 && ++loop_count <= 100);
+
+ if (loop_count > 100)
+ errno = ELOOP;
+
+ if (target[0] == '\0') /* not a single call to readlink succeeded */
+ return (char *)file;
+ return target;
+}
+
+/* MS-Windows version of careadlinkat (cf. ../lib/careadlinkat.c). We
+ have a fixed max size for file names, so we don't need the kind of
+ alloc/malloc/realloc dance the gnulib version does. We also don't
+ support FD-relative symlinks. */
char *
careadlinkat (int fd, char const *filename,
char *buffer, size_t buffer_size,
struct allocator const *alloc,
ssize_t (*preadlinkat) (int, char const *, char *, size_t))
{
- errno = ENOSYS;
+ char linkname[MAX_PATH];
+ ssize_t link_size;
+
+ if (fd != AT_FDCWD)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ link_size = preadlinkat (fd, filename, linkname, sizeof(linkname));
+
+ if (link_size > 0)
+ {
+ char *retval = buffer;
+
+ linkname[link_size++] = '\0';
+ if (link_size > buffer_size)
+ retval = (char *)(alloc ? alloc->allocate : xmalloc) (link_size);
+ if (retval)
+ memcpy (retval, linkname, link_size);
+
+ return retval;
+ }
return NULL;
}
}
static Lisp_Object
-ltime (long time_sec, long time_usec)
+ltime (ULONGLONG time_100ns)
{
- return list3 (make_number ((time_sec >> 16) & 0xffff),
+ ULONGLONG time_sec = time_100ns / 10000000;
+ int subsec = time_100ns % 10000000;
+ return list4 (make_number (time_sec >> 16),
make_number (time_sec & 0xffff),
- make_number (time_usec));
+ make_number (subsec / 10),
+ make_number (subsec % 10 * 100000));
}
-#define U64_TO_LISP_TIME(time) ltime ((time) / 1000000L, (time) % 1000000L)
+#define U64_TO_LISP_TIME(time) ltime (time)
static int
process_times (HANDLE h_proc, Lisp_Object *ctime, Lisp_Object *etime,
GetSystemTimeAsFileTime (&ft_current);
FILETIME_TO_U64 (tem1, ft_kernel);
- tem1 /= 10L;
*stime = U64_TO_LISP_TIME (tem1);
FILETIME_TO_U64 (tem2, ft_user);
- tem2 /= 10L;
*utime = U64_TO_LISP_TIME (tem2);
tem3 = tem1 + tem2;
FILETIME_TO_U64 (tem, ft_creation);
/* Process no 4 (System) returns zero creation time. */
if (tem)
- tem = (tem - utc_base) / 10L;
+ tem -= utc_base;
*ctime = U64_TO_LISP_TIME (tem);
if (tem)
{
FILETIME_TO_U64 (tem3, ft_current);
- tem = (tem3 - utc_base) / 10L - tem;
+ tem = (tem3 - utc_base) - tem;
}
*etime = U64_TO_LISP_TIME (tem);
CHECK_STRING_CAR (dlls);
if ((library_dll = LoadLibrary (SDATA (XCAR (dlls)))))
{
- found = XCAR (dlls);
+ char name[MAX_PATH];
+ DWORD len;
+
+ len = GetModuleFileNameA (library_dll, name, sizeof (name));
+ found = Fcons (XCAR (dlls),
+ (len > 0)
+ /* Possibly truncated */
+ ? make_specified_string (name, -1, len, 1)
+ : Qnil);
break;
}
}
}
\f
-static void
+void
check_windows_init_file (void)
{
/* A common indication that Emacs is not installed properly is when
loadup.el. */
&& NILP (Vpurify_flag))
{
- 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, Fget_load_suffixes (), NULL, Qnil);
+ fd = openp (Vload_path, init_file, Fget_load_suffixes (), NULL, Qnil);
if (fd < 0)
{
- Lisp_Object load_path_print = Fprin1_to_string (full_load_path, Qnil);
+ Lisp_Object load_path_print = Fprin1_to_string (Vload_path, Qnil);
char *init_file_name = SDATA (init_file);
char *load_path = SDATA (load_path_print);
char *buffer = alloca (1024
/* Reset the volume info cache. */
volume_cache = NULL;
}
-
- /* Check to see if Emacs has been installed correctly. */
- check_windows_init_file ();
}
/*
g_b_init_lookup_account_sid = 0;
g_b_init_get_sid_sub_authority = 0;
g_b_init_get_sid_sub_authority_count = 0;
+ g_b_init_get_security_info = 0;
g_b_init_get_file_security = 0;
g_b_init_get_security_descriptor_owner = 0;
g_b_init_get_security_descriptor_group = 0;
g_b_init_get_length_sid = 0;
g_b_init_get_native_system_info = 0;
g_b_init_get_system_times = 0;
+ g_b_init_create_symbolic_link = 0;
num_of_processors = 0;
/* The following sets a handler for shutdown notifications for
console apps. This actually applies to Emacs in both console and
error ("SetCommState() failed");
childp2 = Fplist_put (childp2, QCsummary, build_string (summary));
- p->childp = childp2;
+ PSET (p, childp, childp2);
}
#ifdef HAVE_GNUTLS
{
int n, sc, err;
SELECT_TYPE fdset;
- EMACS_TIME timeout;
+ struct timeval timeout;
struct Lisp_Process *process = (struct Lisp_Process *)p;
int fd = process->infd;
if (err == EWOULDBLOCK)
{
/* Set a small timeout. */
- EMACS_SET_SECS_USECS (timeout, 1, 0);
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
FD_ZERO (&fdset);
FD_SET ((int)fd, &fdset);
/* Use select with the timeout to poll the selector. */
sc = select (fd + 1, &fdset, (SELECT_TYPE *)0, (SELECT_TYPE *)0,
- &timeout);
+ &timeout, NULL);
if (sc > 0)
continue; /* Try again. */