bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] fgetcwd: new module


From: Jim Meyering
Subject: [PATCH] fgetcwd: new module
Date: Sat, 23 Jul 2011 18:14:38 +0200

A couple of months ago, Eric noticed that fts.c used a function
named getcwdat in #ifdef'd-out code (FTS_DEBUG).  That was something
I found useful while investigating fts corner cases, but had never
put in publishable form.

I realized that calling it getcwdat was not appropriate,
so have renamed it to fgetcwd.

This module almost certainly still has room for improvement
but does have a tiny self-test and passes it:

    ./gnulib-tool --create-testdir --with-tests --test fgetcwd

I haven't bothered to add conditionals in the modules file
because for now, no one is using this module.
Currently, one would use it only when debugging fts.

I'm a little leery of having it copy so much of the code of
getcwd.c, but was not inclined to make the replacement getcwd
a wrapper around fgetcwd.

With no official user of this module, I feel no strong urge to add it to
gnulib, and am posting this mainly to justify the uses in fts.c's
debugging code.  Considering that we should be avoiding use of getcwd,
there are nearly as many reasons to avoid this function, it would
not be hard to convince me simply to discard it.

>From dc5ea7a00c9af30dbe68afb43d25c02acbf498b5 Mon Sep 17 00:00:00 2001
From: Jim Meyering <address@hidden>
Date: Wed, 11 May 2011 12:14:30 +0200
Subject: [PATCH] fgetcwd: new module

* modules/fgetcwd: New file.
* lib/fgetcwd.c: New file, mostly copied from getcwd.c.
* lib/fgetcwd.h: New file.
* lib/fts.c: Include "fgetcwd.h", not "getcwdat.h".
[FTS_DEBUG]: s/getcwdat/fgetcwd/g.
* modules/fgetcwd-tests: New file.
* tests/test-fgetcwd.c: New file.
* MODULES.html.sh (File system functions): Add it.
---
 ChangeLog             |   12 ++
 MODULES.html.sh       |    1 +
 lib/fgetcwd.c         |  326 +++++++++++++++++++++++++++++++++++++++++++++++++
 lib/fgetcwd.h         |   11 ++
 lib/fts.c             |   12 +-
 modules/fgetcwd       |   32 +++++
 modules/fgetcwd-tests |   11 ++
 tests/test-fgetcwd.c  |   43 +++++++
 8 files changed, 442 insertions(+), 6 deletions(-)
 create mode 100644 lib/fgetcwd.c
 create mode 100644 lib/fgetcwd.h
 create mode 100644 modules/fgetcwd
 create mode 100644 modules/fgetcwd-tests
 create mode 100644 tests/test-fgetcwd.c

diff --git a/ChangeLog b/ChangeLog
index 4a66446..e3fb863 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2011-07-23  Jim Meyering  <address@hidden>
+
+       fgetcwd: new module
+       * modules/fgetcwd: New file.
+       * lib/fgetcwd.c: New file, mostly copied from getcwd.c.
+       * lib/fgetcwd.h: New file.
+       * lib/fts.c: Include "fgetcwd.h", not "getcwdat.h".
+       [FTS_DEBUG]: s/getcwdat/fgetcwd/g.
+       * modules/fgetcwd-tests: New file.
+       * tests/test-fgetcwd.c: New file.
+       * MODULES.html.sh (File system functions): Add it.
+
 2011-07-22  Paul Eggert  <address@hidden>

        fsusage: port to MacOS X 10.7 with 4 TiB file systems
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 80befa9..c5f971c 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2600,6 +2600,7 @@ func_all_modules ()
   func_module faccessat
   func_module fdopendir
   func_module fdutimensat
+  func_module fgetcwd
   func_module file-type
   func_module fileblocks
   func_module filemode
