diff mbox

[libgfortran] PR60324 Handle arbitrarily long path names

Message ID CAO9iq9GrALPvDLes3vkeMsULZYOV4fFtUTuLvEwP6vw+5G8=Kw@mail.gmail.com
State New
Headers show

Commit Message

Janne Blomqvist May 19, 2014, 8:40 p.m. UTC
Hello,

some systems such as GNU Hurd, don't define PATH_MAX at all, and on
some other systems many syscalls apparently work for paths longer than
PATH_MAX. Thus GFortran shouldn't truncate paths to PATH_MAX
characters, but rather use heap allocated buffers limited only by the
available memory. The attached patch implements this, with the
exception of the backtrace functionality where we cannot use malloc
since backtrace might be called from a signal handler.

Regtested on x86_64-unknown-linux-gnu, Ok for trunk?

2014-05-19  Janne Blomqvist  <jb@gcc.gnu.org>

    PR libfortran/60324
    * config.h.in: Regenerated.
    * configure: Regenerated.
    * configure.ac (AC_CHECK_FUNCS_ONCE): Check for strnlen and
    strndup.
    * libgfortran.h (fc_strdup): New prototype.
    * runtime/string.c (strnlen): New fallback function.
    (strndup): New fallback function.
    (fc_strdup): New function.
    * io/close.c (st_close): Use fc_strdup.
    * io/open.c (new_unit): Likewise.
    (already_open): Likewise.
    * io/unit.c (filename_from_unit): Likewise.
    * io/unix.c (unpack_filename): Remove function.
    (regular_file): Rename to regular_file2, add path argument.
    (regular_file): New function calling regular_file2.
    (compare_file_filename): Use fc_strdup.
    (find_file): Likewise.
    (delete_file): Likewise.
    (file_exists): Likewise.
    (file_size): Likewise.
    (inquire_sequential): Likewise.
    (inquire_direct): Likewise.
    (inquire_formatted): Likewise.
    (inquire_access): Likewise.
    * io/unix.h (unpack_filename): Remove prototype.
    * runtime/main.c (please_free_exe_path_when_done): Change type to
    bool.
    (store_exe_path): Use malloced buffer, grow as needed.

Comments

Steve Kargl May 21, 2014, 1:35 p.m. UTC | #1
On Mon, May 19, 2014 at 11:40:06PM +0300, Janne Blomqvist wrote:
> Hello,
> 
> some systems such as GNU Hurd, don't define PATH_MAX at all, and on
> some other systems many syscalls apparently work for paths longer than
> PATH_MAX. Thus GFortran shouldn't truncate paths to PATH_MAX
> characters, but rather use heap allocated buffers limited only by the
> available memory. The attached patch implements this, with the
> exception of the backtrace functionality where we cannot use malloc
> since backtrace might be called from a signal handler.
> 
> Regtested on x86_64-unknown-linux-gnu, Ok for trunk?
> 
> 2014-05-19  Janne Blomqvist  <jb@gcc.gnu.org>
> 
>     PR libfortran/60324
>     * config.h.in: Regenerated.
>     * configure: Regenerated.
>     * configure.ac (AC_CHECK_FUNCS_ONCE): Check for strnlen and
>     strndup.
>     * libgfortran.h (fc_strdup): New prototype.

Janne, 

