diff options
author | Jeremy Allison <jra@samba.org> | 2006-03-27 19:50:45 +0000 |
---|---|---|
committer | Jeremy Allison <jra@samba.org> | 2006-03-27 19:50:45 +0000 |
commit | 512f3964a00a8c85ed489a558fbc70f5df7d8820 (patch) | |
tree | aa1f5a2dcd492d920f829f32a7b37af791396183 /source | |
parent | ce5a6ad19c08d0e5909b55876ed410c07212df4a (diff) | |
download | samba-512f3964a00a8c85ed489a558fbc70f5df7d8820.tar.gz samba-512f3964a00a8c85ed489a558fbc70f5df7d8820.tar.xz samba-512f3964a00a8c85ed489a558fbc70f5df7d8820.zip |
r14746: Add the Samba4 replacements for opendir/readdir etc. to
Samba3 - with some 64-bit macro madness. Attempt to fix
the broken directory handling in the *BSD-of-the-month
club.
Jeremy.
Diffstat (limited to 'source')
-rw-r--r-- | source/Makefile.in | 2 | ||||
-rw-r--r-- | source/configure.in | 29 | ||||
-rw-r--r-- | source/lib/repdir.c | 218 | ||||
-rw-r--r-- | source/lib/util.c | 2 | ||||
-rw-r--r-- | source/smbd/vfs.c | 2 | ||||
-rw-r--r-- | source/tests/os2_delete.c | 107 |
6 files changed, 352 insertions, 8 deletions
diff --git a/source/Makefile.in b/source/Makefile.in index 5f58e0ff3ba..7d37fa5e5d1 100644 --- a/source/Makefile.in +++ b/source/Makefile.in @@ -204,7 +204,7 @@ RPC_PARSE_OBJ0 = rpc_parse/parse_prs.o rpc_parse/parse_misc.o LIB_OBJ = $(VERSION_OBJ) lib/charcnv.o lib/debug.o lib/fault.o \ lib/getsmbpass.o lib/interface.o lib/md4.o \ - lib/interfaces.o lib/pidfile.o lib/replace.o lib/replace1.o \ + lib/interfaces.o lib/pidfile.o lib/replace.o lib/replace1.o lib/repdir.o \ lib/signal.o lib/system.o lib/sendfile.o lib/time.o \ lib/ufc.o lib/genrand.o lib/username.o \ lib/util_pw.o lib/access.o lib/smbrun.o \ diff --git a/source/configure.in b/source/configure.in index 7fb2b827a0f..2a3b067da9a 100644 --- a/source/configure.in +++ b/source/configure.in @@ -1237,6 +1237,7 @@ AC_CHECK_FUNCS(setpriv setgidx setuidx setgroups sysconf mktime rename ftruncate AC_CHECK_FUNCS(lstat64 fopen64 atexit grantpt dup2 lseek64 ftruncate64) AC_CHECK_FUNCS(fseek64 fseeko64 ftell64 ftello64 setluid getpwanam setlinebuf) AC_CHECK_FUNCS(opendir64 readdir64 seekdir64 telldir64 rewinddir64 closedir64) +AC_CHECK_FUNCS(getdents getdents64) AC_CHECK_FUNCS(srandom random srand rand setenv usleep strcasecmp fcvt fcvtl symlink readlink) AC_CHECK_FUNCS(syslog vsyslog timegm) AC_CHECK_FUNCS(setlocale nl_langinfo) @@ -1256,7 +1257,7 @@ AC_CHECK_FUNCS(__xstat __fxstat __lxstat) AC_CHECK_FUNCS(_stat _lstat _fstat __stat __lstat __fstat) AC_CHECK_FUNCS(_acl __acl _facl __facl _open __open _chdir __chdir) AC_CHECK_FUNCS(_close __close _fchdir __fchdir _fcntl __fcntl) -AC_CHECK_FUNCS(getdents _getdents __getdents _lseek __lseek _read __read) +AC_CHECK_FUNCS(getdents __getdents _lseek __lseek _read __read) AC_CHECK_FUNCS(getdirentries _write __write _fork __fork) AC_CHECK_FUNCS(_stat64 __stat64 _fstat64 __fstat64 _lstat64 __lstat64) AC_CHECK_FUNCS(__sys_llseek llseek _llseek __llseek readdir64 _readdir64 __readdir64) @@ -1976,15 +1977,15 @@ if test x"$samba_cv_HAVE_C99_VSNPRINTF" = x"yes"; then AC_DEFINE(HAVE_C99_VSNPRINTF,1,[Whether there is a C99 compliant vsnprintf]) fi -AC_CACHE_CHECK([for broken readdir],samba_cv_HAVE_BROKEN_READDIR,[ +AC_CACHE_CHECK([for broken readdir name],samba_cv_HAVE_BROKEN_READDIR_NAME,[ AC_TRY_RUN([#include <sys/types.h> #include <dirent.h> main() { struct dirent *di; DIR *d = opendir("."); di = readdir(d); if (di && di->d_name[-2] == '.' && di->d_name[-1] == 0 && di->d_name[0] == 0) exit(0); exit(1);} ], -samba_cv_HAVE_BROKEN_READDIR=yes,samba_cv_HAVE_BROKEN_READDIR=no,samba_cv_HAVE_BROKEN_READDIR=cross)]) -if test x"$samba_cv_HAVE_BROKEN_READDIR" = x"yes"; then - AC_DEFINE(HAVE_BROKEN_READDIR,1,[Whether readdir() is broken]) +samba_cv_HAVE_BROKEN_READDIR_NAME=yes,samba_cv_HAVE_BROKEN_READDIR_NAME=no,samba_cv_HAVE_BROKEN_READDIR_NAME=cross)]) +if test x"$samba_cv_HAVE_BROKEN_READDIR_NAME" = x"yes"; then + AC_DEFINE(HAVE_BROKEN_READDIR_NAME,1,[Whether readdir() returns the wrong name offset]) fi AC_CACHE_CHECK([for utimbuf],samba_cv_HAVE_UTIMBUF,[ @@ -2593,6 +2594,24 @@ if test x"$samba_cv_HAVE_SECURE_MKSTEMP" = x"yes"; then AC_DEFINE(HAVE_SECURE_MKSTEMP,1,[Whether mkstemp is secure]) fi +AC_CACHE_CHECK([for broken readdir],samba_cv_HAVE_BROKEN_READDIR,[ + AC_TRY_RUN([#include "${srcdir-.}/tests/os2_delete.c"], + [samba_cv_HAVE_BROKEN_READDIR=no], + [samba_cv_HAVE_BROKEN_READDIR=yes], + [samba_cv_HAVE_BROKEN_READDIR="assuming not"])]) + +if test x"$samba_cv_HAVE_BROKEN_READDIR" = x"yes"; then +AC_CACHE_CHECK([for replacing readdir],samba_cv_REPLACE_READDIR,[ + AC_TRY_RUN([ +#include "${srcdir-.}/lib/repdir.c" +#include "${srcdir-.}/tests/os2_delete.c"], + samba_cv_REPLACE_READDIR=yes,samba_cv_REPLACE_READDIR=no)]) +fi + +if test x"$samba_cv_REPLACE_READDIR" = x"yes"; then + AC_DEFINE(REPLACE_READDIR,1,[replace readdir]) +fi + AC_CACHE_CHECK([for sysconf(_SC_NGROUPS_MAX)],samba_cv_SYSCONF_SC_NGROUPS_MAX,[ AC_TRY_RUN([#include <unistd.h> main() { exit(sysconf(_SC_NGROUPS_MAX) == -1 ? 1 : 0); }], diff --git a/source/lib/repdir.c b/source/lib/repdir.c new file mode 100644 index 00000000000..b920fe4b0cc --- /dev/null +++ b/source/lib/repdir.c @@ -0,0 +1,218 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2005 + Updated for Samba3 64-bit cleanliness (C) Jeremy Allison 2006 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* + a replacement for opendir/readdir/telldir/seekdir/closedir for BSD systems + + This is needed because the existing directory handling in FreeBSD + and OpenBSD (and possibly NetBSD) doesn't correctly handle unlink() + on files in a directory where telldir() has been used. On a block + boundary it will occasionally miss a file when seekdir() is used to + return to a position previously recorded with telldir(). + + This also fixes a severe performance and memory usage problem with + telldir() on BSD systems. Each call to telldir() in BSD adds an + entry to a linked list, and those entries are cleaned up on + closedir(). This means with a large directory closedir() can take an + arbitrary amount of time, causing network timeouts as millions of + telldir() entries are freed + + Note! This replacement code is not portable. It relies on getdents() + always leaving the file descriptor at a seek offset that is a + multiple of DIR_BUF_SIZE. If the code detects that this doesn't + happen then it will abort(). It also does not handle directories + with offsets larger than can be stored in a long, + + This code is available under other free software licenses as + well. Contact the author. +*/ + +#include <include/includes.h> + + void replace_readdir_dummy(void); + void replace_readdir_dummy(void) {} + +#if defined(REPLACE_READDIR) + +#if defined(PARANOID_MALLOC_CHECKER) +#ifdef malloc +#undef malloc +#endif +#endif + +#define DIR_BUF_BITS 9 +#define DIR_BUF_SIZE (1<<DIR_BUF_BITS) + +struct dir_buf { + int fd; + int nbytes, ofs; + SMB_OFF_T seekpos; + char buf[DIR_BUF_SIZE]; +}; + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_OPENDIR64) + SMB_STRUCT_DIR *opendir64(const char *dname) +#else + SMB_STRUCT_DIR *opendir(const char *dname) +#endif +{ + struct dir_buf *d; + d = malloc(sizeof(*d)); + if (d == NULL) { + errno = ENOMEM; + return NULL; + } +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_OPEN64) + d->fd = open64(dname, O_RDONLY); +#else + d->fd = open(dname, O_RDONLY); +#endif + + if (d->fd == -1) { + free(d); + return NULL; + } + d->ofs = 0; + d->seekpos = 0; + d->nbytes = 0; + return (SMB_STRUCT_DIR *)d; +} + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_READDIR64) + SMB_STRUCT_DIRENT *readdir64(SMB_STRUCT_DIR *dir) +#else + SMB_STRUCT_DIRENT *readdir(SMB_STRUCT_DIR *dir) +#endif +{ + struct dir_buf *d = (struct dir_buf *)dir; + SMB_STRUCT_DIRENT *de; + + if (d->ofs >= d->nbytes) { +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_LSEEK64) + d->seekpos = lseek64(d->fd, 0, SEEK_CUR); +#else + d->seekpos = lseek(d->fd, 0, SEEK_CUR); +#endif + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_GETDENTS64) + d->nbytes = getdents64(d->fd, d->buf, DIR_BUF_SIZE); +#else + d->nbytes = getdents(d->fd, d->buf, DIR_BUF_SIZE); +#endif + d->ofs = 0; + } + if (d->ofs >= d->nbytes) { + return NULL; + } + de = (SMB_STRUCT_DIRENT *)&d->buf[d->ofs]; + d->ofs += de->d_reclen; + return de; +} + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_TELLDIR64) + long telldir64(SMB_STRUCT_DIR *dir) +#else + long telldir(SMB_STRUCT_DIR *dir) +#endif +{ + struct dir_buf *d = (struct dir_buf *)dir; + if (d->ofs >= d->nbytes) { +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_LSEEK64) + d->seekpos = lseek64(d->fd, 0, SEEK_CUR); +#else + d->seekpos = lseek(d->fd, 0, SEEK_CUR); +#endif + d->ofs = 0; + d->nbytes = 0; + } + /* this relies on seekpos always being a multiple of + DIR_BUF_SIZE. Is that always true on BSD systems? */ + if (d->seekpos & (DIR_BUF_SIZE-1)) { + abort(); + } + return d->seekpos + d->ofs; +} + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_SEEKDIR64) + void seekdir64(SMB_STRUCT_DIR *dir, long ofs) +#else + void seekdir(SMB_STRUCT_DIR *dir, long ofs) +#endif +{ + struct dir_buf *d = (struct dir_buf *)dir; +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_LSEEK64) + d->seekpos = lseek64(d->fd, ofs & ~(DIR_BUF_SIZE-1), SEEK_SET); +#else + d->seekpos = lseek(d->fd, ofs & ~(DIR_BUF_SIZE-1), SEEK_SET); +#endif + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_GETDENTS64) + d->nbytes = getdents64(d->fd, d->buf, DIR_BUF_SIZE); +#else + d->nbytes = getdents(d->fd, d->buf, DIR_BUF_SIZE); +#endif + + d->ofs = 0; + while (d->ofs < (ofs & (DIR_BUF_SIZE-1))) { +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_READDIR64) + if (readdir64(dir) == NULL) break; +#else + if (readdir(dir) == NULL) break; +#endif + } +} + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_REWINDDIR64) + void rewinddir64(SMB_STRUCT_DIR *dir) +#else + void rewinddir(SMB_STRUCT_DIR *dir) +#endif +{ +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_SEEKDIR64) + seekdir64(dir, 0); +#else + seekdir(dir, 0); +#endif +} + +#if defined(HAVE_EXPLICIT_LARGEFILE_SUPPORT) && defined(HAVE_CLOSEDIR64) + int closedir64(SMB_STRUCT_DIR *dir) +#else + int closedir(SMB_STRUCT_DIR *dir) +#endif +{ + struct dir_buf *d = (struct dir_buf *)dir; + int r = close(d->fd); + if (r != 0) { + return r; + } + free(d); + return 0; +} + +#ifndef dirfd +/* darn, this is a macro on some systems. */ + int dirfd(SMB_STRUCT_DIR *dir) +{ + struct dir_buf *d = (struct dir_buf *)dir; + return d->fd; +} +#endif +#endif /* REPLACE_READDIR */ diff --git a/source/lib/util.c b/source/lib/util.c index 45be357be4f..5bf9409c2fa 100644 --- a/source/lib/util.c +++ b/source/lib/util.c @@ -1686,7 +1686,7 @@ const char *readdirname(SMB_STRUCT_DIR *p) return(NULL); #endif -#ifdef HAVE_BROKEN_READDIR +#ifdef HAVE_BROKEN_READDIR_NAME /* using /usr/ucb/cc is BAD */ dname = dname - 2; #endif diff --git a/source/smbd/vfs.c b/source/smbd/vfs.c index 001d42d172f..07e18caa5c6 100644 --- a/source/smbd/vfs.c +++ b/source/smbd/vfs.c @@ -712,7 +712,7 @@ char *vfs_readdirname(connection_struct *conn, void *p) return(NULL); #endif -#ifdef HAVE_BROKEN_READDIR +#ifdef HAVE_BROKEN_READDIR_NAME /* using /usr/ucb/cc is BAD */ dname = dname - 2; #endif diff --git a/source/tests/os2_delete.c b/source/tests/os2_delete.c new file mode 100644 index 00000000000..831fa367eb4 --- /dev/null +++ b/source/tests/os2_delete.c @@ -0,0 +1,107 @@ +/* + test readdir/unlink pattern that OS/2 uses + tridge@samba.org July 2005 +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#define NUM_FILES 700 +#define READDIR_SIZE 100 +#define DELETE_SIZE 4 + +#define TESTDIR "test.dir" + +#define FAILED(d) (fprintf(stderr, "Failed for %s - %s\n", d, strerror(errno)), exit(1), 1) + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +static void cleanup(void) +{ + /* I'm a lazy bastard */ + system("rm -rf " TESTDIR); + mkdir(TESTDIR, 0700) == 0 || FAILED("mkdir"); +} + +static void create_files() +{ + int i; + for (i=0;i<NUM_FILES;i++) { + char fname[40]; + sprintf(fname, TESTDIR "/test%u.txt", i); + close(open(fname, O_CREAT|O_RDWR, 0600)) == 0 || FAILED("close"); + } +} + +static int os2_delete(DIR *d) +{ + off_t offsets[READDIR_SIZE]; + int i, j; + struct dirent *de; + char names[READDIR_SIZE][30]; + + /* scan, remembering offsets */ + for (i=0, de=readdir(d); + de && i < READDIR_SIZE; + de=readdir(d), i++) { + offsets[i] = telldir(d); + strcpy(names[i], de->d_name); + } + + if (i == 0) { + return 0; + } + + /* delete the first few */ + for (j=0; j<MIN(i, DELETE_SIZE); j++) { + char fname[40]; + sprintf(fname, TESTDIR "/%s", names[j]); + unlink(fname) == 0 || FAILED("unlink"); + } + + /* seek to just after the deletion */ + seekdir(d, offsets[j-1]); + + /* return number deleted */ + return j; +} + +int main(void) +{ + int total_deleted = 0; + DIR *d; + struct dirent *de; + + cleanup(); + create_files(); + + d = opendir(TESTDIR); + + /* skip past . and .. */ + de = readdir(d); + strcmp(de->d_name, ".") == 0 || FAILED("match ."); + de = readdir(d); + strcmp(de->d_name, "..") == 0 || FAILED("match .."); + + while (1) { + int n = os2_delete(d); + if (n == 0) break; + total_deleted += n; + } + closedir(d); + + printf("Deleted %d files of %d\n", total_deleted, NUM_FILES); + + rmdir(TESTDIR) == 0 || FAILED("rmdir"); + + return 0; +} |