For GNU gettext, I need the equivalent of module 'getcwd-lgpl' with a
'wchar_t *' result. Since both the implementation and the unit test are
similar to 'getcwd-lgpl', it makes sense to keep them together, in gnulib,
instead of having 'getcwd-lgpl' in gnulib and 'wgetcwd-lgpl' in gettext.

Done through these patches:


2023-09-30  Bruno Haible  <br...@clisp.org>

        wgetcwd-lgpl: Add tests.
        * tests/test-wgetcwd-lgpl.c: New file, based on
        tests/test-getcwd-lgpl.c.
        * modules/wgetcwd-lgpl-tests: New file, based on
        modules/getcwd-lgpl-tests.

        wgetcwd-lgpl: New module.
        * lib/wchar.in.h (wgetcwd): New declaration.
        * lib/wgetcwd-lgpl.c: New file, based on lib/getcwd-lgpl.c.
        * m4/wchar_h.m4 (gl_WCHAR_H_REQUIRE_DEFAULTS): Initialize
        GNULIB_WGETCWD.
        * modules/wchar (Makefile.am): Substitute GNULIB_WGETCWD.
        * modules/wgetcwd-lgpl: New file.

2023-09-30  Bruno Haible  <br...@clisp.org>

        getcwd-lgpl: Tweaks.
        * lib/unistd.in.h (getcwd): Mention the module 'getcwd-lgpl'.
        * lib/getcwd-lgpl.c (rpl_getcwd): Minimize scope of local variables.
        * tests/test-getcwd-lgpl.c (main): Use GNU coding style.

>From c3dda22534b5d575f167d601ed1d446744008506 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 30 Sep 2023 16:15:44 +0200
Subject: [PATCH 1/3] getcwd-lgpl: Tweaks.

* lib/unistd.in.h (getcwd): Mention the module 'getcwd-lgpl'.
* lib/getcwd-lgpl.c (rpl_getcwd): Minimize scope of local variables.
* tests/test-getcwd-lgpl.c (main): Use GNU coding style.
---
 ChangeLog                | 7 +++++++
 lib/getcwd-lgpl.c        | 6 +++---
 lib/unistd.in.h          | 8 ++++----
 tests/test-getcwd-lgpl.c | 2 +-
 4 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3cb1e28640..f8082c30a9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2023-09-30  Bruno Haible  <br...@clisp.org>
+
+	getcwd-lgpl: Tweaks.
+	* lib/unistd.in.h (getcwd): Mention the module 'getcwd-lgpl'.
+	* lib/getcwd-lgpl.c (rpl_getcwd): Minimize scope of local variables.
+	* tests/test-getcwd-lgpl.c (main): Use GNU coding style.
+
 2023-09-29  Bruno Haible  <br...@clisp.org>
 
 	Allow different --libtool options from multiple gnulib-tool invocations.
