--- libeio/eio.c 2009/04/22 11:04:49 1.29 +++ libeio/eio.c 2009/06/12 16:48:08 1.37 @@ -1,7 +1,7 @@ /* * libeio implementation * - * Copyright (c) 2007,2008 Marc Alexander Lehmann + * Copyright (c) 2007,2008,2009 Marc Alexander Lehmann * All rights reserved. * * Redistribution and use in source and binary forms, with or without modifica- @@ -70,17 +70,35 @@ #ifdef _WIN32 /*doh*/ - #else # include "config.h" # include # include +# include # include # include # include # include +/* POSIX_SOURCE is useless on bsd's, and XOPEN_SOURCE is unreliable there, too */ +# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +# define _DIRENT_HAVE_D_TYPE /* sigh */ +# define D_INO(de) (de)->d_fileno +# define D_NAMLEN(de) (de)->d_namlen +# elif defined(__linux) || defined(d_ino) || _XOPEN_SOURCE >= 600 +# define D_INO(de) (de)->d_ino +# endif + +#ifdef _D_EXACT_NAMLEN +# undef D_NAMLEN +# define D_NAMLEN(de) _D_EXACT_NAMLEN (de) +#endif + +# ifdef _DIRENT_HAVE_D_TYPE +# define D_TYPE(de) (de)->d_type +# endif + # ifndef EIO_STRUCT_DIRENT # define EIO_STRUCT_DIRENT struct dirent # endif @@ -102,6 +120,16 @@ # endif #endif +#ifndef D_TYPE +# define D_TYPE(de) 0 +#endif +#ifndef D_INO +# define D_INO(de) 0 +#endif +#ifndef D_NAMLEN +# define D_NAMLEN(de) strlen ((de)->d_name) +#endif + /* number of seconds after which an idle threads exit */ #define IDLE_TIMEOUT 10 @@ -161,6 +189,7 @@ closedir (wrk->dirp); \ wrk->dirp = 0; \ } + #define ETP_WORKER_COMMON \ void *dbuf; \ DIR *dirp; @@ -773,7 +802,7 @@ ooffset = lseek (fd, 0, SEEK_CUR); lseek (fd, offset, SEEK_SET); res = write (fd, buf, count); - lseek (fd, offset, SEEK_SET); + lseek (fd, ooffset, SEEK_SET); X_UNLOCK (preadwritelock); return res; @@ -963,6 +992,80 @@ return res; } +static signed char +eio_dent_cmp (const eio_dirent *a, const eio_dirent *b) +{ + return b->score - a->score ? b->score - a->score /* works because our signed char is always 0..100 */ + : a->inode < b->inode ? -1 : a->inode > b->inode ? 1 : 0; +} + +#define EIO_QSORT_CUTOFF 20 /* quite high, but performs well on many filesystems */ + +static void +eio_dent_sort (eio_dirent *dents, int size) +{ + /* should be good for 2**31 entries */ + struct rng { int l, r; } rng [32]; + int i, j; + + i = 0; + rng[0].l = 0; + rng[0].r = size; + + while (expect_true (i >= 0)) + { + int L = rng [i].l; + int R = rng [i].r - 1; + + if (expect_false (L + EIO_QSORT_CUTOFF < R)) + { + eio_dirent piv = dents [L]; + + while (L < R) + { + while (eio_dent_cmp (&dents [R], &piv) >= 0 && L < R) + --R; + + if (L < R) + dents [L++] = dents [R]; + + while (eio_dent_cmp (&dents [L], &piv) <= 0 && L < R) + ++L; + + if (L < R) + dents [R--] = dents [L]; + } + + dents [L] = piv; + + ++i; + rng [i].l = L + 1; + rng [i].r = rng [i - 1].r; + rng [i - 1].r = L; + + if (rng [i].r - rng [i].l > rng [i - 1].r - rng [i - 1].l) + { + struct rng t; + + t = rng [i]; rng [i] = rng [i - 1]; rng [i - 1] = t; + } + } + else + --i; + } + + /* use a simple insertion sort at the end */ + for (i = 1; i < size; ++i) + { + eio_dirent value = dents [i]; + + for (j = i - 1; j >= 0 && eio_dent_cmp (&dents [j], &value) > 0; --j) + dents [j + 1] = dents [j]; + + dents [j + 1] = value; + } +} + /* read a full directory */ static void eio__scandir (eio_req *req, etp_worker *self) @@ -970,54 +1073,215 @@ DIR *dirp; EIO_STRUCT_DIRENT *entp; char *name, *names; - int memlen = 4096; - int memofs = 0; - int res = 0; + int namesalloc = 4096; + int namesoffs = 0; + int flags = req->int1; + eio_dirent *dents = 0; + int dentalloc = 128; + int dentoffs = 0; + + req->result = -1; + + if (!(flags & EIO_READDIR_DENTS)) + flags &= ~(EIO_READDIR_DIRS_FIRST | EIO_READDIR_STAT_ORDER); X_LOCK (wrklock); /* the corresponding closedir is in ETP_WORKER_CLEAR */ self->dirp = dirp = opendir (req->ptr1); - req->flags |= EIO_FLAG_PTR2_FREE; - req->ptr2 = names = malloc (memlen); + req->flags |= EIO_FLAG_PTR1_FREE | EIO_FLAG_PTR2_FREE; + req->ptr1 = names = malloc (namesalloc); + req->ptr2 = dents = flags ? malloc (dentalloc * sizeof (eio_dirent)) : 0; X_UNLOCK (wrklock); - if (dirp && names) + if (dirp && names && (!flags || dents)) for (;;) { errno = 0; entp = readdir (dirp); if (!entp) - break; + { + if (errno) + break; + /* sort etc. */ + req->int1 = flags; + req->result = dentoffs; + + if (dents) + { + eio_dirent *ent = dents + dentoffs; + + while (ent > dents) + (--ent)->name = names + (size_t)ent->name; + } + + if (flags & EIO_READDIR_STAT_ORDER + || !(~flags & (EIO_READDIR_DIRS_FIRST | EIO_READDIR_FOUND_UNKNOWN))) + eio_dent_sort (dents, dentoffs); /* score depends of DIRS_FIRST */ + else if (flags & EIO_READDIR_DIRS_FIRST) + { + /* in this case, all is known, and we just put dirs first and sort them */ + eio_dirent *ent = dents + dentoffs; + eio_dirent *dir = dents; + + /* now move dirs to the front, and non-dirs to the back */ + /* by walking from both sides and swapping if necessary */ + while (ent > dir) + { + if (dir->type == DT_DIR) + ++dir; + else + { + --ent; + + if (ent->type == DT_DIR) + { + eio_dirent tmp = *dir; + *dir = *ent; + *ent = tmp; + + ++dir; + } + } + } + + /* now sort the dirs only */ + eio_dent_sort (dents, dir - dents); + } + + /* only provide the names array unless DENTS is specified */ + if (!(flags & EIO_READDIR_DENTS)) + { + X_LOCK (wrklock); + assert (!dents); + req->ptr1 = 0; + req->ptr2 = names; + X_UNLOCK (wrklock); + } + + break; + } + + /* now add the entry to our list(s) */ name = entp->d_name; + /* skip . and .. entries */ if (name [0] != '.' || (name [1] && (name [1] != '.' || name [2]))) { - int len = strlen (name) + 1; - - res++; + int len = D_NAMLEN (entp) + 1; - while (memofs + len > memlen) + while (expect_false (namesoffs + len > namesalloc)) { - memlen *= 2; + namesalloc *= 2; X_LOCK (wrklock); - req->ptr2 = names = realloc (names, memlen); + req->ptr1 = names = realloc (names, namesalloc); X_UNLOCK (wrklock); if (!names) break; } - memcpy (names + memofs, name, len); - memofs += len; + memcpy (names + namesoffs, name, len); + + if (dents) + { + struct eio_dirent *ent; + + if (expect_false (dentoffs == dentalloc)) + { + dentalloc *= 2; + X_LOCK (wrklock); + req->ptr2 = dents = realloc (dents, dentalloc * sizeof (eio_dirent)); + X_UNLOCK (wrklock); + + if (!dents) + break; + } + + ent = dents + dentoffs; + + ent->name = (char *)(size_t)namesoffs; /* rather dirtily we store the offset in the pointer */ + ent->namelen = len - 1; + ent->inode = D_INO (entp); + + switch (D_TYPE (entp)) + { + default: + ent->type = EIO_DT_UNKNOWN; + flags |= EIO_READDIR_FOUND_UNKNOWN; + break; + + #ifdef DT_FIFO + case DT_FIFO: ent->type = EIO_DT_FIFO; break; + #endif + #ifdef DT_CHR + case DT_CHR: ent->type = EIO_DT_CHR; break; + #endif + #ifdef DT_MPC + case DT_MPC: ent->type = EIO_DT_MPC; break; + #endif + #ifdef DT_DIR + case DT_DIR: ent->type = EIO_DT_DIR; break; + #endif + #ifdef DT_NAM + case DT_NAM: ent->type = EIO_DT_NAM; break; + #endif + #ifdef DT_BLK + case DT_BLK: ent->type = EIO_DT_BLK; break; + #endif + #ifdef DT_MPB + case DT_MPB: ent->type = EIO_DT_MPB; break; + #endif + #ifdef DT_REG + case DT_REG: ent->type = EIO_DT_REG; break; + #endif + #ifdef DT_NWK + case DT_NWK: ent->type = EIO_DT_NWK; break; + #endif + #ifdef DT_CMP + case DT_CMP: ent->type = EIO_DT_CMP; break; + #endif + #ifdef DT_LNK + case DT_LNK: ent->type = EIO_DT_LNK; break; + #endif + #ifdef DT_SOCK + case DT_SOCK: ent->type = EIO_DT_SOCK; break; + #endif + #ifdef DT_DOOR + case DT_DOOR: ent->type = EIO_DT_DOOR; break; + #endif + #ifdef DT_WHT + case DT_WHT: ent->type = EIO_DT_WHT; break; + #endif + } + + ent->score = 0; + + if (flags & EIO_READDIR_DIRS_FIRST) + { + if (ent->type == EIO_DT_UNKNOWN) + { + if (*name == '.') /* leading dots are likely directories, and, in any case, rare */ + ent->score = 98; + else if (!strchr (name, '.')) /* absense of dots indicate likely dirs */ + ent->score = len <= 2 ? len + 6 : len <= 4 ? 5 : len <= 7 ? 4 : 1; /* shorter == more likely dir, but avoid too many classes */ + } + else if (ent->type == EIO_DT_DIR) + ent->score = 100; + } + } + + namesoffs += len; + ++dentoffs; } - } - if (errno) - res = -1; - - req->result = res; + if (EIO_CANCELLED (req)) + { + errno = ECANCELED; + break; + } + } } #if !(_POSIX_MAPPED_FILES && _POSIX_SYNCHRONIZED_IO) @@ -1447,9 +1711,9 @@ return eio__1path (EIO_RMDIR, path, pri, cb, data); } -eio_req *eio_readdir (const char *path, int pri, eio_cb cb, void *data) +eio_req *eio_readdir (const char *path, int flags, int pri, eio_cb cb, void *data) { - return eio__1path (EIO_READDIR, path, pri, cb, data); + REQ (EIO_READDIR); PATH; req->int1 = flags; SEND; } eio_req *eio_mknod (const char *path, mode_t mode, dev_t dev, int pri, eio_cb cb, void *data) @@ -1548,12 +1812,15 @@ ssize_t eio_sendfile_sync (int ofd, int ifd, off_t offset, size_t count) { etp_worker wrk; + ssize_t ret; wrk.dbuf = 0; - eio__sendfile (ofd, ifd, offset, count, &wrk); + ret = eio__sendfile (ofd, ifd, offset, count, &wrk); if (wrk.dbuf) free (wrk.dbuf); + + return ret; }