/* Process support for GNU Emacs on the Microsoft Windows API.
-Copyright (C) 1992, 1995, 1999-2014 Free Software Foundation, Inc.
+Copyright (C) 1992, 1995, 1999-2016 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
#include <ctype.h>
#include <io.h>
#include <fcntl.h>
+#include <unistd.h>
#include <signal.h>
#include <sys/file.h>
#include <mbstring.h>
+#include <locale.h>
/* must include CRT headers *before* config.h */
#include <config.h>
#include "w32.h"
#include "w32common.h"
#include "w32heap.h"
-#include "systime.h"
-#include "syswait.h"
-#include "process.h"
+#include "syswait.h" /* for WNOHANG */
#include "syssignal.h"
#include "w32term.h"
-#include "dispextern.h" /* for xstrcasecmp */
#include "coding.h"
#define RVA_TO_PTR(var,section,filedata) \
+ ((DWORD_PTR)(var) - (section)->VirtualAddress) \
+ (filedata).file_base))
-Lisp_Object Qhigh, Qlow;
-
/* Signal handlers...SIG_DFL == 0 so this is initialized correctly. */
static signal_handler sig_handlers[NSIG];
#endif
}
+\f
+
+/* Here's an overview of how support for subprocesses and
+ network/serial streams is implemented on MS-Windows.
+
+ The management of both subprocesses and network/serial streams
+ circles around the child_procs[] array, which can record up to the
+ grand total of MAX_CHILDREN (= 32) of these. (The reasons for the
+ 32 limitation will become clear below.) Each member of
+ child_procs[] is a child_process structure, defined on w32.h.
+
+ A related data structure is the fd_info[] array, which holds twice
+ as many members, 64, and records the information about file
+ descriptors used for communicating with subprocesses and
+ network/serial devices. Each member of the array is the filedesc
+ structure, which records the Windows handle for communications,
+ such as the read end of the pipe to a subprocess, a socket handle,
+ etc.
+
+ Both these arrays reference each other: there's a member of
+ child_process structure that records the corresponding file
+ descriptor, and there's a member of filedesc structure that holds a
+ pointer to the corresponding child_process.
+
+ Whenever Emacs starts a subprocess or opens a network/serial
+ stream, the function new_child is called to prepare a new
+ child_process structure. new_child looks for the first vacant slot
+ in the child_procs[] array, initializes it, and starts a "reader
+ thread" that will watch the output of the subprocess/stream and its
+ status. (If no vacant slot can be found, new_child returns a
+ failure indication to its caller, and the higher-level Emacs
+ primitive that called it will then fail with EMFILE or EAGAIN.)
+
+ The reader thread started by new_child communicates with the main
+ (a.k.a. "Lisp") thread via two event objects and a status, all of
+ them recorded by the members of the child_process structure in
+ child_procs[]. The event objects serve as semaphores between the
+ reader thread and the 'pselect' emulation in sys_select, as follows:
+
+ . Initially, the reader thread is waiting for the char_consumed
+ event to become signaled by sys_select, which is an indication
+ for the reader thread to go ahead and try reading more stuff
+ from the subprocess/stream.
+
+ . The reader thread then attempts to read by calling a
+ blocking-read function. When the read call returns, either
+ successfully or with some failure indication, the reader thread
+ updates the status of the read accordingly, and signals the 2nd
+ event object, char_avail, on whose handle sys_select is
+ waiting. This tells sys_select that the file descriptor
+ allocated for the subprocess or the the stream is ready to be
+ read from.
+
+ When the subprocess exits or the network/serial stream is closed,
+ the reader thread sets the status accordingly and exits. It also
+ exits when the main thread sets the status to STATUS_READ_ERROR
+ and/or the char_avail and char_consumed event handles become NULL;
+ this is how delete_child, called by Emacs when a subprocess or a
+ stream is terminated, terminates the reader thread as part of
+ deleting the child_process object.
+
+ The sys_select function emulates the Posix 'pselect' function; it
+ is needed because the Windows 'select' function supports only
+ network sockets, while Emacs expects 'pselect' to work for any file
+ descriptor, including pipes and serial streams.
+
+ When sys_select is called, it uses the information in fd_info[]
+ array to convert the file descriptors which it was asked to watch
+ into Windows handles. In general, the handle to watch is the
+ handle of the char_avail event of the child_process structure that
+ corresponds to the file descriptor. In addition, for subprocesses,
+ sys_select watches one more handle: the handle for the subprocess,
+ so that it could emulate the SIGCHLD signal when the subprocess
+ exits.
+
+ If file descriptor zero (stdin) doesn't have its bit set in the
+ 'rfds' argument to sys_select, the function always watches for
+ keyboard interrupts, to be able to interrupt the wait and return
+ when the user presses C-g.
+
+ Having collected the handles to watch, sys_select calls
+ WaitForMultipleObjects to wait for any one of them to become
+ signaled. Since WaitForMultipleObjects can only watch up to 64
+ handles, Emacs on Windows is limited to maximum 32 child_process
+ objects (since a subprocess consumes 2 handles to be watched, see
+ above).
+
+ When any of the handles become signaled, sys_select does whatever
+ is appropriate for the corresponding child_process object:
+
+ . If it's a handle to the char_avail event, sys_select marks the
+ corresponding bit in 'rfds', and Emacs will then read from that
+ file descriptor.
+
+ . If it's a handle to the process, sys_select calls the SIGCHLD
+ handler, to inform Emacs of the fact that the subprocess
+ exited.
+
+ The waitpid emulation works very similar to sys_select, except that
+ it only watches handles of subprocesses, and doesn't synchronize
+ with the reader thread.
+
+ Because socket descriptors on Windows are handles, while Emacs
+ expects them to be file descriptors, all low-level I/O functions,
+ such as 'read' and 'write', and all socket operations, like
+ 'connect', 'recvfrom', 'accept', etc., are redirected to the
+ corresponding 'sys_*' functions, which must convert a file
+ descriptor to a handle using the fd_info[] array, and then invoke
+ the corresponding Windows API on the handle. Most of these
+ redirected 'sys_*' functions are implemented on w32.c.
+
+ When the file descriptor was produced by functions such as 'open',
+ the corresponding handle is obtained by calling _get_osfhandle. To
+ produce a file descriptor for a socket handle, which has no file
+ descriptor as far as Windows is concerned, the function
+ socket_to_fd opens the null device; the resulting file descriptor
+ will never be used directly in any I/O API, but serves as an index
+ into the fd_info[] array, where the socket handle is stored. The
+ SOCK_HANDLE macro retrieves the handle when given the file
+ descriptor.
+
+ The function sys_kill emulates the Posix 'kill' functionality to
+ terminate other processes. It does that by attaching to the
+ foreground window of the process and sending a Ctrl-C or Ctrl-BREAK
+ signal to the process; if that doesn't work, then it calls
+ TerminateProcess to forcibly terminate the process. Note that this
+ only terminates the immediate process whose PID was passed to
+ sys_kill; it doesn't terminate the child processes of that process.
+ This means, for example, that an Emacs subprocess run through a
+ shell might not be killed, because sys_kill will only terminate the
+ shell. (In practice, however, such problems are very rare.) */
+
/* Defined in <process.h> which conflicts with the local copy */
#define _P_NOWAIT 1
{
int rc;
- if (cp->fd >= 0 && fd_info[cp->fd].flags & FILE_LISTEN)
+ if (cp->fd >= 0 && (fd_info[cp->fd].flags & FILE_CONNECT) != 0)
+ rc = _sys_wait_connect (cp->fd);
+ else if (cp->fd >= 0 && (fd_info[cp->fd].flags & FILE_LISTEN) != 0)
rc = _sys_wait_accept (cp->fd);
else
rc = _sys_read_ahead (cp->fd);
return 1;
}
- if (rc == STATUS_READ_ERROR)
- return 1;
+ if (rc == STATUS_READ_ERROR || rc == STATUS_CONNECT_FAILED)
+ return 2;
/* If the read died, the child has died so let the thread die */
if (rc == STATUS_READ_FAILED)
DWORD flags;
char dir[ MAX_PATH ];
char *p;
+ const char *ext;
if (cp == NULL) emacs_abort ();
if (*p == '/')
*p = '\\';
+ /* CreateProcess handles batch files as exe specially. This special
+ handling fails when both the batch file and arguments are quoted.
+ We pass NULL as exe to avoid the special handling. */
+ if (exe && cmdline[0] == '"' &&
+ (ext = strrchr (exe, '.')) &&
+ (xstrcasecmp (ext, ".bat") == 0
+ || xstrcasecmp (ext, ".cmd") == 0))
+ exe = NULL;
+
flags = (!NILP (Vw32_start_process_share_console)
? CREATE_NEW_PROCESS_GROUP
: CREATE_NEW_CONSOLE);
/* Implementation note: This function works with file names encoded in
the current ANSI codepage. */
-static void
+static int
w32_executable_type (char * filename,
int * is_dos_app,
int * is_cygnus_app,
+ int * is_msys_app,
int * is_gui_app)
{
file_data executable;
char * p;
+ int retval = 0;
/* Default values in case we can't tell for sure. */
*is_dos_app = FALSE;
*is_cygnus_app = FALSE;
+ *is_msys_app = FALSE;
*is_gui_app = FALSE;
if (!open_input_file (&executable, filename))
- return;
+ return -1;
p = strrchr (filename, '.');
extension, which is defined in the registry. */
p = egetenv ("COMSPEC");
if (p)
- w32_executable_type (p, is_dos_app, is_cygnus_app, is_gui_app);
+ retval = w32_executable_type (p, is_dos_app, is_cygnus_app, is_msys_app,
+ is_gui_app);
}
else
{
#endif
if (data_dir)
{
- /* Look for cygwin.dll in DLL import list. */
+ /* Look for Cygwin DLL in the DLL import list. */
IMAGE_DATA_DIRECTORY import_dir =
data_dir[IMAGE_DIRECTORY_ENTRY_IMPORT];
- IMAGE_IMPORT_DESCRIPTOR * imports;
- IMAGE_SECTION_HEADER * section;
-
- section = rva_to_section (import_dir.VirtualAddress, nt_header);
- imports = RVA_TO_PTR (import_dir.VirtualAddress, section,
- executable);
+ IMAGE_IMPORT_DESCRIPTOR * imports =
+ RVA_TO_PTR (import_dir.VirtualAddress,
+ rva_to_section (import_dir.VirtualAddress,
+ nt_header),
+ executable);
for ( ; imports->Name; imports++)
{
+ IMAGE_SECTION_HEADER * section =
+ rva_to_section (imports->Name, nt_header);
char * dllname = RVA_TO_PTR (imports->Name, section,
executable);
- /* The exact name of the cygwin dll has changed with
- various releases, but hopefully this will be reasonably
- future proof. */
+ /* The exact name of the Cygwin DLL has changed with
+ various releases, but hopefully this will be
+ reasonably future-proof. */
if (strncmp (dllname, "cygwin", 6) == 0)
{
*is_cygnus_app = TRUE;
break;
}
+ else if (strncmp (dllname, "msys-", 5) == 0)
+ {
+ /* This catches both MSYS 1.x and MSYS2
+ executables (the DLL name is msys-1.0.dll and
+ msys-2.0.dll, respectively). There doesn't
+ seem to be a reason to distinguish between
+ the two, for now. */
+ *is_msys_app = TRUE;
+ break;
+ }
}
}
}
unwind:
close_file_data (&executable);
+ return retval;
}
static int
int arglen, numenv;
pid_t pid;
child_process *cp;
- int is_dos_app, is_cygnus_app, is_gui_app;
+ int is_dos_app, is_cygnus_app, is_msys_app, is_gui_app;
int do_quoting = 0;
/* We pass our process ID to our children by setting up an environment
variable in their environment. */
argument being split into two or more. Arguments with wildcards
are also quoted, for consistency with posix platforms, where wildcards
are not expanded if we run the program directly without a shell.
- Some extra whitespace characters need quoting in Cygwin programs,
+ Some extra whitespace characters need quoting in Cygwin/MSYS programs,
so this list is conditionally modified below. */
char *sepchars = " \t*?";
- /* This is for native w32 apps; modified below for Cygwin apps. */
+ /* This is for native w32 apps; modified below for Cygwin/MSUS apps. */
char escape_char = '\\';
char cmdname_a[MAX_PATH];
absolute. So we double-check this here, just in case. */
if (faccessat (AT_FDCWD, cmdname, X_OK, AT_EACCESS) != 0)
{
- struct gcpro gcpro1;
-
program = build_string (cmdname);
full = Qnil;
- GCPRO1 (program);
openp (Vexec_path, program, Vexec_suffixes, &full, make_number (X_OK), 0);
- UNGCPRO;
if (NILP (full))
{
errno = EINVAL;
return -1;
}
program = ENCODE_FILE (full);
- cmdname = SDATA (program);
+ cmdname = SSDATA (program);
+ }
+ else
+ {
+ char *p = alloca (strlen (cmdname) + 1);
+
+ /* Don't change the command name we were passed by our caller
+ (unixtodos_filename below will destructively mirror forward
+ slashes). */
+ cmdname = strcpy (p, cmdname);
}
/* make sure argv[0] and cmdname are both in DOS format */
/* We explicitly require that the command's file name be encodable
in the current ANSI codepage, because we will be invoking it via
the ANSI APIs. */
- if (_mbspbrk (cmdname_a, "?"))
+ if (_mbspbrk ((unsigned char *)cmdname_a, (const unsigned char *)"?"))
{
errno = ENOENT;
return -1;
cmdname = cmdname_a;
argv[0] = cmdname;
- /* Determine whether program is a 16-bit DOS executable, or a 32-bit Windows
- executable that is implicitly linked to the Cygnus dll (implying it
- was compiled with the Cygnus GNU toolchain and hence relies on
- cygwin.dll to parse the command line - we use this to decide how to
- escape quote chars in command line args that must be quoted).
+ /* Determine whether program is a 16-bit DOS executable, or a 32-bit
+ Windows executable that is implicitly linked to the Cygnus or
+ MSYS dll (implying it was compiled with the Cygnus/MSYS GNU
+ toolchain and hence relies on cygwin.dll or MSYS DLL to parse the
+ command line - we use this to decide how to escape quote chars in
+ command line args that must be quoted).
Also determine whether it is a GUI app, so that we don't hide its
initial window unless specifically requested. */
- w32_executable_type (cmdname, &is_dos_app, &is_cygnus_app, &is_gui_app);
+ w32_executable_type (cmdname, &is_dos_app, &is_cygnus_app, &is_msys_app,
+ &is_gui_app);
/* On Windows 95, if cmdname is a DOS app, we invoke a helper
application to start it by specifying the helper app as cmdname,
cmdname = alloca (MAX_PATH);
if (egetenv ("CMDPROXY"))
- strcpy (cmdname, egetenv ("CMDPROXY"));
+ {
+ /* Implementation note: since process-environment, where
+ 'egetenv' looks, is encoded in the system codepage, we
+ don't need to encode the cmdproxy file name if we get it
+ from the environment. */
+ strcpy (cmdname, egetenv ("CMDPROXY"));
+ }
else
{
- strcpy (cmdname, SDATA (Vinvocation_directory));
- strcat (cmdname, "cmdproxy.exe");
+ char *q = lispstpcpy (cmdname,
+ /* exec-directory needs to be encoded. */
+ ansi_encode_filename (Vexec_directory));
+ /* If we are run from the source tree, use cmdproxy.exe from
+ the same source tree. */
+ for (p = q - 2; p > cmdname; p = CharPrevA (cmdname, p))
+ if (*p == '/')
+ break;
+ if (*p == '/' && xstrcasecmp (p, "/lib-src/") == 0)
+ q = stpcpy (p, "/nt/");
+ strcpy (q, "cmdproxy.exe");
}
/* Can't use unixtodos_filename here, since that needs its file
if (INTEGERP (Vw32_quote_process_args))
escape_char = XINT (Vw32_quote_process_args);
else
- escape_char = is_cygnus_app ? '"' : '\\';
+ escape_char = (is_cygnus_app || is_msys_app) ? '"' : '\\';
}
- /* Cygwin apps needs quoting a bit more often. */
+ /* Cygwin/MSYS apps need quoting a bit more often. */
if (escape_char == '"')
sepchars = "\r\n\t\f '";
for ( ; *p; p++)
{
if (escape_char == '"' && *p == '\\')
- /* If it's a Cygwin app, \ needs to be escaped. */
+ /* If it's a Cygwin/MSYS app, \ needs to be escaped. */
arglen++;
else if (*p == '"')
{
return pid;
}
-/* Emulate the select call
+/* Emulate the select call.
Wait for available input on any of the given rfds, or timeout if
- a timeout is given and no input is detected
- wfds and efds are not supported and must be NULL.
+ a timeout is given and no input is detected. wfds are supported
+ only for asynchronous 'connect' calls. efds are not supported
+ and must be NULL.
For simplicity, we detect the death of child processes here and
synchronously call the SIGCHLD handler. Since it is possible for
sys_select (int nfds, SELECT_TYPE *rfds, SELECT_TYPE *wfds, SELECT_TYPE *efds,
struct timespec *timeout, void *ignored)
{
- SELECT_TYPE orfds;
+ SELECT_TYPE orfds, owfds;
DWORD timeout_ms, start_time;
int i, nh, nc, nr;
DWORD active;
return 0;
}
- /* Otherwise, we only handle rfds, so fail otherwise. */
- if (rfds == NULL || wfds != NULL || efds != NULL)
+ /* Otherwise, we only handle rfds and wfds, so fail otherwise. */
+ if ((rfds == NULL && wfds == NULL) || efds != NULL)
{
errno = EINVAL;
return -1;
}
- orfds = *rfds;
- FD_ZERO (rfds);
+ if (rfds)
+ {
+ orfds = *rfds;
+ FD_ZERO (rfds);
+ }
+ else
+ FD_ZERO (&orfds);
+ if (wfds)
+ {
+ owfds = *wfds;
+ FD_ZERO (wfds);
+ }
+ else
+ FD_ZERO (&owfds);
nr = 0;
/* If interrupt_handle is available and valid, always wait on it, to
/* Build a list of pipe handles to wait on. */
for (i = 0; i < nfds; i++)
- if (FD_ISSET (i, &orfds))
+ if (FD_ISSET (i, &orfds) || FD_ISSET (i, &owfds))
{
if (i == 0)
{
/* Check for any emacs-generated input in the queue since
it won't be detected in the wait */
- if (detect_input_pending ())
+ if (rfds && detect_input_pending ())
{
FD_SET (i, rfds);
return 1;
{
/* Child process and socket/comm port input. */
cp = fd_info[i].cp;
+ if (FD_ISSET (i, &owfds)
+ && cp
+ && (fd_info[i].flags & FILE_CONNECT) == 0)
+ {
+ DebPrint (("sys_select: fd %d is in wfds, but FILE_CONNECT is reset!\n", i));
+ cp = NULL;
+ }
if (cp)
{
int current_status = cp->status;
{
/* Tell reader thread which file handle to use. */
cp->fd = i;
+ /* Zero out the error code. */
+ cp->errcode = 0;
/* Wake up the reader thread for this process */
cp->status = STATUS_READ_READY;
if (!SetEvent (cp->char_consumed))
if (cp->fd >= 0 && (fd_info[cp->fd].flags & FILE_AT_EOF) == 0)
fd_info[cp->fd].flags |= FILE_SEND_SIGCHLD;
- /* SIG_DFL for SIGCHLD is ignore */
+ /* SIG_DFL for SIGCHLD is ignored */
else if (sig_handlers[SIGCHLD] != SIG_DFL &&
sig_handlers[SIGCHLD] != SIG_IGN)
{
errno = EINTR;
return -1;
}
- else if (fdindex[active] == 0)
+ else if (rfds && fdindex[active] == 0)
{
/* Keyboard input available */
FD_SET (0, rfds);
}
else
{
- /* must be a socket or pipe - read ahead should have
- completed, either succeeding or failing. */
- FD_SET (fdindex[active], rfds);
+ /* Must be a socket or pipe - read ahead should have
+ completed, either succeeding or failing. If this handle
+ was waiting for an async 'connect', reset the connect
+ flag, so it could read from now on. */
+ if (wfds && (fd_info[fdindex[active]].flags & FILE_CONNECT) != 0)
+ {
+ cp = fd_info[fdindex[active]].cp;
+ if (cp)
+ {
+ /* Don't reset the FILE_CONNECT bit and don't
+ acknowledge the read if the status is
+ STATUS_CONNECT_FAILED or some other
+ failure. That's because the thread exits in those
+ cases, so it doesn't need the ACK, and we want to
+ keep the FILE_CONNECT bit as evidence that the
+ connect failed, to be checked in sys_read. */
+ if (cp->status == STATUS_READ_SUCCEEDED)
+ {
+ fd_info[cp->fd].flags &= ~FILE_CONNECT;
+ cp->status = STATUS_READ_ACKNOWLEDGED;
+ }
+ ResetEvent (cp->char_avail);
+ }
+ FD_SET (fdindex[active], wfds);
+ }
+ else if (rfds)
+ FD_SET (fdindex[active], rfds);
nr++;
}
filename = Fexpand_file_name (filename, Qnil);
/* luckily, this returns the short version of each element in the path. */
- if (w32_get_short_filename (SDATA (ENCODE_FILE (filename)),
+ if (w32_get_short_filename (SSDATA (ENCODE_FILE (filename)),
shortname, MAX_PATH) == 0)
return Qnil;
/* first expand it. */
filename = Fexpand_file_name (filename, Qnil);
- if (!w32_get_long_filename (SDATA (ENCODE_FILE (filename)), longname,
+ if (!w32_get_long_filename (SSDATA (ENCODE_FILE (filename)), longname,
MAX_UTF8_PATH))
return Qnil;
return result;
}
+DEFUN ("w32-application-type", Fw32_application_type,
+ Sw32_application_type, 1, 1, 0,
+ doc: /* Return the type of an MS-Windows PROGRAM.
+
+Knowing the type of an executable could be useful for formatting
+file names passed to it or for quoting its command-line arguments.
+
+PROGRAM should specify an executable file, including the extension.
+
+The value is one of the following:
+
+`dos' -- a DOS .com program or some other non-PE executable
+`cygwin' -- a Cygwin program that depends on Cygwin DLL
+`msys' -- an MSYS 1.x or MSYS2 program
+`w32-native' -- a native Windows application
+`unknown' -- a file that doesn't exist, or cannot be open, or whose
+ name is not encodable in the current ANSI codepage.
+
+Note that for .bat and .cmd batch files the function returns the type
+of their command interpreter, as specified by the \"COMSPEC\"
+environment variable.
+
+This function returns `unknown' for programs whose file names
+include characters not supported by the current ANSI codepage, as
+such programs cannot be invoked by Emacs anyway. */)
+ (Lisp_Object program)
+{
+ int is_dos_app, is_cygwin_app, is_msys_app, dummy;
+ Lisp_Object encoded_progname;
+ char *progname, progname_a[MAX_PATH];
+
+ program = Fexpand_file_name (program, Qnil);
+ encoded_progname = ENCODE_FILE (program);
+ progname = SSDATA (encoded_progname);
+ unixtodos_filename (progname);
+ filename_to_ansi (progname, progname_a);
+ /* Reject file names that cannot be encoded in the current ANSI
+ codepage. */
+ if (_mbspbrk ((unsigned char *)progname_a, (const unsigned char *)"?"))
+ return Qunknown;
+
+ if (w32_executable_type (progname_a, &is_dos_app, &is_cygwin_app,
+ &is_msys_app, &dummy) != 0)
+ return Qunknown;
+ if (is_dos_app)
+ return Qdos;
+ if (is_cygwin_app)
+ return Qcygwin;
+ if (is_msys_app)
+ return Qmsys;
+ return Qw32_native;
+}
+
#ifdef HAVE_LANGINFO_CODESET
/* Emulation of nl_langinfo. Used in fns.c:Flocale_info. */
char *
function isn't given a context pointer. */
Lisp_Object Vw32_valid_locale_ids;
-static BOOL CALLBACK
+static BOOL CALLBACK ALIGN_STACK
enum_locale_fn (LPTSTR localeNum)
{
DWORD id = int_from_hex (localeNum);
function isn't given a context pointer. */
Lisp_Object Vw32_valid_codepages;
-static BOOL CALLBACK
+static BOOL CALLBACK ALIGN_STACK
enum_codepage_fn (LPTSTR codepageNum)
{
DWORD id = atoi (codepageNum);
DEFUN ("w32-get-codepage-charset", Fw32_get_codepage_charset,
Sw32_get_codepage_charset, 1, 1, 0,
doc: /* Return charset ID corresponding to codepage CP.
-Returns nil if the codepage is not valid. */)
+Returns nil if the codepage is not valid or its charset ID could
+not be determined.
+
+Note that this function is only guaranteed to work with ANSI
+codepages; most console codepages are not supported and will
+yield nil. */)
(Lisp_Object cp)
{
CHARSETINFO info;
+ DWORD_PTR dwcp;
CHECK_NUMBER (cp);
if (!IsValidCodePage (XINT (cp)))
return Qnil;
- if (TranslateCharsetInfo ((DWORD *) XINT (cp), &info, TCI_SRCCODEPAGE))
+ /* Going through a temporary DWORD_PTR variable avoids compiler warning
+ about cast to pointer from integer of different size, when
+ building --with-wide-int or building for 64bit. */
+ dwcp = XINT (cp);
+ if (TranslateCharsetInfo ((DWORD *) dwcp, &info, TCI_SRCCODEPAGE))
return make_number (info.ciCharset);
return Qnil;
CHECK_NUMBER_CAR (layout);
CHECK_NUMBER_CDR (layout);
- kl = (HKL) ((XINT (XCAR (layout)) & 0xffff)
- | (XINT (XCDR (layout)) << 16));
+ kl = (HKL) (UINT_PTR) ((XINT (XCAR (layout)) & 0xffff)
+ | (XINT (XCDR (layout)) << 16));
/* Synchronize layout with input thread. */
if (dwWindowsThreadId)
return Fw32_get_keyboard_layout ();
}
+/* Two variables to interface between get_lcid and the EnumLocales
+ callback function below. */
+#ifndef LOCALE_NAME_MAX_LENGTH
+# define LOCALE_NAME_MAX_LENGTH 85
+#endif
+static LCID found_lcid;
+static char lname[3 * LOCALE_NAME_MAX_LENGTH + 1 + 1];
+
+/* Callback function for EnumLocales. */
+static BOOL CALLBACK
+get_lcid_callback (LPTSTR locale_num_str)
+{
+ char *endp;
+ char locval[2 * LOCALE_NAME_MAX_LENGTH + 1 + 1];
+ LCID try_lcid = strtoul (locale_num_str, &endp, 16);
+
+ if (GetLocaleInfo (try_lcid, LOCALE_SABBREVLANGNAME,
+ locval, LOCALE_NAME_MAX_LENGTH))
+ {
+ size_t locval_len;
+
+ /* This is for when they only specify the language, as in "ENU". */
+ if (stricmp (locval, lname) == 0)
+ {
+ found_lcid = try_lcid;
+ return FALSE;
+ }
+ locval_len = strlen (locval);
+ strcpy (locval + locval_len, "_");
+ if (GetLocaleInfo (try_lcid, LOCALE_SABBREVCTRYNAME,
+ locval + locval_len + 1, LOCALE_NAME_MAX_LENGTH))
+ {
+ locval_len = strlen (locval);
+ if (strnicmp (locval, lname, locval_len) == 0
+ && (lname[locval_len] == '.'
+ || lname[locval_len] == '\0'))
+ {
+ found_lcid = try_lcid;
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+/* Return the Locale ID (LCID) number given the locale's name, a
+ string, in LOCALE_NAME. This works by enumerating all the locales
+ supported by the system, until we find one whose name matches
+ LOCALE_NAME. */
+static LCID
+get_lcid (const char *locale_name)
+{
+ /* A simple cache. */
+ static LCID last_lcid;
+ static char last_locale[1000];
+
+ /* The code below is not thread-safe, as it uses static variables.
+ But this function is called only from the Lisp thread. */
+ if (last_lcid > 0 && strcmp (locale_name, last_locale) == 0)
+ return last_lcid;
+
+ strncpy (lname, locale_name, sizeof (lname) - 1);
+ lname[sizeof (lname) - 1] = '\0';
+ found_lcid = 0;
+ EnumSystemLocales (get_lcid_callback, LCID_SUPPORTED);
+ if (found_lcid > 0)
+ {
+ last_lcid = found_lcid;
+ strcpy (last_locale, locale_name);
+ }
+ return found_lcid;
+}
+
+#ifndef _NLSCMPERROR
+# define _NLSCMPERROR INT_MAX
+#endif
+#ifndef LINGUISTIC_IGNORECASE
+# define LINGUISTIC_IGNORECASE 0x00000010
+#endif
+
+typedef int (WINAPI *CompareStringW_Proc)
+ (LCID, DWORD, LPCWSTR, int, LPCWSTR, int);
+
+int
+w32_compare_strings (const char *s1, const char *s2, char *locname,
+ int ignore_case)
+{
+ LCID lcid = GetThreadLocale ();
+ wchar_t *string1_w, *string2_w;
+ int val, needed;
+ extern BOOL g_b_init_compare_string_w;
+ static CompareStringW_Proc pCompareStringW;
+ DWORD flags = 0;
+
+ USE_SAFE_ALLOCA;
+
+ /* The LCID machinery doesn't seem to support the "C" locale, so we
+ need to do that by hand. */
+ if (locname
+ && ((locname[0] == 'C' && (locname[1] == '\0' || locname[1] == '.'))
+ || strcmp (locname, "POSIX") == 0))
+ return (ignore_case ? stricmp (s1, s2) : strcmp (s1, s2));
+
+ if (!g_b_init_compare_string_w)
+ {
+ if (os_subtype == OS_9X)
+ {
+ pCompareStringW =
+ (CompareStringW_Proc) GetProcAddress (LoadLibrary ("Unicows.dll"),
+ "CompareStringW");
+ if (!pCompareStringW)
+ {
+ errno = EINVAL;
+ /* This return value is compatible with wcscoll and
+ other MS CRT functions. */
+ return _NLSCMPERROR;
+ }
+ }
+ else
+ pCompareStringW = CompareStringW;
+
+ g_b_init_compare_string_w = 1;
+ }
+
+ needed = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, s1, -1, NULL, 0);
+ if (needed > 0)
+ {
+ SAFE_NALLOCA (string1_w, 1, needed + 1);
+ pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, s1, -1,
+ string1_w, needed);
+ }
+ else
+ {
+ errno = EINVAL;
+ return _NLSCMPERROR;
+ }
+
+ needed = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, s2, -1, NULL, 0);
+ if (needed > 0)
+ {
+ SAFE_NALLOCA (string2_w, 1, needed + 1);
+ pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, s2, -1,
+ string2_w, needed);
+ }
+ else
+ {
+ SAFE_FREE ();
+ errno = EINVAL;
+ return _NLSCMPERROR;
+ }
+
+ if (locname)
+ {
+ /* Convert locale name string to LCID. We don't want to use
+ LocaleNameToLCID because (a) it is only available since
+ Vista, and (b) it doesn't accept locale names returned by
+ 'setlocale' and 'GetLocaleInfo'. */
+ LCID new_lcid = get_lcid (locname);
+
+ if (new_lcid > 0)
+ lcid = new_lcid;
+ else
+ error ("Invalid locale %s: Invalid argument", locname);
+ }
+
+ if (ignore_case)
+ {
+ /* NORM_IGNORECASE ignores any tertiary distinction, not just
+ case variants. LINGUISTIC_IGNORECASE is more selective, and
+ is sensitive to the locale's language, but it is not
+ available before Vista. */
+ if (w32_major_version >= 6)
+ flags |= LINGUISTIC_IGNORECASE;
+ else
+ flags |= NORM_IGNORECASE;
+ }
+ /* This approximates what glibc collation functions do when the
+ locale's codeset is UTF-8. */
+ if (!NILP (Vw32_collate_ignore_punctuation))
+ flags |= NORM_IGNORESYMBOLS;
+ val = pCompareStringW (lcid, flags, string1_w, -1, string2_w, -1);
+ SAFE_FREE ();
+ if (!val)
+ {
+ errno = EINVAL;
+ return _NLSCMPERROR;
+ }
+ return val - 2;
+}
+
\f
void
syms_of_ntproc (void)
{
DEFSYM (Qhigh, "high");
DEFSYM (Qlow, "low");
+ DEFSYM (Qcygwin, "cygwin");
+ DEFSYM (Qmsys, "msys");
+ DEFSYM (Qw32_native, "w32-native");
defsubr (&Sw32_has_winsock);
defsubr (&Sw32_unload_winsock);
defsubr (&Sw32_short_file_name);
defsubr (&Sw32_long_file_name);
defsubr (&Sw32_set_process_priority);
+ defsubr (&Sw32_application_type);
defsubr (&Sw32_get_locale_info);
defsubr (&Sw32_get_current_locale_id);
defsubr (&Sw32_get_default_locale_id);
process temporarily). A value of zero disables waiting entirely. */);
w32_pipe_read_delay = 50;
+ DEFVAR_INT ("w32-pipe-buffer-size", w32_pipe_buffer_size,
+ doc: /* Size of buffer for pipes created to communicate with subprocesses.
+The size is in bytes, and must be non-negative. The default is zero,
+which lets the OS use its default size, usually 4KB (4096 bytes).
+Any negative value means to use the default value of zero. */);
+ w32_pipe_buffer_size = 0;
+
DEFVAR_LISP ("w32-downcase-file-names", Vw32_downcase_file_names,
doc: /* Non-nil means convert all-upper case file names to lower case.
This applies when performing completions and file name expansion.
where the performance impact may be noticeable even on modern hardware. */);
Vw32_get_true_file_attributes = Qlocal;
+ DEFVAR_LISP ("w32-collate-ignore-punctuation",
+ Vw32_collate_ignore_punctuation,
+ doc: /* Non-nil causes string collation functions ignore punctuation on MS-Windows.
+On Posix platforms, `string-collate-lessp' and `string-collate-equalp'
+ignore punctuation characters when they compare strings, if the
+locale's codeset is UTF-8, as in \"en_US.UTF-8\". Binding this option
+to a non-nil value will achieve a similar effect on MS-Windows, where
+locales with UTF-8 codeset are not supported.
+
+Note that setting this to non-nil will also ignore blanks and symbols
+in the strings. So do NOT use this option when comparing file names
+for equality, only when you need to sort them. */);
+ Vw32_collate_ignore_punctuation = Qnil;
+
staticpro (&Vw32_valid_locale_ids);
staticpro (&Vw32_valid_codepages);
}