diff --git a/lib/getcwd-lgpl.c b/lib/getcwd-lgpl.c
index 8a5bde9947..da478e42e4 100644
--- a/lib/getcwd-lgpl.c
+++ b/lib/getcwd-lgpl.c
@@ -45,12 +45,12 @@ typedef int dummy;
 char *
 rpl_getcwd (char *buf, size_t size)
 {
-  char *ptr;
   char *result;
 
   /* Handle single size operations.  */
   if (buf)
     {
+      /* Check SIZE argument.  */
       if (!size)
         {
           errno = EINVAL;
@@ -79,7 +79,7 @@ rpl_getcwd (char *buf, size_t size)
   {
     char tmp[4032];
     size = sizeof tmp;
-    ptr = getcwd (tmp, size);
+    char *ptr = getcwd (tmp, size);
     if (ptr)
       {
         result = strdup (ptr);
@@ -95,7 +95,7 @@ rpl_getcwd (char *buf, size_t size)
   do
     {
       size <<= 1;
-      ptr = realloc (buf, size);
+      char *ptr = realloc (buf, size);
       if (ptr == NULL)
         {
           free (buf);
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index ac9f50eb3e..96453c90fd 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -1118,10 +1118,10 @@ _GL_WARN_ON_USE (ftruncate, "ftruncate is unportable - "
    or SIZE was too small.
    See the POSIX:2008 specification
    <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html>.
-   Additionally, the gnulib module 'getcwd' guarantees the following GNU
-   extension: If BUF is NULL, an array is allocated with 'malloc'; the array
-   is SIZE bytes long, unless SIZE == 0, in which case it is as big as
-   necessary.  */
+   Additionally, the gnulib module 'getcwd' or 'getcwd-lgpl' guarantees the
+   following GNU extension: If BUF is NULL, an array is allocated with
+   'malloc'; the array is SIZE bytes long, unless SIZE == 0, in which case
+   it is as big as necessary.  */
 # if @REPLACE_GETCWD@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   define getcwd rpl_getcwd
diff --git a/tests/test-getcwd-lgpl.c b/tests/test-getcwd-lgpl.c
index 821244f3de..26b249c767 100644
--- a/tests/test-getcwd-lgpl.c
+++ b/tests/test-getcwd-lgpl.c
@@ -92,7 +92,7 @@ main (int argc, char **argv)
 
   /* Validate a POSIX requirement on size.  */
   errno = 0;
-  ASSERT (getcwd(pwd2, 0) == NULL);
+  ASSERT (getcwd (pwd2, 0) == NULL);
   ASSERT (errno == EINVAL);
 
   free (pwd1);
-- 
2.34.1

>From bc73de46f7134509bff08ad65c0a5f1700d55c71 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 30 Sep 2023 16:20:14 +0200
Subject: [PATCH 2/3] wgetcwd-lgpl: New module.

* lib/wchar.in.h (wgetcwd): New declaration.
* lib/wgetcwd-lgpl.c: New file, based on lib/getcwd-lgpl.c.
* m4/wchar_h.m4 (gl_WCHAR_H_REQUIRE_DEFAULTS): Initialize
GNULIB_WGETCWD.
* modules/wchar (Makefile.am): Substitute GNULIB_WGETCWD.
* modules/wgetcwd-lgpl: New file.
---
 ChangeLog            |  10 ++++
 lib/wchar.in.h       |  18 ++++++
 lib/wgetcwd-lgpl.c   | 134 +++++++++++++++++++++++++++++++++++++++++++
 m4/wchar_h.m4        |   3 +-
 modules/wchar        |   1 +
 modules/wgetcwd-lgpl |  25 ++++++++
 6 files changed, 190 insertions(+), 1 deletion(-)
 create mode 100644 lib/wgetcwd-lgpl.c
 create mode 100644 modules/wgetcwd-lgpl

diff --git a/ChangeLog b/ChangeLog
index f8082c30a9..cde3af6205 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2023-09-30  Bruno Haible  <br...@clisp.org>
+
+	wgetcwd-lgpl: New module.
+	* lib/wchar.in.h (wgetcwd): New declaration.
+	* lib/wgetcwd-lgpl.c: New file, based on lib/getcwd-lgpl.c.
+	* m4/wchar_h.m4 (gl_WCHAR_H_REQUIRE_DEFAULTS): Initialize
+	GNULIB_WGETCWD.
+	* modules/wchar (Makefile.am): Substitute GNULIB_WGETCWD.
+	* modules/wgetcwd-lgpl: New file.
+
 2023-09-30  Bruno Haible  <br...@clisp.org>
 
 	getcwd-lgpl: Tweaks.
diff --git a/lib/wchar.in.h b/lib/wchar.in.h
index f1bbff6b94..f114bce3f1 100644
--- a/lib/wchar.in.h
+++ b/lib/wchar.in.h
@@ -1684,6 +1684,24 @@ _GL_WARN_ON_USE (wcsftime, "wcsftime is unportable - "
 #endif
 
 
+#if @GNULIB_WGETCWD@ && (defined _WIN32 && !defined __CYGWIN__)
+/* Gets the name of the current working directory.
+   (a) If BUF is non-NULL, it is assumed to have room for SIZE wide characters.
+       This function stores the working directory (NUL-terminated) in BUF and
+       returns BUF.
+   (b) If BUF is NULL, an array is allocated with 'malloc'.  The array is SIZE
+       wide characters long, unless SIZE == 0, in which case it is as big as
+       necessary.
+   If the directory couldn't be determined or SIZE was too small, this function
+   returns NULL and sets errno.  For a directory of length LEN, SIZE should be
+   >= LEN + 3 in case (a) or >= LEN + 1 in case (b).
+   Possible errno values include:
+     - ERANGE if SIZE is too small.
+     - ENOMEM if the memory could no be allocated.  */
+_GL_FUNCDECL_SYS (wgetcwd, wchar_t *, (wchar_t *buf, size_t size));
+#endif
+
+
 #endif /* _@GUARD_PREFIX@_WCHAR_H */
 #endif /* _@GUARD_PREFIX@_WCHAR_H */
 #endif
diff --git a/lib/wgetcwd-lgpl.c b/lib/wgetcwd-lgpl.c
new file mode 100644
index 0000000000..922e0fb810
--- /dev/null
+++ b/lib/wgetcwd-lgpl.c
@@ -0,0 +1,134 @@
+/* Copyright (C) 2011-2023 Free Software Foundation, Inc.
+   This file is part of gnulib.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification */
+#include <wchar.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#if defined _WIN32 && !defined __CYGWIN__
+
+wchar_t *
+wgetcwd (wchar_t *buf, size_t size)
+{
+  wchar_t *result;
+
+  /* Uses _wgetcwd.
+     Documentation:
+     <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/getcwd-wgetcwd>
+     Note that for a directory consisting of LEN wide characters, the SIZE
+     argument to _wgetcwd needs to be >= LEN + 3, not only >= LEN + 1, with
+     some versions of the Microsoft runtime libraries.  */
+
+  /* Handle single size operations.  */
+  if (buf)
+    {
+      /* Check SIZE argument.  */
+      if (!size)
+        {
+          errno = EINVAL;
+          return NULL;
+        }
+      /* Invoke _wgetcwd as-is.  In this case, the caller does not expect
+         an ENOMEM error; therefore don't use temporary memory.  */
+      return _wgetcwd (buf, size);
+    }
+
+  if (size)
+    {
+      /* Allocate room for two more wide characters, so that directory names
+         of length <= SIZE - 1 can be returned.  */
+      buf = malloc ((size + 2) * sizeof (wchar_t));
+      if (!buf)
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+      result = _wgetcwd (buf, size + 2);
+      if (!result)
+        {
+          free (buf);
+          return NULL;
+        }
+      if (wcslen (result) >= size)
+        {
+          free (buf);
+          errno = ERANGE;
+          return NULL;
+        }
+      /* Shrink result before returning it.  */
+      wchar_t *shrinked_result = realloc (result, size * sizeof (wchar_t));
+      if (shrinked_result != NULL)
+        result = shrinked_result;
+      return result;
+    }
+
+  /* Flexible sizing requested.  Avoid over-allocation for the common
+     case of a name that fits within a 4k page, minus some space for
+     local variables, to be sure we don't skip over a guard page.  */
+  {
+    wchar_t tmp[4032 / sizeof (wchar_t)];
+    size = sizeof tmp / sizeof (wchar_t);
+    wchar_t *ptr = _wgetcwd (tmp, size);
+    if (ptr)
+      {
+        result = _wcsdup (ptr);
+        if (!result)
+          errno = ENOMEM;
+        return result;
+      }
+    if (errno != ERANGE)
+      return NULL;
+  }
+
+  /* My what a large directory name we have.  */
+  do
+    {
+      size <<= 1;
+      wchar_t *ptr = realloc (buf, size * sizeof (wchar_t));
+      if (ptr == NULL)
+        {
+          free (buf);
+          errno = ENOMEM;
+          return NULL;
+        }
+      buf = ptr;
+      result = _wgetcwd (buf, size);
+    }
+  while (!result && errno == ERANGE);
+
+  if (!result)
+    free (buf);
+  else
+    {
+      /* Here result == buf.  */
+      /* Shrink result before returning it.  */
+      size_t actual_size = wcslen (result) + 1;
+      if (actual_size < size)
+        {
+          wchar_t *shrinked_result =
+            realloc (result, actual_size * sizeof (wchar_t));
+          if (shrinked_result != NULL)
+            result = shrinked_result;
+        }
+    }
+  return result;
+}
+
+#endif
diff --git a/m4/wchar_h.m4 b/m4/wchar_h.m4
index 31f5b0794d..8d62293646 100644
--- a/m4/wchar_h.m4
+++ b/m4/wchar_h.m4
@@ -7,7 +7,7 @@
 
 dnl Written by Eric Blake.
 
-# wchar_h.m4 serial 61
+# wchar_h.m4 serial 62
 
 AC_DEFUN_ONCE([gl_WCHAR_H],
 [
@@ -186,6 +186,7 @@ AC_DEFUN([gl_WCHAR_H_REQUIRE_DEFAULTS]
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_WCSTOK])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_WCSWIDTH])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_WCSFTIME])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_WGETCWD])
     dnl Support Microsoft deprecated alias function names by default.
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MDA_WCSDUP], [1])
   ])
