Commit | Line | Data |
---|---|---|
da7c5cc6 | 1 | /* |
7604aef4 KM |
2 | * Copyright (c) 1989 The Regents of the University of California. |
3 | * All rights reserved. | |
da7c5cc6 | 4 | * |
b702c21d | 5 | * %sccs.include.redist.c% |
7604aef4 | 6 | * |
b7581321 | 7 | * @(#)ufs_lookup.c 7.29 (Berkeley) %G% |
da7c5cc6 | 8 | */ |
10873320 | 9 | |
94368568 | 10 | #include "param.h" |
c6f5111d | 11 | #include "namei.h" |
94368568 | 12 | #include "buf.h" |
7604aef4 KM |
13 | #include "file.h" |
14 | #include "vnode.h" | |
c6f5111d MK |
15 | |
16 | #include "quota.h" | |
17 | #include "inode.h" | |
18 | #include "fs.h" | |
10873320 | 19 | |
7604aef4 | 20 | struct nchstats nchstats; |
658f5fdc | 21 | int dirchk = 1; |
f93197fc KM |
22 | |
23 | /* | |
7604aef4 | 24 | * Convert a component of a pathname into a pointer to a locked inode. |
6bd0bb92 | 25 | * This is a very central and rather complicated routine. |
4f083fd7 | 26 | * If the file system is not maintained in a strict tree hierarchy, |
7f69b0e6 KM |
27 | * this can result in a deadlock situation (see comments in code below). |
28 | * | |
7604aef4 KM |
29 | * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on |
30 | * whether the name is to be looked up, created, renamed, or deleted. | |
31 | * When CREATE, RENAME, or DELETE is specified, information usable in | |
32 | * creating, renaming, or deleting a directory entry may be calculated. | |
33 | * If flag has LOCKPARENT or'ed into it and the target of the pathname | |
34 | * exists, lookup returns both the target and its parent directory locked. | |
35 | * When creating or renaming and LOCKPARENT is specified, the target may | |
36 | * not be ".". When deleting and LOCKPARENT is specified, the target may | |
37 | * be "."., but the caller must check to ensure it does an vrele and iput | |
38 | * instead of two iputs. | |
f93197fc | 39 | * |
7604aef4 | 40 | * Overall outline of ufs_lookup: |
f93197fc | 41 | * |
6bd0bb92 | 42 | * check accessibility of directory |
f93197fc | 43 | * look for name in cache, if found, then if at end of path |
7604aef4 | 44 | * and deleting or creating, drop it, else return name |
6bd0bb92 BJ |
45 | * search for name in directory, to found or notfound |
46 | * notfound: | |
7604aef4 | 47 | * if creating, return locked directory, leaving info on available slots |
6bd0bb92 BJ |
48 | * else return error |
49 | * found: | |
50 | * if at end of path and deleting, return information to allow delete | |
7604aef4 | 51 | * if at end of path and rewriting (RENAME and LOCKPARENT), lock target |
4f083fd7 | 52 | * inode and return info to allow rewrite |
7f69b0e6 KM |
53 | * if not at end, add name to cache; if at end and neither creating |
54 | * nor deleting, add name to cache | |
4f083fd7 | 55 | * |
7604aef4 | 56 | * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent inode unlocked. |
10873320 | 57 | */ |
b7581321 | 58 | ufs_lookup(vdp, ndp, p) |
a48aecd3 | 59 | register struct vnode *vdp; |
d870be74 | 60 | register struct nameidata *ndp; |
b7581321 | 61 | struct proc *p; |
10873320 | 62 | { |
0be364c5 | 63 | register struct inode *dp; /* the directory we are searching */ |
6bd0bb92 | 64 | register struct fs *fs; /* file system that directory is in */ |
7604aef4 | 65 | struct buf *bp = 0; /* a buffer of directory entries */ |
6bd0bb92 BJ |
66 | register struct direct *ep; /* the current directory entry */ |
67 | int entryoffsetinblock; /* offset of ep in bp's buffer */ | |
6bd0bb92 BJ |
68 | enum {NONE, COMPACT, FOUND} slotstatus; |
69 | int slotoffset = -1; /* offset of area with free space */ | |
70 | int slotsize; /* size of area at slotoffset */ | |
71 | int slotfreespace; /* amount of space free in slot */ | |
72 | int slotneeded; /* size of the entry we're seeking */ | |
84ef30ec KM |
73 | int numdirpasses; /* strategy for directory search */ |
74 | int endsearch; /* offset to end directory search */ | |
d870be74 | 75 | int prevoff; /* ndp->ni_offset of previous entry */ |
6bd0bb92 | 76 | struct inode *pdp; /* saved dp during symlink work */ |
7604aef4 | 77 | struct inode *tdp; /* returned by iget */ |
9e7c949b | 78 | off_t enduseful; /* pointer past last used dir slot */ |
7604aef4 KM |
79 | int flag; /* LOOKUP, CREATE, RENAME, or DELETE */ |
80 | int lockparent; /* 1 => lockparent flag is set */ | |
81 | int wantparent; /* 1 => wantparent or lockparent flag */ | |
82 | int error; | |
83 | ||
a48aecd3 | 84 | ndp->ni_dvp = vdp; |
7604aef4 | 85 | ndp->ni_vp = NULL; |
a48aecd3 | 86 | dp = VTOI(vdp); |
6bd0bb92 | 87 | fs = dp->i_fs; |
7604aef4 | 88 | lockparent = ndp->ni_nameiop & LOCKPARENT; |
10bae9ee | 89 | flag = ndp->ni_nameiop & OPMASK; |
7604aef4 | 90 | wantparent = ndp->ni_nameiop & (LOCKPARENT|WANTPARENT); |
6bd0bb92 | 91 | |
a0aead5a | 92 | /* |
6bd0bb92 | 93 | * Check accessiblity of directory. |
a0aead5a | 94 | */ |
7604aef4 KM |
95 | if ((dp->i_mode&IFMT) != IFDIR) |
96 | return (ENOTDIR); | |
b7581321 | 97 | if (error = ufs_access(vdp, VEXEC, ndp->ni_cred, p)) |
7604aef4 | 98 | return (error); |
e47da406 | 99 | |
f93197fc KM |
100 | /* |
101 | * We now have a segment name to search for, and a directory to search. | |
102 | * | |
103 | * Before tediously performing a linear scan of the directory, | |
104 | * check the name cache to see if the directory/name pair | |
7604aef4 | 105 | * we are looking for is known already. |
f93197fc | 106 | */ |
2bd0d28d KM |
107 | if (error = cache_lookup(ndp)) { |
108 | int vpid; /* capability number of vnode */ | |
109 | ||
110 | if (error == ENOENT) | |
111 | return (error); | |
5bddc6c5 | 112 | #ifdef PARANOID |
a48aecd3 | 113 | if (vdp == ndp->ni_rdir && ndp->ni_isdotdot) |
27c192da | 114 | panic("ufs_lookup: .. through root"); |
5bddc6c5 | 115 | #endif |
7604aef4 KM |
116 | /* |
117 | * Get the next vnode in the path. | |
2bd0d28d | 118 | * See comment below starting `Step through' for |
7604aef4 KM |
119 | * an explaination of the locking protocol. |
120 | */ | |
121 | pdp = dp; | |
0be364c5 | 122 | dp = VTOI(ndp->ni_vp); |
27c192da | 123 | vdp = ndp->ni_vp; |
2bd0d28d | 124 | vpid = vdp->v_id; |
7604aef4 | 125 | if (pdp == dp) { |
8fe1c702 | 126 | VREF(vdp); |
27c192da | 127 | error = 0; |
7604aef4 KM |
128 | } else if (ndp->ni_isdotdot) { |
129 | IUNLOCK(pdp); | |
27c192da | 130 | error = vget(vdp); |
e828c39a KM |
131 | if (!error && lockparent && *ndp->ni_next == '\0') |
132 | ILOCK(pdp); | |
f93197fc | 133 | } else { |
27c192da | 134 | error = vget(vdp); |
e828c39a KM |
135 | if (!lockparent || error || *ndp->ni_next != '\0') |
136 | IUNLOCK(pdp); | |
f93197fc | 137 | } |
2bd0d28d KM |
138 | /* |
139 | * Check that the capability number did not change | |
140 | * while we were waiting for the lock. | |
141 | */ | |
27c192da KM |
142 | if (!error) { |
143 | if (vpid == vdp->v_id) | |
144 | return (0); | |
e828c39a KM |
145 | iput(dp); |
146 | if (lockparent && pdp != dp && *ndp->ni_next == '\0') | |
147 | IUNLOCK(pdp); | |
27c192da | 148 | } |
2bd0d28d KM |
149 | ILOCK(pdp); |
150 | dp = pdp; | |
a48aecd3 | 151 | vdp = ITOV(dp); |
2bd0d28d | 152 | ndp->ni_vp = NULL; |
f93197fc KM |
153 | } |
154 | ||
6459ebe0 | 155 | /* |
6bd0bb92 BJ |
156 | * Suppress search for slots unless creating |
157 | * file and at end of pathname, in which case | |
158 | * we watch for a place to put the new file in | |
159 | * case it doesn't already exist. | |
6459ebe0 | 160 | */ |
6bd0bb92 | 161 | slotstatus = FOUND; |
7604aef4 | 162 | if ((flag == CREATE || flag == RENAME) && *ndp->ni_next == 0) { |
6bd0bb92 BJ |
163 | slotstatus = NONE; |
164 | slotfreespace = 0; | |
d870be74 | 165 | slotneeded = DIRSIZ(&ndp->ni_dent); |
6bd0bb92 | 166 | } |
7604aef4 | 167 | |
84ef30ec | 168 | /* |
7604aef4 KM |
169 | * If there is cached information on a previous search of |
170 | * this directory, pick up where we last left off. | |
f93197fc | 171 | * We cache only lookups as these are the most common |
84ef30ec KM |
172 | * and have the greatest payoff. Caching CREATE has little |
173 | * benefit as it usually must search the entire directory | |
174 | * to determine that the entry does not exist. Caching the | |
7604aef4 KM |
175 | * location of the last DELETE or RENAME has not reduced |
176 | * profiling time and hence has been removed in the interest | |
177 | * of simplicity. | |
84ef30ec | 178 | */ |
7604aef4 | 179 | if (flag != LOOKUP || dp->i_diroff == 0 || dp->i_diroff > dp->i_size) { |
d870be74 | 180 | ndp->ni_offset = 0; |
84ef30ec KM |
181 | numdirpasses = 1; |
182 | } else { | |
7604aef4 | 183 | ndp->ni_offset = dp->i_diroff; |
d870be74 | 184 | entryoffsetinblock = blkoff(fs, ndp->ni_offset); |
84ef30ec | 185 | if (entryoffsetinblock != 0) { |
7604aef4 KM |
186 | error = blkatoff(dp, ndp->ni_offset, (char **)0, &bp); |
187 | if (error) | |
188 | return (error); | |
84ef30ec KM |
189 | } |
190 | numdirpasses = 2; | |
f93197fc | 191 | nchstats.ncs_2passes++; |
84ef30ec KM |
192 | } |
193 | endsearch = roundup(dp->i_size, DIRBLKSIZ); | |
9e7c949b | 194 | enduseful = 0; |
6bd0bb92 | 195 | |
84ef30ec | 196 | searchloop: |
d870be74 | 197 | while (ndp->ni_offset < endsearch) { |
f5039631 BJ |
198 | /* |
199 | * If offset is on a block boundary, | |
200 | * read the next directory block. | |
201 | * Release previous if it exists. | |
202 | */ | |
d870be74 | 203 | if (blkoff(fs, ndp->ni_offset) == 0) { |
f5039631 BJ |
204 | if (bp != NULL) |
205 | brelse(bp); | |
7604aef4 KM |
206 | error = blkatoff(dp, ndp->ni_offset, (char **)0, &bp); |
207 | if (error) | |
208 | return (error); | |
6bd0bb92 | 209 | entryoffsetinblock = 0; |
6459ebe0 KM |
210 | } |
211 | /* | |
6bd0bb92 | 212 | * If still looking for a slot, and at a DIRBLKSIZE |
d82d5deb | 213 | * boundary, have to start looking for free space again. |
6459ebe0 | 214 | */ |
6bd0bb92 | 215 | if (slotstatus == NONE && |
7604aef4 | 216 | (entryoffsetinblock & (DIRBLKSIZ - 1)) == 0) { |
6bd0bb92 BJ |
217 | slotoffset = -1; |
218 | slotfreespace = 0; | |
219 | } | |
6bd0bb92 | 220 | /* |
d82d5deb KM |
221 | * Get pointer to next entry. |
222 | * Full validation checks are slow, so we only check | |
223 | * enough to insure forward progress through the | |
224 | * directory. Complete checks can be run by patching | |
225 | * "dirchk" to be true. | |
6bd0bb92 BJ |
226 | */ |
227 | ep = (struct direct *)(bp->b_un.b_addr + entryoffsetinblock); | |
a5e62f37 | 228 | if (ep->d_reclen == 0 || |
d82d5deb | 229 | dirchk && dirbadentry(ep, entryoffsetinblock)) { |
7604aef4 KM |
230 | int i; |
231 | ||
d870be74 | 232 | dirbad(dp, ndp->ni_offset, "mangled entry"); |
d82d5deb | 233 | i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)); |
d870be74 | 234 | ndp->ni_offset += i; |
6bd0bb92 | 235 | entryoffsetinblock += i; |
6459ebe0 KM |
236 | continue; |
237 | } | |
6bd0bb92 | 238 | |
6459ebe0 | 239 | /* |
6bd0bb92 | 240 | * If an appropriate sized slot has not yet been found, |
6459ebe0 KM |
241 | * check to see if one is available. Also accumulate space |
242 | * in the current block so that we can determine if | |
243 | * compaction is viable. | |
244 | */ | |
6bd0bb92 BJ |
245 | if (slotstatus != FOUND) { |
246 | int size = ep->d_reclen; | |
247 | ||
6459ebe0 KM |
248 | if (ep->d_ino != 0) |
249 | size -= DIRSIZ(ep); | |
250 | if (size > 0) { | |
6bd0bb92 BJ |
251 | if (size >= slotneeded) { |
252 | slotstatus = FOUND; | |
d870be74 | 253 | slotoffset = ndp->ni_offset; |
6bd0bb92 BJ |
254 | slotsize = ep->d_reclen; |
255 | } else if (slotstatus == NONE) { | |
256 | slotfreespace += size; | |
257 | if (slotoffset == -1) | |
d870be74 | 258 | slotoffset = ndp->ni_offset; |
6bd0bb92 BJ |
259 | if (slotfreespace >= slotneeded) { |
260 | slotstatus = COMPACT; | |
d870be74 KM |
261 | slotsize = ndp->ni_offset + |
262 | ep->d_reclen - slotoffset; | |
6bd0bb92 | 263 | } |
6459ebe0 | 264 | } |
f5039631 | 265 | } |
f5039631 | 266 | } |
6bd0bb92 | 267 | |
f5039631 | 268 | /* |
6bd0bb92 | 269 | * Check for a name match. |
f5039631 | 270 | */ |
6bd0bb92 | 271 | if (ep->d_ino) { |
d870be74 | 272 | if (ep->d_namlen == ndp->ni_dent.d_namlen && |
7604aef4 KM |
273 | !bcmp(ndp->ni_ptr, ep->d_name, |
274 | (unsigned)ep->d_namlen)) { | |
275 | /* | |
276 | * Save directory entry's inode number and | |
277 | * reclen in ndp->ni_dent, and release | |
278 | * directory buffer. | |
279 | */ | |
280 | ndp->ni_dent.d_ino = ep->d_ino; | |
281 | ndp->ni_dent.d_reclen = ep->d_reclen; | |
282 | brelse(bp); | |
6bd0bb92 | 283 | goto found; |
7604aef4 | 284 | } |
6bd0bb92 | 285 | } |
d870be74 KM |
286 | prevoff = ndp->ni_offset; |
287 | ndp->ni_offset += ep->d_reclen; | |
6bd0bb92 | 288 | entryoffsetinblock += ep->d_reclen; |
9e7c949b KM |
289 | if (ep->d_ino) |
290 | enduseful = ndp->ni_offset; | |
6bd0bb92 | 291 | } |
f93197fc | 292 | /* notfound: */ |
84ef30ec | 293 | /* |
f93197fc | 294 | * If we started in the middle of the directory and failed |
84ef30ec KM |
295 | * to find our target, we must check the beginning as well. |
296 | */ | |
297 | if (numdirpasses == 2) { | |
298 | numdirpasses--; | |
d870be74 | 299 | ndp->ni_offset = 0; |
7604aef4 | 300 | endsearch = dp->i_diroff; |
84ef30ec KM |
301 | goto searchloop; |
302 | } | |
7604aef4 KM |
303 | if (bp != NULL) |
304 | brelse(bp); | |
6bd0bb92 BJ |
305 | /* |
306 | * If creating, and at end of pathname and current | |
4f083fd7 SL |
307 | * directory has not been removed, then can consider |
308 | * allowing file to be created. | |
6bd0bb92 | 309 | */ |
7604aef4 KM |
310 | if ((flag == CREATE || flag == RENAME) && |
311 | *ndp->ni_next == 0 && dp->i_nlink != 0) { | |
f5039631 | 312 | /* |
6bd0bb92 BJ |
313 | * Access for write is interpreted as allowing |
314 | * creation of files in the directory. | |
f5039631 | 315 | */ |
b7581321 | 316 | if (error = ufs_access(vdp, VWRITE, ndp->ni_cred, p)) |
7604aef4 | 317 | return (error); |
f5039631 | 318 | /* |
6bd0bb92 BJ |
319 | * Return an indication of where the new directory |
320 | * entry should be put. If we didn't find a slot, | |
d870be74 KM |
321 | * then set ndp->ni_count to 0 indicating that the new |
322 | * slot belongs at the end of the directory. If we found | |
323 | * a slot, then the new entry can be put in the range | |
324 | * [ndp->ni_offset .. ndp->ni_offset + ndp->ni_count) | |
f5039631 | 325 | */ |
84ef30ec | 326 | if (slotstatus == NONE) { |
d870be74 KM |
327 | ndp->ni_offset = roundup(dp->i_size, DIRBLKSIZ); |
328 | ndp->ni_count = 0; | |
9e7c949b | 329 | enduseful = ndp->ni_offset; |
84ef30ec | 330 | } else { |
d870be74 KM |
331 | ndp->ni_offset = slotoffset; |
332 | ndp->ni_count = slotsize; | |
9e7c949b KM |
333 | if (enduseful < slotoffset + slotsize) |
334 | enduseful = slotoffset + slotsize; | |
5485e062 | 335 | } |
9e7c949b | 336 | ndp->ni_endoff = roundup(enduseful, DIRBLKSIZ); |
6bd0bb92 | 337 | dp->i_flag |= IUPD|ICHG; |
f5039631 | 338 | /* |
6bd0bb92 BJ |
339 | * We return with the directory locked, so that |
340 | * the parameters we set up above will still be | |
341 | * valid if we actually decide to do a direnter(). | |
7604aef4 KM |
342 | * We return ni_vp == NULL to indicate that the entry |
343 | * does not currently exist; we leave a pointer to | |
344 | * the (locked) directory inode in ndp->ni_dvp. | |
345 | * | |
346 | * NB - if the directory is unlocked, then this | |
347 | * information cannot be used. | |
f5039631 | 348 | */ |
7604aef4 KM |
349 | if (!lockparent) |
350 | IUNLOCK(dp); | |
6bd0bb92 | 351 | } |
2bd0d28d KM |
352 | /* |
353 | * Insert name into cache (as non-existent) if appropriate. | |
354 | */ | |
e828c39a | 355 | if (ndp->ni_makeentry && flag != CREATE) |
2bd0d28d | 356 | cache_enter(ndp); |
7604aef4 KM |
357 | return (ENOENT); |
358 | ||
6bd0bb92 | 359 | found: |
f93197fc KM |
360 | if (numdirpasses == 2) |
361 | nchstats.ncs_pass2++; | |
6bd0bb92 BJ |
362 | /* |
363 | * Check that directory length properly reflects presence | |
364 | * of this entry. | |
365 | */ | |
4a0415d6 | 366 | if (entryoffsetinblock + DIRSIZ(ep) > dp->i_size) { |
d870be74 | 367 | dirbad(dp, ndp->ni_offset, "i_size too small"); |
4a0415d6 | 368 | dp->i_size = entryoffsetinblock + DIRSIZ(ep); |
6bd0bb92 BJ |
369 | dp->i_flag |= IUPD|ICHG; |
370 | } | |
371 | ||
372 | /* | |
84ef30ec | 373 | * Found component in pathname. |
f93197fc | 374 | * If the final component of path name, save information |
84ef30ec KM |
375 | * in the cache as to where the entry was found. |
376 | */ | |
7604aef4 KM |
377 | if (*ndp->ni_next == '\0' && flag == LOOKUP) |
378 | dp->i_diroff = ndp->ni_offset &~ (DIRBLKSIZ - 1); | |
6bd0bb92 BJ |
379 | |
380 | /* | |
381 | * If deleting, and at end of pathname, return | |
382 | * parameters which can be used to remove file. | |
7604aef4 KM |
383 | * If the wantparent flag isn't set, we return only |
384 | * the directory (in ndp->ni_dvp), otherwise we go | |
4f083fd7 | 385 | * on and lock the inode, being careful with ".". |
6bd0bb92 | 386 | */ |
7604aef4 | 387 | if (flag == DELETE && *ndp->ni_next == 0) { |
6bd0bb92 BJ |
388 | /* |
389 | * Write access to directory required to delete files. | |
390 | */ | |
b7581321 | 391 | if (error = ufs_access(vdp, VWRITE, ndp->ni_cred, p)) |
7604aef4 | 392 | return (error); |
6bd0bb92 | 393 | /* |
d870be74 | 394 | * Return pointer to current entry in ndp->ni_offset, |
6bd0bb92 | 395 | * and distance past previous entry (if there |
d870be74 | 396 | * is a previous entry in this block) in ndp->ni_count. |
2bd0d28d | 397 | * Save directory inode pointer in ndp->ni_dvp for dirremove(). |
6bd0bb92 | 398 | */ |
d870be74 KM |
399 | if ((ndp->ni_offset&(DIRBLKSIZ-1)) == 0) |
400 | ndp->ni_count = 0; | |
6bd0bb92 | 401 | else |
d870be74 | 402 | ndp->ni_count = ndp->ni_offset - prevoff; |
7604aef4 | 403 | if (dp->i_number == ndp->ni_dent.d_ino) { |
8fe1c702 | 404 | VREF(vdp); |
a48aecd3 KM |
405 | ndp->ni_vp = vdp; |
406 | return (0); | |
4f083fd7 | 407 | } |
a48aecd3 KM |
408 | if (error = iget(dp, ndp->ni_dent.d_ino, &tdp)) |
409 | return (error); | |
410 | /* | |
411 | * If directory is "sticky", then user must own | |
412 | * the directory, or the file in it, else she | |
413 | * may not delete it (unless she's root). This | |
414 | * implements append-only directories. | |
415 | */ | |
416 | if ((dp->i_mode & ISVTX) && | |
417 | ndp->ni_cred->cr_uid != 0 && | |
418 | ndp->ni_cred->cr_uid != dp->i_uid && | |
419 | tdp->i_uid != ndp->ni_cred->cr_uid) { | |
420 | iput(tdp); | |
421 | return (EPERM); | |
422 | } | |
423 | ndp->ni_vp = ITOV(tdp); | |
424 | if (!lockparent) | |
425 | IUNLOCK(dp); | |
7604aef4 | 426 | return (0); |
6bd0bb92 BJ |
427 | } |
428 | ||
4f083fd7 | 429 | /* |
7604aef4 | 430 | * If rewriting (RENAME), return the inode and the |
4f083fd7 SL |
431 | * information required to rewrite the present directory |
432 | * Must get inode of directory entry to verify it's a | |
7604aef4 | 433 | * regular file, or empty directory. |
4f083fd7 | 434 | */ |
7604aef4 | 435 | if (flag == RENAME && wantparent && *ndp->ni_next == 0) { |
b7581321 | 436 | if (error = ufs_access(vdp, VWRITE, ndp->ni_cred, p)) |
7604aef4 | 437 | return (error); |
4f083fd7 | 438 | /* |
7604aef4 KM |
439 | * Careful about locking second inode. |
440 | * This can only occur if the target is ".". | |
4f083fd7 | 441 | */ |
7604aef4 KM |
442 | if (dp->i_number == ndp->ni_dent.d_ino) |
443 | return (EISDIR); | |
444 | if (error = iget(dp, ndp->ni_dent.d_ino, &tdp)) | |
445 | return (error); | |
446 | ndp->ni_vp = ITOV(tdp); | |
447 | if (!lockparent) | |
448 | IUNLOCK(dp); | |
449 | return (0); | |
4f083fd7 SL |
450 | } |
451 | ||
6bd0bb92 | 452 | /* |
7604aef4 KM |
453 | * Step through the translation in the name. We do not `iput' the |
454 | * directory because we may need it again if a symbolic link | |
bde63aa5 KM |
455 | * is relative to the current directory. Instead we save it |
456 | * unlocked as "pdp". We must get the target inode before unlocking | |
457 | * the directory to insure that the inode will not be removed | |
458 | * before we get it. We prevent deadlock by always fetching | |
459 | * inodes from the root, moving down the directory tree. Thus | |
460 | * when following backward pointers ".." we must unlock the | |
461 | * parent directory before getting the requested directory. | |
462 | * There is a potential race condition here if both the current | |
463 | * and parent directories are removed before the `iget' for the | |
464 | * inode associated with ".." returns. We hope that this occurs | |
465 | * infrequently since we cannot avoid this race condition without | |
a4358a25 | 466 | * implementing a sophisticated deadlock detection algorithm. |
bde63aa5 KM |
467 | * Note also that this simple deadlock detection scheme will not |
468 | * work if the file system has any hard links other than ".." | |
469 | * that point backwards in the directory structure. | |
6bd0bb92 BJ |
470 | */ |
471 | pdp = dp; | |
7604aef4 | 472 | if (ndp->ni_isdotdot) { |
56700195 | 473 | IUNLOCK(pdp); /* race to get the inode */ |
7604aef4 KM |
474 | if (error = iget(dp, ndp->ni_dent.d_ino, &tdp)) { |
475 | ILOCK(pdp); | |
476 | return (error); | |
477 | } | |
a6ad75d0 KM |
478 | if (lockparent && *ndp->ni_next == '\0') |
479 | ILOCK(pdp); | |
7604aef4 | 480 | ndp->ni_vp = ITOV(tdp); |
d870be74 | 481 | } else if (dp->i_number == ndp->ni_dent.d_ino) { |
8fe1c702 | 482 | VREF(vdp); /* we want ourself, ie "." */ |
7604aef4 | 483 | ndp->ni_vp = vdp; |
bde63aa5 | 484 | } else { |
7604aef4 KM |
485 | if (error = iget(dp, ndp->ni_dent.d_ino, &tdp)) |
486 | return (error); | |
a6ad75d0 KM |
487 | if (!lockparent || *ndp->ni_next != '\0') |
488 | IUNLOCK(pdp); | |
7604aef4 | 489 | ndp->ni_vp = ITOV(tdp); |
bde63aa5 | 490 | } |
f93197fc KM |
491 | |
492 | /* | |
a5e62f37 | 493 | * Insert name into cache if appropriate. |
f93197fc | 494 | */ |
7604aef4 KM |
495 | if (ndp->ni_makeentry) |
496 | cache_enter(ndp); | |
497 | return (0); | |
10873320 BJ |
498 | } |
499 | ||
f93197fc | 500 | |
d870be74 | 501 | dirbad(ip, offset, how) |
6bd0bb92 | 502 | struct inode *ip; |
d870be74 | 503 | off_t offset; |
6bd0bb92 BJ |
504 | char *how; |
505 | { | |
506 | ||
507 | printf("%s: bad dir ino %d at offset %d: %s\n", | |
d870be74 | 508 | ip->i_fs->fs_fsmnt, ip->i_number, offset, how); |
da089146 | 509 | panic("bad dir"); |
6bd0bb92 BJ |
510 | } |
511 | ||
d82d5deb KM |
512 | /* |
513 | * Do consistency checking on a directory entry: | |
514 | * record length must be multiple of 4 | |
d82d5deb KM |
515 | * entry must fit in rest of its DIRBLKSIZ block |
516 | * record must be large enough to contain entry | |
517 | * name is not longer than MAXNAMLEN | |
518 | * name must be as long as advertised, and null terminated | |
519 | */ | |
520 | dirbadentry(ep, entryoffsetinblock) | |
6bd0bb92 | 521 | register struct direct *ep; |
d82d5deb | 522 | int entryoffsetinblock; |
6bd0bb92 | 523 | { |
6bd0bb92 BJ |
524 | register int i; |
525 | ||
a5e62f37 | 526 | if ((ep->d_reclen & 0x3) != 0 || |
d82d5deb KM |
527 | ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) || |
528 | ep->d_reclen < DIRSIZ(ep) || ep->d_namlen > MAXNAMLEN) | |
529 | return (1); | |
6bd0bb92 | 530 | for (i = 0; i < ep->d_namlen; i++) |
d870be74 | 531 | if (ep->d_name[i] == '\0') |
6bd0bb92 BJ |
532 | return (1); |
533 | return (ep->d_name[i]); | |
534 | } | |
535 | ||
6bd0bb92 BJ |
536 | /* |
537 | * Write a directory entry after a call to namei, using the parameters | |
7604aef4 KM |
538 | * which it left in nameidata. The argument ip is the inode which the |
539 | * new directory entry will refer to. The nameidata field ndp->ni_dvp | |
540 | * is a pointer to the directory to be written, which was left locked by | |
d870be74 | 541 | * namei. Remaining parameters (ndp->ni_offset, ndp->ni_count) indicate |
6bd0bb92 BJ |
542 | * how the space for the new entry is to be gotten. |
543 | */ | |
d870be74 | 544 | direnter(ip, ndp) |
6bd0bb92 | 545 | struct inode *ip; |
d870be74 | 546 | register struct nameidata *ndp; |
f5039631 | 547 | { |
6bd0bb92 | 548 | register struct direct *ep, *nep; |
7604aef4 | 549 | register struct inode *dp = VTOI(ndp->ni_dvp); |
6bd0bb92 | 550 | struct buf *bp; |
efa3a91c | 551 | int loc, spacefree, error = 0; |
b32450f4 BJ |
552 | u_int dsize; |
553 | int newentrysize; | |
6bd0bb92 | 554 | char *dirbuf; |
f5039631 | 555 | |
d870be74 KM |
556 | ndp->ni_dent.d_ino = ip->i_number; |
557 | newentrysize = DIRSIZ(&ndp->ni_dent); | |
558 | if (ndp->ni_count == 0) { | |
6bd0bb92 | 559 | /* |
d870be74 KM |
560 | * If ndp->ni_count is 0, then namei could find no space in the |
561 | * directory. In this case ndp->ni_offset will be on a directory | |
6bd0bb92 BJ |
562 | * block boundary and we will write the new entry into a fresh |
563 | * block. | |
564 | */ | |
d870be74 | 565 | if (ndp->ni_offset&(DIRBLKSIZ-1)) |
6bd0bb92 | 566 | panic("wdir: newblk"); |
d870be74 | 567 | ndp->ni_dent.d_reclen = DIRBLKSIZ; |
5c299d0e KM |
568 | ndp->ni_count = newentrysize; |
569 | ndp->ni_resid = newentrysize; | |
5f9e6a68 | 570 | ndp->ni_base = (caddr_t)&ndp->ni_dent; |
f460869c KM |
571 | ndp->ni_iov = &ndp->ni_nd.nd_iovec; |
572 | ndp->ni_iovcnt = 1; | |
573 | ndp->ni_rw = UIO_WRITE; | |
7dda6faa | 574 | ndp->ni_uioseg = UIO_SYSSPACE; |
6c41489b KM |
575 | error = |
576 | ufs_write(ndp->ni_dvp, &ndp->ni_uio, IO_SYNC, ndp->ni_cred); | |
15365e07 | 577 | if (DIRBLKSIZ > dp->i_fs->fs_fsize) { |
7604aef4 | 578 | panic("wdir: blksize"); /* XXX - should grow w/balloc */ |
15365e07 | 579 | } else { |
23de9f20 | 580 | dp->i_size = roundup(dp->i_size, DIRBLKSIZ); |
15365e07 KM |
581 | dp->i_flag |= ICHG; |
582 | } | |
f2a5ad78 | 583 | return (error); |
6bd0bb92 BJ |
584 | } |
585 | ||
586 | /* | |
d870be74 KM |
587 | * If ndp->ni_count is non-zero, then namei found space for the new |
588 | * entry in the range ndp->ni_offset to ndp->ni_offset + ndp->ni_count. | |
6bd0bb92 BJ |
589 | * in the directory. To use this space, we may have to compact |
590 | * the entries located there, by copying them together towards | |
591 | * the beginning of the block, leaving the free space in | |
592 | * one usable chunk at the end. | |
593 | */ | |
594 | ||
595 | /* | |
596 | * Increase size of directory if entry eats into new space. | |
597 | * This should never push the size past a new multiple of | |
598 | * DIRBLKSIZE. | |
23de9f20 KM |
599 | * |
600 | * N.B. - THIS IS AN ARTIFACT OF 4.2 AND SHOULD NEVER HAPPEN. | |
6bd0bb92 | 601 | */ |
9e7c949b KM |
602 | if (ndp->ni_offset + ndp->ni_count > dp->i_size) |
603 | dp->i_size = ndp->ni_offset + ndp->ni_count; | |
6bd0bb92 | 604 | /* |
7604aef4 | 605 | * Get the block containing the space for the new directory entry. |
6bd0bb92 | 606 | */ |
f460869c | 607 | if (error = blkatoff(dp, ndp->ni_offset, (char **)&dirbuf, &bp)) |
7604aef4 | 608 | return (error); |
6bd0bb92 BJ |
609 | /* |
610 | * Find space for the new entry. In the simple case, the | |
611 | * entry at offset base will have the space. If it does | |
612 | * not, then namei arranged that compacting the region | |
d870be74 | 613 | * ndp->ni_offset to ndp->ni_offset+ndp->ni_count would yield the space. |
6bd0bb92 BJ |
614 | */ |
615 | ep = (struct direct *)dirbuf; | |
616 | dsize = DIRSIZ(ep); | |
efa3a91c | 617 | spacefree = ep->d_reclen - dsize; |
d870be74 | 618 | for (loc = ep->d_reclen; loc < ndp->ni_count; ) { |
6bd0bb92 BJ |
619 | nep = (struct direct *)(dirbuf + loc); |
620 | if (ep->d_ino) { | |
621 | /* trim the existing slot */ | |
622 | ep->d_reclen = dsize; | |
623 | ep = (struct direct *)((char *)ep + dsize); | |
624 | } else { | |
625 | /* overwrite; nothing there; header is ours */ | |
7604aef4 | 626 | spacefree += dsize; |
6bd0bb92 BJ |
627 | } |
628 | dsize = DIRSIZ(nep); | |
efa3a91c | 629 | spacefree += nep->d_reclen - dsize; |
6bd0bb92 | 630 | loc += nep->d_reclen; |
9f024ffc | 631 | bcopy((caddr_t)nep, (caddr_t)ep, dsize); |
6bd0bb92 BJ |
632 | } |
633 | /* | |
634 | * Update the pointer fields in the previous entry (if any), | |
635 | * copy in the new entry, and write out the block. | |
636 | */ | |
637 | if (ep->d_ino == 0) { | |
efa3a91c | 638 | if (spacefree + dsize < newentrysize) |
6bd0bb92 | 639 | panic("wdir: compact1"); |
d870be74 | 640 | ndp->ni_dent.d_reclen = spacefree + dsize; |
6bd0bb92 | 641 | } else { |
efa3a91c | 642 | if (spacefree < newentrysize) |
6bd0bb92 | 643 | panic("wdir: compact2"); |
d870be74 | 644 | ndp->ni_dent.d_reclen = spacefree; |
6bd0bb92 BJ |
645 | ep->d_reclen = dsize; |
646 | ep = (struct direct *)((char *)ep + dsize); | |
647 | } | |
d870be74 | 648 | bcopy((caddr_t)&ndp->ni_dent, (caddr_t)ep, (u_int)newentrysize); |
7604aef4 | 649 | error = bwrite(bp); |
9e7c949b | 650 | dp->i_flag |= IUPD|ICHG; |
a48aecd3 | 651 | if (!error && ndp->ni_endoff && ndp->ni_endoff < dp->i_size) |
bcf1400c | 652 | error = itrunc(dp, (u_long)ndp->ni_endoff, IO_SYNC); |
f2a5ad78 | 653 | return (error); |
6bd0bb92 BJ |
654 | } |
655 | ||
4f083fd7 | 656 | /* |
5c299d0e KM |
657 | * Remove a directory entry after a call to namei, using |
658 | * the parameters which it left in nameidata. The entry | |
d870be74 KM |
659 | * ni_offset contains the offset into the directory of the |
660 | * entry to be eliminated. The ni_count field contains the | |
4f083fd7 SL |
661 | * size of the previous record in the directory. If this |
662 | * is 0, the first entry is being deleted, so we need only | |
663 | * zero the inode number to mark the entry as free. If the | |
664 | * entry isn't the first in the directory, we must reclaim | |
665 | * the space of the now empty record by adding the record size | |
666 | * to the size of the previous entry. | |
667 | */ | |
d870be74 KM |
668 | dirremove(ndp) |
669 | register struct nameidata *ndp; | |
6bd0bb92 | 670 | { |
7604aef4 | 671 | register struct inode *dp = VTOI(ndp->ni_dvp); |
6bd0bb92 | 672 | struct direct *ep; |
7604aef4 KM |
673 | struct buf *bp; |
674 | int error; | |
6bd0bb92 | 675 | |
d870be74 | 676 | if (ndp->ni_count == 0) { |
6bd0bb92 BJ |
677 | /* |
678 | * First entry in block: set d_ino to zero. | |
679 | */ | |
d870be74 | 680 | ndp->ni_dent.d_ino = 0; |
5c299d0e | 681 | ndp->ni_count = ndp->ni_resid = DIRSIZ(&ndp->ni_dent); |
5f9e6a68 | 682 | ndp->ni_base = (caddr_t)&ndp->ni_dent; |
f460869c KM |
683 | ndp->ni_iov = &ndp->ni_nd.nd_iovec; |
684 | ndp->ni_iovcnt = 1; | |
685 | ndp->ni_rw = UIO_WRITE; | |
7dda6faa | 686 | ndp->ni_uioseg = UIO_SYSSPACE; |
6c41489b KM |
687 | error = |
688 | ufs_write(ndp->ni_dvp, &ndp->ni_uio, IO_SYNC, ndp->ni_cred); | |
36d4ed87 | 689 | } else { |
6bd0bb92 BJ |
690 | /* |
691 | * Collapse new free space into previous entry. | |
692 | */ | |
7604aef4 KM |
693 | if (error = blkatoff(dp, ndp->ni_offset - ndp->ni_count, |
694 | (char **)&ep, &bp)) { | |
695 | return (error); | |
696 | } | |
d870be74 | 697 | ep->d_reclen += ndp->ni_dent.d_reclen; |
7604aef4 | 698 | error = bwrite(bp); |
6bd0bb92 BJ |
699 | dp->i_flag |= IUPD|ICHG; |
700 | } | |
7604aef4 | 701 | return (error); |
10873320 | 702 | } |
6459ebe0 | 703 | |
4f083fd7 SL |
704 | /* |
705 | * Rewrite an existing directory entry to point at the inode | |
706 | * supplied. The parameters describing the directory entry are | |
707 | * set up by a call to namei. | |
708 | */ | |
d870be74 | 709 | dirrewrite(dp, ip, ndp) |
4f083fd7 | 710 | struct inode *dp, *ip; |
d870be74 | 711 | struct nameidata *ndp; |
4f083fd7 SL |
712 | { |
713 | ||
d870be74 | 714 | ndp->ni_dent.d_ino = ip->i_number; |
5c299d0e | 715 | ndp->ni_count = ndp->ni_resid = DIRSIZ(&ndp->ni_dent); |
5f9e6a68 | 716 | ndp->ni_base = (caddr_t)&ndp->ni_dent; |
f460869c KM |
717 | ndp->ni_iov = &ndp->ni_nd.nd_iovec; |
718 | ndp->ni_iovcnt = 1; | |
719 | ndp->ni_rw = UIO_WRITE; | |
7dda6faa | 720 | ndp->ni_uioseg = UIO_SYSSPACE; |
6c41489b | 721 | return (ufs_write(ITOV(dp), &ndp->ni_uio, IO_SYNC, ndp->ni_cred)); |
4f083fd7 SL |
722 | } |
723 | ||
4a0415d6 SL |
724 | /* |
725 | * Return buffer with contents of block "offset" | |
726 | * from the beginning of directory "ip". If "res" | |
727 | * is non-zero, fill it in with a pointer to the | |
728 | * remaining space in the directory. | |
729 | */ | |
7604aef4 | 730 | blkatoff(ip, offset, res, bpp) |
6bd0bb92 BJ |
731 | struct inode *ip; |
732 | off_t offset; | |
733 | char **res; | |
7604aef4 | 734 | struct buf **bpp; |
6459ebe0 | 735 | { |
6bd0bb92 | 736 | register struct fs *fs = ip->i_fs; |
3fd23f5c | 737 | daddr_t lbn = lblkno(fs, offset); |
6bd0bb92 | 738 | int bsize = blksize(fs, ip, lbn); |
7604aef4 | 739 | struct buf *bp; |
7e4f159c | 740 | daddr_t bn; |
7604aef4 | 741 | int error; |
6459ebe0 | 742 | |
7604aef4 | 743 | *bpp = 0; |
bcf1400c | 744 | if (error = bread(ITOV(ip), lbn, bsize, NOCRED, &bp)) { |
6bd0bb92 | 745 | brelse(bp); |
7604aef4 | 746 | return (error); |
6bd0bb92 BJ |
747 | } |
748 | if (res) | |
7e4f159c | 749 | *res = bp->b_un.b_addr + blkoff(fs, offset); |
7604aef4 KM |
750 | *bpp = bp; |
751 | return (0); | |
6459ebe0 | 752 | } |
4f083fd7 SL |
753 | |
754 | /* | |
755 | * Check if a directory is empty or not. | |
756 | * Inode supplied must be locked. | |
17d11193 SL |
757 | * |
758 | * Using a struct dirtemplate here is not precisely | |
759 | * what we want, but better than using a struct direct. | |
760 | * | |
761 | * NB: does not handle corrupted directories. | |
4f083fd7 | 762 | */ |
7604aef4 | 763 | dirempty(ip, parentino, cred) |
05d2bbce | 764 | register struct inode *ip; |
68f21562 | 765 | ino_t parentino; |
7604aef4 | 766 | struct ucred *cred; |
4f083fd7 SL |
767 | { |
768 | register off_t off; | |
17d11193 SL |
769 | struct dirtemplate dbuf; |
770 | register struct direct *dp = (struct direct *)&dbuf; | |
05d2bbce | 771 | int error, count; |
17d11193 | 772 | #define MINDIRSIZ (sizeof (struct dirtemplate) / 2) |
4f083fd7 SL |
773 | |
774 | for (off = 0; off < ip->i_size; off += dp->d_reclen) { | |
b7581321 KM |
775 | error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)dp, MINDIRSIZ, off, |
776 | UIO_SYSSPACE, IO_NODELOCKED, cred, &count, (struct proc *)0); | |
17d11193 SL |
777 | /* |
778 | * Since we read MINDIRSIZ, residual must | |
779 | * be 0 unless we're at end of file. | |
780 | */ | |
781 | if (error || count != 0) | |
4f083fd7 | 782 | return (0); |
d14b14c3 | 783 | /* avoid infinite loops */ |
a5e62f37 | 784 | if (dp->d_reclen == 0) |
d14b14c3 | 785 | return (0); |
17d11193 | 786 | /* skip empty entries */ |
4f083fd7 SL |
787 | if (dp->d_ino == 0) |
788 | continue; | |
17d11193 SL |
789 | /* accept only "." and ".." */ |
790 | if (dp->d_namlen > 2) | |
791 | return (0); | |
4f083fd7 SL |
792 | if (dp->d_name[0] != '.') |
793 | return (0); | |
17d11193 SL |
794 | /* |
795 | * At this point d_namlen must be 1 or 2. | |
796 | * 1 implies ".", 2 implies ".." if second | |
797 | * char is also "." | |
798 | */ | |
68f21562 KM |
799 | if (dp->d_namlen == 1) |
800 | continue; | |
801 | if (dp->d_name[1] == '.' && dp->d_ino == parentino) | |
4f083fd7 SL |
802 | continue; |
803 | return (0); | |
804 | } | |
805 | return (1); | |
806 | } | |
b1aa93b9 KM |
807 | |
808 | /* | |
809 | * Check if source directory is in the path of the target directory. | |
810 | * Target is supplied locked, source is unlocked. | |
811 | * The target is always iput() before returning. | |
812 | */ | |
7604aef4 | 813 | checkpath(source, target, cred) |
b1aa93b9 | 814 | struct inode *source, *target; |
7604aef4 | 815 | struct ucred *cred; |
b1aa93b9 KM |
816 | { |
817 | struct dirtemplate dirbuf; | |
7604aef4 | 818 | struct inode *ip; |
b1aa93b9 KM |
819 | int error = 0; |
820 | ||
821 | ip = target; | |
822 | if (ip->i_number == source->i_number) { | |
823 | error = EEXIST; | |
824 | goto out; | |
825 | } | |
826 | if (ip->i_number == ROOTINO) | |
827 | goto out; | |
828 | ||
829 | for (;;) { | |
830 | if ((ip->i_mode&IFMT) != IFDIR) { | |
831 | error = ENOTDIR; | |
832 | break; | |
833 | } | |
6c41489b | 834 | error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)&dirbuf, |
7604aef4 | 835 | sizeof (struct dirtemplate), (off_t)0, UIO_SYSSPACE, |
b7581321 | 836 | IO_NODELOCKED, cred, (int *)0, (struct proc *)0); |
b1aa93b9 KM |
837 | if (error != 0) |
838 | break; | |
839 | if (dirbuf.dotdot_namlen != 2 || | |
4a287f19 KM |
840 | dirbuf.dotdot_name[0] != '.' || |
841 | dirbuf.dotdot_name[1] != '.') { | |
b1aa93b9 KM |
842 | error = ENOTDIR; |
843 | break; | |
844 | } | |
845 | if (dirbuf.dotdot_ino == source->i_number) { | |
846 | error = EINVAL; | |
847 | break; | |
848 | } | |
849 | if (dirbuf.dotdot_ino == ROOTINO) | |
850 | break; | |
851 | iput(ip); | |
7604aef4 | 852 | if (error = iget(ip, dirbuf.dotdot_ino, &ip)) |
b1aa93b9 | 853 | break; |
b1aa93b9 KM |
854 | } |
855 | ||
856 | out: | |
857 | if (error == ENOTDIR) | |
858 | printf("checkpath: .. not a directory\n"); | |
859 | if (ip != NULL) | |
860 | iput(ip); | |
861 | return (error); | |
862 | } |