diff options
author | Werner Koch <wk@gnupg.org> | 2022-03-04 13:41:19 +0100 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2022-03-04 13:58:09 +0100 |
commit | 32dad4e4135f0bdc436ed684da753d4b0cdb0ea1 (patch) | |
tree | 4ca66f52c77467d7ff932b9440c06e1352032977 | |
parent | 666d64d2bc6539d0ca03dea69abc5a8ccdf37be5 (diff) | |
download | libgpg-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-- | NEWS | 15 | ||||
-rw-r--r-- | src/estream.c | 177 | ||||
-rw-r--r-- | src/gpg-error.def.in | 5 | ||||
-rw-r--r-- | src/gpgrt-int.h | 2 | ||||
-rw-r--r-- | src/protos.h | 3 | ||||
-rw-r--r-- | src/sysutils.c | 149 | ||||
-rw-r--r-- | src/visibility.c | 25 | ||||
-rw-r--r-- | src/visibility.h | 4 | ||||
-rw-r--r-- | src/w32-add.h | 16 | ||||
-rw-r--r-- | src/w32-gettext.c | 4 |
10 files changed, 342 insertions, 58 deletions
@@ -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 * |