diff --git a/modules/wchar b/modules/wchar
index ebdd0ece61..393b2b1837 100644
--- a/modules/wchar
+++ b/modules/wchar
@@ -82,6 +82,7 @@ wchar.h: wchar.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H)
 	      -e 's/@''GNULIB_WCSTOK''@/$(GNULIB_WCSTOK)/g' \
 	      -e 's/@''GNULIB_WCSWIDTH''@/$(GNULIB_WCSWIDTH)/g' \
 	      -e 's/@''GNULIB_WCSFTIME''@/$(GNULIB_WCSFTIME)/g' \
+	      -e 's/@''GNULIB_WGETCWD''@/$(GNULIB_WGETCWD)/g' \
 	      -e 's/@''GNULIB_MDA_WCSDUP''@/$(GNULIB_MDA_WCSDUP)/g' \
 	      -e 's/@''GNULIB_FREE_POSIX''@/$(GNULIB_FREE_POSIX)/g' \
 	      < $(srcdir)/wchar.in.h > $@-t1
diff --git a/modules/wgetcwd-lgpl b/modules/wgetcwd-lgpl
new file mode 100644
index 0000000000..ed33406737
--- /dev/null
+++ b/modules/wgetcwd-lgpl
@@ -0,0 +1,25 @@
+Description:
+Native Windows specific wgetcwd function. Like _wgetcwd, but also ensure
+that wgetcwd(NULL, 0) returns a buffer allocated by the malloc() function.
+
+Files:
+lib/wgetcwd-lgpl.c
+
+Depends-on:
+wchar
+free-posix
+
+configure.ac:
+gl_WCHAR_MODULE_INDICATOR([wgetcwd])
+
+Makefile.am:
+lib_SOURCES += wgetcwd-lgpl.c
+
+Include:
+<wchar.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.34.1

