It is remarkable that the only direct way, in POSIX, to determine the umask
of the current process is through a non-MT-safe function umask().

I understand that the idea was that umask() gets called only in the main()
function, during initialization of the process.

Nevertheless, this makes it hard to write library code that uses the umask,
without requiring extra code in the main() function of the program that
uses the library.

This module fixes the problem: it provides an MT-safe getumask() function.
Like - so far - only the Hurd has in its libc.


2020-07-04  Bruno Haible  <[email protected]>

        getumask: Add tests.
        * tests/test-getumask.c: New file.
        * modules/getumask-tests: New file.

        getumask: New module.
        * lib/sys_stat.in.h (getumask): New declaration.
        * lib/getumask.c: New file.
        * m4/getumask.m4: New file.
        * m4/sys_stat_h.m4 (gl_HEADER_SYS_STAT_H): Test whether getumask is
        declared.
        (gl_SYS_STAT_H_DEFAULTS): Initialize GNULIB_GETUMASK, HAVE_GETUMASK.
        * modules/sys_stat (Makefile.am): Substitute GNULIB_GETUMASK,
        HAVE_GETUMASK.
        * modules/getumask: New file.
        * tests/test-sys_stat-c++.cc (getumask): Check signature.
        * doc/glibc-functions/getumask.texi: New file.
        * doc/gnulib.texi (Glibc sys/stat.h): Include it.

>From 6aa22a864222eb7199a71dabb85906088ee988cc Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 4 Jul 2020 18:14:46 +0200
Subject: [PATCH 1/2] getumask: New module.

* lib/sys_stat.in.h (getumask): New declaration.
* lib/getumask.c: New file.
* m4/getumask.m4: New file.
* m4/sys_stat_h.m4 (gl_HEADER_SYS_STAT_H): Test whether getumask is
declared.
(gl_SYS_STAT_H_DEFAULTS): Initialize GNULIB_GETUMASK, HAVE_GETUMASK.
* modules/sys_stat (Makefile.am): Substitute GNULIB_GETUMASK,
HAVE_GETUMASK.
* modules/getumask: New file.
* tests/test-sys_stat-c++.cc (getumask): Check signature.
* doc/glibc-functions/getumask.texi: New file.
* doc/gnulib.texi (Glibc sys/stat.h): Include it.
---
 ChangeLog                         |  16 +++++
 doc/glibc-functions/getumask.texi |  30 ++++++++
 doc/gnulib.texi                   |   2 +
 lib/getumask.c                    | 140 ++++++++++++++++++++++++++++++++++++++
 lib/sys_stat.in.h                 |  17 +++++
 m4/getumask.m4                    |  24 +++++++
 m4/sys_stat_h.m4                  |   8 ++-
 modules/getumask                  |  32 +++++++++
 modules/sys_stat                  |   2 +
 tests/test-sys_stat-c++.cc        |   4 ++
 10 files changed, 272 insertions(+), 3 deletions(-)
 create mode 100644 doc/glibc-functions/getumask.texi
 create mode 100644 lib/getumask.c
 create mode 100644 m4/getumask.m4
 create mode 100644 modules/getumask

diff --git a/ChangeLog b/ChangeLog
index 15f5dd0..fb1b853 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,21 @@
 2020-07-04  Bruno Haible  <[email protected]>
 
+	getumask: New module.
+	* lib/sys_stat.in.h (getumask): New declaration.
+	* lib/getumask.c: New file.
+	* m4/getumask.m4: New file.
+	* m4/sys_stat_h.m4 (gl_HEADER_SYS_STAT_H): Test whether getumask is
+	declared.
+	(gl_SYS_STAT_H_DEFAULTS): Initialize GNULIB_GETUMASK, HAVE_GETUMASK.
+	* modules/sys_stat (Makefile.am): Substitute GNULIB_GETUMASK,
+	HAVE_GETUMASK.
+	* modules/getumask: New file.
+	* tests/test-sys_stat-c++.cc (getumask): Check signature.
+	* doc/glibc-functions/getumask.texi: New file.
+	* doc/gnulib.texi (Glibc sys/stat.h): Include it.
+
+2020-07-04  Bruno Haible  <[email protected]>
+
 	clean-temp: Add support for temporary files with given mode.
 	* lib/clean-temp.h (gen_register_open_temp): Add mode argument.
 	* lib/clean-temp.c (struct try_create_file_params): New type.