I read through the diff, and did not see anything that threw up
a caution sign.  I only have a cosmetic question.  Why a fc_
prefix instead of the usual gfc_ prefix?  Otherwise, I think
this is OK for trunk.
Janne Blomqvist May 22, 2014, 5:52 a.m. UTC | #2
On Wed, May 21, 2014 at 4:35 PM, Steve Kargl
<sgk@troutmask.apl.washington.edu> wrote:
> On Mon, May 19, 2014 at 11:40:06PM +0300, Janne Blomqvist wrote:
>> Hello,
>>
>> some systems such as GNU Hurd, don't define PATH_MAX at all, and on
>> some other systems many syscalls apparently work for paths longer than
>> PATH_MAX. Thus GFortran shouldn't truncate paths to PATH_MAX
>> characters, but rather use heap allocated buffers limited only by the
>> available memory. The attached patch implements this, with the
>> exception of the backtrace functionality where we cannot use malloc
>> since backtrace might be called from a signal handler.
>>
>> Regtested on x86_64-unknown-linux-gnu, Ok for trunk?
>>
>> 2014-05-19  Janne Blomqvist  <jb@gcc.gnu.org>
>>
>>     PR libfortran/60324
>>     * config.h.in: Regenerated.
>>     * configure: Regenerated.
>>     * configure.ac (AC_CHECK_FUNCS_ONCE): Check for strnlen and
>>     strndup.
>>     * libgfortran.h (fc_strdup): New prototype.
>
> Janne,
>
> I read through the diff, and did not see anything that threw up
> a caution sign.  I only have a cosmetic question.  Why a fc_
> prefix instead of the usual gfc_ prefix?  Otherwise, I think
> this is OK for trunk.

Thanks for the review. The fc_ prefix is shorthand for "fortran-to-c",
as fc_strdup converts a Fortran string to a C string. Similar to the
existing cf_strcpy and fstrlen.
Hans-Peter Nilsson May 22, 2014, 3:36 p.m. UTC | #3
On Mon, 19 May 2014, Janne Blomqvist wrote:
> Hello,
>
> some systems such as GNU Hurd, don't define PATH_MAX at all, and on
> some other systems many syscalls apparently work for paths longer than
> PATH_MAX. Thus GFortran shouldn't truncate paths to PATH_MAX
> characters, but rather use heap allocated buffers limited only by the
> available memory. The attached patch implements this, with the
> exception of the backtrace functionality where we cannot use malloc
> since backtrace might be called from a signal handler.
>
> Regtested on x86_64-unknown-linux-gnu, Ok for trunk?
>
> 2014-05-19  Janne Blomqvist  <jb@gcc.gnu.org>
>
>     PR libfortran/60324
>     * config.h.in: Regenerated.
>     * configure: Regenerated.
>     * configure.ac (AC_CHECK_FUNCS_ONCE): Check for strnlen and
>     strndup.
>     * libgfortran.h (fc_strdup): New prototype.
>     * runtime/string.c (strnlen): New fallback function.
>     (strndup): New fallback function.
>     (fc_strdup): New function.
>     * io/close.c (st_close): Use fc_strdup.
>     * io/open.c (new_unit): Likewise.
>     (already_open): Likewise.
>     * io/unit.c (filename_from_unit): Likewise.
>     * io/unix.c (unpack_filename): Remove function.
>     (regular_file): Rename to regular_file2, add path argument.
>     (regular_file): New function calling regular_file2.
>     (compare_file_filename): Use fc_strdup.
>     (find_file): Likewise.
>     (delete_file): Likewise.
>     (file_exists): Likewise.
>     (file_size): Likewise.
>     (inquire_sequential): Likewise.
>     (inquire_direct): Likewise.
>     (inquire_formatted): Likewise.
>     (inquire_access): Likewise.
>     * io/unix.h (unpack_filename): Remove prototype.
>     * runtime/main.c (please_free_exe_path_when_done): Change type to
>     bool.
>     (store_exe_path): Use malloced buffer, grow as needed.

This patch broke build for newlib targets; you need AC_DEFINE
clauses for those in the "if-then"-leg where you patched the
"else"-leg. There's also a need for #include <stdlib.h> for the
malloc use in runtime/string.c.