>From cbc02d24a5e064f4699939cbb15cce1a448d444a Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 30 Sep 2023 16:23:28 +0200
Subject: [PATCH 3/3] wgetcwd-lgpl: Add tests.

* tests/test-wgetcwd-lgpl.c: New file, based on
tests/test-getcwd-lgpl.c.
* modules/wgetcwd-lgpl-tests: New file, based on
modules/getcwd-lgpl-tests.
---
 ChangeLog                  |  6 +++
 modules/wgetcwd-lgpl-tests | 13 +++++
 tests/test-wgetcwd-lgpl.c  | 98 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 117 insertions(+)
 create mode 100644 modules/wgetcwd-lgpl-tests
 create mode 100644 tests/test-wgetcwd-lgpl.c

diff --git a/ChangeLog b/ChangeLog
index cde3af6205..a8b06d31c6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2023-09-30  Bruno Haible  <br...@clisp.org>
 
+	wgetcwd-lgpl: Add tests.
+	* tests/test-wgetcwd-lgpl.c: New file, based on
+	tests/test-getcwd-lgpl.c.
+	* modules/wgetcwd-lgpl-tests: New file, based on
+	modules/getcwd-lgpl-tests.
+
 	wgetcwd-lgpl: New module.
 	* lib/wchar.in.h (wgetcwd): New declaration.
 	* lib/wgetcwd-lgpl.c: New file, based on lib/getcwd-lgpl.c.