diff --git a/doc/glibc-functions/getumask.texi b/doc/glibc-functions/getumask.texi
new file mode 100644
index 0000000..2fbf9e3
--- /dev/null
+++ b/doc/glibc-functions/getumask.texi
@@ -0,0 +1,30 @@
+@node getumask
+@subsection @code{getumask}
+@findex getumask
+
+Documentation:
+@itemize
+@item
+@ifinfo
+@ref{Setting Permissions,,Assigning File Permissions,libc},
+@end ifinfo
+@ifnotinfo
+@url{https://www.gnu.org/software/libc/manual/html_node/Setting-Permissions.html},
+@end ifnotinfo
+@item
+@uref{https://www.kernel.org/doc/man-pages/online/pages/man3/getumask.3.html,,man getumask}.
+@end itemize
+
+Gnulib module: getumask
+
+Portability problems fixed by Gnulib:
+@itemize
+@item
+This function exists only on Hurd and is therefore
+missing on all non-glibc platforms:
+glibc/Linux, glibc/kFreeBSD, Mac OS X 10.13, FreeBSD 12.0, NetBSD 9.0, OpenBSD 6.7, Minix 3.3, AIX 7.2, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin, mingw, MSVC 14, Android 9.0.
+@end itemize
+
+Portability problems not fixed by Gnulib:
+@itemize
+@end itemize
diff --git a/doc/gnulib.texi b/doc/gnulib.texi
index ec6e633..812e7d0 100644
--- a/doc/gnulib.texi
+++ b/doc/gnulib.texi
@@ -6119,10 +6119,12 @@ This list of functions is sorted according to the header that declares them.
 @section Glibc Extensions to @code{<sys/stat.h>}
 
 @menu
+* getumask::
 * lchmod::
 * statx::
 @end menu
 
+@include glibc-functions/getumask.texi
 @include glibc-functions/lchmod.texi
 @include glibc-functions/statx.texi
 
diff --git a/lib/getumask.c b/lib/getumask.c
new file mode 100644
index 0000000..6168ef6
--- /dev/null
+++ b/lib/getumask.c
@@ -0,0 +1,140 @@
+/* Retrieve the umask of the process (multithread-safe).
+   Copyright (C) 2020 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/>.  */
+
+/* Written by Bruno Haible <[email protected]>, 2020.  */
+
+/* There are three ways to implement a getumask() function on systems that
+   don't have it:
+     (a) Through system calls on the file system.
+     (b) Through a global variable that the main() function has to set,
+         together with an override of the umask() function.
+     (c) Through { mode_t mask = umask (0); umask (mask); }.
+
+   Each has its drawbacks:
+     (a) Causes additional system calls. May fail in some rare cases.
+     (b) Causes globally visible code complexity / maintainer effort.
+     (c) Is not multithread-safe: open() calls in other threads may
+         create files with wrong access permissions.
+
+   Here we implement (a), as the least evil.  */
+
+#include <config.h>
+/* The package may define ASSUME_UMASK_CONSTANT to 1, to indicate that the
+   program does not call umask().  */
+/* #define ASSUME_UMASK_CONSTANT 1 */
+
+/* Specification.  */
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "clean-temp.h"
+#include "tempname.h"
+
+mode_t
+getumask (void)
+{
+#if 0
+  /* This is not multithread-safe!  */
+  mode_t mask = umask (0);
+  umask (mask);
+  return mask;
+#else
+# if ASSUME_UMASK_CONSTANT
+  static int cached_umask = -1;
+  if (cached_umask >= 0)
+    return cached_umask;
+# endif
+
+  int mask = -1;
+# if defined __linux__
+  {
+    /* In Linux >= 4.7, the umask can be retrieved from an "Umask:" line in the
+       /proc/self/status file.  */
+    char buf[4096];
+    int fd = open ("/proc/self/status", O_RDONLY);
+    if (fd >= 0)
+      {
+        ssize_t n = read (fd, buf, sizeof (buf));
+        if (n > 0)
+          {
+            const char *p_end = buf + n;
+            const char *p = buf;
+
+            for (;;)
+              {
+                /* Here we're at the beginning of a line.  */
+                if (p_end - p > 8 && memcmp (p, "Umask:\t0", 8) == 0)
+                  {
+                    unsigned int value = 0;
+                    p += 8;
+                    for (; p < p_end && *p >= '0' && *p <= '7'; p++)
+                      value = 8 * value + (*p - '0');
+                    if (p < p_end && *p == '\n')
+                      mask = value;
+                    break;
+                  }
+                /* Search the start of the next line.  */
+                for (; p < p_end && *p != '\n'; p++)
+                  ;
+                if (p == p_end)
+                  break;
+                p++;
+              }
+          }
+        close (fd);
+      }
+  }
+# endif
+  if (mask < 0)
+    {
+      /* Create a temporary file and inspect its access permissions.  */
+      const char *tmpdir = getenv ("TMPDIR");
+      if (tmpdir == NULL || *tmpdir == '\0')
+        tmpdir = "/tmp";
+      size_t tmpdir_length = strlen (tmpdir);
+      char *temp_filename = (char *) malloc (tmpdir_length + 15 + 1);
+      if (temp_filename != NULL)
+        {
+          memcpy (temp_filename, tmpdir, tmpdir_length);
+          strcpy (temp_filename + tmpdir_length, "/gtumask.XXXXXX");
+          int fd = gen_register_open_temp (temp_filename, 0, O_RDWR,
+                                           S_IRWXU|S_IRWXG|S_IRWXO);
+          if (fd >= 0)
+            {
+              struct stat statbuf;
+              if (fstat (fd, &statbuf) >= 0)
+                mask = (S_IRWXU|S_IRWXG|S_IRWXO) & ~statbuf.st_mode;
+              close_temp (fd);
+              cleanup_temporary_file (temp_filename, false);
+            }
+          free (temp_filename);
+        }
+    }
+  if (mask < 0)
+    {
+      /* We still don't know!  Assume a paranoid user.  */
+      mask = 077;
+    }
+# if ASSUME_UMASK_CONSTANT
+  cached_umask = mask;
+# endif
+  return mask;
+#endif
+}
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index dc8881e..c1618a6 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -515,6 +515,23 @@ _GL_WARN_ON_USE (futimens, "futimens is not portable - "
 #endif
 
 
+#if @GNULIB_GETUMASK@
+# if !@HAVE_GETUMASK@
+_GL_FUNCDECL_SYS (getumask, mode_t, (void));
+# endif
+_GL_CXXALIAS_SYS (getumask, mode_t, (void));
+# if @HAVE_GETUMASK@
+_GL_CXXALIASWARN (getumask);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef getumask
+# if HAVE_RAW_DECL_GETUMASK
+_GL_WARN_ON_USE (getumask, "getumask is not portable - "
+                 "use gnulib module getumask for portability");
+# endif
+#endif
+
+
 #if @GNULIB_LCHMOD@
 /* Change the mode of FILENAME to MODE, without dereferencing it if FILENAME
    denotes a symbolic link.  */
diff --git a/m4/getumask.m4 b/m4/getumask.m4
new file mode 100644
index 0000000..f8b526d
--- /dev/null
+++ b/m4/getumask.m4
@@ -0,0 +1,24 @@
+# getumask.m4 serial 1
+dnl Copyright 2020 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_GETUMASK],
+[
+  AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+
+  dnl Persuade glibc <sys/stat.h> to declare getumask().
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_FUNCS_ONCE([getumask])
+  if test $ac_cv_func_getumask = no; then
+    HAVE_GETUMASK=0
+  fi
+])
+
+# Prerequisites of lib/getumask.c.
+AC_DEFUN([gl_PREREQ_GETUMASK],
+[
+  :
+])
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index 3efba5a..929144d 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,4 +1,4 @@
-# sys_stat_h.m4 serial 33   -*- Autoconf -*-
+# sys_stat_h.m4 serial 34   -*- Autoconf -*-
 dnl Copyright (C) 2006-2020 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -46,8 +46,8 @@ AC_DEFUN([gl_HEADER_SYS_STAT_H],
   dnl Check for declarations of anything we want to poison if the
   dnl corresponding gnulib module is not in use.
   gl_WARN_ON_USE_PREPARE([[#include <sys/stat.h>
-    ]], [fchmodat fstat fstatat futimens lchmod lstat mkdirat mkfifo mkfifoat
-    mknod mknodat stat utimensat])
+    ]], [fchmodat fstat fstatat futimens getumask lchmod lstat
+    mkdirat mkfifo mkfifoat mknod mknodat stat utimensat])
 
   AC_REQUIRE([AC_C_RESTRICT])
 ])