brgds, H-P
Janne Blomqvist May 22, 2014, 9:29 p.m. UTC | #4
On Thu, May 22, 2014 at 6:36 PM, Hans-Peter Nilsson <hp@bitrange.com> wrote:
> On Mon, 19 May 2014, Janne Blomqvist wrote:
>> Hello,
>>
>> some systems such as GNU Hurd, don't define PATH_MAX at all, and on
>> some other systems many syscalls apparently work for paths longer than
>> PATH_MAX. Thus GFortran shouldn't truncate paths to PATH_MAX
>> characters, but rather use heap allocated buffers limited only by the
>> available memory. The attached patch implements this, with the
>> exception of the backtrace functionality where we cannot use malloc
>> since backtrace might be called from a signal handler.
>>
>> Regtested on x86_64-unknown-linux-gnu, Ok for trunk?
>>
>> 2014-05-19  Janne Blomqvist  <jb@gcc.gnu.org>
>>
>>     PR libfortran/60324
>>     * config.h.in: Regenerated.
>>     * configure: Regenerated.
>>     * configure.ac (AC_CHECK_FUNCS_ONCE): Check for strnlen and
>>     strndup.
>>     * libgfortran.h (fc_strdup): New prototype.
>>     * runtime/string.c (strnlen): New fallback function.
>>     (strndup): New fallback function.
>>     (fc_strdup): New function.
>>     * io/close.c (st_close): Use fc_strdup.
>>     * io/open.c (new_unit): Likewise.
>>     (already_open): Likewise.
>>     * io/unit.c (filename_from_unit): Likewise.
>>     * io/unix.c (unpack_filename): Remove function.
>>     (regular_file): Rename to regular_file2, add path argument.
>>     (regular_file): New function calling regular_file2.
>>     (compare_file_filename): Use fc_strdup.
>>     (find_file): Likewise.
>>     (delete_file): Likewise.
>>     (file_exists): Likewise.
>>     (file_size): Likewise.
>>     (inquire_sequential): Likewise.
>>     (inquire_direct): Likewise.
>>     (inquire_formatted): Likewise.
>>     (inquire_access): Likewise.
>>     * io/unix.h (unpack_filename): Remove prototype.
>>     * runtime/main.c (please_free_exe_path_when_done): Change type to
>>     bool.
>>     (store_exe_path): Use malloced buffer, grow as needed.
>
> This patch broke build for newlib targets; you need AC_DEFINE
> clauses for those in the "if-then"-leg where you patched the
> "else"-leg.

Do I? The patch does include fallback implementations for strnlen and
strndup in case the target doesn't supply them. I don't know whether
newlib does, or if current versions do have it but old still in use
versions don't etc. I suggest that whoever was insisting on this
stupid hard-coded func list for newlib takes care of figuring that out
and, if it turns out that all relevant newlib versions do supply one
or both of those functions, posts a patch.

Or hmm, is there some symbol name conflict, in case we try to use a
fallback implementation but the target already defines it?

> There's also a need for #include <stdlib.h> for the
> malloc use in runtime/string.c.

This, OTOH, is clearly a bug. Committed the obvious bugfix as r210827,
thanks for reporting. (Annoying that glibc apparently defines malloc
via some other includes as well so I didn't notice it.)
diff mbox

Patch

diff --git a/libgfortran/configure.ac b/libgfortran/configure.ac
index 2126285..fb29c14 100644
--- a/libgfortran/configure.ac
+++ b/libgfortran/configure.ac
@@ -287,7 +287,7 @@  else
    strcasestr getrlimit gettimeofday stat fstat lstat getpwuid vsnprintf dup \
    getcwd localtime_r gmtime_r getpwuid_r ttyname_r clock_gettime \
    readlink getgid getpid getppid getuid geteuid umask getegid \
-   secure_getenv __secure_getenv mkostemp)
+   secure_getenv __secure_getenv mkostemp strnlen strndup)
 fi
 
 # Check strerror_r, cannot be above as versions with two and three arguments exist
diff --git a/libgfortran/io/close.c b/libgfortran/io/close.c
index 63da0c4..55f49da 100644
--- a/libgfortran/io/close.c
+++ b/libgfortran/io/close.c
@@ -72,8 +72,7 @@  st_close (st_parameter_close *clp)
 	    generate_error (&clp->common, LIBERROR_BAD_OPTION,
 			    "Can't KEEP a scratch file on CLOSE");
 #if !HAVE_UNLINK_OPEN_FILE
-	  path = (char *) gfc_alloca (u->file_len + 1);
-          unpack_filename (path, u->file, u->file_len);
+	  path = fc_strdup (u->file, u->file_len);
 #endif
 	}
       else
