OLD | NEW |
| (Empty) |
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 | |
25 systems using getdirentries | |
26 | |
27 This is needed because the existing directory handling in FreeBSD | |
28 and OpenBSD (and possibly NetBSD) doesn't correctly handle unlink() | |
29 on files in a directory where telldir() has been used. On a block | |
30 boundary it will occasionally miss a file when seekdir() is used to | |
31 return to a position previously recorded with telldir(). | |
32 | |
33 This also fixes a severe performance and memory usage problem with | |
34 telldir() on BSD systems. Each call to telldir() in BSD adds an | |
35 entry to a linked list, and those entries are cleaned up on | |
36 closedir(). This means with a large directory closedir() can take an | |
37 arbitrary amount of time, causing network timeouts as millions of | |
38 telldir() entries are freed | |
39 | |
40 Note! This replacement code is not portable. It relies on | |
41 getdirentries() always leaving the file descriptor at a seek offset | |
42 that is a multiple of DIR_BUF_SIZE. If the code detects that this | |
43 doesn't happen then it will abort(). It also does not handle | |
44 directories with offsets larger than can be stored in a long, | |
45 | |
46 This code is available under other free software licenses as | |
47 well. Contact the author. | |
48 */ | |
49 | |
50 #include "replace.h" | |
51 #include <stdlib.h> | |
52 #include <sys/stat.h> | |
53 #include <unistd.h> | |
54 #include <sys/types.h> | |
55 #include <errno.h> | |
56 #include <fcntl.h> | |
57 #include <dirent.h> | |
58 | |
59 #define DIR_BUF_BITS 9 | |
60 #define DIR_BUF_SIZE (1<<DIR_BUF_BITS) | |
61 | |
62 struct dir_buf { | |
63 int fd; | |
64 int nbytes, ofs; | |
65 off_t seekpos; | |
66 char buf[DIR_BUF_SIZE]; | |
67 }; | |
68 | |
69 DIR *opendir(const char *dname) | |
70 { | |
71 struct dir_buf *d; | |
72 struct stat sb; | |
73 d = malloc(sizeof(*d)); | |
74 if (d == NULL) { | |
75 errno = ENOMEM; | |
76 return NULL; | |
77 } | |
78 d->fd = open(dname, O_RDONLY); | |
79 if (d->fd == -1) { | |
80 free(d); | |
81 return NULL; | |
82 } | |
83 if (fstat(d->fd, &sb) < 0) { | |
84 close(d->fd); | |
85 free(d); | |
86 return NULL; | |
87 } | |
88 if (!S_ISDIR(sb.st_mode)) { | |
89 close(d->fd); | |
90 free(d); | |
91 errno = ENOTDIR; | |
92 return NULL; | |
93 } | |
94 d->ofs = 0; | |
95 d->seekpos = 0; | |
96 d->nbytes = 0; | |
97 return (DIR *)d; | |
98 } | |
99 | |
100 struct dirent *readdir(DIR *dir) | |
101 { | |
102 struct dir_buf *d = (struct dir_buf *)dir; | |
103 struct dirent *de; | |
104 | |
105 if (d->ofs >= d->nbytes) { | |
106 long pos; | |
107 d->nbytes = getdirentries(d->fd, d->buf, DIR_BUF_SIZE, &pos); | |
108 d->seekpos = pos; | |
109 d->ofs = 0; | |
110 } | |
111 if (d->ofs >= d->nbytes) { | |
112 return NULL; | |
113 } | |
114 de = (struct dirent *)&d->buf[d->ofs]; | |
115 d->ofs += de->d_reclen; | |
116 return de; | |
117 } | |
118 | |
119 #ifdef TELLDIR_TAKES_CONST_DIR | |
120 long telldir(const DIR *dir) | |
121 #else | |
122 long telldir(DIR *dir) | |
123 #endif | |
124 { | |
125 struct dir_buf *d = (struct dir_buf *)dir; | |
126 if (d->ofs >= d->nbytes) { | |
127 d->seekpos = lseek(d->fd, 0, SEEK_CUR); | |
128 d->ofs = 0; | |
129 d->nbytes = 0; | |
130 } | |
131 /* this relies on seekpos always being a multiple of | |
132 DIR_BUF_SIZE. Is that always true on BSD systems? */ | |
133 if (d->seekpos & (DIR_BUF_SIZE-1)) { | |
134 abort(); | |
135 } | |
136 return d->seekpos + d->ofs; | |
137 } | |
138 | |
139 #ifdef SEEKDIR_RETURNS_INT | |
140 int seekdir(DIR *dir, long ofs) | |
141 #else | |
142 void seekdir(DIR *dir, long ofs) | |
143 #endif | |
144 { | |
145 struct dir_buf *d = (struct dir_buf *)dir; | |
146 long pos; | |
147 d->seekpos = lseek(d->fd, ofs & ~(DIR_BUF_SIZE-1), SEEK_SET); | |
148 d->nbytes = getdirentries(d->fd, d->buf, DIR_BUF_SIZE, &pos); | |
149 d->ofs = 0; | |
150 while (d->ofs < (ofs & (DIR_BUF_SIZE-1))) { | |
151 if (readdir(dir) == NULL) break; | |
152 } | |
153 #ifdef SEEKDIR_RETURNS_INT | |
154 return -1; | |
155 #endif | |
156 } | |
157 | |
158 void rewinddir(DIR *dir) | |
159 { | |
160 seekdir(dir, 0); | |
161 } | |
162 | |
163 int closedir(DIR *dir) | |
164 { | |
165 struct dir_buf *d = (struct dir_buf *)dir; | |
166 int r = close(d->fd); | |
167 if (r != 0) { | |
168 return r; | |
169 } | |
170 free(d); | |
171 return 0; | |
172 } | |
173 | |
174 #ifndef dirfd | |
175 /* darn, this is a macro on some systems. */ | |
176 int dirfd(DIR *dir) | |
177 { | |
178 struct dir_buf *d = (struct dir_buf *)dir; | |
179 return d->fd; | |
180 } | |
181 #endif | |
182 | |
183 | |
OLD | NEW |