@@ -68,6 +68,7 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
   GNULIB_FSTAT=0;       AC_SUBST([GNULIB_FSTAT])
   GNULIB_FSTATAT=0;     AC_SUBST([GNULIB_FSTATAT])
   GNULIB_FUTIMENS=0;    AC_SUBST([GNULIB_FUTIMENS])
+  GNULIB_GETUMASK=0;    AC_SUBST([GNULIB_GETUMASK])
   GNULIB_LCHMOD=0;      AC_SUBST([GNULIB_LCHMOD])
   GNULIB_LSTAT=0;       AC_SUBST([GNULIB_LSTAT])
   GNULIB_MKDIRAT=0;     AC_SUBST([GNULIB_MKDIRAT])
@@ -82,6 +83,7 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
   HAVE_FCHMODAT=1;      AC_SUBST([HAVE_FCHMODAT])
   HAVE_FSTATAT=1;       AC_SUBST([HAVE_FSTATAT])
   HAVE_FUTIMENS=1;      AC_SUBST([HAVE_FUTIMENS])
+  HAVE_GETUMASK=1;      AC_SUBST([HAVE_GETUMASK])
   HAVE_LCHMOD=1;        AC_SUBST([HAVE_LCHMOD])
   HAVE_LSTAT=1;         AC_SUBST([HAVE_LSTAT])
   HAVE_MKDIRAT=1;       AC_SUBST([HAVE_MKDIRAT])
