~zulcss/samba/server-dailies-3.4

« back to all changes in this revision

Viewing changes to lib/replace/repdir_getdents.c

  • Committer: Chuck Short
  • Date: 2010-09-28 20:38:39 UTC
  • Revision ID: zulcss@ubuntu.com-20100928203839-pgjulytsi9ue63x1
Initial version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* 
 
2
   Unix SMB/CIFS implementation.
 
3
 
 
4
   Copyright (C) Andrew Tridgell 2005
 
5
 
 
6
     ** NOTE! The following LGPL license applies to the replace
 
7
     ** library. This does NOT imply that all of Samba is released
 
8
     ** under the LGPL
 
9
   
 
10
   This library is free software; you can redistribute it and/or
 
11
   modify it under the terms of the GNU Lesser General Public
 
12
   License as published by the Free Software Foundation; either
 
13
   version 3 of the License, or (at your option) any later version.
 
14
 
 
15
   This library is distributed in the hope that it will be useful,
 
16
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
18
   Lesser General Public License for more details.
 
19
 
 
20
   You should have received a copy of the GNU Lesser General Public
 
21
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
 
22
*/
 
23
/*
 
24
  a replacement for opendir/readdir/telldir/seekdir/closedir for BSD systems
 
25
 
 
26
  This is needed because the existing directory handling in FreeBSD
 
27
  and OpenBSD (and possibly NetBSD) doesn't correctly handle unlink()
 
28
  on files in a directory where telldir() has been used. On a block
 
29
  boundary it will occasionally miss a file when seekdir() is used to
 
30
  return to a position previously recorded with telldir().
 
31
 
 
32
  This also fixes a severe performance and memory usage problem with
 
33
  telldir() on BSD systems. Each call to telldir() in BSD adds an
 
34
  entry to a linked list, and those entries are cleaned up on
 
35
  closedir(). This means with a large directory closedir() can take an
 
36
  arbitrary amount of time, causing network timeouts as millions of
 
37
  telldir() entries are freed
 
38
 
 
39
  Note! This replacement code is not portable. It relies on getdents()
 
40
  always leaving the file descriptor at a seek offset that is a
 
41
  multiple of DIR_BUF_SIZE. If the code detects that this doesn't
 
42
  happen then it will abort(). It also does not handle directories
 
43
  with offsets larger than can be stored in a long,
 
44
 
 
45
  This code is available under other free software licenses as
 
46
  well. Contact the author.
 
47
*/
 
48
 
 
49
#include <stdlib.h>
 
50
#include <sys/stat.h>
 
51
#include <unistd.h>
 
52
#include <sys/types.h>
 
53
#include <errno.h>
 
54
#include <fcntl.h>
 
55
#include <dirent.h>
 
56
 
 
57
#define DIR_BUF_BITS 9
 
58
#define DIR_BUF_SIZE (1<<DIR_BUF_BITS)
 
59
 
 
60
struct dir_buf {
 
61
        int fd;
 
62
        int nbytes, ofs;
 
63
        off_t seekpos;
 
64
        char buf[DIR_BUF_SIZE];
 
65
};
 
66
 
 
67
DIR *opendir(const char *dname)
 
68
{
 
69
        struct dir_buf *d;
 
70
        struct stat sb;
 
71
        d = malloc(sizeof(*d));
 
72
        if (d == NULL) {
 
73
                errno = ENOMEM;
 
74
                return NULL;
 
75
        }
 
76
        d->fd = open(dname, O_RDONLY);
 
77
        if (d->fd == -1) {
 
78
                free(d);
 
79
                return NULL;
 
80
        }
 
81
        if (fstat(d->fd, &sb) < 0) {
 
82
                close(d->fd);
 
83
                free(d);
 
84
                return NULL;
 
85
        }
 
86
        if (!S_ISDIR(sb.st_mode)) {
 
87
                close(d->fd);
 
88
                free(d);   
 
89
                errno = ENOTDIR;
 
90
                return NULL;
 
91
        }
 
92
        d->ofs = 0;
 
93
        d->seekpos = 0;
 
94
        d->nbytes = 0;
 
95
        return (DIR *)d;
 
96
}
 
97
 
 
98
struct dirent *readdir(DIR *dir)
 
99
{
 
100
        struct dir_buf *d = (struct dir_buf *)dir;
 
101
        struct dirent *de;
 
102
 
 
103
        if (d->ofs >= d->nbytes) {
 
104
                d->seekpos = lseek(d->fd, 0, SEEK_CUR);
 
105
                d->nbytes = getdents(d->fd, d->buf, DIR_BUF_SIZE);
 
106
                d->ofs = 0;
 
107
        }
 
108
        if (d->ofs >= d->nbytes) {
 
109
                return NULL;
 
110
        }
 
111
        de = (struct dirent *)&d->buf[d->ofs];
 
112
        d->ofs += de->d_reclen;
 
113
        return de;
 
114
}
 
115
 
 
116
long telldir(DIR *dir)
 
117
{
 
118
        struct dir_buf *d = (struct dir_buf *)dir;
 
119
        if (d->ofs >= d->nbytes) {
 
120
                d->seekpos = lseek(d->fd, 0, SEEK_CUR);
 
121
                d->ofs = 0;
 
122
                d->nbytes = 0;
 
123
        }
 
124
        /* this relies on seekpos always being a multiple of
 
125
           DIR_BUF_SIZE. Is that always true on BSD systems? */
 
126
        if (d->seekpos & (DIR_BUF_SIZE-1)) {
 
127
                abort();
 
128
        }
 
129
        return d->seekpos + d->ofs;
 
130
}
 
131
 
 
132
void seekdir(DIR *dir, long ofs)
 
133
{
 
134
        struct dir_buf *d = (struct dir_buf *)dir;
 
135
        d->seekpos = lseek(d->fd, ofs & ~(DIR_BUF_SIZE-1), SEEK_SET);
 
136
        d->nbytes = getdents(d->fd, d->buf, DIR_BUF_SIZE);
 
137
        d->ofs = 0;
 
138
        while (d->ofs < (ofs & (DIR_BUF_SIZE-1))) {
 
139
                if (readdir(dir) == NULL) break;
 
140
        }
 
141
}
 
142
 
 
143
void rewinddir(DIR *dir)
 
144
{
 
145
        seekdir(dir, 0);
 
146
}
 
147
 
 
148
int closedir(DIR *dir)
 
149
{
 
150
        struct dir_buf *d = (struct dir_buf *)dir;
 
151
        int r = close(d->fd);
 
152
        if (r != 0) {
 
153
                return r;
 
154
        }
 
155
        free(d);
 
156
        return 0;
 
157
}
 
158
 
 
159
#ifndef dirfd
 
160
/* darn, this is a macro on some systems. */
 
161
int dirfd(DIR *dir)
 
162
{
 
163
        struct dir_buf *d = (struct dir_buf *)dir;
 
164
        return d->fd;
 
165
}
 
166
#endif