diff --git a/modules/wgetcwd-lgpl-tests b/modules/wgetcwd-lgpl-tests
new file mode 100644
index 0000000000..4893da1db2
--- /dev/null
+++ b/modules/wgetcwd-lgpl-tests
@@ -0,0 +1,13 @@
+Files:
+tests/test-wgetcwd-lgpl.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-wgetcwd-lgpl
+check_PROGRAMS += test-wgetcwd-lgpl
+test_wgetcwd_lgpl_LDADD = $(LDADD) $(LIBINTL)
diff --git a/tests/test-wgetcwd-lgpl.c b/tests/test-wgetcwd-lgpl.c
new file mode 100644
index 0000000000..382111a65e
--- /dev/null
+++ b/tests/test-wgetcwd-lgpl.c
@@ -0,0 +1,98 @@
+/* Test of wgetcwd() function.
+   Copyright (C) 2009-2023 Free Software Foundation, Inc.
+
+   This program 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.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <wchar.h>
+
+#include "signature.h"
+#if defined _WIN32 && !defined __CYGWIN__
+SIGNATURE_CHECK (wgetcwd, wchar_t *, (wchar_t *, size_t));
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "macros.h"
+
+int
+main ()
+{
+#if defined _WIN32 && !defined __CYGWIN__
+  wchar_t *pwd1;
+  wchar_t *pwd2;
+
+  pwd1 = wgetcwd (NULL, 0);
+  ASSERT (pwd1 && *pwd1);
+
+  /* Make sure the result is usable.  */
+  ASSERT (_wchdir (pwd1) == 0);
+  ASSERT (_wchdir (L".//./.") == 0);
+
+  /* Make sure that result is normalized.  */
+  pwd2 = wgetcwd (NULL, 0);
+  ASSERT (pwd2);
+  ASSERT (wcscmp (pwd1, pwd2) == 0);
+  free (pwd2);
+  {
+    size_t len = wcslen (pwd1);
+    ssize_t i = len - 10;
+    if (i < 1)
+      i = 1;
+    pwd2 = wgetcwd (NULL, len + 1);
+    ASSERT (pwd2);
+    free (pwd2);
+    pwd2 = malloc ((len + 3) * sizeof (wchar_t));
+    for ( ; i <= len; i++)
+      {
+        wchar_t *tmp;
+        errno = 0;
+        ASSERT (wgetcwd (pwd2, i) == NULL);
+        ASSERT (errno == ERANGE);
+        /* Allow either glibc or BSD behavior, since POSIX allows both.  */
+        errno = 0;
+        tmp = wgetcwd (NULL, i);
+        if (tmp)
+          {
+            ASSERT (wcscmp (pwd1, tmp) == 0);
+            free (tmp);
+          }
+        else
+          {
+            ASSERT (errno == ERANGE);
+          }
+      }
+    /* The SIZE argument here needs to be len + 3, not len + 1.  */
+    ASSERT (wgetcwd (pwd2, len + 3) == pwd2);
+    pwd2[len] = L'/';
+    pwd2[len + 1] = L'\0';
+  }
+  ASSERT (wcsstr (pwd2, L"/./") == NULL);
+  ASSERT (wcsstr (pwd2, L"/../") == NULL);
+  ASSERT (wcsstr (pwd2 + 1 + (pwd2[1] == L'/'), L"//") == NULL);
+
+  /* Validate a POSIX requirement on size.  */
+  errno = 0;
+  ASSERT (wgetcwd (pwd2, 0) == NULL);
+  ASSERT (errno == EINVAL);
+
+  free (pwd1);
+  free (pwd2);
+#endif
+
+  return 0;
+}
-- 
2.34.1

Reply via email to