]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2000-2002 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * The contents of this file constitute Original Code as defined in and | |
7 | * are subject to the Apple Public Source License Version 1.1 (the | |
8 | * "License"). You may not use this file except in compliance with the | |
9 | * License. Please obtain a copy of the License at | |
10 | * http://www.apple.com/publicsource and read it before using this file. | |
11 | * | |
12 | * This Original Code and all software distributed under the License are | |
13 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
14 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
15 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the | |
17 | * License for the specific language governing rights and limitations | |
18 | * under the License. | |
19 | * | |
20 | * @APPLE_LICENSE_HEADER_END@ | |
21 | */ | |
22 | /* | |
23 | * Copyright 1997,1998 Julian Elischer. All rights reserved. | |
24 | * julian@freebsd.org | |
25 | * | |
26 | * Redistribution and use in source and binary forms, with or without | |
27 | * modification, are permitted provided that the following conditions are | |
28 | * met: | |
29 | * 1. Redistributions of source code must retain the above copyright | |
30 | * notice, this list of conditions and the following disclaimer. | |
31 | * 2. Redistributions in binary form must reproduce the above copyright notice, | |
32 | * this list of conditions and the following disclaimer in the documentation | |
33 | * and/or other materials provided with the distribution. | |
34 | * | |
35 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS | |
36 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
37 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
38 | * DISCLAIMED. IN NO EVENT SHALL THE HOLDER OR CONTRIBUTORS BE LIABLE FOR | |
39 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
40 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
41 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
42 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
43 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
44 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
45 | * SUCH DAMAGE. | |
46 | * | |
47 | * devfs_vnops.c | |
48 | */ | |
49 | ||
50 | /* | |
51 | * HISTORY | |
52 | * Clark Warner (warner_c@apple.com) Tue Feb 10 2000 | |
53 | * - Added err_copyfile to the vnode operations table | |
54 | * Dieter Siegmund (dieter@apple.com) Thu Apr 8 14:08:19 PDT 1999 | |
55 | * - instead of duplicating specfs here, created a vnode-ops table | |
56 | * that redirects most operations to specfs (as is done with ufs); | |
57 | * - removed routines that made no sense | |
58 | * - cleaned up reclaim: replaced devfs_vntodn() with a macro VTODN() | |
59 | * - cleaned up symlink, link locking | |
60 | * - added the devfs_lock to protect devfs data structures against | |
61 | * driver's calling devfs_add_devswf()/etc. | |
62 | * Dieter Siegmund (dieter@apple.com) Wed Jul 14 13:37:59 PDT 1999 | |
63 | * - free the devfs devnode in devfs_inactive(), not just in devfs_reclaim() | |
64 | * to free up kernel memory as soon as it's available | |
65 | * - got rid of devfsspec_{read, write} | |
66 | * Dieter Siegmund (dieter@apple.com) Fri Sep 17 09:58:38 PDT 1999 | |
67 | * - update the mod/access times | |
68 | */ | |
69 | ||
70 | #include <sys/param.h> | |
71 | #include <sys/systm.h> | |
72 | #include <sys/buf.h> | |
73 | #include <sys/namei.h> | |
74 | #include <sys/kernel.h> | |
75 | #include <sys/fcntl.h> | |
76 | #include <sys/conf.h> | |
77 | #include <sys/disklabel.h> | |
78 | #include <sys/lock.h> | |
79 | #include <sys/stat.h> | |
80 | #include <sys/mount.h> | |
81 | #include <sys/proc.h> | |
82 | #include <sys/time.h> | |
83 | #include <sys/vnode.h> | |
84 | #include <miscfs/specfs/specdev.h> | |
85 | #include <sys/dirent.h> | |
86 | #include <sys/vmmeter.h> | |
87 | #include <sys/vm.h> | |
88 | ||
89 | #include "devfsdefs.h" | |
90 | ||
91 | /* | |
92 | * Convert a component of a pathname into a pointer to a locked node. | |
93 | * This is a very central and rather complicated routine. | |
94 | * If the file system is not maintained in a strict tree hierarchy, | |
95 | * this can result in a deadlock situation (see comments in code below). | |
96 | * | |
97 | * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on | |
98 | * whether the name is to be looked up, created, renamed, or deleted. | |
99 | * When CREATE, RENAME, or DELETE is specified, information usable in | |
100 | * creating, renaming, or deleting a directory entry may be calculated. | |
101 | * If flag has LOCKPARENT or'ed into it and the target of the pathname | |
102 | * exists, lookup returns both the target and its parent directory locked. | |
103 | * When creating or renaming and LOCKPARENT is specified, the target may | |
104 | * not be ".". When deleting and LOCKPARENT is specified, the target may | |
105 | * be "."., but the caller must check to ensure it does an vrele and DNUNLOCK | |
106 | * instead of two DNUNLOCKs. | |
107 | * | |
108 | * Overall outline of devfs_lookup: | |
109 | * | |
110 | * check accessibility of directory | |
111 | * null terminate the component (lookup leaves the whole string alone) | |
112 | * look for name in cache, if found, then if at end of path | |
113 | * and deleting or creating, drop it, else return name | |
114 | * search for name in directory, to found or notfound | |
115 | * notfound: | |
116 | * if creating, return locked directory, | |
117 | * else return error | |
118 | * found: | |
119 | * if at end of path and deleting, return information to allow delete | |
120 | * if at end of path and rewriting (RENAME and LOCKPARENT), lock target | |
121 | * node and return info to allow rewrite | |
122 | * if not at end, add name to cache; if at end and neither creating | |
123 | * nor deleting, add name to cache | |
124 | * On return to lookup, remove the null termination we put in at the start. | |
125 | * | |
126 | * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent node unlocked. | |
127 | */ | |
128 | static int | |
129 | devfs_lookup(struct vop_lookup_args *ap) | |
130 | /*struct vop_lookup_args { | |
131 | struct vnode * a_dvp; directory vnode ptr | |
132 | struct vnode ** a_vpp; where to put the result | |
133 | struct componentname * a_cnp; the name we want | |
134 | };*/ | |
135 | { | |
136 | struct componentname *cnp = ap->a_cnp; | |
137 | struct vnode *dir_vnode = ap->a_dvp; | |
138 | struct vnode **result_vnode = ap->a_vpp; | |
139 | devnode_t * dir_node; /* the directory we are searching */ | |
140 | devnode_t * node = NULL; /* the node we are searching for */ | |
141 | devdirent_t * nodename; | |
142 | int flags = cnp->cn_flags; | |
143 | int op = cnp->cn_nameiop; /* LOOKUP, CREATE, RENAME, or DELETE */ | |
144 | int lockparent = flags & LOCKPARENT; | |
145 | int wantparent = flags & (LOCKPARENT|WANTPARENT); | |
146 | int error = 0; | |
147 | struct proc *p = cnp->cn_proc; | |
148 | char heldchar; /* the char at the end of the name componet */ | |
149 | ||
150 | *result_vnode = NULL; /* safe not sorry */ /*XXX*/ | |
151 | ||
152 | if (dir_vnode->v_usecount == 0) | |
153 | printf("devfs_lookup: dir had no refs "); | |
154 | dir_node = VTODN(dir_vnode); | |
155 | ||
156 | /* | |
157 | * Check accessiblity of directory. | |
158 | */ | |
159 | if (dir_node->dn_type != DEV_DIR) { | |
160 | return (ENOTDIR); | |
161 | } | |
162 | ||
163 | if ((error = VOP_ACCESS(dir_vnode, VEXEC, cnp->cn_cred, p)) != 0) { | |
164 | return (error); | |
165 | } | |
166 | ||
167 | /* temporarily terminate string component */ | |
168 | heldchar = cnp->cn_nameptr[cnp->cn_namelen]; | |
169 | cnp->cn_nameptr[cnp->cn_namelen] = '\0'; | |
170 | DEVFS_LOCK(p); | |
171 | nodename = dev_findname(dir_node,cnp->cn_nameptr); | |
172 | if (nodename) { | |
173 | /* entry exists */ | |
174 | node = nodename->de_dnp; | |
175 | node->dn_last_lookup = nodename; /* for unlink */ | |
176 | /* Do potential vnode allocation here inside the lock | |
177 | * to make sure that our device node has a non-NULL dn_vn | |
178 | * associated with it. The device node might otherwise | |
179 | * get deleted out from under us (see devfs_dn_free()). | |
180 | */ | |
181 | error = devfs_dntovn(node, result_vnode, p); | |
182 | } | |
183 | DEVFS_UNLOCK(p); | |
184 | /* restore saved character */ | |
185 | cnp->cn_nameptr[cnp->cn_namelen] = heldchar; | |
186 | ||
187 | if (error) | |
188 | return (error); | |
189 | ||
190 | if (!nodename) { /* no entry */ | |
191 | /* If it doesn't exist and we're not the last component, | |
192 | * or we're at the last component, but we're not creating | |
193 | * or renaming, return ENOENT. | |
194 | */ | |
195 | if (!(flags & ISLASTCN) || !(op == CREATE || op == RENAME)) { | |
196 | return ENOENT; | |
197 | } | |
198 | /* | |
199 | * Access for write is interpreted as allowing | |
200 | * creation of files in the directory. | |
201 | */ | |
202 | if ((error = VOP_ACCESS(dir_vnode, VWRITE, | |
203 | cnp->cn_cred, p)) != 0) | |
204 | { | |
205 | return (error); | |
206 | } | |
207 | /* | |
208 | * We return with the directory locked, so that | |
209 | * the parameters we set up above will still be | |
210 | * valid if we actually decide to add a new entry. | |
211 | * We return ni_vp == NULL to indicate that the entry | |
212 | * does not currently exist; we leave a pointer to | |
213 | * the (locked) directory vnode in namei_data->ni_dvp. | |
214 | * The pathname buffer is saved so that the name | |
215 | * can be obtained later. | |
216 | * | |
217 | * NB - if the directory is unlocked, then this | |
218 | * information cannot be used. | |
219 | */ | |
220 | cnp->cn_flags |= SAVENAME; | |
221 | if (!lockparent) | |
222 | VOP_UNLOCK(dir_vnode, 0, p); | |
223 | return (EJUSTRETURN); | |
224 | } | |
225 | ||
226 | /* | |
227 | * If deleting, and at end of pathname, return | |
228 | * parameters which can be used to remove file. | |
229 | * If the wantparent flag isn't set, we return only | |
230 | * the directory (in namei_data->ni_dvp), otherwise we go | |
231 | * on and lock the node, being careful with ".". | |
232 | */ | |
233 | if (op == DELETE && (flags & ISLASTCN)) { | |
234 | /* | |
235 | * Write access to directory required to delete files. | |
236 | */ | |
237 | if ((error = VOP_ACCESS(dir_vnode, VWRITE, | |
238 | cnp->cn_cred, p)) != 0) | |
239 | return (error); | |
240 | /* | |
241 | * we are trying to delete '.'. What does this mean? XXX | |
242 | */ | |
243 | if (dir_node == node) { | |
244 | VREF(dir_vnode); | |
245 | *result_vnode = dir_vnode; | |
246 | return (0); | |
247 | } | |
248 | #ifdef NOTYET | |
249 | /* | |
250 | * If directory is "sticky", then user must own | |
251 | * the directory, or the file in it, else she | |
252 | * may not delete it (unless she's root). This | |
253 | * implements append-only directories. | |
254 | */ | |
255 | if ((dir_node->mode & ISVTX) && | |
256 | cnp->cn_cred->cr_uid != 0 && | |
257 | cnp->cn_cred->cr_uid != dir_node->uid && | |
258 | cnp->cn_cred->cr_uid != node->uid) { | |
259 | VOP_UNLOCK(*result_vnode, 0, p); | |
260 | return (EPERM); | |
261 | } | |
262 | #endif | |
263 | if (!lockparent) | |
264 | VOP_UNLOCK(dir_vnode, 0, p); | |
265 | return (0); | |
266 | } | |
267 | ||
268 | /* | |
269 | * If rewriting (RENAME), return the vnode and the | |
270 | * information required to rewrite the present directory | |
271 | * Must get node of directory entry to verify it's a | |
272 | * regular file, or empty directory. | |
273 | */ | |
274 | if (op == RENAME && wantparent && (flags & ISLASTCN)) { | |
275 | /* | |
276 | * Are we allowed to change the holding directory? | |
277 | */ | |
278 | if ((error = VOP_ACCESS(dir_vnode, VWRITE, | |
279 | cnp->cn_cred, p)) != 0) | |
280 | return (error); | |
281 | /* | |
282 | * Careful about locking second node. | |
283 | * This can only occur if the target is ".". | |
284 | */ | |
285 | if (dir_node == node) | |
286 | return (EISDIR); | |
287 | /* hmm save the 'from' name (we need to delete it) */ | |
288 | cnp->cn_flags |= SAVENAME; | |
289 | if (!lockparent) | |
290 | VOP_UNLOCK(dir_vnode, 0, p); | |
291 | return (0); | |
292 | } | |
293 | ||
294 | /* | |
295 | * Step through the translation in the name. We do not unlock the | |
296 | * directory because we may need it again if a symbolic link | |
297 | * is relative to the current directory. Instead we save it | |
298 | * unlocked as "saved_dir_node" XXX. We must get the target | |
299 | * node before unlocking | |
300 | * the directory to insure that the node will not be removed | |
301 | * before we get it. We prevent deadlock by always fetching | |
302 | * nodes from the root, moving down the directory tree. Thus | |
303 | * when following backward pointers ".." we must unlock the | |
304 | * parent directory before getting the requested directory. | |
305 | * There is a potential race condition here if both the current | |
306 | * and parent directories are removed before the lock for the | |
307 | * node associated with ".." returns. We hope that this occurs | |
308 | * infrequently since we cannot avoid this race condition without | |
309 | * implementing a sophisticated deadlock detection algorithm. | |
310 | * Note also that this simple deadlock detection scheme will not | |
311 | * work if the file system has any hard links other than ".." | |
312 | * that point backwards in the directory structure. | |
313 | */ | |
314 | if (flags & ISDOTDOT) { | |
315 | VOP_UNLOCK(dir_vnode, 0, p); /* race to get the node */ | |
316 | if (lockparent && (flags & ISLASTCN)) | |
317 | vn_lock(dir_vnode, LK_EXCLUSIVE | LK_RETRY, p); | |
318 | } else if (dir_node == node) { | |
319 | #if 0 | |
320 | /* | |
321 | * this next statement is wrong: we already did a vget in | |
322 | * devfs_dntovn(); DWS 4/16/1999 | |
323 | */ | |
324 | VREF(dir_vnode); /* we want ourself, ie "." */ | |
325 | #endif | |
326 | *result_vnode = dir_vnode; | |
327 | } else { | |
328 | if (!lockparent || (flags & ISLASTCN)) | |
329 | VOP_UNLOCK(dir_vnode, 0, p); | |
330 | } | |
331 | ||
332 | return (0); | |
333 | } | |
334 | ||
335 | static int | |
336 | devfs_access(struct vop_access_args *ap) | |
337 | /*struct vop_access_args { | |
338 | struct vnode *a_vp; | |
339 | int a_mode; | |
340 | struct ucred *a_cred; | |
341 | struct proc *a_p; | |
342 | } */ | |
343 | { | |
344 | /* | |
345 | * mode is filled with a combination of VREAD, VWRITE, | |
346 | * and/or VEXEC bits turned on. In an octal number these | |
347 | * are the Y in 0Y00. | |
348 | */ | |
349 | struct vnode *vp = ap->a_vp; | |
350 | int mode = ap->a_mode; | |
351 | struct ucred *cred = ap->a_cred; | |
352 | devnode_t * file_node; | |
353 | gid_t *gp; | |
354 | int i; | |
355 | struct proc *p = ap->a_p; | |
356 | ||
357 | file_node = VTODN(vp); | |
358 | /* | |
359 | * if we are not running as a process, we are in the | |
360 | * kernel and we DO have permission | |
361 | */ | |
362 | if (p == NULL) | |
363 | return 0; | |
364 | ||
365 | /* | |
366 | * Access check is based on only one of owner, group, public. | |
367 | * If not owner, then check group. If not a member of the | |
368 | * group, then check public access. | |
369 | */ | |
370 | if (cred->cr_uid != file_node->dn_uid) | |
371 | { | |
372 | /* failing that.. try groups */ | |
373 | mode >>= 3; | |
374 | gp = cred->cr_groups; | |
375 | for (i = 0; i < cred->cr_ngroups; i++, gp++) | |
376 | { | |
377 | if (file_node->dn_gid == *gp) | |
378 | { | |
379 | goto found; | |
380 | } | |
381 | } | |
382 | /* failing that.. try general access */ | |
383 | mode >>= 3; | |
384 | found: | |
385 | ; | |
386 | } | |
387 | if ((file_node->dn_mode & mode) == mode) | |
388 | return (0); | |
389 | /* | |
390 | * Root gets to do anything. | |
391 | * but only use suser prives as a last resort | |
392 | * (Use of super powers is recorded in ap->a_p->p_acflag) | |
393 | */ | |
394 | if( suser(cred, &ap->a_p->p_acflag) == 0) /* XXX what if no proc? */ | |
395 | return 0; | |
396 | return (EACCES); | |
397 | } | |
398 | ||
399 | static int | |
400 | devfs_getattr(struct vop_getattr_args *ap) | |
401 | /*struct vop_getattr_args { | |
402 | struct vnode *a_vp; | |
403 | struct vattr *a_vap; | |
404 | struct ucred *a_cred; | |
405 | struct proc *a_p; | |
406 | } */ | |
407 | { | |
408 | struct vnode *vp = ap->a_vp; | |
409 | struct vattr *vap = ap->a_vap; | |
410 | devnode_t * file_node; | |
411 | struct timeval tv; | |
412 | ||
413 | file_node = VTODN(vp); | |
414 | tv = time; | |
415 | dn_times(file_node, tv, tv); | |
416 | vap->va_rdev = 0;/* default value only */ | |
417 | vap->va_mode = file_node->dn_mode; | |
418 | switch (file_node->dn_type) | |
419 | { | |
420 | case DEV_DIR: | |
421 | vap->va_rdev = (dev_t)file_node->dn_dvm; | |
422 | vap->va_mode |= (S_IFDIR); | |
423 | break; | |
424 | case DEV_CDEV: | |
425 | vap->va_rdev = file_node->dn_typeinfo.dev; | |
426 | vap->va_mode |= (S_IFCHR); | |
427 | break; | |
428 | case DEV_BDEV: | |
429 | vap->va_rdev = file_node->dn_typeinfo.dev; | |
430 | vap->va_mode |= (S_IFBLK); | |
431 | break; | |
432 | case DEV_SLNK: | |
433 | vap->va_mode |= (S_IFLNK); | |
434 | break; | |
435 | } | |
436 | vap->va_type = vp->v_type; | |
437 | vap->va_nlink = file_node->dn_links; | |
438 | vap->va_uid = file_node->dn_uid; | |
439 | vap->va_gid = file_node->dn_gid; | |
440 | vap->va_fsid = (int32_t)(void *)file_node->dn_dvm; | |
441 | vap->va_fileid = (int32_t)(void *)file_node; | |
442 | vap->va_size = file_node->dn_len; /* now a u_quad_t */ | |
443 | /* this doesn't belong here */ | |
444 | if (vp->v_type == VBLK) | |
445 | vap->va_blocksize = BLKDEV_IOSIZE; | |
446 | else if (vp->v_type == VCHR) | |
447 | vap->va_blocksize = MAXPHYSIO; | |
448 | else | |
449 | vap->va_blocksize = vp->v_mount->mnt_stat.f_iosize; | |
450 | /* if the time is bogus, set it to the boot time */ | |
451 | if (file_node->dn_ctime.tv_sec == 0) | |
452 | file_node->dn_ctime.tv_sec = boottime.tv_sec; | |
453 | if (file_node->dn_mtime.tv_sec == 0) | |
454 | file_node->dn_mtime.tv_sec = boottime.tv_sec; | |
455 | if (file_node->dn_atime.tv_sec == 0) | |
456 | file_node->dn_atime.tv_sec = boottime.tv_sec; | |
457 | vap->va_ctime = file_node->dn_ctime; | |
458 | vap->va_mtime = file_node->dn_mtime; | |
459 | vap->va_atime = file_node->dn_atime; | |
460 | vap->va_gen = 0; | |
461 | vap->va_flags = 0; | |
462 | vap->va_bytes = file_node->dn_len; /* u_quad_t */ | |
463 | vap->va_filerev = 0; /* XXX */ /* u_quad_t */ | |
464 | vap->va_vaflags = 0; /* XXX */ | |
465 | return 0; | |
466 | } | |
467 | ||
468 | static int | |
469 | devfs_setattr(struct vop_setattr_args *ap) | |
470 | /*struct vop_setattr_args { | |
471 | struct vnode *a_vp; | |
472 | struct vattr *a_vap; | |
473 | struct ucred *a_cred; | |
474 | struct proc *a_p; | |
475 | } */ | |
476 | { | |
477 | struct vnode *vp = ap->a_vp; | |
478 | struct vattr *vap = ap->a_vap; | |
479 | struct ucred *cred = ap->a_cred; | |
480 | struct proc *p = ap->a_p; | |
481 | int error = 0; | |
482 | gid_t *gp; | |
483 | int i; | |
484 | devnode_t * file_node; | |
485 | struct timeval atimeval, mtimeval; | |
486 | ||
487 | if (vap->va_flags != VNOVAL) /* XXX needs to be implemented */ | |
488 | return (EOPNOTSUPP); | |
489 | ||
490 | file_node = VTODN(vp); | |
491 | ||
492 | if ((vap->va_type != VNON) || | |
493 | (vap->va_nlink != VNOVAL) || | |
494 | (vap->va_fsid != VNOVAL) || | |
495 | (vap->va_fileid != VNOVAL) || | |
496 | (vap->va_blocksize != VNOVAL) || | |
497 | (vap->va_rdev != VNOVAL) || | |
498 | (vap->va_bytes != VNOVAL) || | |
499 | (vap->va_gen != VNOVAL )) | |
500 | { | |
501 | return EINVAL; | |
502 | } | |
503 | ||
504 | /* | |
505 | * Go through the fields and update iff not VNOVAL. | |
506 | */ | |
507 | if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { | |
508 | if (cred->cr_uid != file_node->dn_uid && | |
509 | (error = suser(cred, &p->p_acflag)) && | |
510 | ((vap->va_vaflags & VA_UTIMES_NULL) == 0 || | |
511 | (error = VOP_ACCESS(vp, VWRITE, cred, p)))) | |
512 | return (error); | |
513 | if (vap->va_atime.tv_sec != VNOVAL) | |
514 | file_node->dn_flags |= DN_ACCESS; | |
515 | if (vap->va_mtime.tv_sec != VNOVAL) | |
516 | file_node->dn_flags |= DN_CHANGE | DN_UPDATE; | |
517 | atimeval.tv_sec = vap->va_atime.tv_sec; | |
518 | atimeval.tv_usec = vap->va_atime.tv_nsec / 1000; | |
519 | mtimeval.tv_sec = vap->va_mtime.tv_sec; | |
520 | mtimeval.tv_usec = vap->va_mtime.tv_nsec / 1000; | |
521 | if (error = VOP_UPDATE(vp, &atimeval, &mtimeval, 1)) | |
522 | return (error); | |
523 | } | |
524 | ||
525 | /* | |
526 | * Change the permissions.. must be root or owner to do this. | |
527 | */ | |
528 | if (vap->va_mode != (u_short)VNOVAL) { | |
529 | if ((cred->cr_uid != file_node->dn_uid) | |
530 | && (error = suser(cred, &p->p_acflag))) | |
531 | return (error); | |
532 | file_node->dn_mode &= ~07777; | |
533 | file_node->dn_mode |= vap->va_mode & 07777; | |
534 | } | |
535 | ||
536 | /* | |
537 | * Change the owner.. must be root to do this. | |
538 | */ | |
539 | if (vap->va_uid != (uid_t)VNOVAL) { | |
540 | if (error = suser(cred, &p->p_acflag)) | |
541 | return (error); | |
542 | file_node->dn_uid = vap->va_uid; | |
543 | } | |
544 | ||
545 | /* | |
546 | * Change the group.. must be root or owner to do this. | |
547 | * If we are the owner, we must be in the target group too. | |
548 | * don't use suser() unless you have to as it reports | |
549 | * whether you needed suser powers or not. | |
550 | */ | |
551 | if (vap->va_gid != (gid_t)VNOVAL) { | |
552 | if (cred->cr_uid == file_node->dn_uid){ | |
553 | gp = cred->cr_groups; | |
554 | for (i = 0; i < cred->cr_ngroups; i++, gp++) { | |
555 | if (vap->va_gid == *gp) | |
556 | goto cando; | |
557 | } | |
558 | } | |
559 | /* | |
560 | * we can't do it with normal privs, | |
561 | * do we have an ace up our sleeve? | |
562 | */ | |
563 | if (error = suser(cred, &p->p_acflag)) | |
564 | return (error); | |
565 | cando: | |
566 | file_node->dn_gid = vap->va_gid; | |
567 | } | |
568 | #if 0 | |
569 | /* | |
570 | * Copied from somewhere else | |
571 | * but only kept as a marker and reminder of the fact that | |
572 | * flags should be handled some day | |
573 | */ | |
574 | if (vap->va_flags != VNOVAL) { | |
575 | if (error = suser(cred, &p->p_acflag)) | |
576 | return error; | |
577 | if (cred->cr_uid == 0) | |
578 | ; | |
579 | else { | |
580 | } | |
581 | } | |
582 | #endif | |
583 | return error; | |
584 | } | |
585 | ||
586 | static int | |
587 | devfs_read(struct vop_read_args *ap) | |
588 | /*struct vop_read_args { | |
589 | struct vnode *a_vp; | |
590 | struct uio *a_uio; | |
591 | int a_ioflag; | |
592 | struct ucred *a_cred; | |
593 | } */ | |
594 | { | |
595 | devnode_t * dn_p = VTODN(ap->a_vp); | |
596 | ||
597 | switch (ap->a_vp->v_type) { | |
598 | case VDIR: { | |
599 | dn_p->dn_flags |= DN_ACCESS; | |
600 | return VOP_READDIR(ap->a_vp, ap->a_uio, ap->a_cred, | |
601 | NULL, NULL, NULL); | |
602 | } | |
603 | default: { | |
604 | printf("devfs_read(): bad file type %d", ap->a_vp->v_type); | |
605 | return(EINVAL); | |
606 | break; | |
607 | } | |
608 | } | |
609 | return (0); /* not reached */ | |
610 | } | |
611 | ||
612 | static int | |
613 | devfs_close(ap) | |
614 | struct vop_close_args /* { | |
615 | struct vnode *a_vp; | |
616 | int a_fflag; | |
617 | struct ucred *a_cred; | |
618 | struct proc *a_p; | |
619 | } */ *ap; | |
620 | { | |
621 | struct vnode * vp = ap->a_vp; | |
622 | register devnode_t * dnp = VTODN(vp); | |
623 | ||
624 | simple_lock(&vp->v_interlock); | |
625 | if (vp->v_usecount > 1) | |
626 | dn_times(dnp, time, time); | |
627 | simple_unlock(&vp->v_interlock); | |
628 | return (0); | |
629 | } | |
630 | ||
631 | static int | |
632 | devfsspec_close(ap) | |
633 | struct vop_close_args /* { | |
634 | struct vnode *a_vp; | |
635 | int a_fflag; | |
636 | struct ucred *a_cred; | |
637 | struct proc *a_p; | |
638 | } */ *ap; | |
639 | { | |
640 | struct vnode * vp = ap->a_vp; | |
641 | register devnode_t * dnp = VTODN(vp); | |
642 | ||
643 | simple_lock(&vp->v_interlock); | |
644 | if (vp->v_usecount > 1) | |
645 | dn_times(dnp, time, time); | |
646 | simple_unlock(&vp->v_interlock); | |
647 | return (VOCALL (spec_vnodeop_p, VOFFSET(vop_close), ap)); | |
648 | } | |
649 | ||
650 | static int | |
651 | devfsspec_read(struct vop_read_args *ap) | |
652 | /*struct vop_read_args { | |
653 | struct vnode *a_vp; | |
654 | struct uio *a_uio; | |
655 | int a_ioflag; | |
656 | struct ucred *a_cred; | |
657 | } */ | |
658 | { | |
659 | VTODN(ap->a_vp)->dn_flags |= DN_ACCESS; | |
660 | return (VOCALL (spec_vnodeop_p, VOFFSET(vop_read), ap)); | |
661 | } | |
662 | ||
663 | static int | |
664 | devfsspec_write(struct vop_write_args *ap) | |
665 | /*struct vop_write_args { | |
666 | struct vnode *a_vp; | |
667 | struct uio *a_uio; | |
668 | int a_ioflag; | |
669 | struct ucred *a_cred; | |
670 | } */ | |
671 | { | |
672 | VTODN(ap->a_vp)->dn_flags |= DN_CHANGE | DN_UPDATE; | |
673 | return (VOCALL (spec_vnodeop_p, VOFFSET(vop_write), ap)); | |
674 | } | |
675 | ||
676 | /* | |
677 | * Write data to a file or directory. | |
678 | */ | |
679 | static int | |
680 | devfs_write(struct vop_write_args *ap) | |
681 | /*struct vop_write_args { | |
682 | struct vnode *a_vp; | |
683 | struct uio *a_uio; | |
684 | int a_ioflag; | |
685 | struct ucred *a_cred; | |
686 | } */ | |
687 | { | |
688 | switch (ap->a_vp->v_type) { | |
689 | case VDIR: | |
690 | return(EISDIR); | |
691 | default: | |
692 | printf("devfs_write(): bad file type %d", ap->a_vp->v_type); | |
693 | return (EINVAL); | |
694 | } | |
695 | return 0; /* not reached */ | |
696 | } | |
697 | ||
698 | static int | |
699 | devfs_remove(struct vop_remove_args *ap) | |
700 | /*struct vop_remove_args { | |
701 | struct vnode *a_dvp; | |
702 | struct vnode *a_vp; | |
703 | struct componentname *a_cnp; | |
704 | } */ | |
705 | { | |
706 | struct vnode *vp = ap->a_vp; | |
707 | struct vnode *dvp = ap->a_dvp; | |
708 | struct componentname *cnp = ap->a_cnp; | |
709 | devnode_t * tp; | |
710 | devnode_t * tdp; | |
711 | devdirent_t * tnp; | |
712 | int doingdirectory = 0; | |
713 | int error = 0; | |
714 | uid_t ouruid = cnp->cn_cred->cr_uid; | |
715 | struct proc *p = cnp->cn_proc; | |
716 | ||
717 | /* | |
718 | * Lock our directories and get our name pointers | |
719 | * assume that the names are null terminated as they | |
720 | * are the end of the path. Get pointers to all our | |
721 | * devfs structures. | |
722 | */ | |
723 | tp = VTODN(vp); | |
724 | tdp = VTODN(dvp); | |
725 | /* | |
726 | * Assuming we are atomic, dev_lookup left this for us | |
727 | */ | |
728 | tnp = tp->dn_last_lookup; | |
729 | ||
730 | /* | |
731 | * Check we are doing legal things WRT the new flags | |
732 | */ | |
733 | if ((tp->dn_flags & (IMMUTABLE | APPEND)) | |
734 | || (tdp->dn_flags & APPEND) /*XXX eh?*/ ) { | |
735 | error = EPERM; | |
736 | goto abort; | |
737 | } | |
738 | ||
739 | /* | |
740 | * Make sure that we don't try do something stupid | |
741 | */ | |
742 | if ((tp->dn_type) == DEV_DIR) { | |
743 | /* | |
744 | * Avoid ".", "..", and aliases of "." for obvious reasons. | |
745 | */ | |
746 | if ( (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') | |
747 | || (cnp->cn_flags&ISDOTDOT) ) { | |
748 | error = EINVAL; | |
749 | goto abort; | |
750 | } | |
751 | doingdirectory++; | |
752 | } | |
753 | ||
754 | /*********************************** | |
755 | * Start actually doing things.... * | |
756 | ***********************************/ | |
757 | tdp->dn_flags |= DN_CHANGE | DN_UPDATE; | |
758 | ||
759 | /* | |
760 | * own the parent directory, or the destination of the rename, | |
761 | * otherwise the destination may not be changed (except by | |
762 | * root). This implements append-only directories. | |
763 | * XXX shoudn't this be in generic code? | |
764 | */ | |
765 | if ((tdp->dn_mode & S_ISTXT) | |
766 | && ouruid != 0 | |
767 | && ouruid != tdp->dn_uid | |
768 | && ouruid != tp->dn_uid ) { | |
769 | error = EPERM; | |
770 | goto abort; | |
771 | } | |
772 | /* | |
773 | * Target must be empty if a directory and have no links | |
774 | * to it. Also, ensure source and target are compatible | |
775 | * (both directories, or both not directories). | |
776 | */ | |
777 | if (( doingdirectory) && (tp->dn_links > 2)) { | |
778 | error = ENOTEMPTY; | |
779 | goto abort; | |
780 | } | |
781 | DEVFS_LOCK(p); | |
782 | dev_free_name(tnp); | |
783 | DEVFS_UNLOCK(p); | |
784 | abort: | |
785 | if (dvp == vp) | |
786 | vrele(vp); | |
787 | else | |
788 | vput(vp); | |
789 | vput(dvp); | |
790 | return (error); | |
791 | } | |
792 | ||
793 | /* | |
794 | */ | |
795 | static int | |
796 | devfs_link(struct vop_link_args *ap) | |
797 | /*struct vop_link_args { | |
798 | struct vnode *a_tdvp; | |
799 | struct vnode *a_vp; | |
800 | struct componentname *a_cnp; | |
801 | } */ | |
802 | { | |
803 | struct vnode *vp = ap->a_vp; | |
804 | struct vnode *tdvp = ap->a_tdvp; | |
805 | struct componentname *cnp = ap->a_cnp; | |
806 | struct proc *p = cnp->cn_proc; | |
807 | devnode_t * fp; | |
808 | devnode_t * tdp; | |
809 | devdirent_t * tnp; | |
810 | int error = 0; | |
811 | struct timeval tv; | |
812 | ||
813 | /* | |
814 | * First catch an arbitrary restriction for this FS | |
815 | */ | |
816 | if (cnp->cn_namelen > DEVMAXNAMESIZE) { | |
817 | error = ENAMETOOLONG; | |
818 | goto out1; | |
819 | } | |
820 | ||
821 | /* | |
822 | * Lock our directories and get our name pointers | |
823 | * assume that the names are null terminated as they | |
824 | * are the end of the path. Get pointers to all our | |
825 | * devfs structures. | |
826 | */ | |
827 | tdp = VTODN(tdvp); | |
828 | fp = VTODN(vp); | |
829 | ||
830 | if (tdvp->v_mount != vp->v_mount) { | |
831 | error = EXDEV; | |
832 | VOP_ABORTOP(tdvp, cnp); | |
833 | goto out2; | |
834 | } | |
835 | if (tdvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE, p))) { | |
836 | VOP_ABORTOP(tdvp, cnp); | |
837 | goto out2; | |
838 | } | |
839 | ||
840 | /* | |
841 | * Check we are doing legal things WRT the new flags | |
842 | */ | |
843 | if (fp->dn_flags & (IMMUTABLE | APPEND)) { | |
844 | VOP_ABORTOP(tdvp, cnp); | |
845 | error = EPERM; | |
846 | goto out1; | |
847 | } | |
848 | ||
849 | /*********************************** | |
850 | * Start actually doing things.... * | |
851 | ***********************************/ | |
852 | fp->dn_flags |= DN_CHANGE; | |
853 | tv = time; | |
854 | error = VOP_UPDATE(vp, &tv, &tv, 1); | |
855 | if (!error) { | |
856 | DEVFS_LOCK(p); | |
857 | error = dev_add_name(cnp->cn_nameptr, tdp, NULL, fp, &tnp); | |
858 | DEVFS_UNLOCK(p); | |
859 | } | |
860 | out1: | |
861 | if (tdvp != vp) | |
862 | VOP_UNLOCK(vp, 0, p); | |
863 | out2: | |
864 | vput(tdvp); | |
865 | return (error); | |
866 | } | |
867 | ||
868 | /* | |
869 | * Check if source directory is in the path of the target directory. | |
870 | * Target is supplied locked, source is unlocked. | |
871 | * The target is always vput before returning. | |
872 | */ | |
873 | int | |
874 | devfs_checkpath(source, target) | |
875 | devnode_t *source, *target; | |
876 | { | |
877 | int error = 0; | |
878 | devnode_t * ntmp; | |
879 | devnode_t * tmp; | |
880 | struct vnode *vp; | |
881 | ||
882 | vp = target->dn_vn; | |
883 | tmp = target; | |
884 | ||
885 | do { | |
886 | if (tmp == source) { | |
887 | error = EINVAL; | |
888 | break; | |
889 | } | |
890 | ntmp = tmp; | |
891 | } while ((tmp = tmp->dn_typeinfo.Dir.parent) != ntmp); | |
892 | ||
893 | if (vp != NULL) | |
894 | vput(vp); | |
895 | return (error); | |
896 | } | |
897 | ||
898 | /* | |
899 | * Rename system call. Seems overly complicated to me... | |
900 | * rename("foo", "bar"); | |
901 | * is essentially | |
902 | * unlink("bar"); | |
903 | * link("foo", "bar"); | |
904 | * unlink("foo"); | |
905 | * but ``atomically''. | |
906 | * | |
907 | * When the target exists, both the directory | |
908 | * and target vnodes are locked. | |
909 | * the source and source-parent vnodes are referenced | |
910 | * | |
911 | * | |
912 | * Basic algorithm is: | |
913 | * | |
914 | * 1) Bump link count on source while we're linking it to the | |
915 | * target. This also ensure the inode won't be deleted out | |
916 | * from underneath us while we work (it may be truncated by | |
917 | * a concurrent `trunc' or `open' for creation). | |
918 | * 2) Link source to destination. If destination already exists, | |
919 | * delete it first. | |
920 | * 3) Unlink source reference to node if still around. If a | |
921 | * directory was moved and the parent of the destination | |
922 | * is different from the source, patch the ".." entry in the | |
923 | * directory. | |
924 | */ | |
925 | static int | |
926 | devfs_rename(struct vop_rename_args *ap) | |
927 | /*struct vop_rename_args { | |
928 | struct vnode *a_fdvp; | |
929 | struct vnode *a_fvp; | |
930 | struct componentname *a_fcnp; | |
931 | struct vnode *a_tdvp; | |
932 | struct vnode *a_tvp; | |
933 | struct componentname *a_tcnp; | |
934 | } */ | |
935 | { | |
936 | struct vnode *tvp = ap->a_tvp; | |
937 | struct vnode *tdvp = ap->a_tdvp; | |
938 | struct vnode *fvp = ap->a_fvp; | |
939 | struct vnode *fdvp = ap->a_fdvp; | |
940 | struct componentname *tcnp = ap->a_tcnp; | |
941 | struct componentname *fcnp = ap->a_fcnp; | |
942 | struct proc *p = fcnp->cn_proc; | |
943 | devnode_t *fp, *fdp, *tp, *tdp; | |
944 | devdirent_t *fnp,*tnp; | |
945 | int doingdirectory = 0; | |
946 | int error = 0; | |
947 | struct timeval tv; | |
948 | ||
949 | /* | |
950 | * First catch an arbitrary restriction for this FS | |
951 | */ | |
952 | if(tcnp->cn_namelen > DEVMAXNAMESIZE) { | |
953 | error = ENAMETOOLONG; | |
954 | goto abortit; | |
955 | } | |
956 | ||
957 | /* | |
958 | * Lock our directories and get our name pointers | |
959 | * assume that the names are null terminated as they | |
960 | * are the end of the path. Get pointers to all our | |
961 | * devfs structures. | |
962 | */ | |
963 | tdp = VTODN(tdvp); | |
964 | fdp = VTODN(fdvp); | |
965 | fp = VTODN(fvp); | |
966 | fnp = fp->dn_last_lookup; | |
967 | tp = NULL; | |
968 | tnp = NULL; | |
969 | if (tvp) { | |
970 | tp = VTODN(tvp); | |
971 | tnp = tp->dn_last_lookup; | |
972 | } | |
973 | ||
974 | /* | |
975 | * trying to move it out of devfs? | |
976 | * if we move a dir across mnt points. we need to fix all | |
977 | * the mountpoint pointers! XXX | |
978 | * so for now keep dirs within the same mount | |
979 | */ | |
980 | if ((fvp->v_mount != tdvp->v_mount) || | |
981 | (tvp && (fvp->v_mount != tvp->v_mount))) { | |
982 | error = EXDEV; | |
983 | abortit: | |
984 | VOP_ABORTOP(tdvp, tcnp); | |
985 | if (tdvp == tvp) /* eh? */ | |
986 | vrele(tdvp); | |
987 | else | |
988 | vput(tdvp); | |
989 | if (tvp) | |
990 | vput(tvp); | |
991 | VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ | |
992 | vrele(fdvp); | |
993 | vrele(fvp); | |
994 | return (error); | |
995 | } | |
996 | ||
997 | /* | |
998 | * Check we are doing legal things WRT the new flags | |
999 | */ | |
1000 | if ((tp && (tp->dn_flags & (IMMUTABLE | APPEND))) | |
1001 | || (fp->dn_flags & (IMMUTABLE | APPEND)) | |
1002 | || (fdp->dn_flags & APPEND)) { | |
1003 | error = EPERM; | |
1004 | goto abortit; | |
1005 | } | |
1006 | ||
1007 | /* | |
1008 | * Make sure that we don't try do something stupid | |
1009 | */ | |
1010 | if ((fp->dn_type) == DEV_DIR) { | |
1011 | /* | |
1012 | * Avoid ".", "..", and aliases of "." for obvious reasons. | |
1013 | */ | |
1014 | if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') | |
1015 | || (fcnp->cn_flags&ISDOTDOT) | |
1016 | || (tcnp->cn_namelen == 1 && tcnp->cn_nameptr[0] == '.') | |
1017 | || (tcnp->cn_flags&ISDOTDOT) | |
1018 | || (tdp == fp )) { | |
1019 | error = EINVAL; | |
1020 | goto abortit; | |
1021 | } | |
1022 | doingdirectory++; | |
1023 | } | |
1024 | ||
1025 | /* | |
1026 | * If ".." must be changed (ie the directory gets a new | |
1027 | * parent) then the source directory must not be in the | |
1028 | * directory hierarchy above the target, as this would | |
1029 | * orphan everything below the source directory. Also | |
1030 | * the user must have write permission in the source so | |
1031 | * as to be able to change "..". | |
1032 | */ | |
1033 | if (doingdirectory && (tdp != fdp)) { | |
1034 | devnode_t * tmp, *ntmp; | |
1035 | error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_proc); | |
1036 | tmp = tdp; | |
1037 | do { | |
1038 | if(tmp == fp) { | |
1039 | /* XXX unlock stuff here probably */ | |
1040 | error = EINVAL; | |
1041 | goto out; | |
1042 | } | |
1043 | ntmp = tmp; | |
1044 | } while ((tmp = tmp->dn_typeinfo.Dir.parent) != ntmp); | |
1045 | } | |
1046 | ||
1047 | /*********************************** | |
1048 | * Start actually doing things.... * | |
1049 | ***********************************/ | |
1050 | fp->dn_flags |= DN_CHANGE; | |
1051 | tv = time; | |
1052 | if (error = VOP_UPDATE(fvp, &tv, &tv, 1)) { | |
1053 | VOP_UNLOCK(fvp, 0, p); | |
1054 | goto bad; | |
1055 | } | |
1056 | /* | |
1057 | * Check if just deleting a link name. | |
1058 | */ | |
1059 | if (fvp == tvp) { | |
1060 | if (fvp->v_type == VDIR) { | |
1061 | error = EINVAL; | |
1062 | goto abortit; | |
1063 | } | |
1064 | ||
1065 | /* Release destination completely. */ | |
1066 | VOP_ABORTOP(tdvp, tcnp); | |
1067 | vput(tdvp); | |
1068 | vput(tvp); | |
1069 | ||
1070 | /* Delete source. */ | |
1071 | VOP_ABORTOP(fdvp, fcnp); /*XXX*/ | |
1072 | vrele(fdvp); | |
1073 | vrele(fvp); | |
1074 | dev_free_name(fnp); | |
1075 | return 0; | |
1076 | } | |
1077 | ||
1078 | vrele(fdvp); | |
1079 | ||
1080 | /* | |
1081 | * 1) Bump link count while we're moving stuff | |
1082 | * around. If we crash somewhere before | |
1083 | * completing our work, too bad :) | |
1084 | */ | |
1085 | fp->dn_links++; | |
1086 | /* | |
1087 | * If the target exists zap it (unless it's a non-empty directory) | |
1088 | * We could do that as well but won't | |
1089 | */ | |
1090 | if (tp) { | |
1091 | int ouruid = tcnp->cn_cred->cr_uid; | |
1092 | /* | |
1093 | * If the parent directory is "sticky", then the user must | |
1094 | * own the parent directory, or the destination of the rename, | |
1095 | * otherwise the destination may not be changed (except by | |
1096 | * root). This implements append-only directories. | |
1097 | * XXX shoudn't this be in generic code? | |
1098 | */ | |
1099 | if ((tdp->dn_mode & S_ISTXT) | |
1100 | && ouruid != 0 | |
1101 | && ouruid != tdp->dn_uid | |
1102 | && ouruid != tp->dn_uid ) { | |
1103 | error = EPERM; | |
1104 | goto bad; | |
1105 | } | |
1106 | /* | |
1107 | * Target must be empty if a directory and have no links | |
1108 | * to it. Also, ensure source and target are compatible | |
1109 | * (both directories, or both not directories). | |
1110 | */ | |
1111 | if (( doingdirectory) && (tp->dn_links > 2)) { | |
1112 | error = ENOTEMPTY; | |
1113 | goto bad; | |
1114 | } | |
1115 | dev_free_name(tnp); | |
1116 | tp = NULL; | |
1117 | } | |
1118 | dev_add_name(tcnp->cn_nameptr,tdp,NULL,fp,&tnp); | |
1119 | fnp->de_dnp = NULL; | |
1120 | fp->dn_links--; /* one less link to it.. */ | |
1121 | dev_free_name(fnp); | |
1122 | fp->dn_links--; /* we added one earlier*/ | |
1123 | if (tdp) | |
1124 | vput(tdvp); | |
1125 | if (tp) | |
1126 | vput(fvp); | |
1127 | vrele(fvp); | |
1128 | return (error); | |
1129 | ||
1130 | bad: | |
1131 | if (tp) | |
1132 | vput(tvp); | |
1133 | vput(tdvp); | |
1134 | out: | |
1135 | if (vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY, p) == 0) { | |
1136 | fp->dn_links--; /* we added one earlier*/ | |
1137 | vput(fvp); | |
1138 | } else | |
1139 | vrele(fvp); | |
1140 | return (error); | |
1141 | } | |
1142 | ||
1143 | static int | |
1144 | devfs_symlink(struct vop_symlink_args *ap) | |
1145 | /*struct vop_symlink_args { | |
1146 | struct vnode *a_dvp; | |
1147 | struct vnode **a_vpp; | |
1148 | struct componentname *a_cnp; | |
1149 | struct vattr *a_vap; | |
1150 | char *a_target; | |
1151 | } */ | |
1152 | { | |
1153 | struct componentname * cnp = ap->a_cnp; | |
1154 | struct vnode *vp = NULL; | |
1155 | int error = 0; | |
1156 | devnode_t * dir_p; | |
1157 | devnode_type_t typeinfo; | |
1158 | devdirent_t * nm_p; | |
1159 | devnode_t * dev_p; | |
1160 | struct vattr * vap = ap->a_vap; | |
1161 | struct vnode * * vpp = ap->a_vpp; | |
1162 | struct proc *p = cnp->cn_proc; | |
1163 | struct timeval tv; | |
1164 | ||
1165 | dir_p = VTODN(ap->a_dvp); | |
1166 | typeinfo.Slnk.name = ap->a_target; | |
1167 | typeinfo.Slnk.namelen = strlen(ap->a_target); | |
1168 | DEVFS_LOCK(p); | |
1169 | error = dev_add_entry(cnp->cn_nameptr, dir_p, DEV_SLNK, | |
1170 | &typeinfo, NULL, NULL, &nm_p); | |
1171 | DEVFS_UNLOCK(p); | |
1172 | if (error) { | |
1173 | goto failure; | |
1174 | } | |
1175 | ||
1176 | dev_p = nm_p->de_dnp; | |
1177 | dev_p->dn_uid = dir_p->dn_uid; | |
1178 | dev_p->dn_gid = dir_p->dn_gid; | |
1179 | dev_p->dn_mode = vap->va_mode; | |
1180 | dn_copy_times(dev_p, dir_p); | |
1181 | error = devfs_dntovn(dev_p, vpp, p); | |
1182 | if (error) | |
1183 | goto failure; | |
1184 | vp = *vpp; | |
1185 | vput(vp); | |
1186 | failure: | |
1187 | if ((cnp->cn_flags & SAVESTART) == 0) { | |
1188 | char *tmp = cnp->cn_pnbuf; | |
1189 | cnp->cn_pnbuf = NULL; | |
1190 | cnp->cn_flags &= ~HASBUF; | |
1191 | FREE_ZONE(tmp, cnp->cn_pnlen, M_NAMEI); | |
1192 | } | |
1193 | vput(ap->a_dvp); | |
1194 | return error; | |
1195 | } | |
1196 | ||
1197 | /* | |
1198 | * Mknod vnode call | |
1199 | */ | |
1200 | /* ARGSUSED */ | |
1201 | int | |
1202 | devfs_mknod(ap) | |
1203 | struct vop_mknod_args /* { | |
1204 | struct vnode *a_dvp; | |
1205 | struct vnode **a_vpp; | |
1206 | struct componentname *a_cnp; | |
1207 | struct vattr *a_vap; | |
1208 | } */ *ap; | |
1209 | { | |
1210 | struct componentname * cnp = ap->a_cnp; | |
1211 | devnode_t * dev_p; | |
1212 | devdirent_t * devent; | |
1213 | devnode_t * dir_p; /* devnode for parent directory */ | |
1214 | struct vnode * dvp = ap->a_dvp; | |
1215 | int error = 0; | |
1216 | devnode_type_t typeinfo; | |
1217 | struct vattr * vap = ap->a_vap; | |
1218 | struct vnode ** vpp = ap->a_vpp; | |
1219 | struct proc * p = cnp->cn_proc; | |
1220 | ||
1221 | *vpp = NULL; | |
1222 | if (!vap->va_type == VBLK && !vap->va_type == VCHR) { | |
1223 | error = EINVAL; /* only support mknod of special files */ | |
1224 | goto failure; | |
1225 | } | |
1226 | dir_p = VTODN(dvp); | |
1227 | typeinfo.dev = vap->va_rdev; | |
1228 | DEVFS_LOCK(p); | |
1229 | error = dev_add_entry(cnp->cn_nameptr, dir_p, | |
1230 | (vap->va_type == VBLK) ? DEV_BDEV : DEV_CDEV, | |
1231 | &typeinfo, NULL, NULL, &devent); | |
1232 | DEVFS_UNLOCK(p); | |
1233 | if (error) { | |
1234 | goto failure; | |
1235 | } | |
1236 | dev_p = devent->de_dnp; | |
1237 | error = devfs_dntovn(dev_p, vpp, p); | |
1238 | if (error) | |
1239 | goto failure; | |
1240 | dev_p->dn_uid = cnp->cn_cred->cr_uid; | |
1241 | dev_p->dn_gid = dir_p->dn_gid; | |
1242 | dev_p->dn_mode = vap->va_mode; | |
1243 | failure: | |
1244 | if (*vpp) { | |
1245 | vput(*vpp); | |
1246 | *vpp = 0; | |
1247 | } | |
1248 | if ((cnp->cn_flags & SAVESTART) == 0) { | |
1249 | char *tmp = cnp->cn_pnbuf; | |
1250 | cnp->cn_pnbuf = NULL; | |
1251 | cnp->cn_flags &= ~HASBUF; | |
1252 | FREE_ZONE(tmp, cnp->cn_pnlen, M_NAMEI); | |
1253 | } | |
1254 | vput(dvp); | |
1255 | return (error); | |
1256 | } | |
1257 | ||
1258 | /* | |
1259 | * Vnode op for readdir | |
1260 | */ | |
1261 | static int | |
1262 | devfs_readdir(struct vop_readdir_args *ap) | |
1263 | /*struct vop_readdir_args { | |
1264 | struct vnode *a_vp; | |
1265 | struct uio *a_uio; | |
1266 | struct ucred *a_cred; | |
1267 | int *eofflag; | |
1268 | int *ncookies; | |
1269 | u_int **cookies; | |
1270 | } */ | |
1271 | { | |
1272 | struct vnode *vp = ap->a_vp; | |
1273 | struct uio *uio = ap->a_uio; | |
1274 | struct dirent dirent; | |
1275 | devnode_t * dir_node; | |
1276 | devdirent_t * name_node; | |
1277 | char *name; | |
1278 | int error = 0; | |
1279 | int reclen; | |
1280 | int nodenumber; | |
1281 | int startpos,pos; | |
1282 | struct proc * p = uio->uio_procp; | |
1283 | ||
1284 | /* set up refs to dir */ | |
1285 | dir_node = VTODN(vp); | |
1286 | if(dir_node->dn_type != DEV_DIR) | |
1287 | return(ENOTDIR); | |
1288 | ||
1289 | pos = 0; | |
1290 | startpos = uio->uio_offset; | |
1291 | DEVFS_LOCK(p); | |
1292 | name_node = dir_node->dn_typeinfo.Dir.dirlist; | |
1293 | nodenumber = 0; | |
1294 | dir_node->dn_flags |= DN_ACCESS; | |
1295 | ||
1296 | while ((name_node || (nodenumber < 2)) && (uio->uio_resid > 0)) | |
1297 | { | |
1298 | switch(nodenumber) | |
1299 | { | |
1300 | case 0: | |
1301 | dirent.d_fileno = (int32_t)(void *)dir_node; | |
1302 | name = "."; | |
1303 | dirent.d_namlen = 1; | |
1304 | dirent.d_type = DT_DIR; | |
1305 | break; | |
1306 | case 1: | |
1307 | if(dir_node->dn_typeinfo.Dir.parent) | |
1308 | dirent.d_fileno | |
1309 | = (int32_t)dir_node->dn_typeinfo.Dir.parent; | |
1310 | else | |
1311 | dirent.d_fileno = (u_int32_t)dir_node; | |
1312 | name = ".."; | |
1313 | dirent.d_namlen = 2; | |
1314 | dirent.d_type = DT_DIR; | |
1315 | break; | |
1316 | default: | |
1317 | dirent.d_fileno = (int32_t)(void *)name_node->de_dnp; | |
1318 | dirent.d_namlen = strlen(name_node->de_name); | |
1319 | name = name_node->de_name; | |
1320 | switch(name_node->de_dnp->dn_type) { | |
1321 | case DEV_BDEV: | |
1322 | dirent.d_type = DT_BLK; | |
1323 | break; | |
1324 | case DEV_CDEV: | |
1325 | dirent.d_type = DT_CHR; | |
1326 | break; | |
1327 | case DEV_DIR: | |
1328 | dirent.d_type = DT_DIR; | |
1329 | break; | |
1330 | case DEV_SLNK: | |
1331 | dirent.d_type = DT_LNK; | |
1332 | break; | |
1333 | default: | |
1334 | dirent.d_type = DT_UNKNOWN; | |
1335 | } | |
1336 | } | |
1337 | #define GENERIC_DIRSIZ(dp) \ | |
1338 | ((sizeof (struct dirent) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3)) | |
1339 | ||
1340 | reclen = dirent.d_reclen = GENERIC_DIRSIZ(&dirent); | |
1341 | ||
1342 | if(pos >= startpos) /* made it to the offset yet? */ | |
1343 | { | |
1344 | if (uio->uio_resid < reclen) /* will it fit? */ | |
1345 | break; | |
1346 | strcpy( dirent.d_name,name); | |
1347 | if ((error = uiomove ((caddr_t)&dirent, | |
1348 | dirent.d_reclen, uio)) != 0) | |
1349 | break; | |
1350 | } | |
1351 | pos += reclen; | |
1352 | if((nodenumber >1) && name_node) | |
1353 | name_node = name_node->de_next; | |
1354 | nodenumber++; | |
1355 | } | |
1356 | DEVFS_UNLOCK(p); | |
1357 | uio->uio_offset = pos; | |
1358 | ||
1359 | return (error); | |
1360 | } | |
1361 | ||
1362 | ||
1363 | /* | |
1364 | */ | |
1365 | static int | |
1366 | devfs_readlink(struct vop_readlink_args *ap) | |
1367 | /*struct vop_readlink_args { | |
1368 | struct vnode *a_vp; | |
1369 | struct uio *a_uio; | |
1370 | struct ucred *a_cred; | |
1371 | } */ | |
1372 | { | |
1373 | struct vnode *vp = ap->a_vp; | |
1374 | struct uio *uio = ap->a_uio; | |
1375 | devnode_t * lnk_node; | |
1376 | int error = 0; | |
1377 | ||
1378 | /* set up refs to dir */ | |
1379 | lnk_node = VTODN(vp); | |
1380 | if(lnk_node->dn_type != DEV_SLNK) | |
1381 | return(EINVAL); | |
1382 | if ((error = VOP_ACCESS(vp, VREAD, ap->a_cred, NULL)) != 0) { /* XXX */ | |
1383 | return error; | |
1384 | } | |
1385 | error = uiomove(lnk_node->dn_typeinfo.Slnk.name, | |
1386 | lnk_node->dn_typeinfo.Slnk.namelen, uio); | |
1387 | return error; | |
1388 | } | |
1389 | ||
1390 | static int | |
1391 | devfs_reclaim(struct vop_reclaim_args *ap) | |
1392 | /*struct vop_reclaim_args { | |
1393 | struct vnode *a_vp; | |
1394 | } */ | |
1395 | { | |
1396 | struct vnode * vp = ap->a_vp; | |
1397 | devnode_t * dnp = VTODN(vp); | |
1398 | ||
1399 | if (dnp) { | |
1400 | /* | |
1401 | * do the same as devfs_inactive in case it is not called | |
1402 | * before us (can that ever happen?) | |
1403 | */ | |
1404 | dnp->dn_vn = NULL; | |
1405 | vp->v_data = NULL; | |
1406 | if (dnp->dn_delete) { | |
1407 | devnode_free(dnp); | |
1408 | } | |
1409 | } | |
1410 | return(0); | |
1411 | } | |
1412 | ||
1413 | /* | |
1414 | * Print out the contents of a /devfs vnode. | |
1415 | */ | |
1416 | static int | |
1417 | devfs_print(struct vop_print_args *ap) | |
1418 | /*struct vop_print_args { | |
1419 | struct vnode *a_vp; | |
1420 | } */ | |
1421 | { | |
1422 | ||
1423 | return (0); | |
1424 | } | |
1425 | ||
1426 | /**************************************************************************\ | |
1427 | * pseudo ops * | |
1428 | \**************************************************************************/ | |
1429 | ||
1430 | /* | |
1431 | * | |
1432 | * struct vop_inactive_args { | |
1433 | * struct vnode *a_vp; | |
1434 | * struct proc *a_p; | |
1435 | * } | |
1436 | */ | |
1437 | ||
1438 | static int | |
1439 | devfs_inactive(struct vop_inactive_args *ap) | |
1440 | { | |
1441 | struct vnode * vp = ap->a_vp; | |
1442 | devnode_t * dnp = VTODN(vp); | |
1443 | ||
1444 | if (dnp) { | |
1445 | dnp->dn_vn = NULL; | |
1446 | vp->v_data = NULL; | |
1447 | if (dnp->dn_delete) { | |
1448 | devnode_free(dnp); | |
1449 | } | |
1450 | } | |
1451 | VOP_UNLOCK(vp, 0, ap->a_p); | |
1452 | return (0); | |
1453 | } | |
1454 | ||
1455 | int | |
1456 | devfs_update(ap) | |
1457 | struct vop_update_args /* { | |
1458 | struct vnode *a_vp; | |
1459 | struct timeval *a_access; | |
1460 | struct timeval *a_modify; | |
1461 | int a_waitfor; | |
1462 | } */ *ap; | |
1463 | { | |
1464 | register struct fs *fs; | |
1465 | int error; | |
1466 | devnode_t * ip; | |
1467 | ||
1468 | ip = VTODN(ap->a_vp); | |
1469 | if (ap->a_vp->v_mount->mnt_flag & MNT_RDONLY) { | |
1470 | ip->dn_flags &= | |
1471 | ~(DN_ACCESS | DN_CHANGE | DN_MODIFIED | DN_UPDATE); | |
1472 | return (0); | |
1473 | } | |
1474 | if ((ip->dn_flags & | |
1475 | (DN_ACCESS | DN_CHANGE | DN_MODIFIED | DN_UPDATE)) == 0) | |
1476 | return (0); | |
1477 | dn_times(ip, time, time); | |
1478 | return (0); | |
1479 | } | |
1480 | ||
1481 | #define VOPFUNC int (*)(void *) | |
1482 | ||
1483 | /* The following ops are used by directories and symlinks */ | |
1484 | int (**devfs_vnodeop_p)(void *); | |
1485 | static struct vnodeopv_entry_desc devfs_vnodeop_entries[] = { | |
1486 | { &vop_default_desc, (VOPFUNC)vn_default_error }, | |
1487 | { &vop_lookup_desc, (VOPFUNC)devfs_lookup }, /* lookup */ | |
1488 | { &vop_create_desc, (VOPFUNC)err_create }, /* create */ | |
1489 | { &vop_whiteout_desc, (VOPFUNC)err_whiteout }, /* whiteout */ | |
1490 | { &vop_mknod_desc, (VOPFUNC)devfs_mknod }, /* mknod */ | |
1491 | { &vop_open_desc, (VOPFUNC)nop_open }, /* open */ | |
1492 | { &vop_close_desc, (VOPFUNC)devfs_close }, /* close */ | |
1493 | { &vop_access_desc, (VOPFUNC)devfs_access }, /* access */ | |
1494 | { &vop_getattr_desc, (VOPFUNC)devfs_getattr }, /* getattr */ | |
1495 | { &vop_setattr_desc, (VOPFUNC)devfs_setattr }, /* setattr */ | |
1496 | { &vop_read_desc, (VOPFUNC)devfs_read }, /* read */ | |
1497 | { &vop_write_desc, (VOPFUNC)devfs_write }, /* write */ | |
1498 | { &vop_lease_desc, (VOPFUNC)nop_lease }, /* lease */ | |
1499 | { &vop_ioctl_desc, (VOPFUNC)err_ioctl }, /* ioctl */ | |
1500 | { &vop_select_desc, (VOPFUNC)err_select }, /* select */ | |
1501 | { &vop_revoke_desc, (VOPFUNC)err_revoke }, /* revoke */ | |
1502 | { &vop_mmap_desc, (VOPFUNC)err_mmap }, /* mmap */ | |
1503 | { &vop_fsync_desc, (VOPFUNC)nop_fsync }, /* fsync */ | |
1504 | { &vop_seek_desc, (VOPFUNC)err_seek }, /* seek */ | |
1505 | { &vop_remove_desc, (VOPFUNC)devfs_remove }, /* remove */ | |
1506 | { &vop_link_desc, (VOPFUNC)devfs_link }, /* link */ | |
1507 | { &vop_rename_desc, (VOPFUNC)devfs_rename }, /* rename */ | |
1508 | { &vop_mkdir_desc, (VOPFUNC)err_mkdir }, /* mkdir */ | |
1509 | { &vop_rmdir_desc, (VOPFUNC)err_rmdir }, /* rmdir */ | |
1510 | { &vop_symlink_desc, (VOPFUNC)devfs_symlink }, /* symlink */ | |
1511 | { &vop_readdir_desc, (VOPFUNC)devfs_readdir }, /* readdir */ | |
1512 | { &vop_readlink_desc, (VOPFUNC)devfs_readlink }, /* readlink */ | |
1513 | { &vop_abortop_desc, (VOPFUNC)nop_abortop }, /* abortop */ | |
1514 | { &vop_inactive_desc, (VOPFUNC)devfs_inactive }, /* inactive */ | |
1515 | { &vop_reclaim_desc, (VOPFUNC)devfs_reclaim }, /* reclaim */ | |
1516 | { &vop_lock_desc, (VOPFUNC)nop_lock }, /* lock */ | |
1517 | { &vop_unlock_desc, (VOPFUNC)nop_unlock }, /* unlock */ | |
1518 | { &vop_bmap_desc, (VOPFUNC)err_bmap }, /* bmap */ | |
1519 | { &vop_strategy_desc, (VOPFUNC)err_strategy }, /* strategy */ | |
1520 | { &vop_print_desc, (VOPFUNC)err_print }, /* print */ | |
1521 | { &vop_islocked_desc, (VOPFUNC)nop_islocked }, /* islocked */ | |
1522 | { &vop_pathconf_desc, (VOPFUNC)err_pathconf }, /* pathconf */ | |
1523 | { &vop_advlock_desc, (VOPFUNC)err_advlock }, /* advlock */ | |
1524 | { &vop_blkatoff_desc, (VOPFUNC)err_blkatoff }, /* blkatoff */ | |
1525 | { &vop_valloc_desc, (VOPFUNC)err_valloc }, /* valloc */ | |
1526 | { &vop_reallocblks_desc, (VOPFUNC)err_reallocblks }, /* reallocblks */ | |
1527 | { &vop_vfree_desc, (VOPFUNC)err_vfree }, /* vfree */ | |
1528 | { &vop_truncate_desc, (VOPFUNC)err_truncate }, /* truncate */ | |
1529 | { &vop_update_desc, (VOPFUNC)devfs_update }, /* update */ | |
1530 | { &vop_bwrite_desc, (VOPFUNC)err_bwrite }, | |
1531 | { &vop_pagein_desc, (VOPFUNC)err_pagein }, /* Pagein */ | |
1532 | { &vop_pageout_desc, (VOPFUNC)err_pageout }, /* Pageout */ | |
1533 | { &vop_copyfile_desc, (VOPFUNC)err_copyfile }, /* Copyfile */ | |
1534 | { &vop_blktooff_desc, (VOPFUNC)err_blktooff }, /* blktooff */ | |
1535 | { &vop_offtoblk_desc, (VOPFUNC)err_offtoblk }, /* offtoblk */ | |
1536 | { &vop_cmap_desc, (VOPFUNC)err_cmap }, /* cmap */ | |
1537 | { (struct vnodeop_desc*)NULL, (int(*)())NULL } | |
1538 | }; | |
1539 | struct vnodeopv_desc devfs_vnodeop_opv_desc = | |
1540 | { &devfs_vnodeop_p, devfs_vnodeop_entries }; | |
1541 | ||
1542 | /* The following ops are used by the device nodes */ | |
1543 | int (**devfs_spec_vnodeop_p)(void *); | |
1544 | static struct vnodeopv_entry_desc devfs_spec_vnodeop_entries[] = { | |
1545 | { &vop_default_desc, (VOPFUNC)vn_default_error }, | |
1546 | { &vop_lookup_desc, (VOPFUNC)spec_lookup }, /* lookup */ | |
1547 | { &vop_create_desc, (VOPFUNC)spec_create }, /* create */ | |
1548 | { &vop_mknod_desc, (VOPFUNC)spec_mknod }, /* mknod */ | |
1549 | { &vop_open_desc, (VOPFUNC)spec_open }, /* open */ | |
1550 | { &vop_close_desc, (VOPFUNC)devfsspec_close }, /* close */ | |
1551 | { &vop_access_desc, (VOPFUNC)devfs_access }, /* access */ | |
1552 | { &vop_getattr_desc, (VOPFUNC)devfs_getattr }, /* getattr */ | |
1553 | { &vop_setattr_desc, (VOPFUNC)devfs_setattr }, /* setattr */ | |
1554 | { &vop_read_desc, (VOPFUNC)devfsspec_read }, /* read */ | |
1555 | { &vop_write_desc, (VOPFUNC)devfsspec_write }, /* write */ | |
1556 | { &vop_lease_desc, (VOPFUNC)spec_lease_check }, /* lease */ | |
1557 | { &vop_ioctl_desc, (VOPFUNC)spec_ioctl }, /* ioctl */ | |
1558 | { &vop_select_desc, (VOPFUNC)spec_select }, /* select */ | |
1559 | { &vop_revoke_desc, (VOPFUNC)spec_revoke }, /* revoke */ | |
1560 | { &vop_mmap_desc, (VOPFUNC)spec_mmap }, /* mmap */ | |
1561 | { &vop_fsync_desc, (VOPFUNC)spec_fsync }, /* fsync */ | |
1562 | { &vop_seek_desc, (VOPFUNC)spec_seek }, /* seek */ | |
1563 | { &vop_remove_desc, (VOPFUNC)devfs_remove }, /* remove */ | |
1564 | { &vop_link_desc, (VOPFUNC)devfs_link }, /* link */ | |
1565 | { &vop_rename_desc, (VOPFUNC)spec_rename }, /* rename */ | |
1566 | { &vop_mkdir_desc, (VOPFUNC)spec_mkdir }, /* mkdir */ | |
1567 | { &vop_rmdir_desc, (VOPFUNC)spec_rmdir }, /* rmdir */ | |
1568 | { &vop_symlink_desc, (VOPFUNC)spec_symlink }, /* symlink */ | |
1569 | { &vop_readdir_desc, (VOPFUNC)spec_readdir }, /* readdir */ | |
1570 | { &vop_readlink_desc, (VOPFUNC)spec_readlink }, /* readlink */ | |
1571 | { &vop_abortop_desc, (VOPFUNC)spec_abortop }, /* abortop */ | |
1572 | { &vop_inactive_desc, (VOPFUNC)devfs_inactive }, /* inactive */ | |
1573 | { &vop_reclaim_desc, (VOPFUNC)devfs_reclaim }, /* reclaim */ | |
1574 | { &vop_lock_desc, (VOPFUNC)nop_lock }, /* lock */ | |
1575 | { &vop_unlock_desc, (VOPFUNC)nop_unlock }, /* unlock */ | |
1576 | { &vop_bmap_desc, (VOPFUNC)spec_bmap }, /* bmap */ | |
1577 | { &vop_strategy_desc, (VOPFUNC)spec_strategy }, /* strategy */ | |
1578 | { &vop_print_desc, (VOPFUNC)devfs_print }, /* print */ | |
1579 | { &vop_islocked_desc, (VOPFUNC)nop_islocked }, /* islocked */ | |
1580 | { &vop_pathconf_desc, (VOPFUNC)spec_pathconf }, /* pathconf */ | |
1581 | { &vop_advlock_desc, (VOPFUNC)spec_advlock }, /* advlock */ | |
1582 | { &vop_blkatoff_desc, (VOPFUNC)spec_blkatoff }, /* blkatoff */ | |
1583 | { &vop_valloc_desc, (VOPFUNC)spec_valloc }, /* valloc */ | |
1584 | { &vop_reallocblks_desc, (VOPFUNC)spec_reallocblks }, /* reallocblks */ | |
1585 | { &vop_vfree_desc, (VOPFUNC)nop_vfree }, /* vfree */ | |
1586 | { &vop_truncate_desc, (VOPFUNC)spec_truncate }, /* truncate */ | |
1587 | { &vop_update_desc, (VOPFUNC)devfs_update }, /* update */ | |
1588 | { &vop_bwrite_desc, (VOPFUNC)vn_bwrite }, | |
1589 | { &vop_devblocksize_desc, (VOPFUNC)spec_devblocksize }, /* devblocksize */ | |
1590 | { &vop_pagein_desc, (VOPFUNC)err_pagein }, /* Pagein */ | |
1591 | { &vop_pageout_desc, (VOPFUNC)err_pageout }, /* Pageout */ | |
1592 | { &vop_copyfile_desc, (VOPFUNC)err_copyfile }, /* Copyfile */ | |
1593 | { &vop_blktooff_desc, (VOPFUNC)spec_blktooff }, /* blktooff */ | |
1594 | { &vop_blktooff_desc, (VOPFUNC)spec_offtoblk }, /* blkofftoblk */ | |
1595 | { &vop_cmap_desc, (VOPFUNC)spec_cmap }, /* cmap */ | |
1596 | { (struct vnodeop_desc*)NULL, (int(*)())NULL } | |
1597 | }; | |
1598 | struct vnodeopv_desc devfs_spec_vnodeop_opv_desc = | |
1599 | { &devfs_spec_vnodeop_p, devfs_spec_vnodeop_entries }; | |
1600 |