diff --git a/modules/getumask b/modules/getumask
new file mode 100644
index 0000000..9f19a0e
--- /dev/null
+++ b/modules/getumask
@@ -0,0 +1,32 @@
+Description:
+getumask() function: retrieve the umask of the process (multithread-safe)
+
+Files:
+lib/getumask.c
+m4/getumask.m4
+
+Depends-on:
+sys_stat
+extensions
+unistd          [test $HAVE_GETUMASK = 0]
+clean-temp      [test $HAVE_GETUMASK = 0]
+tempname        [test $HAVE_GETUMASK = 0]
+
+configure.ac:
+gl_FUNC_GETUMASK
+if test $HAVE_GETUMASK = 0; then
+  AC_LIBOBJ([getumask])
+  gl_PREREQ_GETUMASK
+fi
+gl_SYS_STAT_MODULE_INDICATOR([getumask])
+
+Makefile.am:
+
+Include:
+<sys/stat.h>
+
+License:
+GPL
+
+Maintainer:
+all
diff --git a/modules/sys_stat b/modules/sys_stat
index 4783c7e..af276ab 100644
--- a/modules/sys_stat
+++ b/modules/sys_stat
@@ -38,6 +38,7 @@ sys/stat.h: sys_stat.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNU
 	      -e 's/@''GNULIB_FSTAT''@/$(GNULIB_FSTAT)/g' \
 	      -e 's/@''GNULIB_FSTATAT''@/$(GNULIB_FSTATAT)/g' \
 	      -e 's/@''GNULIB_FUTIMENS''@/$(GNULIB_FUTIMENS)/g' \