@@ -83,8 +82,7 @@  st_close (st_parameter_close *clp)
 #if HAVE_UNLINK_OPEN_FILE
 	      delete_file (u);
 #else
-	      path = (char *) gfc_alloca (u->file_len + 1);
-              unpack_filename (path, u->file, u->file_len);
+	      path = fc_strdup (u->file, u->file_len);
 #endif
             }
 	}
@@ -93,7 +91,10 @@  st_close (st_parameter_close *clp)
 
 #if !HAVE_UNLINK_OPEN_FILE
       if (path != NULL)
-        unlink (path);
+	{
+	  unlink (path);
+	  free (path);
+	}
 #endif
     }
 
diff --git a/libgfortran/io/open.c b/libgfortran/io/open.c
index 06fd594..b803859 100644
--- a/libgfortran/io/open.c
+++ b/libgfortran/io/open.c
@@ -502,12 +502,9 @@  new_unit (st_parameter_open *opp, gfc_unit *u, unit_flags * flags)
   s = open_external (opp, flags);
   if (s == NULL)
     {
-      char *path, *msg;
-      size_t msglen;
-      path = (char *) gfc_alloca (opp->file_len + 1);
-      msglen = opp->file_len + 51;
-      msg = (char *) gfc_alloca (msglen);
-      unpack_filename (path, opp->file, opp->file_len);
+      char *path = fc_strdup (opp->file, opp->file_len);
+      size_t msglen = opp->file_len + 51;
+      char *msg = xmalloc (msglen);
 
       switch (errno)
 	{
@@ -529,10 +526,13 @@  new_unit (st_parameter_open *opp, gfc_unit *u, unit_flags * flags)
 	  break;
 
 	default:
+	  free (msg);
 	  msg = NULL;
 	}
 
       generate_error (&opp->common, LIBERROR_OS, msg);
+      free (msg);
+      free (path);
       goto cleanup;
     }
 
@@ -676,15 +676,6 @@  already_open (st_parameter_open *opp, gfc_unit * u, unit_flags * flags)
 
   if (!compare_file_filename (u, opp->file, opp->file_len))
     {
-#if !HAVE_UNLINK_OPEN_FILE
-      char *path = NULL;
-      if (u->file && u->flags.status == STATUS_SCRATCH)
-	{
-	  path = (char *) gfc_alloca (u->file_len + 1);
-	  unpack_filename (path, u->file, u->file_len);
-	}
-#endif
-
       if (sclose (u->s) == -1)
 	{
 	  unlock_unit (u);
@@ -699,8 +690,14 @@  already_open (st_parameter_open *opp, gfc_unit * u, unit_flags * flags)
       u->file_len = 0;
 
 #if !HAVE_UNLINK_OPEN_FILE
+      char *path = NULL;
+      if (u->file && u->flags.status == STATUS_SCRATCH)
+	path = fc_strdup (u->file, u->file_len);
       if (path != NULL)
-	unlink (path);
+	{
+	  unlink (path);
+	  free (path);
+	}
 #endif
 
       u = new_unit (opp, u, flags);
diff --git a/libgfortran/io/unit.c b/libgfortran/io/unit.c
index 385818a..a406c9e 100644
--- a/libgfortran/io/unit.c
+++ b/libgfortran/io/unit.c
@@ -786,7 +786,6 @@  unit_truncate (gfc_unit * u, gfc_offset pos, st_parameter_common * common)
 char *
 filename_from_unit (int n)
 {
-  char *filename;
   gfc_unit *u;
   int c;
 
@@ -805,11 +804,7 @@  filename_from_unit (int n)
 
   /* Get the filename.  */
   if (u != NULL)
-    {
-      filename = (char *) xmalloc (u->file_len + 1);
-      unpack_filename (filename, u->file, u->file_len);
-      return filename;
-    }
+    return fc_strdup (u->file, u->file_len);
   else
     return (char *) NULL;
 }
diff --git a/libgfortran/io/unix.c b/libgfortran/io/unix.c
index 76ed84e..3721b71 100644
--- a/libgfortran/io/unix.c
+++ b/libgfortran/io/unix.c
@@ -114,9 +114,6 @@  id_from_fd (const int fd)
     typeof (b) _b = (b);	\
     _a < _b ? _a : _b; })
 