diff --git a/lib/fgetcwd.c b/lib/fgetcwd.c
new file mode 100644
index 0000000..1f0e95d
--- /dev/null
+++ b/lib/fgetcwd.c
@@ -0,0 +1,326 @@
+/* Copyright (C) 2006-2011 Free Software Foundation, Inc.
+   Derived from getcwd.c.
+
+   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 <http://www.gnu.org/licenses/>.  */
+
+#if !_LIBC
+# include <config.h>
+# include <unistd.h>
+#endif
+
+#include "fgetcwd.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <fcntl.h>
+
+#include <assert.h>
+
+#ifndef __set_errno
+# define __set_errno(val) (errno = (val))
+#endif
+
+#include <dirent.h>
+#ifndef _D_EXACT_NAMLEN
+# define _D_EXACT_NAMLEN(d) strlen ((d)->d_name)
+#endif
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef MAX
+# define MAX(a, b) ((a) < (b) ? (b) : (a))
+#endif
+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+#  define PATH_MAX MAXPATHLEN
+# else
+#  define PATH_MAX 1024
+# endif
+#endif
+
+#if D_INO_IN_DIRENT
+# define MATCHING_INO(dp, ino) ((dp)->d_ino == (ino))
+#else
+# define MATCHING_INO(dp, ino) true
+#endif
+
+#if !_LIBC
+# define __lstat lstat
+# define __closedir closedir
+# define __readdir readdir
+#endif
+
+#ifndef NDEBUG
+static bool
+valid_fd (int fd)
+{
+  return 0 <= fcntl (fd, F_GETFD);
+}
+#endif
+
+/* Get the name of the directory open on FD, and put it in SIZE
+   bytes of BUF.  Returns NULL if the directory couldn't be determined or
+   SIZE was too small.  If successful, returns BUF.  In GNU, 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.  */
+
+char *
+fgetcwd (int fd, char *buf, size_t size)
+{
+  /* Length of file name.  This number is not an upper bound;
+     it is merely a large value suitable for an initial allocation,
+     designed to be large enough for most real-world uses.  */
+  enum
+    {
+      BIG_FILE_NAME_LENGTH = MIN (4095, PATH_MAX - 1),
+    };
+
+  bool fd_needs_closing = false;
+  DIR *dirstream = NULL;
+  dev_t rootdev, thisdev;
+  ino_t rootino, thisino;
+  char *dir;
+  register char *dirp;
+  struct stat st;
+  size_t allocated = size;
+  size_t used;
+
+  if (fd < 0)
+    {
+      __set_errno (EINVAL);
+      return NULL;
+    }
+
+  /* Dup it, so we don't close the caller's fd. */
+  fd = dup (fd);
+  if (fd < 0)
+    return NULL;
+
+  if (size == 0)
+    {
+      if (buf != NULL)
+        {
+          __set_errno (EINVAL);
+          return NULL;
+        }
+
+      allocated = BIG_FILE_NAME_LENGTH + 1;
+    }
+
+  if (buf == NULL)
+    {
+      dir = malloc (allocated);
+      if (dir == NULL)
+        return NULL;
+    }
+  else
+    dir = buf;
+
+  dirp = dir + allocated;
+  *--dirp = '\0';
+
+  if (fstat (fd, &st) < 0)
+    goto lose;
+  thisdev = st.st_dev;
+  thisino = st.st_ino;
+
+  if (__lstat ("/", &st) < 0)
+    goto lose;
+  rootdev = st.st_dev;
+  rootino = st.st_ino;
+
+  while (!(thisdev == rootdev && thisino == rootino))
+    {
+      struct dirent *d;
+      dev_t dotdev;
+      ino_t dotino;
+      bool mount_point;
+      int parent_status;
+      size_t dirroom;
+      size_t namlen;
+      bool use_d_ino = true;
+      int prev_fd = fd;
+
+      /* Look at the parent directory.  */
+      fd = openat (fd, "..", O_RDONLY);
+      if (fd < 0)
+        goto lose;
+      close (prev_fd);
+      assert (valid_fd (fd));
+      fd_needs_closing = true;
+      parent_status = fstat (fd, &st);
+      if (parent_status != 0)
+        goto lose;
+
+      if (dirstream && __closedir (dirstream) != 0)
+        {
+          dirstream = NULL;
+          goto lose;
+        }
+
+      /* Figure out if this directory is a mount point.  */
+      dotdev = st.st_dev;
+      dotino = st.st_ino;
+      mount_point = dotdev != thisdev;
+
+      /* Search for the last directory.  */
+      {
+        int tmp_fd = dup (fd);
+        if (tmp_fd < 0)
+          goto lose;
+        dirstream = fdopendir (tmp_fd);
+      }
+      if (dirstream == NULL)
+        goto lose;
+      fd_needs_closing = false;
+      for (;;)
+        {
+          /* Clear errno to distinguish EOF from error if readdir returns
+             NULL.  */
+          __set_errno (0);
+          d = __readdir (dirstream);
+
+          /* When we've iterated through all directory entries without finding
+             one with a matching d_ino, rewind the stream and consider each
+             name again, but this time, using lstat.  This is necessary in a
+             chroot on at least one system (glibc-2.3.6 + linux 2.6.12), where
+             .., ../.., ../../.., etc. all had the same device number, yet the
+             d_ino values for entries in / did not match those obtained
+             via lstat.  */
+          if (d == NULL && errno == 0 && use_d_ino)
+            {
+              use_d_ino = false;
+              rewinddir (dirstream);
+              d = __readdir (dirstream);
+            }
+
+          if (d == NULL)
+            {
+              if (errno == 0)
+                /* EOF on dirstream, which can mean e.g., that the current
+                   directory has been removed.  */
+                __set_errno (ENOENT);
+              goto lose;
+            }
+          if (d->d_name[0] == '.' &&
+              (d->d_name[1] == '\0' ||
+               (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+            continue;
+
+          if (use_d_ino)
+            {
+              bool match = (MATCHING_INO (d, thisino) || mount_point);
+              if (! match)
+                continue;
+            }
+
+          {
+            int entry_status;
+            entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW);
+            assert (valid_fd (fd));
+            /* We don't fail here if we cannot stat() a directory entry.
+               This can happen when (network) file systems fail.  If this
+               entry is in fact the one we are looking for we will find
+               out soon as we reach the end of the directory without
+               having found anything.  */
+            if (entry_status == 0 && S_ISDIR (st.st_mode)
+                && st.st_dev == thisdev && st.st_ino == thisino)
+              break;
+          }
+        }
+
+      dirroom = dirp - dir;
+      namlen = _D_EXACT_NAMLEN (d);
+
+      if (dirroom <= namlen)
+        {
+          if (size != 0)
+            {
+              __set_errno (ERANGE);
+              goto lose;
+            }
+          else
+            {
+              char *tmp;
+              size_t oldsize = allocated;
+
+              allocated += MAX (allocated, namlen);
+              if (allocated < oldsize
+                  || ! (tmp = realloc (dir, allocated)))
+                goto memory_exhausted;
+
+              /* Move current contents up to the end of the buffer.
+                 This is guaranteed to be non-overlapping.  */
+              dirp = memcpy (tmp + allocated - (oldsize - dirroom),
+                             tmp + dirroom,
+                             oldsize - dirroom);
+              dir = tmp;
+            }
+        }
+      dirp -= namlen;
+      memcpy (dirp, d->d_name, namlen);
+      *--dirp = '/';
+
+      thisdev = dotdev;
+      thisino = dotino;
+    }
+
+  if (dirstream && __closedir (dirstream) != 0)
+    {
+      dirstream = NULL;
+      goto lose;
+    }
+
+  if (dirp == &dir[allocated - 1])
+    *--dirp = '/';
+
+  used = dir + allocated - dirp;
+  memmove (dir, dirp, used);
+
+  if (buf == NULL && size == 0)
+    /* Ensure that the buffer is only as large as necessary.  */
+    buf = realloc (dir, used);
+
+  if (buf == NULL)
+    /* Either buf was NULL all along, or `realloc' failed but
+       we still have the original string.  */
+    buf = dir;
+
+  close (fd);
+  return buf;
+
+ memory_exhausted:
+  __set_errno (ENOMEM);
+ lose:
+  {
+    int save = errno;
+    if (dirstream)
+      __closedir (dirstream);
+    if (fd_needs_closing)
+      close (fd);
+    if (buf == NULL)
+      free (dir);
+    __set_errno (save);
+  }
+  return NULL;
+}
diff --git a/lib/fgetcwd.h b/lib/fgetcwd.h
new file mode 100644
index 0000000..1793cf1
--- /dev/null
+++ b/lib/fgetcwd.h
@@ -0,0 +1,11 @@
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char *fgetcwd (int fd, char *buf, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/fts.c b/lib/fts.c
index 7210c1b..b318d6c 100644
--- a/lib/fts.c
+++ b/lib/fts.c
@@ -243,7 +243,7 @@ static int      fts_safe_changedir (FTS *, FTSENT *, int, 
const char *)
 # include <inttypes.h>
 # include <stdint.h>
 # include <stdio.h>
-# include "getcwdat.h"
+# include "fgetcwd.h"
 bool fts_debug = false;
 # define Dprintf(x) do { if (fts_debug) printf x; } while (false)
 #else
@@ -1612,7 +1612,7 @@ fd_ring_print (FTS const *sp, FILE *stream, char const 
*msg)
 {
   I_ring const *fd_ring = &sp->fts_fd_ring;
   unsigned int i = fd_ring->fts_front;
-  char *cwd = getcwdat (sp->fts_cwd_fd, NULL, 0);
+  char *cwd = fgetcwd (sp->fts_cwd_fd, NULL, 0);
   fprintf (stream, "=== %s ========== %s\n", msg, cwd);
   free (cwd);
   if (i_ring_empty (fd_ring))
@@ -1625,7 +1625,7 @@ fd_ring_print (FTS const *sp, FILE *stream, char const 
*msg)
         fprintf (stream, "%d: %d:\n", i, fd);
       else
         {
-          char *wd = getcwdat (fd, NULL, 0);
+          char *wd = fgetcwd (fd, NULL, 0);
           fprintf (stream, "%d: %d: %s\n", i, fd, wd);
           free (wd);
         }
@@ -1648,7 +1648,7 @@ fd_ring_check (FTS const *sp)

   int cwd_fd = sp->fts_cwd_fd;
   cwd_fd = dup (cwd_fd);
-  char *dot = getcwdat (cwd_fd, NULL, 0);
+  char *dot = fgetcwd (cwd_fd, NULL, 0);
   error (0, 0, "===== check ===== cwd: %s", dot);
   free (dot);
   while ( ! i_ring_empty (&fd_w))
@@ -1664,9 +1664,9 @@ fd_ring_check (FTS const *sp)
             }
           if (!same_fd (fd, parent_fd))
             {
-              char *cwd = getcwdat (fd, NULL, 0);
+              char *cwd = fgetcwd (fd, NULL, 0);
               error (0, errno, "ring  : %s", cwd);
-              char *c2 = getcwdat (parent_fd, NULL, 0);
+              char *c2 = fgetcwd (parent_fd, NULL, 0);
               error (0, errno, "parent: %s", c2);
               free (cwd);
               free (c2);
diff --git a/modules/fgetcwd b/modules/fgetcwd
new file mode 100644
index 0000000..d308709
--- /dev/null
+++ b/modules/fgetcwd
@@ -0,0 +1,32 @@
+Description:
+fgetcwd: like getcwd, but use a file descriptor open on a directory
+
+Files:
+lib/fgetcwd.c
+lib/fgetcwd.h
+
+Depends-on:
+unistd
+extensions
+mempcpy
+d-ino
+memmove
+openat
+stdbool
+malloc-posix
+strdup-posix
+
+configure.ac:
+gl_USE_SYSTEM_EXTENSIONS
+
+Makefile.am:
+lib_SOURCES += fgetcwd.c
+
+Include:
+"fgetcwd.h"
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering
diff --git a/modules/fgetcwd-tests b/modules/fgetcwd-tests
new file mode 100644
index 0000000..9a5a8be
--- /dev/null
+++ b/modules/fgetcwd-tests
@@ -0,0 +1,11 @@
+Files:
+tests/test-fgetcwd.c
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-fgetcwd
+check_PROGRAMS += test-fgetcwd
diff --git a/tests/test-fgetcwd.c b/tests/test-fgetcwd.c
new file mode 100644
index 0000000..0915588
--- /dev/null
+++ b/tests/test-fgetcwd.c
@@ -0,0 +1,43 @@
+/* Test fgetcwd.c
+   Copyright (C) 2011 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 <http://www.gnu.org/licenses/>.  */
+
+/* Written by Jim Meyering.  */
+
+#include <config.h>
+
+#include "fgetcwd.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include "macros.h"
+
+int
+main (void)
+{
+  int fd = open (".", O_RDONLY);
+  char *wd, *wd2;
+  ASSERT (0 <= fd);
+  wd = fgetcwd (fd, NULL, 0);
+  close (fd);
+  ASSERT (wd && *wd);
+  wd2 = getcwd (NULL, 0);
+  ASSERT (STREQ (wd, wd2));
+  free (wd);
+  free (wd2);
+  return 0;
+}
--
1.7.6.609.gbf6a9



reply via email to

[Prev in Thread] Current Thread [Next in Thread]