+	      -e 's/@''GNULIB_GETUMASK''@/$(GNULIB_GETUMASK)/g' \
 	      -e 's/@''GNULIB_LCHMOD''@/$(GNULIB_LCHMOD)/g' \
 	      -e 's/@''GNULIB_LSTAT''@/$(GNULIB_LSTAT)/g' \
 	      -e 's/@''GNULIB_MKDIRAT''@/$(GNULIB_MKDIRAT)/g' \
@@ -51,6 +52,7 @@ sys/stat.h: sys_stat.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNU
 	      -e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \
 	      -e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \
 	      -e 's|@''HAVE_FUTIMENS''@|$(HAVE_FUTIMENS)|g' \
+	      -e 's|@''HAVE_GETUMASK''@|$(HAVE_GETUMASK)|g' \
 	      -e 's|@''HAVE_LCHMOD''@|$(HAVE_LCHMOD)|g' \
 	      -e 's|@''HAVE_LSTAT''@|$(HAVE_LSTAT)|g' \
 	      -e 's|@''HAVE_MKDIRAT''@|$(HAVE_MKDIRAT)|g' \
diff --git a/tests/test-sys_stat-c++.cc b/tests/test-sys_stat-c++.cc
index 9b2e733..981199b 100644
--- a/tests/test-sys_stat-c++.cc
+++ b/tests/test-sys_stat-c++.cc
@@ -43,6 +43,10 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::futimens, int,
                  (int, struct timespec const[2]));
 #endif
 
+#if GNULIB_TEST_GETUMASK
+SIGNATURE_CHECK (GNULIB_NAMESPACE::getumask, mode_t, (void));
+#endif
+
 #if GNULIB_TEST_LCHMOD
 SIGNATURE_CHECK (GNULIB_NAMESPACE::lchmod, int, (const char *, mode_t));
 #endif
-- 
2.7.4

>From daa03df900fa6c24a5a331b570ae65ab632c529c Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 4 Jul 2020 18:16:06 +0200
Subject: [PATCH 2/2] getumask: Add tests.

* tests/test-getumask.c: New file.
* modules/getumask-tests: New file.
---
 ChangeLog              |  4 ++++
 modules/getumask-tests | 13 +++++++++++++
 tests/test-getumask.c  | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 69 insertions(+)
 create mode 100644 modules/getumask-tests
 create mode 100644 tests/test-getumask.c

diff --git a/ChangeLog b/ChangeLog
index fb1b853..cbf1ccc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2020-07-04  Bruno Haible  <[email protected]>
 
+	getumask: Add tests.
+	* tests/test-getumask.c: New file.
+	* modules/getumask-tests: New file.
+
 	getumask: New module.
 	* lib/sys_stat.in.h (getumask): New declaration.
 	* lib/getumask.c: New file.
diff --git a/modules/getumask-tests b/modules/getumask-tests
new file mode 100644
index 0000000..2968f37
--- /dev/null
+++ b/modules/getumask-tests
@@ -0,0 +1,13 @@
+Files:
+tests/test-getumask.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-getumask
+check_PROGRAMS += test-getumask
+test_getumask_LDADD = $(LDADD) $(LIB_GETRANDOM)
diff --git a/tests/test-getumask.c b/tests/test-getumask.c
new file mode 100644
index 0000000..b18afa2
--- /dev/null
+++ b/tests/test-getumask.c
@@ -0,0 +1,52 @@
+/* Test of retrieving the umask of the process.
+   Copyright (C) 2020 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/>.  */
+
+/* Written by Bruno Haible.  */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (getumask, mode_t, (void));
+
+#include "macros.h"
+
+int
+main (void)
+{
+  int mask;
+
+  mask = getumask ();
+  ASSERT (mask >= 0);
+
+#if !ASSUME_UMASK_CONSTANT
+  /* These tests fail if the umask is required to not change.  */
+
+  umask (002);
+
+  mask = getumask ();
+  ASSERT (mask == 002);
+
+  umask (077);
+
+  mask = getumask ();
+  ASSERT (mask == 077);
+
+#endif
+
+  return 0;
+}
-- 
2.7.4

Reply via email to