Replace two copies of readlink code with single gnulib version.
authorPaul Eggert <eggert@cs.ucla.edu>
Fri, 1 Apr 2011 06:28:48 +0000 (23:28 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Fri, 1 Apr 2011 06:28:48 +0000 (23:28 -0700)
13 files changed:
ChangeLog
Makefile.in
lib/allocator.h [new file with mode: 0644]
lib/careadlinkat.c [new file with mode: 0644]
lib/careadlinkat.h [new file with mode: 0644]
lib/gnulib.mk
m4/gl-comp.m4
m4/ssize_t.m4 [new file with mode: 0644]
src/ChangeLog
src/fileio.c
src/filelock.c
src/lisp.h
src/sysdep.c

index bf7a6af..979b3ef 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2011-04-01  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Replace two copies of readlink code with single gnulib version.
+       * Makefile.in (GNULIB_MODULES): Add careadlinkat.
+       * lib/allocator.h, lib/careadlinkat.c, lib/careadlinkat.h:
+       * m4/ssize_t.m4: New files, automatically generated from gnulib.
+
 2011-03-28  Glenn Morris  <rgm@gnu.org>
 
        * autogen/update_autogen: Pass -f to autoreconf.
index 699589c..1ac77ed 100644 (file)
@@ -331,7 +331,7 @@ DOS_gnulib_comp.m4 = gl-comp.m4
 # $(gnulib_srcdir) (relative to $(srcdir) and should have build tools
 # as per $(gnulib_srcdir)/DEPENDENCIES.
 GNULIB_MODULES = \
-  crypto/md5 dtoastr filemode getloadavg getopt-gnu \
+  careadlinkat crypto/md5 dtoastr filemode getloadavg getopt-gnu \
   ignore-value intprops lstat mktime readlink \
   socklen stdio strftime symlink sys_stat
 GNULIB_TOOL_FLAGS = \
diff --git a/lib/allocator.h b/lib/allocator.h
new file mode 100644 (file)
index 0000000..54cc5ff
--- /dev/null
@@ -0,0 +1,45 @@
+/* Memory allocators such as malloc+free.
+
+   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 Paul Eggert.  */
+
+#ifndef _GL_ALLOCATOR_H
+
+#include <stddef.h>
+
+struct allocator
+{
+  /* Call MALLOC to allocate memory, like 'malloc'.  On failure MALLOC
+     should return NULL, though not necessarily set errno.  When given
+     a zero size it may return NULL even if successful.  */
+  void *(*malloc) (size_t);
+
+  /* If nonnull, call REALLOC to reallocate memory, like 'realloc'.
+     On failure REALLOC should return NULL, though not necessarily set
+     errno.  When given a zero size it may return NULL even if
+     successful.  */
+  void *(*realloc) (void *, size_t);
+
+  /* Call FREE to free memory, like 'free'.  */
+  void (*free) (void *);
+
+  /* If nonnull, call DIE if MALLOC or REALLOC fails.  DIE should
+     not return.  */
+  void (*die) (void);
+};
+
+#endif
diff --git a/lib/careadlinkat.c b/lib/careadlinkat.c
new file mode 100644 (file)
index 0000000..828c050
--- /dev/null
@@ -0,0 +1,179 @@
+/* Read symbolic links into a buffer without size limitation, relative to fd.
+
+   Copyright (C) 2001, 2003-2004, 2007, 2009-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 Paul Eggert, Bruno Haible, and Jim Meyering.  */
+
+#include <config.h>
+
+#include "careadlinkat.h"
+
+#include "allocator.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Use the system functions, not the gnulib overrides, because this
+   module does not depend on GNU or POSIX semantics.  */
+#undef malloc
+#undef realloc
+
+/* Define this independently so that stdint.h is not a prerequisite.  */
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+#if ! HAVE_READLINKAT
+/* Ignore FD.  Get the symbolic link value of FILENAME and put it into
+   BUFFER, with size BUFFER_SIZE.  This function acts like readlink
+   but has readlinkat's signature.  */
+ssize_t
+careadlinkatcwd (int fd, char const *filename, char *buffer,
+                 size_t buffer_size)
+{
+  (void) fd;
+  return readlink (filename, buffer, buffer_size);
+}
+#endif
+
+/* Assuming the current directory is FD, get the symbolic link value
+   of FILENAME as a null-terminated string and put it into a buffer.
+   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
+   working directory, as in openat.
+
+   If the link is small enough to fit into BUFFER put it there.
+   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
+   if BUFFER_SIZE is zero.
+
+   If the link is not small, put it into a dynamically allocated
+   buffer managed by ALLOC.  It is the caller's responsibility to free
+   the returned value if it is nonnull and is not BUFFER.  A null
+   ALLOC stands for the standard allocator.
+
+   The PREADLINKAT function specifies how to read links.
+
+   If successful, return the buffer address; otherwise return NULL and
+   set errno.  */
+
+char *
+careadlinkat (int fd, char const *filename,
+              char *buffer, size_t buffer_size,
+              struct allocator const *alloc,
+              ssize_t (*preadlinkat) (int, char const *, char *, size_t))
+{
+  char *buf;
+  size_t buf_size;
+  size_t buf_size_max =
+    SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
+  char stack_buf[1024];
+
+  void *(*pmalloc) (size_t) = malloc;
+  void *(*prealloc) (void *, size_t) = realloc;
+  void (*pfree) (void *) = free;
+  void (*pdie) (void) = NULL;
+  if (alloc)
+    {
+      pmalloc = alloc->malloc;
+      prealloc = alloc->realloc;
+      pfree = alloc->free;
+      pdie = alloc->die;
+    }
+
+  if (! buffer_size)
+    {
+      /* Allocate the initial buffer on the stack.  This way, in the
+         common case of a symlink of small size, we get away with a
+         single small malloc() instead of a big malloc() followed by a
+         shrinking realloc().  */
+      buffer = stack_buf;
+      buffer_size = sizeof stack_buf;
+    }
+
+  buf = buffer;
+  buf_size = buffer_size;
+
+  do
+    {
+      /* Attempt to read the link into the current buffer.  */
+      ssize_t link_length = preadlinkat (fd, filename, buf, buf_size);
+      size_t link_size;
+      if (link_length < 0)
+        {
+          /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
+             with errno == ERANGE if the buffer is too small.  */
+          int readlinkat_errno = errno;
+          if (readlinkat_errno != ERANGE)
+            {
+              if (buf != buffer)
+                {
+                  pfree (buf);
+                  errno = readlinkat_errno;
+                }
+              return NULL;
+            }
+        }
+
+      link_size = link_length;
+
+      if (link_size < buf_size)
+        {
+          buf[link_size++] = '\0';
+
+          if (buf == stack_buf)
+            {
+              char *b = (char *) pmalloc (link_size);
+              if (! b)
+                break;
+              memcpy (b, buf, link_size);
+              buf = b;
+            }
+          else if (link_size < buf_size && buf != buffer && prealloc)
+            {
+              /* Shrink BUF before returning it.  */
+              char *b = (char *) prealloc (buf, link_size);
+              if (b)
+                buf = b;
+            }
+
+          return buf;
+        }
+
+      if (buf != buffer)
+        pfree (buf);
+
+      if (buf_size <= buf_size_max / 2)
+        buf_size *= 2;
+      else if (buf_size < buf_size_max)
+        buf_size = buf_size_max;
+      else
+        break;
+      buf = (char *) pmalloc (buf_size);
+    }
+  while (buf);
+
+  if (pdie)
+    pdie ();
+  errno = ENOMEM;
+  return NULL;
+}
diff --git a/lib/careadlinkat.h b/lib/careadlinkat.h
new file mode 100644 (file)
index 0000000..c5e4bcf
--- /dev/null
@@ -0,0 +1,67 @@
+/* Read symbolic links into a buffer without size limitation, relative to fd.
+
+   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 Paul Eggert, Bruno Haible, and Jim Meyering.  */
+
+#ifndef _GL_CAREADLINKAT_H
+
+#include <fcntl.h>
+#include <unistd.h>
+
+struct allocator;
+
+/* Assuming the current directory is FD, get the symbolic link value
+   of FILENAME as a null-terminated string and put it into a buffer.
+   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
+   working directory, as in openat.
+
+   If the link is small enough to fit into BUFFER put it there.
+   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
+   if BUFFER_SIZE is zero.
+
+   If the link is not small, put it into a dynamically allocated
+   buffer managed by ALLOC.  It is the caller's responsibility to free
+   the returned value if it is nonnull and is not BUFFER.
+
+   The PREADLINKAT function specifies how to read links.
+
+   If successful, return the buffer address; otherwise return NULL and
+   set errno.  */
+
+char *careadlinkat (int fd, char const *filename,
+                    char *buffer, size_t buffer_size,
+                    struct allocator const *alloc,
+                    ssize_t (*preadlinkat) (int, char const *,
+                                            char *, size_t));
+
+/* Suitable values for careadlinkat's FD and PREADLINKAT arguments,
+   when doing a plain readlink.  */
+#if HAVE_READLINKAT
+# define careadlinkatcwd readlinkat
+#else
+/* Define AT_FDCWD independently, so that the careadlinkat module does
+   not depend on the fcntl-h module.  The value does not matter, since
+   careadlinkatcwd ignores it, but we might as well use the same value
+   as fcntl-h.  */
+# ifndef AT_FDCWD
+#  define AT_FDCWD (-3041965)
+# endif
+ssize_t careadlinkatcwd (int fd, char const *filename,
+                         char *buffer, size_t buffer_size);
+#endif
+
+#endif /* _GL_CAREADLINKAT_H */
index 030f95b..bb5bdcf 100644 (file)
@@ -9,7 +9,7 @@
 # the same distribution terms as the rest of that program.
 #
 # Generated by gnulib-tool.
-# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --makefile-name=gnulib.mk --no-libtool --macro-prefix=gl --no-vc-files crypto/md5 dtoastr filemode getloadavg getopt-gnu ignore-value intprops lstat mktime readlink socklen stdio strftime symlink sys_stat
+# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=. --makefile-name=gnulib.mk --no-libtool --macro-prefix=gl --no-vc-files careadlinkat crypto/md5 dtoastr filemode getloadavg getopt-gnu ignore-value intprops lstat mktime readlink socklen stdio strftime symlink sys_stat
 
 
 MOSTLYCLEANFILES += core *.stackdump
@@ -69,6 +69,14 @@ EXTRA_DIST += $(top_srcdir)/./c++defs.h
 
 ## end   gnulib module c++defs
 
+## begin gnulib module careadlinkat
+
+libgnu_a_SOURCES += careadlinkat.c
+
+EXTRA_DIST += allocator.h careadlinkat.h
+
+## end   gnulib module careadlinkat
+
 ## begin gnulib module crypto/md5
 
 
index af3cae7..43cce9b 100644 (file)
@@ -28,6 +28,7 @@ AC_DEFUN([gl_EARLY],
   AC_REQUIRE([AC_PROG_RANLIB])
   # Code from module arg-nonnull:
   # Code from module c++defs:
+  # Code from module careadlinkat:
   # Code from module crypto/md5:
   # Code from module dosname:
   # Code from module dtoastr:
@@ -46,6 +47,7 @@ AC_DEFUN([gl_EARLY],
   # Code from module multiarch:
   # Code from module readlink:
   # Code from module socklen:
+  # Code from module ssize_t:
   # Code from module stat:
   # Code from module stdbool:
   # Code from module stddef:
@@ -79,6 +81,8 @@ AC_DEFUN([gl_INIT],
   gl_source_base='lib'
   # Code from module arg-nonnull:
   # Code from module c++defs:
+  # Code from module careadlinkat:
+  AC_CHECK_FUNCS_ONCE([readlinkat])
   # Code from module crypto/md5:
   gl_MD5
   # Code from module dosname:
@@ -115,6 +119,8 @@ AC_DEFUN([gl_INIT],
   gl_UNISTD_MODULE_INDICATOR([readlink])
   # Code from module socklen:
   gl_TYPE_SOCKLEN_T
+  # Code from module ssize_t:
+  gt_TYPE_SSIZE_T
   # Code from module stat:
   gl_FUNC_STAT
   gl_SYS_STAT_MODULE_INDICATOR([stat])
@@ -287,6 +293,9 @@ AC_DEFUN([gl_FILE_LIST], [
   build-aux/arg-nonnull.h
   build-aux/c++defs.h
   build-aux/warn-on-use.h
+  lib/allocator.h
+  lib/careadlinkat.c
+  lib/careadlinkat.h
   lib/dosname.h
   lib/dtoastr.c
   lib/filemode.c
@@ -335,6 +344,7 @@ AC_DEFUN([gl_FILE_LIST], [
   m4/multiarch.m4
   m4/readlink.m4
   m4/socklen.m4
+  m4/ssize_t.m4
   m4/st_dm_mode.m4
   m4/stat.m4
   m4/stdbool.m4
diff --git a/m4/ssize_t.m4 b/m4/ssize_t.m4
new file mode 100644 (file)
index 0000000..d712752
--- /dev/null
@@ -0,0 +1,23 @@
+# ssize_t.m4 serial 5 (gettext-0.18.2)
+dnl Copyright (C) 2001-2003, 2006, 2010-2011 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.
+
+dnl From Bruno Haible.
+dnl Test whether ssize_t is defined.
+
+AC_DEFUN([gt_TYPE_SSIZE_T],
+[
+  AC_CACHE_CHECK([for ssize_t], [gt_cv_ssize_t],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <sys/types.h>]],
+          [[int x = sizeof (ssize_t *) + sizeof (ssize_t);
+            return !x;]])],
+       [gt_cv_ssize_t=yes], [gt_cv_ssize_t=no])])
+  if test $gt_cv_ssize_t = no; then
+    AC_DEFINE([ssize_t], [int],
+              [Define as a signed type of the same size as size_t.])
+  fi
+])
index c2e2825..5649c88 100644 (file)
@@ -1,3 +1,15 @@
+2011-04-01  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Replace two copies of readlink code with single gnulib version.
+       The gnulib version avoids calling malloc in the usual case,
+       and on 64-bit hosts doesn't have some arbitrary 32-bit limits.
+       * fileio.c (Ffile_symlink_p): Use emacs_readlink.
+       * filelock.c (current_lock_owner): Likewise.
+       * lisp.h (READLINK_BUFSIZE, emacs_readlink): New function.
+       * sysdep.c: Include allocator.h, careadlinkat.h.
+       (emacs_no_realloc_allocator): New static constant.
+       (emacs_readlink): New function.
+
 2011-03-31  Juanma Barranquero  <lekktu@gmail.com>
 
        * xdisp.c (redisplay_internal): Fix prototype.
index 85431df..552044f 100644 (file)
@@ -2579,9 +2579,8 @@ points to a nonexistent file.  */)
 {
   Lisp_Object handler;
   char *buf;
-  int bufsize;
-  int valsize;
   Lisp_Object val;
+  char readlink_buf[READLINK_BUFSIZE];
 
   CHECK_STRING (filename);
   filename = Fexpand_file_name (filename, Qnil);
@@ -2594,36 +2593,15 @@ points to a nonexistent file.  */)
 
   filename = ENCODE_FILE (filename);
 
-  bufsize = 50;
-  buf = NULL;
-  do
-    {
-      bufsize *= 2;
-      buf = (char *) xrealloc (buf, bufsize);
-      memset (buf, 0, bufsize);
-
-      errno = 0;
-      valsize = readlink (SSDATA (filename), buf, bufsize);
-      if (valsize == -1)
-       {
-#ifdef ERANGE
-         /* HP-UX reports ERANGE if buffer is too small.  */
-         if (errno == ERANGE)
-           valsize = bufsize;
-         else
-#endif
-           {
-             xfree (buf);
-             return Qnil;
-           }
-       }
-    }
-  while (valsize >= bufsize);
+  buf = emacs_readlink (SSDATA (filename), readlink_buf);
+  if (! buf)
+    return Qnil;
 
-  val = make_string (buf, valsize);
+  val = build_string (buf);
   if (buf[0] == '/' && strchr (buf, ':'))
     val = concat2 (build_string ("/:"), val);
-  xfree (buf);
+  if (buf != readlink_buf)
+    xfree (buf);
   val = DECODE_FILE (val);
   return val;
 }
index 2138eaa..13b27c7 100644 (file)
@@ -396,36 +396,16 @@ within_one_second (time_t a, time_t b)
 static int
 current_lock_owner (lock_info_type *owner, char *lfname)
 {
-  int len, ret;
+  int ret;
+  size_t len;
   int local_owner = 0;
   char *at, *dot, *colon;
-  char *lfinfo = 0;
-  int bufsize = 50;
-  /* Read arbitrarily-long contents of symlink.  Similar code in
-     file-symlink-p in fileio.c.  */
-  do
-    {
-      bufsize *= 2;
-      lfinfo = (char *) xrealloc (lfinfo, bufsize);
-      errno = 0;
-      len = readlink (lfname, lfinfo, bufsize);
-#ifdef ERANGE
-      /* HP-UX reports ERANGE if the buffer is too small.  */
-      if (len == -1 && errno == ERANGE)
-       len = bufsize;
-#endif
-    }
-  while (len >= bufsize);
+  char readlink_buf[READLINK_BUFSIZE];
+  char *lfinfo = emacs_readlink (lfname, readlink_buf);
 
   /* If nonexistent lock file, all is well; otherwise, got strange error. */
-  if (len == -1)
-    {
-      xfree (lfinfo);
-      return errno == ENOENT ? 0 : -1;
-    }
-
-  /* Link info exists, so `len' is its length.  Null terminate.  */
-  lfinfo[len] = 0;
+  if (!lfinfo)
+    return errno == ENOENT ? 0 : -1;
 
   /* Even if the caller doesn't want the owner info, we still have to
      read it to determine return value, so allocate it.  */
@@ -441,7 +421,8 @@ current_lock_owner (lock_info_type *owner, char *lfname)
   dot = strrchr (lfinfo, '.');
   if (!at || !dot)
     {
-      xfree (lfinfo);
+      if (lfinfo != readlink_buf)
+       xfree (lfinfo);
       return -1;
     }
   len = at - lfinfo;
@@ -467,7 +448,8 @@ current_lock_owner (lock_info_type *owner, char *lfname)
   owner->host[len] = 0;
 
   /* We're done looking at the link info.  */
-  xfree (lfinfo);
+  if (lfinfo != readlink_buf)
+    xfree (lfinfo);
 
   /* On current host?  */
   if (STRINGP (Fsystem_name ())
index 85838d1..63f346f 100644 (file)
@@ -3340,6 +3340,8 @@ extern int emacs_open (const char *, int, int);
 extern int emacs_close (int);
 extern int emacs_read (int, char *, unsigned int);
 extern int emacs_write (int, const char *, unsigned int);
+enum { READLINK_BUFSIZE = 1024 };
+extern char *emacs_readlink (const char *, char [READLINK_BUFSIZE]);
 #ifndef HAVE_MEMSET
 extern void *memset (void *, int, size_t);
 #endif
index 1bb4004..a165a9c 100644 (file)
@@ -31,6 +31,8 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #endif /* HAVE_LIMITS_H */
 #include <unistd.h>
 
+#include <allocator.h>
+#include <careadlinkat.h>
 #include <ignore-value.h>
 
 #include "lisp.h"
@@ -1866,6 +1868,22 @@ emacs_write (int fildes, const char *buf, unsigned int nbyte)
     }
   return (bytes_written);
 }
+
+static struct allocator const emacs_norealloc_allocator =
+  { xmalloc, NULL, xfree, memory_full };
+
+/* Get the symbolic link value of FILENAME.  Return a pointer to a
+   NUL-terminated string.  If readlink fails, return NULL and set
+   errno.  If the value fits in INITIAL_BUF, return INITIAL_BUF.
+   Otherwise, allocate memory and return a pointer to that memory.  If
+   memory allocation fails, diagnose and fail without returning.  If
+   successful, store the length of the symbolic link into *LINKLEN.  */
+char *
+emacs_readlink (char const *filename, char initial_buf[READLINK_BUFSIZE])
+{
+  return careadlinkat (AT_FDCWD, filename, initial_buf, READLINK_BUFSIZE,
+                      &emacs_norealloc_allocator, careadlinkatcwd);
+}
 \f
 #ifdef USG
 /*