-#ifndef PATH_MAX
-#define PATH_MAX 1024
-#endif
 
 /* These flags aren't defined on all targets (mingw32), so provide them
    here.  */
@@ -1060,26 +1057,6 @@  unit_to_fd (int unit)
 }
 
 
-/* unpack_filename()-- Given a fortran string and a pointer to a
- * buffer that is PATH_MAX characters, convert the fortran string to a
- * C string in the buffer.  Returns nonzero if this is not possible.  */
-
-int
-unpack_filename (char *cstring, const char *fstring, int len)
-{
-  if (fstring == NULL)
-    return EFAULT;
-  len = fstrlen (fstring, len);
-  if (len >= PATH_MAX)
-    return ENAMETOOLONG;
-
-  memmove (cstring, fstring, len);
-  cstring[len] = '\0';
-
-  return 0;
-}
-
-
 /* Set the close-on-exec flag for an existing fd, if the system
    supports such.  */
 
@@ -1244,27 +1221,18 @@  tempfile (st_parameter_open *opp)
 }
 
 
-/* regular_file()-- Open a regular file.
+/* regular_file2()-- Open a regular file.
  * Change flags->action if it is ACTION_UNSPECIFIED on entry,
  * unless an error occurs.
  * Returns the descriptor, which is less than zero on error. */
 
 static int
-regular_file (st_parameter_open *opp, unit_flags *flags)
+regular_file2 (const char *path, st_parameter_open *opp, unit_flags *flags)
 {
-  char path[min(PATH_MAX, opp->file_len + 1)];
   int mode;
   int rwflag;
   int crflag, crflag2;
   int fd;
-  int err;
-
-  err = unpack_filename (path, opp->file, opp->file_len);
-  if (err)
-    {
-      errno = err;		/* Fake an OS error */
-      return -1;
-    }
 
 #ifdef __CYGWIN__
   if (opp->file_len == 7)
@@ -1404,6 +1372,18 @@  regular_file (st_parameter_open *opp, unit_flags *flags)
 }
 
 
+/* Wrapper around regular_file2, to make sure we free the path after
+   we're done.  */
+
+static int
+regular_file (st_parameter_open *opp, unit_flags *flags)
+{
+  char *path = fc_strdup (opp->file, opp->file_len);
+  int fd = regular_file2 (path, opp, flags);
+  free (path);
+  return fd;
+}
+
 /* open_external()-- Open an external file, unix specific version.
  * Change flags->action if it is ACTION_UNSPECIFIED on entry.
  * Returns NULL on operating system error. */
@@ -1494,8 +1474,8 @@  error_stream (void)
 int
 compare_file_filename (gfc_unit *u, const char *name, int len)
 {
-  char path[min(PATH_MAX, len + 1)];
   struct stat st;
+  int ret;
 #ifdef HAVE_WORKING_STAT
   unix_stream *s;
 #else
@@ -1504,18 +1484,21 @@  compare_file_filename (gfc_unit *u, const char *name, int len)
 # endif
 #endif
 
-  if (unpack_filename (path, name, len))
-    return 0;			/* Can't be the same */
+  char *path = fc_strdup (name, len);
 
   /* If the filename doesn't exist, then there is no match with the
    * existing file. */
 
   if (stat (path, &st) < 0)
-    return 0;
+    {
+      ret = 0;
+      goto done;
+    }
 
 #ifdef HAVE_WORKING_STAT
   s = (unix_stream *) (u->s);
-  return (st.st_dev == s->st_dev) && (st.st_ino == s->st_ino);
+  ret = (st.st_dev == s->st_dev) && (st.st_ino == s->st_ino);
+  goto done;
 #else
 
 # ifdef __MINGW32__
@@ -1525,13 +1508,20 @@  compare_file_filename (gfc_unit *u, const char *name, int len)
   id1 = id_from_path (path);
   id2 = id_from_fd (((unix_stream *) (u->s))->fd);
   if (id1 || id2)
-    return (id1 == id2);
+    {
+      ret = (id1 == id2);
+      goto done;
+    }
 # endif
 
   if (len != u->file_len)
-    return 0;
-  return (memcmp(path, u->file, len) == 0);
+    ret = 0;
+  else
+    ret = (memcmp(path, u->file, len) == 0);
 #endif
+ done:
+  free (path);
+  return ret;
 }
 
 
