diff --git a/libguile/filesys.c b/libguile/filesys.c index 7c4819f41..371d03aaf 100644 --- a/libguile/filesys.c +++ b/libguile/filesys.c @@ -28,6 +28,23 @@ # include #endif +/* AIX requires this to be the first thing in the file. The #pragma + directive is indented so pre-ANSI compilers will ignore it, rather + than choke on it. */ +#ifndef __GNUC__ +# if HAVE_ALLOCA_H +# include +# else +# ifdef _AIX +# pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ +char *alloca (); +# endif +# endif +# endif +#endif + #include #include @@ -35,6 +52,7 @@ #include "libguile/smob.h" #include "libguile/feature.h" #include "libguile/fports.h" +#include "libguile/private-gc.h" /* for SCM_MAX */ #include "libguile/iselect.h" #include "libguile/strings.h" #include "libguile/vectors.h" @@ -92,6 +110,7 @@ #if defined (__MINGW32__) || defined (_MSC_VER) || defined (__BORLANDC__) # include "win32-dirent.h" # define NAMLEN(dirent) strlen((dirent)->d_name) +/* The following bits are per AC_HEADER_DIRENT doco in the autoconf manual */ #elif HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) @@ -178,6 +197,13 @@ # define fsync(fd) _commit (fd) # define fchmod(fd, mode) (-1) #endif /* __MINGW32__ */ + +/* This definition is for Solaris 10, it's probably not right elsewhere, but + that's ok, it shouldn't be used elsewhere. */ +#if ! HAVE_DIRFD +#define dirfd(dirstream) (dirstream->dd_fd) +#endif + /* Two helper macros for an often used pattern */ @@ -831,8 +857,9 @@ SCM_DEFINE (scm_opendir, "opendir", 1, 0, 0, /* FIXME: The glibc manual has a portability note that readdir_r may not null-terminate its return string. The circumstances outlined for this - are not clear, nor is it clear what should be done about it. Lets worry - about this if/when someone can figure it out. */ + are not clear, nor is it clear what should be done about it. Lets use + NAMLEN and worry about what else should be done if/when someone can + figure it out. */ SCM_DEFINE (scm_readdir, "readdir", 1, 0, 0, (SCM port), @@ -847,29 +874,67 @@ SCM_DEFINE (scm_readdir, "readdir", 1, 0, 0, if (!SCM_DIR_OPEN_P (port)) SCM_MISC_ERROR ("Directory ~S is not open.", scm_list_1 (port)); - errno = 0; - { #if HAVE_READDIR_R - /* On Solaris 2.7, struct dirent only contains "char d_name[1]" and one is - expected to provide a buffer of "sizeof(struct dirent) + NAME_MAX" - bytes. The glibc 2.3.2 manual notes this sort of thing too, and - advises "offsetof(struct dirent,d_name) + NAME_MAX + 1". Either should - suffice, we give both to be certain. */ - union { - struct dirent ent; - char pad1 [sizeof(struct dirent) + NAME_MAX]; - char pad2 [offsetof (struct dirent, d_name) + NAME_MAX + 1]; - } u; - SCM_SYSCALL (readdir_r ((DIR *) SCM_CELL_WORD_1 (port), &u.ent, &rdent)); + /* As noted in the glibc manual, on various systems (such as Solaris) the + d_name[] field is only 1 char and you're expected to size the dirent + buffer for readdir_r based on NAME_MAX. The SCM_MAX expressions below + effectively give either sizeof(d_name) or NAME_MAX+1, whichever is + bigger. + + On solaris 10 there's no NAME_MAX constant, it's necessary to use + pathconf(). We prefer NAME_MAX though, since it should be a constant + and will therefore save a system call. We also prefer it since dirfd() + is not available everywhere. + + An alternative to dirfd() would be to open() the directory and then use + fdopendir(), if the latter is available. That'd let us hold the fd + somewhere in the smob, or just the dirent size calculated once. */ + { + struct dirent de; /* just for sizeof */ + DIR *ds = (DIR *) SCM_CELL_WORD_1 (port); + size_t namlen; +#ifdef NAME_MAX + char buf [SCM_MAX (sizeof (de), + sizeof (de) - sizeof (de.d_name) + NAME_MAX + 1)]; #else - SCM_SYSCALL (rdent = readdir ((DIR *) SCM_CELL_WORD_1 (port))); + char *buf; + long name_max = fpathconf (dirfd (ds), _PC_NAME_MAX); + if (name_max == -1) + SCM_SYSERROR; + buf = alloca (SCM_MAX (sizeof (de), + sizeof (de) - sizeof (de.d_name) + name_max + 1)); #endif + + errno = 0; + SCM_SYSCALL (readdir_r (ds, (struct dirent *) buf, &rdent)); if (errno != 0) SCM_SYSERROR; + if (! rdent) + return SCM_EOF_VAL; + + namlen = NAMLEN (rdent); return (rdent ? scm_from_locale_stringn (rdent->d_name, NAMLEN (rdent)) : SCM_EOF_VAL); } +#else + { + SCM ret; + scm_dynwind_begin (0); + scm_i_dynwind_pthread_mutex_lock (&scm_i_misc_mutex); + + errno = 0; + SCM_SYSCALL (rdent = readdir ((DIR *) SCM_CELL_WORD_1 (port))); + if (errno != 0) + SCM_SYSERROR; + + ret = (rdent ? scm_from_locale_stringn (rdent->d_name, NAMLEN (rdent)) + : SCM_EOF_VAL); + + scm_dynwind_end (); + return ret; + } +#endif } #undef FUNC_NAME