summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2022-03-04 13:41:19 +0100
committerWerner Koch <wk@gnupg.org>2022-03-04 13:58:09 +0100
commit32dad4e4135f0bdc436ed684da753d4b0cdb0ea1 (patch)
tree4ca66f52c77467d7ff932b9440c06e1352032977
parent666d64d2bc6539d0ca03dea69abc5a8ccdf37be5 (diff)
downloadlibgpg-error-32dad4e4135f0bdc436ed684da753d4b0cdb0ea1.tar.gz
w32: Support file names longer than MAX_PATH.
* src/sysutils.c (any8bitchar): Remove. (_gpgrt_fname_to_wchar): New. (_gpgrt_mkdir): Use instead of plain utf8 conversion. (_gpgrt_access): Reimplement using GetFileAttributesW so that long files names are supported. * src/estream.c (_gpgrt_w32_get_last_err_code): New. (func_file_create_w32): New. (_gpgrt_fopen): Handle the "sysopen" mode flag. Support mapping of /dev/null. (_gpgrt_freopen): Support mapping of /dev/null. * src/w32-gettext.c (load_domain): Allow long file names. * src/visibility.c (gpgrt_free_wchar): Make function public. (gpgrt_fname_to_wchar): Ditto. (gpgrt_utf8_to_wchar): Ditto. (gpgrt_wchar_to_utf8): Ditto. * src/w32-add.h (gpgrt_free_wchar): New prototype. (gpgrt_fname_to_wchar): Ditto. (gpgrt_utf8_to_wchar): Ditto. (gpgrt_wchar_to_utf8): Ditto. * src/gpg-error.def.in: Add them here too.
-rw-r--r--NEWS15
-rw-r--r--src/estream.c177
-rw-r--r--src/gpg-error.def.in5
-rw-r--r--src/gpgrt-int.h2
-rw-r--r--src/protos.h3
-rw-r--r--src/sysutils.c149
-rw-r--r--src/visibility.c25
-rw-r--r--src/visibility.h4
-rw-r--r--src/w32-add.h16
-rw-r--r--src/w32-gettext.c4
10 files changed, 342 insertions, 58 deletions
diff --git a/NEWS b/NEWS
index 6a359b6..5aa5a9e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,21 @@
Noteworthy changes in version 1.45 (unreleased) [C32/A32/R_]
-----------------------------------------------
+ * Support the "sysopen" mode parameter for gpgrt_fopen so that file
+ names longer than MAX_PATH are supported under Windows.
+
+ * gpgrt_access and gpgrt_mkdir now support file anmes longer than
+ MAX_PATH.
+
+ * gpgrt_fopen now maps "/dev/null" to "nul" on Windows.
+
+ * Interface changes relative to the 1.42 release:
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ gpgrt_free_wchar NEW.
+ gpgrt_fname_to_wchar NEW.
+ gpgrt_utf8_to_wchar NEW.
+ gpgrt_wchar_to_utf8 NEW.
+
Release-info: https://dev.gnupg.org/T5802
diff --git a/src/estream.c b/src/estream.c
index 36be4bb..0542f59 100644
--- a/src/estream.c
+++ b/src/estream.c
@@ -362,6 +362,15 @@ _gpgrt_w32_set_errno (int ec)
}
+gpg_err_code_t
+_gpgrt_w32_get_last_err_code (void)
+{
+ int ec = GetLastError ();
+ errno = map_w32_to_errno (ec);
+ return _gpg_err_code_from_errno (errno);
+}
+
+
#endif /*HAVE_W32_SYSTEM*/
/*
@@ -1742,6 +1751,100 @@ func_file_create (void **cookie, int *filedes,
}
+/* Create function for objects identified by a file name. Windows
+ * version to use CreateFile. */
+#ifdef HAVE_W32_SYSTEM
+static int
+func_file_create_w32 (void **cookie, HANDLE *rethd, const char *path,
+ unsigned int modeflags, unsigned int cmode)
+{
+ estream_cookie_w32_t hd_cookie;
+ wchar_t *wpath = NULL;
+ int err = 0;
+ HANDLE hd;
+ DWORD desired_access;
+ DWORD share_mode;
+ DWORD creation_distribution;
+
+ (void)cmode;
+
+ hd_cookie = mem_alloc (sizeof *hd_cookie);
+ if (!hd_cookie)
+ {
+ err = -1;
+ goto leave;
+ }
+
+ wpath = _gpgrt_fname_to_wchar (path);
+ if (!wpath)
+ {
+ err = -1;
+ goto leave;
+ }
+
+ if ((modeflags & O_WRONLY))
+ {
+ desired_access = GENERIC_WRITE;
+ share_mode = FILE_SHARE_WRITE;
+ }
+ else if ((modeflags & O_RDWR))
+ {
+ desired_access = GENERIC_READ | GENERIC_WRITE;
+ share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ }
+ else
+ {
+ desired_access = GENERIC_READ;
+ share_mode = FILE_SHARE_READ;
+ }
+
+
+ creation_distribution = 0;
+ if ((modeflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ creation_distribution |= CREATE_NEW;
+ else if ((modeflags & O_TRUNC) == O_TRUNC)
+ {
+ if ((modeflags & O_CREAT) == O_CREAT)
+ creation_distribution |= CREATE_ALWAYS;
+ else if ((modeflags & O_RDONLY) != O_RDONLY)
+ creation_distribution |= TRUNCATE_EXISTING;
+ }
+ else if ((modeflags & O_APPEND) == O_APPEND)
+ creation_distribution |= OPEN_EXISTING;
+ else if ((modeflags & O_CREAT) == O_CREAT)
+ creation_distribution |= OPEN_ALWAYS;
+ else
+ creation_distribution |= OPEN_EXISTING;
+
+ hd = CreateFileW (wpath,
+ desired_access,
+ share_mode,
+ NULL, /* security attributes */
+ creation_distribution,
+ 0, /* flags and attributes */
+ NULL); /* template file */
+ if (hd == INVALID_HANDLE_VALUE)
+ {
+ _set_errno (map_w32_to_errno (GetLastError ()));
+ err = -1;
+ goto leave;
+ }
+
+ hd_cookie->hd = hd;
+ hd_cookie->no_close = 0;
+ hd_cookie->no_syscall_clamp = 0;
+ *cookie = hd_cookie;
+ *rethd = hd;
+
+ leave:
+ _gpgrt_free_wchar (wpath);
+ if (err)
+ mem_free (hd_cookie);
+ return err;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
/* Flags used by parse_mode and friends. */
#define X_SAMETHREAD (1 << 0)
@@ -1780,10 +1883,11 @@ func_file_create (void **cookie, int *filedes,
*
* sysopen
*
- * The object is opened in sysmode. On POSIX this is a NOP but
- * under Windows the direct W32 API functions (HANDLE) are used
- * instead of their libc counterparts (fd).
- * FIXME: The functionality is not yet implemented.
+ * The object is opened in GPGRT_SYSHD_HANDLE mode. On POSIX this
+ * is a NOP but under Windows the direct W32 API functions (HANDLE)
+ * are used instead of their libc counterparts (fd). This flag
+ * also allows to use file names longer than MAXPATH. Note that
+ * gpgrt_fileno does not not work for such a stream under Windows.
*
* pollable
*
@@ -2181,7 +2285,7 @@ deinit_stream_obj (estream_t stream)
/*
* Create a new stream and initialize it. On success the new stream
- * handle is tsored at R_STREAM. On failure NULL is stored at
+ * handle is stored at R_STREAM. On failure NULL is stored at
* R_STREAM.
*/
static int
@@ -3210,40 +3314,57 @@ _gpgrt_fopen (const char *_GPGRT__RESTRICT path,
const char *_GPGRT__RESTRICT mode)
{
unsigned int modeflags, cmode, xmode;
- int create_called;
- estream_t stream;
- void *cookie;
+ int create_called = 0;
+ estream_t stream = NULL;
+ void *cookie = NULL;
int err;
- int fd;
+ struct cookie_io_functions_s *functions;
es_syshd_t syshd;
-
- stream = NULL;
- cookie = NULL;
- create_called = 0;
+ int kind;
err = parse_mode (mode, &modeflags, &xmode, &cmode);
if (err)
- goto out;
+ goto leave;
- err = func_file_create (&cookie, &fd, path, modeflags, cmode);
+ /* Convenience hack so that we can use /dev/null on Windows. */
+#ifdef HAVE_W32_SYSTEM
+ if (path && !strcmp (path, "/dev/null"))
+ path = "nul";
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+ if ((xmode & X_SYSOPEN))
+ {
+ kind = BACKEND_W32;
+ functions = &estream_functions_w32;
+ syshd.type = ES_SYSHD_HANDLE;
+ err = func_file_create_w32 (&cookie, &syshd.u.handle,
+ path, modeflags, cmode);
+ }
+ else
+#endif /* W32 */
+ {
+ kind = BACKEND_FD;
+ functions = &estream_functions_fd;
+ syshd.type = ES_SYSHD_FD;
+ err = func_file_create (&cookie, &syshd.u.fd,
+ path, modeflags, cmode);
+ }
if (err)
- goto out;
+ goto leave;
- syshd.type = ES_SYSHD_FD;
- syshd.u.fd = fd;
create_called = 1;
- err = create_stream (&stream, cookie, &syshd, BACKEND_FD,
- estream_functions_fd, modeflags, xmode, 0);
+ err = create_stream (&stream, cookie, &syshd, kind,
+ *functions, modeflags, xmode, 0);
if (err)
- goto out;
+ goto leave;
if (stream && path)
fname_set_internal (stream, path, 1);
- out:
-
+ leave:
if (err && create_called)
- (*estream_functions_fd.public.func_close) (cookie);
+ functions->public.func_close (cookie);
return stream;
}
@@ -3542,7 +3663,7 @@ do_w32open (HANDLE hd, const char *mode,
/* If we are pollable we create the function cookie with syscall
* clamp disabled. This is because functions are called from
- * separatre reader and writer threads in w32-stream. */
+ * separate reader and writer threads in w32-stream. */
err = func_w32_create (&cookie, hd, modeflags,
no_close, !!(xmode & X_POLLABLE));
if (err)
@@ -3715,6 +3836,12 @@ _gpgrt_freopen (const char *_GPGRT__RESTRICT path,
cookie = NULL;
create_called = 0;
+ /* Convenience hack so that we can use /dev/null on Windows. */
+#ifdef HAVE_W32_SYSTEM
+ if (!strcmp (path, "/dev/null"))
+ path = "nul";
+#endif
+
xmode = stream->intern->samethread ? X_SAMETHREAD : 0;
lock_stream (stream);
diff --git a/src/gpg-error.def.in b/src/gpg-error.def.in
index 769be1b..c75b317 100644
--- a/src/gpg-error.def.in
+++ b/src/gpg-error.def.in
@@ -242,4 +242,9 @@ EXPORTS
gpgrt_access @183
+ gpgrt_free_wchar @184
+ gpgrt_fname_to_wchar @185
+ gpgrt_utf8_to_wchar @186
+ gpgrt_wchar_to_utf8 @187
+
;; end of file with public symbols for Windows.
diff --git a/src/gpgrt-int.h b/src/gpgrt-int.h
index 8ca52c6..b53ebe9 100644
--- a/src/gpgrt-int.h
+++ b/src/gpgrt-int.h
@@ -822,6 +822,8 @@ char *_gpgrt_w32_reg_query_string (const char *root,
const char *name);
char *_gpgrt_w32_reg_get_string (const char *key);
+wchar_t *_gpgrt_fname_to_wchar (const char *fname);
+
#endif /*HAVE_W32_SYSTEM*/
diff --git a/src/protos.h b/src/protos.h
index 7cc3489..407caf6 100644
--- a/src/protos.h
+++ b/src/protos.h
@@ -25,7 +25,10 @@
wchar_t *_gpgrt_utf8_to_wchar (const char *string);
void _gpgrt_free_wchar (wchar_t *wstring);
char *_gpgrt_wchar_to_utf8 (const wchar_t *string, size_t length);
+
+/*-- estream.c --*/
void _gpgrt_w32_set_errno (int ec);
+gpg_err_code_t _gpgrt_w32_get_last_err_code (void);
#endif /*_GPGRT_PROTOS_H*/
diff --git a/src/sysutils.c b/src/sysutils.c
index 8f70a66..bc3a68d 100644
--- a/src/sysutils.c
+++ b/src/sysutils.c
@@ -39,20 +39,6 @@
#include "gpgrt-int.h"
-#ifdef HAVE_W32_SYSTEM
-/* Return true if STRING has any 8 bit character. */
-static int
-any8bitchar (const char *string)
-{
- if (string)
- for ( ; *string; string++)
- if ((*string & 0x80))
- return 1;
- return 0;
-}
-#endif /*HAVE_W32_SYSTEM*/
-
-
/* Return true if FD is valid. */
int
_gpgrt_fd_valid_p (int fd)
@@ -238,6 +224,94 @@ _gpgrt_setenv (const char *name, const char *value, int overwrite)
}
+#ifdef HAVE_W32_SYSTEM
+/* Convert an UTF-8 encode file name to wchar. If the file name is
+ * close to the limit of MAXPATH the API functions will fail. The
+ * method to overcome this API limitation is to use a prefix which
+ * bypasses the checking by CreateFile. This also required to first
+ * convert the name to an absolute file name. */
+wchar_t *
+_gpgrt_fname_to_wchar (const char *fname)
+{
+ wchar_t *wname;
+ wchar_t *wfullpath = NULL;
+ int success = 0;
+
+ wname = _gpgrt_utf8_to_wchar (fname);
+ if (!wname)
+ return NULL;
+
+ if (!strncmp (fname, "\\\\?\\", 4))
+ success = 1; /* Already translated. */
+ else if (wcslen (wname) > 230)
+ {
+ int wlen = 1024;
+ int extralen;
+ DWORD res;
+ wchar_t *w;
+
+ try_again:
+ wfullpath = xtrymalloc (wlen * sizeof *wfullpath);
+ if (!wfullpath)
+ goto leave;
+
+ if (*fname == '\\' && fname[1] == '\\' && fname[2])
+ {
+ wcscpy (wfullpath, L"\\\\?\\UNC\\");
+ extralen = 8;
+ }
+ else
+ {
+ wcscpy (wfullpath, L"\\\\?\\");
+ extralen = 4;
+ }
+ res = GetFullPathNameW (wname, wlen-extralen, wfullpath+extralen, NULL);
+ if (!res)
+ {
+ _gpgrt_w32_set_errno (-1);
+ goto leave;
+ }
+ else if (res >= wlen - extralen)
+ {
+ /* Truncated - increase to the desired length. */
+ if (wlen > 1024)
+ {
+ /* We should never get to here. */
+ errno = ENAMETOOLONG;
+ goto leave;
+ }
+ /* GetFullPathNameW indicated the required buffer length. */
+ _gpgrt_free_wchar (wfullpath);
+ wfullpath = NULL;
+ wlen = res + extralen;
+ goto try_again;
+ }
+ _gpgrt_free_wchar (wname);
+ wname = wfullpath;
+ wfullpath = NULL;
+ /* Need to make sure that all slashes are mapped. */
+ for (w = wname; *w; w++)
+ if (*w == L'/')
+ *w = L'\\';
+ success = 1;
+ }
+ else
+ success = 1;
+
+ leave:
+ _gpgrt_free_wchar (wfullpath);
+ if (!success)
+ {
+ _gpgrt_free_wchar (wname);
+ wname = NULL;
+ }
+ return wname;
+}
+
+#endif /*HAVE_W32_SYSTEM*/
+
+
+
#ifndef HAVE_W32_SYSTEM
static mode_t
modestr_to_mode (const char *modestr)
@@ -296,9 +370,10 @@ _gpgrt_mkdir (const char *name, const char *modestr)
(void)modestr;
/* Note: Fixme: We should set appropriate permissions. */
- wname = _gpgrt_utf8_to_wchar (name);
+ wname = _gpgrt_fname_to_wchar (name);
if (!wname)
return _gpg_err_code_from_syserror ();
+
if (!CreateDirectoryW (wname, NULL))
{
_gpgrt_w32_set_errno (-1);
@@ -306,8 +381,10 @@ _gpgrt_mkdir (const char *name, const char *modestr)
}
else
ec = 0;
+
_gpgrt_free_wchar (wname);
return ec;
+
#elif MKDIR_TAKES_ONE_ARG
(void)modestr;
if (mkdir (name))
@@ -322,9 +399,8 @@ _gpgrt_mkdir (const char *name, const char *modestr)
/* A simple wrapper around chdir. NAME is expected to be utf8
- * encoded.
- * Note that in addition to returning an gpg-error error code ERRNO is
- * also set by this function. */
+ * encoded. Note that in addition to returning an gpg-error error
+ * code ERRNO is also set by this function. */
gpg_err_code_t
_gpgrt_chdir (const char *name)
{
@@ -332,6 +408,8 @@ _gpgrt_chdir (const char *name)
wchar_t *wname;
gpg_err_code_t ec;
+ /* Note that the \\?\ trick does not work with SetCurrentDirectoryW
+ * Thus we use the plain conversion function. */
wname = _gpgrt_utf8_to_wchar (name);
if (!wname)
return _gpg_err_code_from_syserror ();
@@ -376,6 +454,8 @@ _gpgrt_getcwd (void)
}
else if (wlen > MAX_PATH)
{
+ /* FWIW: I tried to use GetFullPathNameW (L".") but found no way
+ * to execute a test program at a too long cwd. */
_gpg_err_set_errno (ENAMETOOLONG);
return NULL;
}
@@ -417,28 +497,35 @@ _gpgrt_access (const char *fname, int mode)
gpg_err_code_t ec;
#ifdef HAVE_W32_SYSTEM
- if (any8bitchar (fname))
- {
- wchar_t *wfname;
+ wchar_t *wfname;
+ DWORD attribs;
- wfname = _gpgrt_utf8_to_wchar (fname);
- if (!wfname)
- ec = _gpg_err_code_from_syserror ();
- else
+ wfname = _gpgrt_fname_to_wchar (fname);
+ if (!wfname)
+ return _gpg_err_code_from_syserror ();
+
+ attribs = GetFileAttributesW (wfname);
+ if (attribs == (DWORD)(-1))
+ ec = _gpgrt_w32_get_last_err_code ();
+ else
+ {
+ if ((mode & W_OK) && (attribs & FILE_ATTRIBUTE_READONLY))
{
- ec = _waccess (wfname, mode)? _gpg_err_code_from_syserror () : 0;
- _gpgrt_free_wchar (wfname);
+ _gpg_err_set_errno (EACCES);
+ ec = _gpg_err_code_from_syserror ();
}
+ else
+ ec = 0;
}
- else
-#endif /*HAVE_W32_SYSTEM*/
- ec = access (fname, mode)? _gpg_err_code_from_syserror () : 0;
+ _gpgrt_free_wchar (wfname);
+#else /* Unix */
+ ec = access (fname, mode)? _gpg_err_code_from_syserror () : 0;
+#endif /* Unix */
return ec;
}
-
/* Get the standard home directory for user NAME. If NAME is NULL the
* directory for the current user is returned. Caller must release
* the returned string. */
diff --git a/src/visibility.c b/src/visibility.c
index 03a6c45..fdf4805 100644
--- a/src/visibility.c
+++ b/src/visibility.c
@@ -1241,6 +1241,31 @@ gpgrt_absfnameconcat (const char *first, ... )
* specific function despite that they are technically not needed. */
#ifdef HAVE_W32_SYSTEM
+void
+gpgrt_free_wchar (wchar_t *wstring)
+{
+ if (wstring)
+ _gpgrt_free_wchar (wstring);
+}
+
+wchar_t *
+gpgrt_fname_to_wchar (const char *fname)
+{
+ return _gpgrt_fname_to_wchar (fname);
+}
+
+wchar_t *
+gpgrt_utf8_to_wchar (const char *string)
+{
+ return _gpgrt_utf8_to_wchar (string);
+}
+
+char *
+gpgrt_wchar_to_utf8 (const wchar_t *string)
+{
+ return _gpgrt_wchar_to_utf8 (string, (size_t)(-1));
+}
+
char *
gpgrt_w32_reg_query_string (const char *root, const char *dir, const char *name)
{
diff --git a/src/visibility.h b/src/visibility.h
index f9218b5..3a71ad8 100644
--- a/src/visibility.h
+++ b/src/visibility.h
@@ -411,6 +411,10 @@ MARK_VISIBLE (gpgrt_absfnameconcat);
#define gpgrt_absfnameconcat _gpgrt_USE_UNDERSCORED_FUNCTION
/* Windows specific functions. */
+#define gpgrt_free_wchar _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_utf8_to_wchar _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_wchar_to_utf8 _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_fname_to_wchar _gpgrt_USE_UNDERSCORED_FUNCTION
#define gpgrt_w32_reg_query_string _gpgrt_USE_UNDERSCORED_FUNCTION
diff --git a/src/w32-add.h b/src/w32-add.h
index 3428961..74696bd 100644
--- a/src/w32-add.h
+++ b/src/w32-add.h
@@ -61,6 +61,22 @@ size_t gpgrt_w32_iconv (gpgrt_w32_iconv_t cd,
# define iconv(a,b,c,d,e) gpgrt_w32_iconv ((a),(b),(c),(d),(e))
#endif /*GPGRT_ENABLE_W32_ICONV_MACROS*/
+/* Release a wchar_t * buffer. */
+void gpgrt_free_wchar (wchar_t *wstring);
+
+/* Convert an UTF-8 encoded file name to wchar.
+ * Prepend a '\\?\' prefix if needed. */
+wchar_t *gpgrt_fname_to_wchar (const char *fname);
+
+/* Convert an UTF8 string to a WCHAR string. Caller should use
+ * gpgrt_free_wchar to release the result.
+ * Returns NULL on error and sets ERRNO. */
+wchar_t *gpgrt_utf8_to_wchar (const char *string);
+
+/* Convert a WCHAR string to UTF-8. Caller should use gpgrt_free to
+ * release the result. Returns NULL on error and sets ERRNO. */
+char *gpgrt_wchar_to_utf8 (const wchar_t *wstring);
+
/* Query a string in the registry. */
char *gpgrt_w32_reg_query_string (const char *root,
const char *dir,
diff --git a/src/w32-gettext.c b/src/w32-gettext.c
index 7c4c9b0..817c1ca 100644
--- a/src/w32-gettext.c
+++ b/src/w32-gettext.c
@@ -1211,7 +1211,7 @@ load_domain (const char *filename)
char *read_ptr;
{
- wchar_t *wfilename = _gpgrt_utf8_to_wchar (filename);
+ wchar_t *wfilename = _gpgrt_fname_to_wchar (filename);
if (!wfilename)
fh = INVALID_HANDLE_VALUE;
@@ -1322,7 +1322,7 @@ load_domain (const char *filename)
/* Return a malloced wide char string from an UTF-8 encoded input
string STRING. Caller must free this value. On failure returns
NULL. The result of calling this function with STRING set to NULL
- is not defined. If LENGTH is zero and RETLEN NULL the fucntion
+ is not defined. If LENGTH is zero and RETLEN NULL the function
assumes that STRING is a nul-terminated string and returns a
(wchar_t)0-terminated string. */
static wchar_t *