@@ -1594,18 +1584,19 @@  find_file0 (gfc_unit *u, FIND_FILE0_DECL)
 gfc_unit *
 find_file (const char *file, gfc_charlen_type file_len)
 {
-  char path[min(PATH_MAX, file_len + 1)];
   struct stat st[1];
   gfc_unit *u;
 #if defined(__MINGW32__) && !HAVE_WORKING_STAT
   uint64_t id = 0ULL;
 #endif
 
-  if (unpack_filename (path, file, file_len))
-    return NULL;
+  char *path = fc_strdup (file, file_len);
 
   if (stat (path, &st[0]) < 0)
-    return NULL;
+    {
+      u = NULL;
+      goto done;
+    }
 
 #if defined(__MINGW32__) && !HAVE_WORKING_STAT
   id = id_from_path (path);
@@ -1621,7 +1612,7 @@  retry:
 	{
 	  /* assert (u->closed == 0); */
 	  __gthread_mutex_unlock (&unit_lock);
-	  return u;
+	  goto done;
 	}
 
       inc_waiting_locked (u);
@@ -1641,6 +1632,8 @@  retry:
 
       dec_waiting_unlocked (u);
     }
+ done:
+  free (path);
   return u;
 }
 
@@ -1713,16 +1706,10 @@  flush_all_units (void)
 int
 delete_file (gfc_unit * u)
 {
-  char path[min(PATH_MAX, u->file_len + 1)];
-  int err = unpack_filename (path, u->file, u->file_len);
-
-  if (err)
-    {				/* Shouldn't be possible */
-      errno = err;
-      return 1;
-    }
-
-  return unlink (path);
+  char *path = fc_strdup (u->file, u->file_len);
+  int err = unlink (path);
+  free (path);
+  return err;
 }
 
 
@@ -1732,12 +1719,10 @@  delete_file (gfc_unit * u)
 int
 file_exists (const char *file, gfc_charlen_type file_len)
 {
-  char path[min(PATH_MAX, file_len + 1)];
-
-  if (unpack_filename (path, file, file_len))
-    return 0;
-
-  return !(access (path, F_OK));
+  char *path = fc_strdup (file, file_len);
+  int res = !(access (path, F_OK));
+  free (path);
+  return res;
 }
 
 
@@ -1746,15 +1731,12 @@  file_exists (const char *file, gfc_charlen_type file_len)
 GFC_IO_INT
 file_size (const char *file, gfc_charlen_type file_len)
 {
-  char path[min(PATH_MAX, file_len + 1)];
+  char *path = fc_strdup (file, file_len);
   struct stat statbuf;
-
-  if (unpack_filename (path, file, file_len))
-    return -1;
-
-  if (stat (path, &statbuf) < 0)
+  int err = stat (path, &statbuf);
+  free (path);
+  if (err == -1)
     return -1;
-
   return (GFC_IO_INT) statbuf.st_size;
 }
 
@@ -1767,11 +1749,15 @@  static const char yes[] = "YES", no[] = "NO", unknown[] = "UNKNOWN";
 const char *
 inquire_sequential (const char *string, int len)
 {
-  char path[min(PATH_MAX, len + 1)];
   struct stat statbuf;
 
-  if (string == NULL ||
-      unpack_filename (path, string, len) || stat (path, &statbuf) < 0)
+  if (string == NULL)
+    return unknown;
+
+  char *path = fc_strdup (string, len);
+  int err = stat (path, &statbuf);
+  free (path);
+  if (err == -1)
     return unknown;
 
   if (S_ISREG (statbuf.st_mode) ||
@@ -1791,11 +1777,15 @@  inquire_sequential (const char *string, int len)
 const char *
 inquire_direct (const char *string, int len)
 {
-  char path[min(PATH_MAX, len + 1)];
   struct stat statbuf;
 
-  if (string == NULL ||
-      unpack_filename (path, string, len) || stat (path, &statbuf) < 0)
+  if (string == NULL)
+    return unknown;
+
+  char *path = fc_strdup (string, len);
+  int err = stat (path, &statbuf);
+  free (path);
+  if (err == -1)
     return unknown;
 
   if (S_ISREG (statbuf.st_mode) || S_ISBLK (statbuf.st_mode))
@@ -1815,11 +1805,15 @@  inquire_direct (const char *string, int len)
 const char *
 inquire_formatted (const char *string, int len)
 {
-  char path[min(PATH_MAX, len + 1)];
   struct stat statbuf;
 
-  if (string == NULL ||
-      unpack_filename (path, string, len) || stat (path, &statbuf) < 0)
+  if (string == NULL)
+    return unknown;
+
+  char *path = fc_strdup (string, len);
+  int err = stat (path, &statbuf);
+  free (path);
+  if (err == -1)
     return unknown;
 
   if (S_ISREG (statbuf.st_mode) ||
@@ -1850,10 +1844,12 @@  inquire_unformatted (const char *string, int len)
 static const char *
 inquire_access (const char *string, int len, int mode)
 {
-  char path[min(PATH_MAX, len + 1)];
-
-  if (string == NULL || unpack_filename (path, string, len) ||
-      access (path, mode) < 0)
+  if (string == NULL)
+    return no;
+  char *path = fc_strdup (string, len);
+  int res = access (path, mode);
+  free (path);
+  if (res == -1)
     return no;
 
   return yes;
diff --git a/libgfortran/io/unix.h b/libgfortran/io/unix.h
index 3756110..910f2c2 100644
--- a/libgfortran/io/unix.h
+++ b/libgfortran/io/unix.h
@@ -185,8 +185,4 @@  internal_proto(stream_isatty);
 extern int stream_ttyname (stream *, char *, size_t);
 internal_proto(stream_ttyname);
 
-extern int unpack_filename (char *, const char *, int);
-internal_proto(unpack_filename);
-
-
 #endif
diff --git a/libgfortran/libgfortran.h b/libgfortran/libgfortran.h
index 0d6f432..ba6c1e9 100644
--- a/libgfortran/libgfortran.h
+++ b/libgfortran/libgfortran.h
@@ -822,6 +822,9 @@  extern gfc_charlen_type string_len_trim_char4 (gfc_charlen_type,
 					       const gfc_char4_t *);
 export_proto(string_len_trim_char4);
 
+extern char *fc_strdup(const char *, gfc_charlen_type);
+internal_proto(fc_strdup);
+
 /* io/intrinsics.c */
 
 extern void flush_all_units (void);
diff --git a/libgfortran/runtime/main.c b/libgfortran/runtime/main.c
index 58ec6cc..a52e0a8 100644
--- a/libgfortran/runtime/main.c
+++ b/libgfortran/runtime/main.c
@@ -26,6 +26,7 @@  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include <errno.h>
 
 
 #ifdef HAVE_UNISTD_H
@@ -70,23 +71,18 @@  static int argc_save;
 static char **argv_save;
 
 static const char *exe_path;
-static int please_free_exe_path_when_done;
+static bool please_free_exe_path_when_done;
 
 /* Save the path under which the program was called, for use in the
    backtrace routines.  */
 void
 store_exe_path (const char * argv0)
 {
-#ifndef PATH_MAX
-#define PATH_MAX 1024
-#endif
-
 #ifndef DIR_SEPARATOR   
 #define DIR_SEPARATOR '/'
 #endif
 
-  char buf[PATH_MAX], *path;
-  const char *cwd;
+  char *cwd, *path;
 
   /* This can only happen if store_exe_path is called multiple times.  */
   if (please_free_exe_path_when_done)
@@ -95,13 +91,27 @@  store_exe_path (const char * argv0)
   /* Reading the /proc/self/exe symlink is Linux-specific(?), but if
      it works it gives the correct answer.  */
 #ifdef HAVE_READLINK
-  int len;
-  if ((len = readlink ("/proc/self/exe", buf, sizeof (buf) - 1)) != -1)
+  ssize_t len, psize = 256;
+  while (1)
     {
-      buf[len] = '\0';
-      exe_path = strdup (buf);
-      please_free_exe_path_when_done = 1;
-      return;
+      path = xmalloc (psize);
+      len = readlink ("/proc/self/exe", path, psize);
+      if (len < 0)
+	{
+	  free (path);
+	  break;
+	}
+      else if (len < psize)
+	{
+	  path[len] = '\0';
+	  exe_path = strdup (path);
+	  free (path);
+	  please_free_exe_path_when_done = true;
+	  return;
+	}
+      /* The remaining option is len == psize.  */
+      free (path);
+      psize *= 2;
     }
 #endif
 
@@ -117,12 +127,29 @@  store_exe_path (const char * argv0)
 #endif
     {
       exe_path = argv0;
-      please_free_exe_path_when_done = 0;
+      please_free_exe_path_when_done = false;
       return;
     }
 
 #ifdef HAVE_GETCWD
-  cwd = getcwd (buf, sizeof (buf));
+  size_t cwdsize = 256;
+  while (1)
+    {
+      cwd = xmalloc (cwdsize);
+      if (getcwd (cwd, cwdsize))
+	break;
+      else if (errno == ERANGE)
+	{
+	  free (cwd);
+	  cwdsize *= 2;
+	}
+      else
+	{
+	  free (cwd);
+	  cwd = NULL;
+	  break;
+	}
+    }
 #else
   cwd = NULL;
 #endif
@@ -130,7 +157,7 @@  store_exe_path (const char * argv0)
   if (!cwd)
     {
       exe_path = argv0;
-      please_free_exe_path_when_done = 0;
+      please_free_exe_path_when_done = false;
       return;
     }
 
@@ -138,10 +165,11 @@  store_exe_path (const char * argv0)
      if the executable is not in the cwd, but at this point we're out
      of better ideas.  */
   size_t pathlen = strlen (cwd) + 1 + strlen (argv0) + 1;
-  path = malloc (pathlen);
+  path = xmalloc (pathlen);
   snprintf (path, pathlen, "%s%c%s", cwd, DIR_SEPARATOR, argv0);
+  free (cwd);
   exe_path = path;
-  please_free_exe_path_when_done = 1;
+  please_free_exe_path_when_done = true;
 }
 
 
diff --git a/libgfortran/runtime/string.c b/libgfortran/runtime/string.c
index a7f68bf..5beb0fb 100644
--- a/libgfortran/runtime/string.c
+++ b/libgfortran/runtime/string.c
@@ -90,6 +90,49 @@  cf_strcpy (char *dest, gfc_charlen_type dest_len, const char *src)
 }
 
 
+#ifndef HAVE_STRNLEN
+static size_t
+strnlen (const char *s, size_t maxlen)
+{
+  for (size_t ii = 0; ii < maxlen; ii++)
+    {
+      if (s[ii] == '\0')
+	return ii;
+    }
+  return maxlen;
+}
+#endif
+
+
+#ifndef HAVE_STRNDUP
+static char *
+strndup (const char *s, size_t n)
+{
+  size_t len = strnlen (s, n);
+  char *p = malloc (len + 1);
+  if (!p)
+    return NULL;
+  memcpy (p, s, len);
+  p[len] = '\0';
+  return p;
+}
+#endif
+
+
+/* Duplicate a non-null-terminated Fortran string to a malloced
+   null-terminated C string.  */
+
+char *
+fc_strdup (const char *src, gfc_charlen_type src_len)
+{
+  gfc_charlen_type n = fstrlen (src, src_len);
+  char *p = strndup (src, n);
+  if (!p)
+    os_error ("Memory allocation failed in fc_strdup");
+  return p;
+}
+
+
 /* Given a fortran string and an array of st_option structures, search through
    the array to find a match.  If the option is not found, we generate an error
    if no default is provided.  */