]>
Commit | Line | Data |
---|---|---|
9bccf70c | 1 | /* |
fe8ab488 | 2 | * Copyright (c) 2000-2014 Apple Inc. All rights reserved. |
5d5c5d0d | 3 | * |
2d21ac55 | 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
9bccf70c | 5 | * |
2d21ac55 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
8f6c56a5 | 14 | * |
2d21ac55 A |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
8f6c56a5 A |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
2d21ac55 A |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
8f6c56a5 | 25 | * |
2d21ac55 | 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
9bccf70c A |
27 | */ |
28 | ||
fe8ab488 | 29 | #include <stdbool.h> |
9bccf70c | 30 | #include <sys/systm.h> |
cf7d32b8 | 31 | #include <sys/param.h> |
b0d623f7 | 32 | #include <sys/kernel.h> |
91447636 | 33 | #include <sys/file_internal.h> |
9bccf70c A |
34 | #include <sys/dirent.h> |
35 | #include <sys/stat.h> | |
36 | #include <sys/buf.h> | |
316670eb | 37 | #include <sys/buf_internal.h> |
9bccf70c | 38 | #include <sys/mount.h> |
2d21ac55 | 39 | #include <sys/vnode_if.h> |
91447636 | 40 | #include <sys/vnode_internal.h> |
9bccf70c | 41 | #include <sys/malloc.h> |
9bccf70c | 42 | #include <sys/ubc.h> |
2d21ac55 | 43 | #include <sys/ubc_internal.h> |
91447636 | 44 | #include <sys/paths.h> |
9bccf70c | 45 | #include <sys/quota.h> |
55e303ae A |
46 | #include <sys/time.h> |
47 | #include <sys/disk.h> | |
91447636 A |
48 | #include <sys/kauth.h> |
49 | #include <sys/uio_internal.h> | |
6d2010ae A |
50 | #include <sys/fsctl.h> |
51 | #include <sys/cprotect.h> | |
316670eb | 52 | #include <sys/xattr.h> |
6d2010ae | 53 | #include <string.h> |
22ba694c A |
54 | #include <sys/fsevents.h> |
55 | #include <kern/kalloc.h> | |
9bccf70c A |
56 | |
57 | #include <miscfs/specfs/specdev.h> | |
58 | #include <miscfs/fifofs/fifo.h> | |
59 | #include <vfs/vfs_support.h> | |
60 | #include <machine/spl.h> | |
61 | ||
62 | #include <sys/kdebug.h> | |
3a60a9f5 | 63 | #include <sys/sysctl.h> |
fe8ab488 | 64 | #include <stdbool.h> |
9bccf70c A |
65 | |
66 | #include "hfs.h" | |
67 | #include "hfs_catalog.h" | |
68 | #include "hfs_cnode.h" | |
9bccf70c A |
69 | #include "hfs_dbg.h" |
70 | #include "hfs_mount.h" | |
71 | #include "hfs_quota.h" | |
72 | #include "hfs_endian.h" | |
fe8ab488 | 73 | #include "hfs_kdebug.h" |
9bccf70c A |
74 | |
75 | #include "hfscommon/headers/BTreesInternal.h" | |
76 | #include "hfscommon/headers/FileMgrInternal.h" | |
77 | ||
55e303ae | 78 | #define KNDETACH_VNLOCKED 0x00000001 |
9bccf70c | 79 | |
9bccf70c A |
80 | /* Global vfs data structures for hfs */ |
81 | ||
3a60a9f5 A |
82 | /* Always F_FULLFSYNC? 1=yes,0=no (default due to "various" reasons is 'no') */ |
83 | int always_do_fullfsync = 0; | |
b0d623f7 | 84 | SYSCTL_DECL(_vfs_generic); |
6d2010ae | 85 | SYSCTL_INT (_vfs_generic, OID_AUTO, always_do_fullfsync, CTLFLAG_RW | CTLFLAG_LOCKED, &always_do_fullfsync, 0, "always F_FULLFSYNC when fsync is called"); |
9bccf70c | 86 | |
6d2010ae | 87 | int hfs_makenode(struct vnode *dvp, struct vnode **vpp, |
91447636 A |
88 | struct componentname *cnp, struct vnode_attr *vap, |
89 | vfs_context_t ctx); | |
6d2010ae A |
90 | int hfs_metasync(struct hfsmount *hfsmp, daddr64_t node, __unused struct proc *p); |
91 | int hfs_metasync_all(struct hfsmount *hfsmp); | |
9bccf70c | 92 | |
6d2010ae A |
93 | int hfs_removedir(struct vnode *, struct vnode *, struct componentname *, |
94 | int, int); | |
95 | int hfs_removefile(struct vnode *, struct vnode *, struct componentname *, | |
96 | int, int, int, struct vnode *, int); | |
9bccf70c | 97 | |
316670eb A |
98 | /* Used here and in cnode teardown -- for symlinks */ |
99 | int hfs_removefile_callback(struct buf *bp, void *hfsmp); | |
100 | ||
fe8ab488 A |
101 | enum { |
102 | HFS_MOVE_DATA_INCLUDE_RSRC = 1, | |
103 | }; | |
104 | typedef uint32_t hfs_move_data_options_t; | |
105 | ||
106 | static int hfs_move_data(cnode_t *from_cp, cnode_t *to_cp, | |
107 | hfs_move_data_options_t options); | |
108 | static int hfs_move_fork(filefork_t *srcfork, cnode_t *src, | |
109 | filefork_t *dstfork, cnode_t *dst); | |
110 | static const struct cat_fork * | |
111 | hfs_prepare_fork_for_update(const filefork_t *pfork, | |
112 | struct cat_fork *fork_buf, | |
113 | uint32_t block_size); | |
114 | ||
115 | #if HFS_COMPRESSION | |
116 | static int hfs_move_compressed(cnode_t *from_vp, cnode_t *to_vp); | |
117 | #endif | |
55e303ae | 118 | |
39236c6e A |
119 | decmpfs_cnode* hfs_lazy_init_decmpfs_cnode (struct cnode *cp); |
120 | ||
2d21ac55 A |
121 | #if FIFO |
122 | static int hfsfifo_read(struct vnop_read_args *); | |
123 | static int hfsfifo_write(struct vnop_write_args *); | |
124 | static int hfsfifo_close(struct vnop_close_args *); | |
2d21ac55 A |
125 | |
126 | extern int (**fifo_vnodeop_p)(void *); | |
127 | #endif /* FIFO */ | |
91447636 | 128 | |
6d2010ae A |
129 | int hfs_vnop_close(struct vnop_close_args*); |
130 | int hfs_vnop_create(struct vnop_create_args*); | |
131 | int hfs_vnop_exchange(struct vnop_exchange_args*); | |
132 | int hfs_vnop_fsync(struct vnop_fsync_args*); | |
133 | int hfs_vnop_mkdir(struct vnop_mkdir_args*); | |
134 | int hfs_vnop_mknod(struct vnop_mknod_args*); | |
135 | int hfs_vnop_getattr(struct vnop_getattr_args*); | |
136 | int hfs_vnop_open(struct vnop_open_args*); | |
137 | int hfs_vnop_readdir(struct vnop_readdir_args*); | |
138 | int hfs_vnop_remove(struct vnop_remove_args*); | |
139 | int hfs_vnop_rename(struct vnop_rename_args*); | |
140 | int hfs_vnop_rmdir(struct vnop_rmdir_args*); | |
141 | int hfs_vnop_symlink(struct vnop_symlink_args*); | |
142 | int hfs_vnop_setattr(struct vnop_setattr_args*); | |
143 | int hfs_vnop_readlink(struct vnop_readlink_args *); | |
144 | int hfs_vnop_pathconf(struct vnop_pathconf_args *); | |
6d2010ae A |
145 | int hfs_vnop_mmap(struct vnop_mmap_args *ap); |
146 | int hfsspec_read(struct vnop_read_args *); | |
147 | int hfsspec_write(struct vnop_write_args *); | |
148 | int hfsspec_close(struct vnop_close_args *); | |
55e303ae A |
149 | |
150 | /* Options for hfs_removedir and hfs_removefile */ | |
91447636 | 151 | #define HFSRM_SKIP_RESERVE 0x01 |
55e303ae A |
152 | |
153 | ||
9bccf70c | 154 | |
9bccf70c A |
155 | /***************************************************************************** |
156 | * | |
157 | * Common Operations on vnodes | |
158 | * | |
159 | *****************************************************************************/ | |
160 | ||
316670eb A |
161 | /* |
162 | * Is the given cnode either the .journal or .journal_info_block file on | |
163 | * a volume with an active journal? Many VNOPs use this to deny access | |
164 | * to those files. | |
165 | * | |
166 | * Note: the .journal file on a volume with an external journal still | |
167 | * returns true here, even though it does not actually hold the contents | |
168 | * of the volume's journal. | |
169 | */ | |
170 | static _Bool | |
171 | hfs_is_journal_file(struct hfsmount *hfsmp, struct cnode *cp) | |
172 | { | |
173 | if (hfsmp->jnl != NULL && | |
174 | (cp->c_fileid == hfsmp->hfs_jnlinfoblkid || | |
175 | cp->c_fileid == hfsmp->hfs_jnlfileid)) { | |
176 | return true; | |
177 | } else { | |
178 | return false; | |
179 | } | |
180 | } | |
181 | ||
9bccf70c | 182 | /* |
91447636 A |
183 | * Create a regular file. |
184 | */ | |
6d2010ae | 185 | int |
91447636 | 186 | hfs_vnop_create(struct vnop_create_args *ap) |
9bccf70c | 187 | { |
2d21ac55 | 188 | /* |
fe8ab488 A |
189 | * We leave handling of certain race conditions here to the caller |
190 | * which will have a better understanding of the semantics it | |
191 | * requires. For example, if it turns out that the file exists, | |
192 | * it would be wrong of us to return a reference to the existing | |
193 | * file because the caller might not want that and it would be | |
194 | * misleading to suggest the file had been created when it hadn't | |
195 | * been. Note that our NFS server code does not set the | |
196 | * VA_EXCLUSIVE flag so you cannot assume that callers don't want | |
197 | * EEXIST errors if it's not set. The common case, where users | |
198 | * are calling open with the O_CREAT mode, is handled in VFS; when | |
199 | * we return EEXIST, it will loop and do the look-up again. | |
2d21ac55 | 200 | */ |
fe8ab488 | 201 | return hfs_makenode(ap->a_dvp, ap->a_vpp, ap->a_cnp, ap->a_vap, ap->a_context); |
9bccf70c A |
202 | } |
203 | ||
9bccf70c | 204 | /* |
91447636 A |
205 | * Make device special file. |
206 | */ | |
6d2010ae | 207 | int |
91447636 | 208 | hfs_vnop_mknod(struct vnop_mknod_args *ap) |
9bccf70c | 209 | { |
91447636 A |
210 | struct vnode_attr *vap = ap->a_vap; |
211 | struct vnode *dvp = ap->a_dvp; | |
9bccf70c A |
212 | struct vnode **vpp = ap->a_vpp; |
213 | struct cnode *cp; | |
214 | int error; | |
215 | ||
91447636 A |
216 | if (VTOVCB(dvp)->vcbSigWord != kHFSPlusSigWord) { |
217 | return (ENOTSUP); | |
9bccf70c A |
218 | } |
219 | ||
220 | /* Create the vnode */ | |
91447636 | 221 | error = hfs_makenode(dvp, vpp, ap->a_cnp, vap, ap->a_context); |
9bccf70c A |
222 | if (error) |
223 | return (error); | |
91447636 | 224 | |
9bccf70c | 225 | cp = VTOC(*vpp); |
91447636 A |
226 | cp->c_touch_acctime = TRUE; |
227 | cp->c_touch_chgtime = TRUE; | |
228 | cp->c_touch_modtime = TRUE; | |
229 | ||
9bccf70c A |
230 | if ((vap->va_rdev != VNOVAL) && |
231 | (vap->va_type == VBLK || vap->va_type == VCHR)) | |
232 | cp->c_rdev = vap->va_rdev; | |
91447636 | 233 | |
9bccf70c A |
234 | return (0); |
235 | } | |
236 | ||
b0d623f7 A |
237 | #if HFS_COMPRESSION |
238 | /* | |
239 | * hfs_ref_data_vp(): returns the data fork vnode for a given cnode. | |
240 | * In the (hopefully rare) case where the data fork vnode is not | |
241 | * present, it will use hfs_vget() to create a new vnode for the | |
242 | * data fork. | |
243 | * | |
244 | * NOTE: If successful and a vnode is returned, the caller is responsible | |
245 | * for releasing the returned vnode with vnode_rele(). | |
246 | */ | |
247 | static int | |
248 | hfs_ref_data_vp(struct cnode *cp, struct vnode **data_vp, int skiplock) | |
249 | { | |
7e4a7d39 A |
250 | int vref = 0; |
251 | ||
b0d623f7 A |
252 | if (!data_vp || !cp) /* sanity check incoming parameters */ |
253 | return EINVAL; | |
254 | ||
255 | /* maybe we should take the hfs cnode lock here, and if so, use the skiplock parameter to tell us not to */ | |
256 | ||
39236c6e | 257 | if (!skiplock) hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); |
b0d623f7 A |
258 | struct vnode *c_vp = cp->c_vp; |
259 | if (c_vp) { | |
260 | /* we already have a data vnode */ | |
261 | *data_vp = c_vp; | |
7e4a7d39 | 262 | vref = vnode_ref(*data_vp); |
b0d623f7 | 263 | if (!skiplock) hfs_unlock(cp); |
7e4a7d39 A |
264 | if (vref == 0) { |
265 | return 0; | |
266 | } | |
267 | return EINVAL; | |
b0d623f7 A |
268 | } |
269 | /* no data fork vnode in the cnode, so ask hfs for one. */ | |
270 | ||
271 | if (!cp->c_rsrc_vp) { | |
272 | /* if we don't have either a c_vp or c_rsrc_vp, we can't really do anything useful */ | |
273 | *data_vp = NULL; | |
274 | if (!skiplock) hfs_unlock(cp); | |
275 | return EINVAL; | |
276 | } | |
277 | ||
6d2010ae | 278 | if (0 == hfs_vget(VTOHFS(cp->c_rsrc_vp), cp->c_cnid, data_vp, 1, 0) && |
b0d623f7 | 279 | 0 != data_vp) { |
7e4a7d39 | 280 | vref = vnode_ref(*data_vp); |
b0d623f7 A |
281 | vnode_put(*data_vp); |
282 | if (!skiplock) hfs_unlock(cp); | |
7e4a7d39 A |
283 | if (vref == 0) { |
284 | return 0; | |
285 | } | |
286 | return EINVAL; | |
b0d623f7 A |
287 | } |
288 | /* there was an error getting the vnode */ | |
289 | *data_vp = NULL; | |
290 | if (!skiplock) hfs_unlock(cp); | |
291 | return EINVAL; | |
292 | } | |
293 | ||
294 | /* | |
295 | * hfs_lazy_init_decmpfs_cnode(): returns the decmpfs_cnode for a cnode, | |
39236c6e A |
296 | * allocating it if necessary; returns NULL if there was an allocation error. |
297 | * function is non-static so that it can be used from the FCNTL handler. | |
b0d623f7 | 298 | */ |
39236c6e | 299 | decmpfs_cnode * |
b0d623f7 A |
300 | hfs_lazy_init_decmpfs_cnode(struct cnode *cp) |
301 | { | |
302 | if (!cp->c_decmp) { | |
303 | decmpfs_cnode *dp = NULL; | |
304 | MALLOC_ZONE(dp, decmpfs_cnode *, sizeof(decmpfs_cnode), M_DECMPFS_CNODE, M_WAITOK); | |
305 | if (!dp) { | |
306 | /* error allocating a decmpfs cnode */ | |
307 | return NULL; | |
308 | } | |
309 | decmpfs_cnode_init(dp); | |
310 | if (!OSCompareAndSwapPtr(NULL, dp, (void * volatile *)&cp->c_decmp)) { | |
311 | /* another thread got here first, so free the decmpfs_cnode we allocated */ | |
312 | decmpfs_cnode_destroy(dp); | |
313 | FREE_ZONE(dp, sizeof(*dp), M_DECMPFS_CNODE); | |
314 | } | |
315 | } | |
316 | ||
317 | return cp->c_decmp; | |
318 | } | |
319 | ||
320 | /* | |
321 | * hfs_file_is_compressed(): returns 1 if the file is compressed, and 0 (zero) if not. | |
322 | * if the file's compressed flag is set, makes sure that the decmpfs_cnode field | |
323 | * is allocated by calling hfs_lazy_init_decmpfs_cnode(), then makes sure it is populated, | |
324 | * or else fills it in via the decmpfs_file_is_compressed() function. | |
325 | */ | |
326 | int | |
327 | hfs_file_is_compressed(struct cnode *cp, int skiplock) | |
328 | { | |
329 | int ret = 0; | |
330 | ||
331 | /* fast check to see if file is compressed. If flag is clear, just answer no */ | |
316670eb | 332 | if (!(cp->c_bsdflags & UF_COMPRESSED)) { |
b0d623f7 A |
333 | return 0; |
334 | } | |
335 | ||
336 | decmpfs_cnode *dp = hfs_lazy_init_decmpfs_cnode(cp); | |
337 | if (!dp) { | |
338 | /* error allocating a decmpfs cnode, treat the file as uncompressed */ | |
339 | return 0; | |
340 | } | |
341 | ||
342 | /* flag was set, see if the decmpfs_cnode state is valid (zero == invalid) */ | |
343 | uint32_t decmpfs_state = decmpfs_cnode_get_vnode_state(dp); | |
344 | switch(decmpfs_state) { | |
345 | case FILE_IS_COMPRESSED: | |
346 | case FILE_IS_CONVERTING: /* treat decompressing files as if they are compressed */ | |
347 | return 1; | |
348 | case FILE_IS_NOT_COMPRESSED: | |
349 | return 0; | |
350 | /* otherwise the state is not cached yet */ | |
351 | } | |
352 | ||
353 | /* decmpfs hasn't seen this file yet, so call decmpfs_file_is_compressed() to init the decmpfs_cnode struct */ | |
354 | struct vnode *data_vp = NULL; | |
355 | if (0 == hfs_ref_data_vp(cp, &data_vp, skiplock)) { | |
356 | if (data_vp) { | |
357 | ret = decmpfs_file_is_compressed(data_vp, VTOCMP(data_vp)); // fill in decmpfs_cnode | |
358 | vnode_rele(data_vp); | |
359 | } | |
360 | } | |
361 | return ret; | |
362 | } | |
363 | ||
364 | /* hfs_uncompressed_size_of_compressed_file() - get the uncompressed size of the file. | |
365 | * if the caller has passed a valid vnode (has a ref count > 0), then hfsmp and fid are not required. | |
366 | * if the caller doesn't have a vnode, pass NULL in vp, and pass valid hfsmp and fid. | |
367 | * files size is returned in size (required) | |
6d2010ae A |
368 | * if the indicated file is a directory (or something that doesn't have a data fork), then this call |
369 | * will return an error and the caller should fall back to treating the item as an uncompressed file | |
b0d623f7 A |
370 | */ |
371 | int | |
372 | hfs_uncompressed_size_of_compressed_file(struct hfsmount *hfsmp, struct vnode *vp, cnid_t fid, off_t *size, int skiplock) | |
373 | { | |
374 | int ret = 0; | |
375 | int putaway = 0; /* flag to remember if we used hfs_vget() */ | |
376 | ||
377 | if (!size) { | |
378 | return EINVAL; /* no place to put the file size */ | |
379 | } | |
380 | ||
381 | if (NULL == vp) { | |
382 | if (!hfsmp || !fid) { /* make sure we have the required parameters */ | |
383 | return EINVAL; | |
384 | } | |
6d2010ae | 385 | if (0 != hfs_vget(hfsmp, fid, &vp, skiplock, 0)) { /* vnode is null, use hfs_vget() to get it */ |
b0d623f7 A |
386 | vp = NULL; |
387 | } else { | |
388 | putaway = 1; /* note that hfs_vget() was used to aquire the vnode */ | |
389 | } | |
390 | } | |
391 | /* this double check for compression (hfs_file_is_compressed) | |
392 | * ensures the cached size is present in case decmpfs hasn't | |
393 | * encountered this node yet. | |
394 | */ | |
6d2010ae A |
395 | if (vp) { |
396 | if (hfs_file_is_compressed(VTOC(vp), skiplock) ) { | |
397 | *size = decmpfs_cnode_get_vnode_cached_size(VTOCMP(vp)); /* file info will be cached now, so get size */ | |
398 | } else { | |
399 | if (VTOCMP(vp) && VTOCMP(vp)->cmp_type >= CMP_MAX) { | |
400 | if (VTOCMP(vp)->cmp_type != DATALESS_CMPFS_TYPE) { | |
401 | // if we don't recognize this type, just use the real data fork size | |
402 | if (VTOC(vp)->c_datafork) { | |
403 | *size = VTOC(vp)->c_datafork->ff_size; | |
404 | ret = 0; | |
405 | } else { | |
406 | ret = EINVAL; | |
407 | } | |
408 | } else { | |
409 | *size = decmpfs_cnode_get_vnode_cached_size(VTOCMP(vp)); /* file info will be cached now, so get size */ | |
410 | ret = 0; | |
411 | } | |
412 | } else { | |
413 | ret = EINVAL; | |
414 | } | |
415 | } | |
b0d623f7 A |
416 | } |
417 | ||
418 | if (putaway) { /* did we use hfs_vget() to get this vnode? */ | |
419 | vnode_put(vp); /* if so, release it and set it to null */ | |
420 | vp = NULL; | |
421 | } | |
422 | return ret; | |
423 | } | |
424 | ||
425 | int | |
426 | hfs_hides_rsrc(vfs_context_t ctx, struct cnode *cp, int skiplock) | |
427 | { | |
428 | if (ctx == decmpfs_ctx) | |
429 | return 0; | |
430 | if (!hfs_file_is_compressed(cp, skiplock)) | |
431 | return 0; | |
432 | return decmpfs_hides_rsrc(ctx, cp->c_decmp); | |
433 | } | |
434 | ||
435 | int | |
436 | hfs_hides_xattr(vfs_context_t ctx, struct cnode *cp, const char *name, int skiplock) | |
437 | { | |
438 | if (ctx == decmpfs_ctx) | |
439 | return 0; | |
440 | if (!hfs_file_is_compressed(cp, skiplock)) | |
441 | return 0; | |
442 | return decmpfs_hides_xattr(ctx, cp->c_decmp, name); | |
443 | } | |
444 | #endif /* HFS_COMPRESSION */ | |
445 | ||
22ba694c A |
446 | |
447 | // | |
448 | // This function gets the doc_tombstone structure for the | |
449 | // current thread. If the thread doesn't have one, the | |
450 | // structure is allocated. | |
451 | // | |
452 | static struct doc_tombstone * | |
453 | get_uthread_doc_tombstone(void) | |
454 | { | |
455 | struct uthread *ut; | |
456 | ut = get_bsdthread_info(current_thread()); | |
457 | ||
458 | if (ut->t_tombstone == NULL) { | |
459 | ut->t_tombstone = kalloc(sizeof(struct doc_tombstone)); | |
460 | if (ut->t_tombstone) { | |
461 | memset(ut->t_tombstone, 0, sizeof(struct doc_tombstone)); | |
462 | } | |
463 | } | |
464 | ||
465 | return ut->t_tombstone; | |
466 | } | |
467 | ||
468 | // | |
469 | // This routine clears out the current tombstone for the | |
470 | // current thread and if necessary passes the doc-id of | |
471 | // the tombstone on to the dst_cnode. | |
472 | // | |
473 | // If the doc-id transfers to dst_cnode, we also generate | |
474 | // a doc-id changed fsevent. Unlike all the other fsevents, | |
475 | // doc-id changed events can only be generated here in HFS | |
476 | // where we have the necessary info. | |
477 | // | |
478 | static void | |
fe8ab488 | 479 | clear_tombstone_docid(struct doc_tombstone *ut, __unused struct hfsmount *hfsmp, struct cnode *dst_cnode) |
22ba694c A |
480 | { |
481 | uint32_t old_id = ut->t_lastop_document_id; | |
482 | ||
483 | ut->t_lastop_document_id = 0; | |
484 | ut->t_lastop_parent = NULL; | |
485 | ut->t_lastop_parent_vid = 0; | |
486 | ut->t_lastop_filename[0] = '\0'; | |
487 | ||
488 | // | |
489 | // If the lastop item is still the same and needs to be cleared, | |
490 | // clear it. | |
491 | // | |
492 | if (dst_cnode && old_id && ut->t_lastop_item && vnode_vid(ut->t_lastop_item) == ut->t_lastop_item_vid) { | |
493 | // | |
494 | // clear the document_id from the file that used to have it. | |
495 | // XXXdbg - we need to lock the other vnode and make sure to | |
496 | // update it on disk. | |
497 | // | |
498 | struct cnode *ocp = VTOC(ut->t_lastop_item); | |
499 | struct FndrExtendedFileInfo *ofip = (struct FndrExtendedFileInfo *)((char *)&ocp->c_attr.ca_finderinfo + 16); | |
500 | ||
501 | // printf("clearing doc-id from ino %d\n", ocp->c_desc.cd_cnid); | |
502 | ofip->document_id = 0; | |
503 | ocp->c_bsdflags &= ~UF_TRACKED; | |
504 | ocp->c_flag |= C_MODIFIED | C_FORCEUPDATE; // mark it dirty | |
505 | /* cat_update(hfsmp, &ocp->c_desc, &ocp->c_attr, NULL, NULL); */ | |
506 | ||
507 | } | |
508 | ||
509 | #if CONFIG_FSE | |
510 | if (dst_cnode && old_id) { | |
511 | struct FndrExtendedFileInfo *fip = (struct FndrExtendedFileInfo *)((char *)&dst_cnode->c_attr.ca_finderinfo + 16); | |
512 | ||
513 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
514 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
515 | FSE_ARG_INO, (ino64_t)ut->t_lastop_fileid, // src inode # | |
516 | FSE_ARG_INO, (ino64_t)dst_cnode->c_fileid, // dst inode # | |
517 | FSE_ARG_INT32, (uint32_t)fip->document_id, | |
518 | FSE_ARG_DONE); | |
519 | } | |
520 | #endif | |
521 | // last, clear these now that we're all done | |
522 | ut->t_lastop_item = NULL; | |
523 | ut->t_lastop_fileid = 0; | |
524 | ut->t_lastop_item_vid = 0; | |
525 | } | |
526 | ||
527 | ||
528 | // | |
529 | // This function is used to filter out operations on temp | |
530 | // filenames. We have to filter out operations on certain | |
531 | // temp filenames to work-around questionable application | |
532 | // behavior from apps like Autocad that perform unusual | |
533 | // sequences of file system operations for a "safe save". | |
534 | static int | |
535 | is_ignorable_temp_name(const char *nameptr, int len) | |
536 | { | |
537 | if (len == 0) { | |
538 | len = strlen(nameptr); | |
539 | } | |
540 | ||
541 | if ( strncmp(nameptr, "atmp", 4) == 0 | |
542 | || (len > 4 && strncmp(nameptr+len-4, ".bak", 4) == 0) | |
543 | || (len > 4 && strncmp(nameptr+len-4, ".tmp", 4) == 0)) { | |
544 | return 1; | |
545 | } | |
546 | ||
547 | return 0; | |
548 | } | |
549 | ||
550 | // | |
551 | // Decide if we need to save a tombstone or not. Normally we always | |
552 | // save a tombstone - but if there already is one and the name we're | |
553 | // given is an ignorable name, then we will not save a tombstone. | |
554 | // | |
555 | static int | |
556 | should_save_docid_tombstone(struct doc_tombstone *ut, struct vnode *vp, struct componentname *cnp) | |
557 | { | |
558 | if (cnp->cn_nameptr == NULL) { | |
559 | return 0; | |
560 | } | |
561 | ||
562 | if (ut->t_lastop_document_id && ut->t_lastop_item == vp && is_ignorable_temp_name(cnp->cn_nameptr, cnp->cn_namelen)) { | |
563 | return 0; | |
564 | } | |
565 | ||
566 | return 1; | |
567 | } | |
568 | ||
569 | ||
570 | // | |
571 | // This function saves a tombstone for the given vnode and name. The | |
572 | // tombstone represents the parent directory and name where the document | |
573 | // used to live and the document-id of that file. This info is recorded | |
574 | // in the doc_tombstone structure hanging off the uthread (which assumes | |
575 | // that all safe-save operations happen on the same thread). | |
576 | // | |
577 | // If later on the same parent/name combo comes back into existence then | |
578 | // we'll preserve the doc-id from this vnode onto the new vnode. | |
579 | // | |
580 | static void | |
581 | save_tombstone(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int for_unlink) | |
582 | { | |
583 | struct cnode *cp = VTOC(vp); | |
584 | struct doc_tombstone *ut; | |
585 | ut = get_uthread_doc_tombstone(); | |
586 | ||
587 | if (for_unlink && vp->v_type == VREG && cp->c_linkcount > 1) { | |
588 | // | |
589 | // a regular file that is being unlinked and that is also | |
590 | // hardlinked should not clear the UF_TRACKED state or | |
591 | // mess with the tombstone because somewhere else in the | |
592 | // file system the file is still alive. | |
593 | // | |
594 | return; | |
595 | } | |
596 | ||
597 | ut->t_lastop_parent = dvp; | |
598 | ut->t_lastop_parent_vid = vnode_vid(dvp); | |
599 | ut->t_lastop_fileid = cp->c_fileid; | |
600 | if (for_unlink) { | |
601 | ut->t_lastop_item = NULL; | |
602 | ut->t_lastop_item_vid = 0; | |
603 | } else { | |
604 | ut->t_lastop_item = vp; | |
605 | ut->t_lastop_item_vid = vnode_vid(vp); | |
606 | } | |
607 | ||
608 | strlcpy((char *)&ut->t_lastop_filename[0], cnp->cn_nameptr, sizeof(ut->t_lastop_filename)); | |
609 | ||
610 | struct FndrExtendedFileInfo *fip = (struct FndrExtendedFileInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); | |
611 | ut->t_lastop_document_id = fip->document_id; | |
612 | ||
613 | if (for_unlink) { | |
614 | // clear this so it's never returned again | |
615 | fip->document_id = 0; | |
616 | cp->c_bsdflags &= ~UF_TRACKED; | |
617 | ||
618 | if (ut->t_lastop_document_id) { | |
619 | (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL); | |
620 | ||
621 | #if CONFIG_FSE | |
622 | // this event is more of a "pending-delete" | |
623 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
624 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
625 | FSE_ARG_INO, (ino64_t)cp->c_fileid, // src inode # | |
626 | FSE_ARG_INO, (ino64_t)0, // dst inode # | |
627 | FSE_ARG_INT32, ut->t_lastop_document_id, // document id | |
628 | FSE_ARG_DONE); | |
629 | #endif | |
630 | } | |
631 | } | |
632 | } | |
633 | ||
634 | ||
9bccf70c | 635 | /* |
91447636 A |
636 | * Open a file/directory. |
637 | */ | |
6d2010ae | 638 | int |
91447636 | 639 | hfs_vnop_open(struct vnop_open_args *ap) |
9bccf70c A |
640 | { |
641 | struct vnode *vp = ap->a_vp; | |
91447636 | 642 | struct filefork *fp; |
55e303ae | 643 | struct timeval tv; |
91447636 | 644 | int error; |
b0d623f7 A |
645 | static int past_bootup = 0; |
646 | struct cnode *cp = VTOC(vp); | |
647 | struct hfsmount *hfsmp = VTOHFS(vp); | |
648 | ||
649 | #if HFS_COMPRESSION | |
650 | if (ap->a_mode & FWRITE) { | |
651 | /* open for write */ | |
652 | if ( hfs_file_is_compressed(cp, 1) ) { /* 1 == don't take the cnode lock */ | |
653 | /* opening a compressed file for write, so convert it to decompressed */ | |
654 | struct vnode *data_vp = NULL; | |
655 | error = hfs_ref_data_vp(cp, &data_vp, 1); /* 1 == don't take the cnode lock */ | |
656 | if (0 == error) { | |
657 | if (data_vp) { | |
658 | error = decmpfs_decompress_file(data_vp, VTOCMP(data_vp), -1, 1, 0); | |
659 | vnode_rele(data_vp); | |
660 | } else { | |
661 | error = EINVAL; | |
662 | } | |
663 | } | |
664 | if (error != 0) | |
665 | return error; | |
666 | } | |
667 | } else { | |
668 | /* open for read */ | |
669 | if (hfs_file_is_compressed(cp, 1) ) { /* 1 == don't take the cnode lock */ | |
670 | if (VNODE_IS_RSRC(vp)) { | |
671 | /* opening the resource fork of a compressed file, so nothing to do */ | |
672 | } else { | |
673 | /* opening a compressed file for read, make sure it validates */ | |
674 | error = decmpfs_validate_compressed_file(vp, VTOCMP(vp)); | |
675 | if (error != 0) | |
676 | return error; | |
677 | } | |
678 | } | |
679 | } | |
680 | #endif | |
9bccf70c A |
681 | |
682 | /* | |
683 | * Files marked append-only must be opened for appending. | |
684 | */ | |
316670eb | 685 | if ((cp->c_bsdflags & APPEND) && !vnode_isdir(vp) && |
9bccf70c A |
686 | (ap->a_mode & (FWRITE | O_APPEND)) == FWRITE) |
687 | return (EPERM); | |
688 | ||
91447636 A |
689 | if (vnode_isreg(vp) && !UBCINFOEXISTS(vp)) |
690 | return (EBUSY); /* file is in use by the kernel */ | |
55e303ae | 691 | |
316670eb A |
692 | /* Don't allow journal to be opened externally. */ |
693 | if (hfs_is_journal_file(hfsmp, cp)) | |
91447636 | 694 | return (EPERM); |
b0d623f7 | 695 | |
b0d623f7 A |
696 | if ((hfsmp->hfs_flags & HFS_READ_ONLY) || |
697 | (hfsmp->jnl == NULL) || | |
2d21ac55 A |
698 | #if NAMEDSTREAMS |
699 | !vnode_isreg(vp) || vnode_isinuse(vp, 0) || vnode_isnamedstream(vp)) { | |
700 | #else | |
91447636 | 701 | !vnode_isreg(vp) || vnode_isinuse(vp, 0)) { |
2d21ac55 | 702 | #endif |
55e303ae A |
703 | return (0); |
704 | } | |
91447636 | 705 | |
39236c6e | 706 | if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) |
91447636 | 707 | return (error); |
ebb1b9f4 A |
708 | |
709 | #if QUOTA | |
710 | /* If we're going to write to the file, initialize quotas. */ | |
711 | if ((ap->a_mode & FWRITE) && (hfsmp->hfs_flags & HFS_QUOTAS)) | |
712 | (void)hfs_getinoquota(cp); | |
713 | #endif /* QUOTA */ | |
714 | ||
715 | /* | |
716 | * On the first (non-busy) open of a fragmented | |
717 | * file attempt to de-frag it (if its less than 20MB). | |
718 | */ | |
55e303ae A |
719 | fp = VTOF(vp); |
720 | if (fp->ff_blocks && | |
721 | fp->ff_extents[7].blockCount != 0 && | |
722 | fp->ff_size <= (20 * 1024 * 1024)) { | |
b0d623f7 | 723 | int no_mods = 0; |
2d21ac55 | 724 | struct timeval now; |
55e303ae A |
725 | /* |
726 | * Wait until system bootup is done (3 min). | |
2d21ac55 A |
727 | * And don't relocate a file that's been modified |
728 | * within the past minute -- this can lead to | |
729 | * system thrashing. | |
55e303ae | 730 | */ |
b0d623f7 A |
731 | |
732 | if (!past_bootup) { | |
733 | microuptime(&tv); | |
734 | if (tv.tv_sec > (60*3)) { | |
735 | past_bootup = 1; | |
736 | } | |
737 | } | |
738 | ||
2d21ac55 | 739 | microtime(&now); |
b0d623f7 A |
740 | if ((now.tv_sec - cp->c_mtime) > 60) { |
741 | no_mods = 1; | |
742 | } | |
743 | ||
744 | if (past_bootup && no_mods) { | |
745 | (void) hfs_relocate(vp, hfsmp->nextAllocation + 4096, | |
746 | vfs_context_ucred(ap->a_context), | |
747 | vfs_context_proc(ap->a_context)); | |
55e303ae | 748 | } |
55e303ae | 749 | } |
ebb1b9f4 | 750 | |
b0d623f7 | 751 | hfs_unlock(cp); |
55e303ae | 752 | |
9bccf70c A |
753 | return (0); |
754 | } | |
755 | ||
9bccf70c | 756 | |
91447636 A |
757 | /* |
758 | * Close a file/directory. | |
759 | */ | |
6d2010ae | 760 | int |
91447636 A |
761 | hfs_vnop_close(ap) |
762 | struct vnop_close_args /* { | |
9bccf70c A |
763 | struct vnode *a_vp; |
764 | int a_fflag; | |
91447636 | 765 | vfs_context_t a_context; |
9bccf70c A |
766 | } */ *ap; |
767 | { | |
768 | register struct vnode *vp = ap->a_vp; | |
91447636 A |
769 | register struct cnode *cp; |
770 | struct proc *p = vfs_context_proc(ap->a_context); | |
771 | struct hfsmount *hfsmp; | |
772 | int busy; | |
cf7d32b8 | 773 | int tooktrunclock = 0; |
b0d623f7 | 774 | int knownrefs = 0; |
55e303ae | 775 | |
39236c6e | 776 | if ( hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) |
9bccf70c | 777 | return (0); |
91447636 A |
778 | cp = VTOC(vp); |
779 | hfsmp = VTOHFS(vp); | |
9bccf70c | 780 | |
b0d623f7 A |
781 | /* |
782 | * If the rsrc fork is a named stream, it can cause the data fork to | |
783 | * stay around, preventing de-allocation of these blocks. | |
784 | * Do checks for truncation on close. Purge extra extents if they exist. | |
785 | * Make sure the vp is not a directory, and that it has a resource fork, | |
786 | * and that resource fork is also a named stream. | |
cf7d32b8 | 787 | */ |
b0d623f7 | 788 | |
cf7d32b8 A |
789 | if ((vp->v_type == VREG) && (cp->c_rsrc_vp) |
790 | && (vnode_isnamedstream(cp->c_rsrc_vp))) { | |
791 | uint32_t blks; | |
792 | ||
793 | blks = howmany(VTOF(vp)->ff_size, VTOVCB(vp)->blockSize); | |
794 | /* | |
b0d623f7 A |
795 | * If there are extra blocks and there are only 2 refs on |
796 | * this vp (ourselves + rsrc fork holding ref on us), go ahead | |
797 | * and try to truncate. | |
cf7d32b8 A |
798 | */ |
799 | if ((blks < VTOF(vp)->ff_blocks) && (!vnode_isinuse(vp, 2))) { | |
b0d623f7 A |
800 | // release cnode lock; must acquire truncate lock BEFORE cnode lock |
801 | hfs_unlock(cp); | |
cf7d32b8 | 802 | |
39236c6e | 803 | hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
cf7d32b8 | 804 | tooktrunclock = 1; |
b0d623f7 | 805 | |
39236c6e A |
806 | if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) { |
807 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); | |
b0d623f7 A |
808 | // bail out if we can't re-acquire cnode lock |
809 | return 0; | |
cf7d32b8 | 810 | } |
b0d623f7 | 811 | // now re-test to make sure it's still valid |
cf7d32b8 A |
812 | if (cp->c_rsrc_vp) { |
813 | knownrefs = 1 + vnode_isnamedstream(cp->c_rsrc_vp); | |
b0d623f7 A |
814 | if (!vnode_isinuse(vp, knownrefs)){ |
815 | // now we can truncate the file, if necessary | |
cf7d32b8 | 816 | blks = howmany(VTOF(vp)->ff_size, VTOVCB(vp)->blockSize); |
b0d623f7 | 817 | if (blks < VTOF(vp)->ff_blocks){ |
fe8ab488 A |
818 | (void) hfs_truncate(vp, VTOF(vp)->ff_size, IO_NDELAY, |
819 | 0, ap->a_context); | |
cf7d32b8 A |
820 | } |
821 | } | |
822 | } | |
823 | } | |
824 | } | |
825 | ||
b0d623f7 | 826 | |
91447636 | 827 | // if we froze the fs and we're exiting, then "thaw" the fs |
fe8ab488 A |
828 | if (hfsmp->hfs_freeze_state == HFS_FROZEN |
829 | && hfsmp->hfs_freezing_proc == p && proc_exiting(p)) { | |
830 | hfs_thaw(hfsmp, p); | |
91447636 | 831 | } |
9bccf70c | 832 | |
91447636 | 833 | busy = vnode_isinuse(vp, 1); |
9bccf70c | 834 | |
91447636 A |
835 | if (busy) { |
836 | hfs_touchtimes(VTOHFS(vp), cp); | |
837 | } | |
838 | if (vnode_isdir(vp)) { | |
839 | hfs_reldirhints(cp, busy); | |
840 | } else if (vnode_issystem(vp) && !busy) { | |
841 | vnode_recycle(vp); | |
9bccf70c | 842 | } |
b0d623f7 A |
843 | |
844 | if (tooktrunclock){ | |
39236c6e | 845 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
cf7d32b8 | 846 | } |
91447636 | 847 | hfs_unlock(cp); |
e2fac8b1 A |
848 | |
849 | if (ap->a_fflag & FWASWRITTEN) { | |
850 | hfs_sync_ejectable(hfsmp); | |
851 | } | |
852 | ||
9bccf70c A |
853 | return (0); |
854 | } | |
855 | ||
fe8ab488 A |
856 | static bool hfs_should_generate_document_id(hfsmount_t *hfsmp, cnode_t *cp) |
857 | { | |
858 | return (!ISSET(hfsmp->hfs_flags, HFS_READ_ONLY) | |
859 | && ISSET(cp->c_bsdflags, UF_TRACKED) | |
860 | && cp->c_desc.cd_cnid != kHFSRootFolderID | |
861 | && (S_ISDIR(cp->c_mode) || S_ISREG(cp->c_mode) || S_ISLNK(cp->c_mode))); | |
862 | } | |
863 | ||
9bccf70c | 864 | /* |
91447636 A |
865 | * Get basic attributes. |
866 | */ | |
6d2010ae | 867 | int |
91447636 | 868 | hfs_vnop_getattr(struct vnop_getattr_args *ap) |
9bccf70c | 869 | { |
2d21ac55 A |
870 | #define VNODE_ATTR_TIMES \ |
871 | (VNODE_ATTR_va_access_time|VNODE_ATTR_va_change_time|VNODE_ATTR_va_modify_time) | |
872 | #define VNODE_ATTR_AUTH \ | |
873 | (VNODE_ATTR_va_mode | VNODE_ATTR_va_uid | VNODE_ATTR_va_gid | \ | |
874 | VNODE_ATTR_va_flags | VNODE_ATTR_va_acl) | |
875 | ||
9bccf70c | 876 | struct vnode *vp = ap->a_vp; |
91447636 | 877 | struct vnode_attr *vap = ap->a_vap; |
935ed37a | 878 | struct vnode *rvp = NULLVP; |
91447636 A |
879 | struct hfsmount *hfsmp; |
880 | struct cnode *cp; | |
2d21ac55 | 881 | uint64_t data_size; |
91447636 A |
882 | enum vtype v_type; |
883 | int error = 0; | |
91447636 | 884 | cp = VTOC(vp); |
2d21ac55 | 885 | |
b0d623f7 A |
886 | #if HFS_COMPRESSION |
887 | /* we need to inspect the decmpfs state of the file before we take the hfs cnode lock */ | |
888 | int compressed = 0; | |
889 | int hide_size = 0; | |
890 | off_t uncompressed_size = -1; | |
891 | if (VATTR_IS_ACTIVE(vap, va_data_size) || VATTR_IS_ACTIVE(vap, va_total_alloc) || VATTR_IS_ACTIVE(vap, va_data_alloc) || VATTR_IS_ACTIVE(vap, va_total_size)) { | |
892 | /* we only care about whether the file is compressed if asked for the uncompressed size */ | |
893 | if (VNODE_IS_RSRC(vp)) { | |
894 | /* if it's a resource fork, decmpfs may want us to hide the size */ | |
895 | hide_size = hfs_hides_rsrc(ap->a_context, cp, 0); | |
896 | } else { | |
897 | /* if it's a data fork, we need to know if it was compressed so we can report the uncompressed size */ | |
898 | compressed = hfs_file_is_compressed(cp, 0); | |
899 | } | |
6d2010ae A |
900 | if ((VATTR_IS_ACTIVE(vap, va_data_size) || VATTR_IS_ACTIVE(vap, va_total_size))) { |
901 | // if it's compressed | |
902 | if (compressed || (!VNODE_IS_RSRC(vp) && cp->c_decmp && cp->c_decmp->cmp_type >= CMP_MAX)) { | |
903 | if (0 != hfs_uncompressed_size_of_compressed_file(NULL, vp, 0, &uncompressed_size, 0)) { | |
904 | /* failed to get the uncompressed size, we'll check for this later */ | |
905 | uncompressed_size = -1; | |
906 | } else { | |
907 | // fake that it's compressed | |
908 | compressed = 1; | |
909 | } | |
b0d623f7 A |
910 | } |
911 | } | |
912 | } | |
913 | #endif | |
914 | ||
2d21ac55 A |
915 | /* |
916 | * Shortcut for vnode_authorize path. Each of the attributes | |
917 | * in this set is updated atomically so we don't need to take | |
918 | * the cnode lock to access them. | |
919 | */ | |
920 | if ((vap->va_active & ~VNODE_ATTR_AUTH) == 0) { | |
921 | /* Make sure file still exists. */ | |
922 | if (cp->c_flag & C_NOEXISTS) | |
923 | return (ENOENT); | |
924 | ||
925 | vap->va_uid = cp->c_uid; | |
926 | vap->va_gid = cp->c_gid; | |
927 | vap->va_mode = cp->c_mode; | |
316670eb | 928 | vap->va_flags = cp->c_bsdflags; |
2d21ac55 A |
929 | vap->va_supported |= VNODE_ATTR_AUTH & ~VNODE_ATTR_va_acl; |
930 | ||
931 | if ((cp->c_attr.ca_recflags & kHFSHasSecurityMask) == 0) { | |
932 | vap->va_acl = (kauth_acl_t) KAUTH_FILESEC_NONE; | |
933 | VATTR_SET_SUPPORTED(vap, va_acl); | |
934 | } | |
b0d623f7 | 935 | |
2d21ac55 A |
936 | return (0); |
937 | } | |
b0d623f7 | 938 | |
91447636 | 939 | hfsmp = VTOHFS(vp); |
91447636 | 940 | v_type = vnode_vtype(vp); |
fe8ab488 A |
941 | |
942 | if (VATTR_IS_ACTIVE(vap, va_document_id)) { | |
943 | uint32_t document_id; | |
944 | ||
945 | if (cp->c_desc.cd_cnid == kHFSRootFolderID) | |
946 | document_id = kHFSRootFolderID; | |
947 | else { | |
948 | /* | |
949 | * This is safe without a lock because we're just reading | |
950 | * a 32 bit aligned integer which should be atomic on all | |
951 | * platforms we support. | |
952 | */ | |
953 | document_id = hfs_get_document_id(cp); | |
954 | ||
955 | if (!document_id && hfs_should_generate_document_id(hfsmp, cp)) { | |
956 | uint32_t new_document_id; | |
957 | ||
958 | error = hfs_generate_document_id(hfsmp, &new_document_id); | |
959 | if (error) | |
960 | return error; | |
961 | ||
962 | error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
963 | if (error) | |
964 | return error; | |
965 | ||
966 | bool want_docid_fsevent = false; | |
967 | ||
968 | // Need to check again now that we have the lock | |
969 | document_id = hfs_get_document_id(cp); | |
970 | if (!document_id && hfs_should_generate_document_id(hfsmp, cp)) { | |
971 | cp->c_attr.ca_finderextendeddirinfo.document_id = document_id = new_document_id; | |
972 | want_docid_fsevent = true; | |
973 | SET(cp->c_flag, C_MODIFIED); | |
974 | } | |
975 | ||
976 | hfs_unlock(cp); | |
977 | ||
978 | if (want_docid_fsevent) { | |
979 | #if CONFIG_FSE | |
980 | add_fsevent(FSE_DOCID_CHANGED, ap->a_context, | |
981 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
982 | FSE_ARG_INO, (ino64_t)0, // src inode # | |
983 | FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # | |
984 | FSE_ARG_INT32, document_id, | |
985 | FSE_ARG_DONE); | |
986 | ||
987 | if (need_fsevent(FSE_STAT_CHANGED, vp)) { | |
988 | add_fsevent(FSE_STAT_CHANGED, ap->a_context, | |
989 | FSE_ARG_VNODE, vp, FSE_ARG_DONE); | |
990 | } | |
991 | #endif | |
992 | } | |
993 | } | |
994 | } | |
995 | ||
996 | vap->va_document_id = document_id; | |
997 | VATTR_SET_SUPPORTED(vap, va_document_id); | |
998 | } | |
999 | ||
2d21ac55 A |
1000 | /* |
1001 | * If time attributes are requested and we have cnode times | |
1002 | * that require updating, then acquire an exclusive lock on | |
1003 | * the cnode before updating the times. Otherwise we can | |
1004 | * just acquire a shared lock. | |
1005 | */ | |
1006 | if ((vap->va_active & VNODE_ATTR_TIMES) && | |
1007 | (cp->c_touch_acctime || cp->c_touch_chgtime || cp->c_touch_modtime)) { | |
39236c6e | 1008 | if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) |
2d21ac55 A |
1009 | return (error); |
1010 | hfs_touchtimes(hfsmp, cp); | |
b0d623f7 A |
1011 | } |
1012 | else { | |
39236c6e | 1013 | if ((error = hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) |
2d21ac55 A |
1014 | return (error); |
1015 | } | |
1016 | ||
91447636 | 1017 | if (v_type == VDIR) { |
2d21ac55 A |
1018 | data_size = (cp->c_entries + 2) * AVERAGE_HFSDIRENTRY_SIZE; |
1019 | ||
91447636 | 1020 | if (VATTR_IS_ACTIVE(vap, va_nlink)) { |
2d21ac55 | 1021 | int nlink; |
91447636 | 1022 | |
2d21ac55 A |
1023 | /* |
1024 | * For directories, the va_nlink is esentially a count | |
1025 | * of the ".." references to a directory plus the "." | |
1026 | * reference and the directory itself. So for HFS+ this | |
1027 | * becomes the sub-directory count plus two. | |
1028 | * | |
1029 | * In the absence of a sub-directory count we use the | |
1030 | * directory's item count. This will be too high in | |
1031 | * most cases since it also includes files. | |
1032 | */ | |
1033 | if ((hfsmp->hfs_flags & HFS_FOLDERCOUNT) && | |
1034 | (cp->c_attr.ca_recflags & kHFSHasFolderCountMask)) | |
1035 | nlink = cp->c_attr.ca_dircount; /* implied ".." entries */ | |
1036 | else | |
1037 | nlink = cp->c_entries; | |
1038 | ||
1039 | /* Account for ourself and our "." entry */ | |
1040 | nlink += 2; | |
1041 | /* Hide our private directories. */ | |
1042 | if (cp->c_cnid == kHFSRootFolderID) { | |
1043 | if (hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid != 0) { | |
1044 | --nlink; | |
1045 | } | |
1046 | if (hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid != 0) { | |
1047 | --nlink; | |
1048 | } | |
91447636 | 1049 | } |
2d21ac55 A |
1050 | VATTR_RETURN(vap, va_nlink, (u_int64_t)nlink); |
1051 | } | |
91447636 A |
1052 | if (VATTR_IS_ACTIVE(vap, va_nchildren)) { |
1053 | int entries; | |
1054 | ||
1055 | entries = cp->c_entries; | |
2d21ac55 A |
1056 | /* Hide our private files and directories. */ |
1057 | if (cp->c_cnid == kHFSRootFolderID) { | |
1058 | if (hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid != 0) | |
1059 | --entries; | |
1060 | if (hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid != 0) | |
1061 | --entries; | |
1062 | if (hfsmp->jnl || ((hfsmp->vcbAtrb & kHFSVolumeJournaledMask) && (hfsmp->hfs_flags & HFS_READ_ONLY))) | |
1063 | entries -= 2; /* hide the journal files */ | |
91447636 A |
1064 | } |
1065 | VATTR_RETURN(vap, va_nchildren, entries); | |
1066 | } | |
2d21ac55 A |
1067 | /* |
1068 | * The va_dirlinkcount is the count of real directory hard links. | |
1069 | * (i.e. its not the sum of the implied "." and ".." references) | |
1070 | */ | |
1071 | if (VATTR_IS_ACTIVE(vap, va_dirlinkcount)) { | |
1072 | VATTR_RETURN(vap, va_dirlinkcount, (uint32_t)cp->c_linkcount); | |
1073 | } | |
1074 | } else /* !VDIR */ { | |
1075 | data_size = VCTOF(vp, cp)->ff_size; | |
1076 | ||
1077 | VATTR_RETURN(vap, va_nlink, (u_int64_t)cp->c_linkcount); | |
1078 | if (VATTR_IS_ACTIVE(vap, va_data_alloc)) { | |
1079 | u_int64_t blocks; | |
1080 | ||
b0d623f7 A |
1081 | #if HFS_COMPRESSION |
1082 | if (hide_size) { | |
1083 | VATTR_RETURN(vap, va_data_alloc, 0); | |
1084 | } else if (compressed) { | |
1085 | /* for compressed files, we report all allocated blocks as belonging to the data fork */ | |
1086 | blocks = cp->c_blocks; | |
1087 | VATTR_RETURN(vap, va_data_alloc, blocks * (u_int64_t)hfsmp->blockSize); | |
1088 | } | |
1089 | else | |
1090 | #endif | |
1091 | { | |
1092 | blocks = VCTOF(vp, cp)->ff_blocks; | |
1093 | VATTR_RETURN(vap, va_data_alloc, blocks * (u_int64_t)hfsmp->blockSize); | |
1094 | } | |
2d21ac55 | 1095 | } |
91447636 | 1096 | } |
9bccf70c | 1097 | |
91447636 A |
1098 | /* conditional because 64-bit arithmetic can be expensive */ |
1099 | if (VATTR_IS_ACTIVE(vap, va_total_size)) { | |
1100 | if (v_type == VDIR) { | |
2d21ac55 | 1101 | VATTR_RETURN(vap, va_total_size, (cp->c_entries + 2) * AVERAGE_HFSDIRENTRY_SIZE); |
91447636 | 1102 | } else { |
b0d623f7 | 1103 | u_int64_t total_size = ~0ULL; |
91447636 | 1104 | struct cnode *rcp; |
b0d623f7 A |
1105 | #if HFS_COMPRESSION |
1106 | if (hide_size) { | |
1107 | /* we're hiding the size of this file, so just return 0 */ | |
1108 | total_size = 0; | |
1109 | } else if (compressed) { | |
1110 | if (uncompressed_size == -1) { | |
1111 | /* | |
1112 | * We failed to get the uncompressed size above, | |
1113 | * so we'll fall back to the standard path below | |
1114 | * since total_size is still -1 | |
1115 | */ | |
1116 | } else { | |
1117 | /* use the uncompressed size we fetched above */ | |
1118 | total_size = uncompressed_size; | |
1119 | } | |
91447636 | 1120 | } |
b0d623f7 A |
1121 | #endif |
1122 | if (total_size == ~0ULL) { | |
1123 | if (cp->c_datafork) { | |
1124 | total_size = cp->c_datafork->ff_size; | |
91447636 | 1125 | } |
b0d623f7 A |
1126 | |
1127 | if (cp->c_blocks - VTOF(vp)->ff_blocks) { | |
1128 | /* We deal with rsrc fork vnode iocount at the end of the function */ | |
fe8ab488 | 1129 | error = hfs_vgetrsrc(hfsmp, vp, &rvp); |
b0d623f7 | 1130 | if (error) { |
6d2010ae A |
1131 | /* |
1132 | * Note that we call hfs_vgetrsrc with error_on_unlinked | |
1133 | * set to FALSE. This is because we may be invoked via | |
1134 | * fstat() on an open-unlinked file descriptor and we must | |
1135 | * continue to support access to the rsrc fork until it disappears. | |
1136 | * The code at the end of this function will be | |
1137 | * responsible for releasing the iocount generated by | |
1138 | * hfs_vgetrsrc. This is because we can't drop the iocount | |
1139 | * without unlocking the cnode first. | |
b7266188 | 1140 | */ |
b0d623f7 A |
1141 | goto out; |
1142 | } | |
1143 | ||
1144 | rcp = VTOC(rvp); | |
1145 | if (rcp && rcp->c_rsrcfork) { | |
1146 | total_size += rcp->c_rsrcfork->ff_size; | |
1147 | } | |
91447636 A |
1148 | } |
1149 | } | |
b0d623f7 | 1150 | |
91447636 | 1151 | VATTR_RETURN(vap, va_total_size, total_size); |
91447636 | 1152 | } |
9bccf70c | 1153 | } |
91447636 A |
1154 | if (VATTR_IS_ACTIVE(vap, va_total_alloc)) { |
1155 | if (v_type == VDIR) { | |
1156 | VATTR_RETURN(vap, va_total_alloc, 0); | |
1157 | } else { | |
2d21ac55 | 1158 | VATTR_RETURN(vap, va_total_alloc, (u_int64_t)cp->c_blocks * (u_int64_t)hfsmp->blockSize); |
91447636 | 1159 | } |
9bccf70c A |
1160 | } |
1161 | ||
9bccf70c | 1162 | /* |
91447636 A |
1163 | * If the VFS wants extended security data, and we know that we |
1164 | * don't have any (because it never told us it was setting any) | |
1165 | * then we can return the supported bit and no data. If we do | |
1166 | * have extended security, we can just leave the bit alone and | |
1167 | * the VFS will use the fallback path to fetch it. | |
9bccf70c | 1168 | */ |
91447636 A |
1169 | if (VATTR_IS_ACTIVE(vap, va_acl)) { |
1170 | if ((cp->c_attr.ca_recflags & kHFSHasSecurityMask) == 0) { | |
2d21ac55 | 1171 | vap->va_acl = (kauth_acl_t) KAUTH_FILESEC_NONE; |
91447636 A |
1172 | VATTR_SET_SUPPORTED(vap, va_acl); |
1173 | } | |
55e303ae | 1174 | } |
91447636 | 1175 | if (VATTR_IS_ACTIVE(vap, va_access_time)) { |
2d21ac55 | 1176 | /* Access times are lazily updated, get current time if needed */ |
91447636 A |
1177 | if (cp->c_touch_acctime) { |
1178 | struct timeval tv; | |
1179 | ||
1180 | microtime(&tv); | |
1181 | vap->va_access_time.tv_sec = tv.tv_sec; | |
1182 | } else { | |
1183 | vap->va_access_time.tv_sec = cp->c_atime; | |
1184 | } | |
1185 | vap->va_access_time.tv_nsec = 0; | |
1186 | VATTR_SET_SUPPORTED(vap, va_access_time); | |
1187 | } | |
2d21ac55 A |
1188 | vap->va_create_time.tv_sec = cp->c_itime; |
1189 | vap->va_create_time.tv_nsec = 0; | |
91447636 A |
1190 | vap->va_modify_time.tv_sec = cp->c_mtime; |
1191 | vap->va_modify_time.tv_nsec = 0; | |
91447636 A |
1192 | vap->va_change_time.tv_sec = cp->c_ctime; |
1193 | vap->va_change_time.tv_nsec = 0; | |
91447636 | 1194 | vap->va_backup_time.tv_sec = cp->c_btime; |
2d21ac55 A |
1195 | vap->va_backup_time.tv_nsec = 0; |
1196 | ||
6d2010ae A |
1197 | /* See if we need to emit the date added field to the user */ |
1198 | if (VATTR_IS_ACTIVE(vap, va_addedtime)) { | |
1199 | u_int32_t dateadded = hfs_get_dateadded (cp); | |
1200 | if (dateadded) { | |
1201 | vap->va_addedtime.tv_sec = dateadded; | |
1202 | vap->va_addedtime.tv_nsec = 0; | |
1203 | VATTR_SET_SUPPORTED (vap, va_addedtime); | |
1204 | } | |
1205 | } | |
1206 | ||
2d21ac55 A |
1207 | /* XXX is this really a good 'optimal I/O size'? */ |
1208 | vap->va_iosize = hfsmp->hfs_logBlockSize; | |
1209 | vap->va_uid = cp->c_uid; | |
1210 | vap->va_gid = cp->c_gid; | |
1211 | vap->va_mode = cp->c_mode; | |
316670eb | 1212 | vap->va_flags = cp->c_bsdflags; |
91447636 | 1213 | |
9bccf70c A |
1214 | /* |
1215 | * Exporting file IDs from HFS Plus: | |
1216 | * | |
1217 | * For "normal" files the c_fileid is the same value as the | |
1218 | * c_cnid. But for hard link files, they are different - the | |
1219 | * c_cnid belongs to the active directory entry (ie the link) | |
1220 | * and the c_fileid is for the actual inode (ie the data file). | |
1221 | * | |
91447636 A |
1222 | * The stat call (getattr) uses va_fileid and the Carbon APIs, |
1223 | * which are hardlink-ignorant, will ask for va_linkid. | |
9bccf70c | 1224 | */ |
2d21ac55 | 1225 | vap->va_fileid = (u_int64_t)cp->c_fileid; |
935ed37a A |
1226 | /* |
1227 | * We need to use the origin cache for both hardlinked files | |
1228 | * and directories. Hardlinked directories have multiple cnids | |
1229 | * and parents (one per link). Hardlinked files also have their | |
1230 | * own parents and link IDs separate from the indirect inode number. | |
1231 | * If we don't use the cache, we could end up vending the wrong ID | |
1232 | * because the cnode will only reflect the link that was looked up most recently. | |
1233 | */ | |
1234 | if (cp->c_flag & C_HARDLINK) { | |
2d21ac55 A |
1235 | vap->va_linkid = (u_int64_t)hfs_currentcnid(cp); |
1236 | vap->va_parentid = (u_int64_t)hfs_currentparent(cp); | |
1237 | } else { | |
1238 | vap->va_linkid = (u_int64_t)cp->c_cnid; | |
1239 | vap->va_parentid = (u_int64_t)cp->c_parentcnid; | |
1240 | } | |
b0d623f7 | 1241 | vap->va_fsid = hfsmp->hfs_raw_dev; |
2d21ac55 A |
1242 | vap->va_filerev = 0; |
1243 | vap->va_encoding = cp->c_encoding; | |
1244 | vap->va_rdev = (v_type == VBLK || v_type == VCHR) ? cp->c_rdev : 0; | |
b0d623f7 A |
1245 | #if HFS_COMPRESSION |
1246 | if (VATTR_IS_ACTIVE(vap, va_data_size)) { | |
1247 | if (hide_size) | |
1248 | vap->va_data_size = 0; | |
1249 | else if (compressed) { | |
1250 | if (uncompressed_size == -1) { | |
1251 | /* failed to get the uncompressed size above, so just return data_size */ | |
1252 | vap->va_data_size = data_size; | |
1253 | } else { | |
1254 | /* use the uncompressed size we fetched above */ | |
1255 | vap->va_data_size = uncompressed_size; | |
1256 | } | |
1257 | } else | |
1258 | vap->va_data_size = data_size; | |
1259 | // vap->va_supported |= VNODE_ATTR_va_data_size; | |
1260 | VATTR_SET_SUPPORTED(vap, va_data_size); | |
1261 | } | |
1262 | #else | |
2d21ac55 | 1263 | vap->va_data_size = data_size; |
b0d623f7 A |
1264 | vap->va_supported |= VNODE_ATTR_va_data_size; |
1265 | #endif | |
22ba694c | 1266 | |
fe8ab488 A |
1267 | #if CONFIG_PROTECT |
1268 | if (VATTR_IS_ACTIVE(vap, va_dataprotect_class)) { | |
1269 | vap->va_dataprotect_class = cp->c_cpentry ? cp->c_cpentry->cp_pclass : 0; | |
1270 | VATTR_SET_SUPPORTED(vap, va_dataprotect_class); | |
1271 | } | |
1272 | #endif | |
1273 | if (VATTR_IS_ACTIVE(vap, va_write_gencount)) { | |
1274 | if (ubc_is_mapped_writable(vp)) { | |
1275 | /* | |
1276 | * Return 0 to the caller to indicate the file may be | |
1277 | * changing. There is no need for us to increment the | |
1278 | * generation counter here because it gets done as part of | |
1279 | * page-out and also when the file is unmapped (to account | |
1280 | * for changes we might not have seen). | |
1281 | */ | |
1282 | vap->va_write_gencount = 0; | |
22ba694c | 1283 | } else { |
fe8ab488 | 1284 | vap->va_write_gencount = hfs_get_gencount(cp); |
22ba694c | 1285 | } |
fe8ab488 A |
1286 | |
1287 | VATTR_SET_SUPPORTED(vap, va_write_gencount); | |
22ba694c A |
1288 | } |
1289 | ||
2d21ac55 A |
1290 | /* Mark them all at once instead of individual VATTR_SET_SUPPORTED calls. */ |
1291 | vap->va_supported |= VNODE_ATTR_va_create_time | VNODE_ATTR_va_modify_time | | |
1292 | VNODE_ATTR_va_change_time| VNODE_ATTR_va_backup_time | | |
1293 | VNODE_ATTR_va_iosize | VNODE_ATTR_va_uid | | |
1294 | VNODE_ATTR_va_gid | VNODE_ATTR_va_mode | | |
1295 | VNODE_ATTR_va_flags |VNODE_ATTR_va_fileid | | |
1296 | VNODE_ATTR_va_linkid | VNODE_ATTR_va_parentid | | |
1297 | VNODE_ATTR_va_fsid | VNODE_ATTR_va_filerev | | |
b0d623f7 | 1298 | VNODE_ATTR_va_encoding | VNODE_ATTR_va_rdev; |
91447636 | 1299 | |
b0d623f7 A |
1300 | /* If this is the root, let VFS to find out the mount name, which |
1301 | * may be different from the real name. Otherwise, we need to take care | |
1302 | * for hardlinked files, which need to be looked up, if necessary | |
935ed37a | 1303 | */ |
2d21ac55 | 1304 | if (VATTR_IS_ACTIVE(vap, va_name) && (cp->c_cnid != kHFSRootFolderID)) { |
935ed37a A |
1305 | struct cat_desc linkdesc; |
1306 | int lockflags; | |
1307 | int uselinkdesc = 0; | |
1308 | cnid_t nextlinkid = 0; | |
b0d623f7 | 1309 | cnid_t prevlinkid = 0; |
935ed37a A |
1310 | |
1311 | /* Get the name for ATTR_CMN_NAME. We need to take special care for hardlinks | |
1312 | * here because the info. for the link ID requested by getattrlist may be | |
1313 | * different than what's currently in the cnode. This is because the cnode | |
1314 | * will be filled in with the information for the most recent link ID that went | |
1315 | * through namei/lookup(). If there are competing lookups for hardlinks that point | |
b0d623f7 A |
1316 | * to the same inode, one (or more) getattrlists could be vended incorrect name information. |
1317 | * Also, we need to beware of open-unlinked files which could have a namelen of 0. | |
935ed37a | 1318 | */ |
b0d623f7 A |
1319 | |
1320 | if ((cp->c_flag & C_HARDLINK) && | |
935ed37a | 1321 | ((cp->c_desc.cd_namelen == 0) || (vap->va_linkid != cp->c_cnid))) { |
39236c6e A |
1322 | /* |
1323 | * If we have no name and our link ID is the raw inode number, then we may | |
b0d623f7 | 1324 | * have an open-unlinked file. Go to the next link in this case. |
935ed37a A |
1325 | */ |
1326 | if ((cp->c_desc.cd_namelen == 0) && (vap->va_linkid == cp->c_fileid)) { | |
6d2010ae | 1327 | if ((error = hfs_lookup_siblinglinks(hfsmp, vap->va_linkid, &prevlinkid, &nextlinkid))){ |
935ed37a A |
1328 | goto out; |
1329 | } | |
b0d623f7 | 1330 | } |
935ed37a | 1331 | else { |
b0d623f7 | 1332 | /* just use link obtained from vap above */ |
935ed37a | 1333 | nextlinkid = vap->va_linkid; |
2d21ac55 | 1334 | } |
b0d623f7 A |
1335 | |
1336 | /* We need to probe the catalog for the descriptor corresponding to the link ID | |
1337 | * stored in nextlinkid. Note that we don't know if we have the exclusive lock | |
1338 | * for the cnode here, so we can't just update the descriptor. Instead, | |
1339 | * we should just store the descriptor's value locally and then use it to pass | |
1340 | * out the name value as needed below. | |
1341 | */ | |
1342 | if (nextlinkid){ | |
935ed37a | 1343 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); |
b0d623f7 A |
1344 | error = cat_findname(hfsmp, nextlinkid, &linkdesc); |
1345 | hfs_systemfile_unlock(hfsmp, lockflags); | |
935ed37a A |
1346 | if (error == 0) { |
1347 | uselinkdesc = 1; | |
1348 | } | |
2d21ac55 | 1349 | } |
935ed37a | 1350 | } |
b0d623f7 A |
1351 | |
1352 | /* By this point, we've either patched up the name above and the c_desc | |
1353 | * points to the correct data, or it already did, in which case we just proceed | |
1354 | * by copying the name into the vap. Note that we will never set va_name to | |
1355 | * supported if nextlinkid is never initialized. This could happen in the degenerate | |
1356 | * case above involving the raw inode number, where it has no nextlinkid. In this case | |
1357 | * we will simply not mark the name bit as supported. | |
935ed37a A |
1358 | */ |
1359 | if (uselinkdesc) { | |
b0d623f7 | 1360 | strlcpy(vap->va_name, (const char*) linkdesc.cd_nameptr, MAXPATHLEN); |
935ed37a | 1361 | VATTR_SET_SUPPORTED(vap, va_name); |
b0d623f7 A |
1362 | cat_releasedesc(&linkdesc); |
1363 | } | |
935ed37a | 1364 | else if (cp->c_desc.cd_namelen) { |
b0d623f7 | 1365 | strlcpy(vap->va_name, (const char*) cp->c_desc.cd_nameptr, MAXPATHLEN); |
935ed37a | 1366 | VATTR_SET_SUPPORTED(vap, va_name); |
91447636 | 1367 | } |
9bccf70c | 1368 | } |
9bccf70c | 1369 | |
91447636 A |
1370 | out: |
1371 | hfs_unlock(cp); | |
b0d623f7 A |
1372 | /* |
1373 | * We need to vnode_put the rsrc fork vnode only *after* we've released | |
1374 | * the cnode lock, since vnode_put can trigger an inactive call, which | |
1375 | * will go back into HFS and try to acquire a cnode lock. | |
935ed37a | 1376 | */ |
91447636 | 1377 | if (rvp) { |
b0d623f7 | 1378 | vnode_put (rvp); |
91447636 | 1379 | } |
b0d623f7 | 1380 | |
91447636 A |
1381 | return (error); |
1382 | } | |
9bccf70c | 1383 | |
6d2010ae | 1384 | int |
91447636 A |
1385 | hfs_vnop_setattr(ap) |
1386 | struct vnop_setattr_args /* { | |
9bccf70c | 1387 | struct vnode *a_vp; |
91447636 A |
1388 | struct vnode_attr *a_vap; |
1389 | vfs_context_t a_context; | |
9bccf70c A |
1390 | } */ *ap; |
1391 | { | |
91447636 | 1392 | struct vnode_attr *vap = ap->a_vap; |
9bccf70c | 1393 | struct vnode *vp = ap->a_vp; |
91447636 A |
1394 | struct cnode *cp = NULL; |
1395 | struct hfsmount *hfsmp; | |
1396 | kauth_cred_t cred = vfs_context_ucred(ap->a_context); | |
1397 | struct proc *p = vfs_context_proc(ap->a_context); | |
1398 | int error = 0; | |
1399 | uid_t nuid; | |
1400 | gid_t ngid; | |
6d2010ae | 1401 | time_t orig_ctime; |
9bccf70c | 1402 | |
6d2010ae A |
1403 | orig_ctime = VTOC(vp)->c_ctime; |
1404 | ||
b0d623f7 A |
1405 | #if HFS_COMPRESSION |
1406 | int decmpfs_reset_state = 0; | |
1407 | /* | |
1408 | we call decmpfs_update_attributes even if the file is not compressed | |
1409 | because we want to update the incoming flags if the xattrs are invalid | |
1410 | */ | |
1411 | error = decmpfs_update_attributes(vp, vap); | |
1412 | if (error) | |
1413 | return error; | |
39236c6e | 1414 | #endif |
6d2010ae A |
1415 | // |
1416 | // if this is not a size-changing setattr and it is not just | |
1417 | // an atime update, then check for a snapshot. | |
1418 | // | |
1419 | if (!VATTR_IS_ACTIVE(vap, va_data_size) && !(vap->va_active == VNODE_ATTR_va_access_time)) { | |
39236c6e | 1420 | check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_METADATA_MOD, NSPACE_REARM_NO_ARG); |
6d2010ae | 1421 | } |
6d2010ae A |
1422 | |
1423 | #if CONFIG_PROTECT | |
316670eb | 1424 | if ((error = cp_handle_vnop(vp, CP_WRITE_ACCESS, 0)) != 0) { |
6d2010ae A |
1425 | return (error); |
1426 | } | |
1427 | #endif /* CONFIG_PROTECT */ | |
1428 | ||
91447636 | 1429 | hfsmp = VTOHFS(vp); |
9bccf70c | 1430 | |
316670eb A |
1431 | /* Don't allow modification of the journal. */ |
1432 | if (hfs_is_journal_file(hfsmp, VTOC(vp))) { | |
91447636 | 1433 | return (EPERM); |
55e303ae A |
1434 | } |
1435 | ||
22ba694c A |
1436 | // |
1437 | // Check if we'll need a document_id and if so, get it before we lock the | |
1438 | // the cnode to avoid any possible deadlock with the root vnode which has | |
1439 | // to get locked to get the document id | |
1440 | // | |
1441 | u_int32_t document_id=0; | |
1442 | if (VATTR_IS_ACTIVE(vap, va_flags) && (vap->va_flags & UF_TRACKED) && !(VTOC(vp)->c_bsdflags & UF_TRACKED)) { | |
1443 | struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&(VTOC(vp)->c_attr.ca_finderinfo) + 16); | |
1444 | // | |
1445 | // If the document_id is not set, get a new one. It will be set | |
1446 | // on the file down below once we hold the cnode lock. | |
1447 | // | |
1448 | if (fip->document_id == 0) { | |
1449 | if (hfs_generate_document_id(hfsmp, &document_id) != 0) { | |
1450 | document_id = 0; | |
1451 | } | |
1452 | } | |
1453 | } | |
1454 | ||
1455 | ||
91447636 A |
1456 | /* |
1457 | * File size change request. | |
1458 | * We are guaranteed that this is not a directory, and that | |
1459 | * the filesystem object is writeable. | |
b0d623f7 A |
1460 | * |
1461 | * NOTE: HFS COMPRESSION depends on the data_size being set *before* the bsd flags are updated | |
91447636 A |
1462 | */ |
1463 | VATTR_SET_SUPPORTED(vap, va_data_size); | |
1464 | if (VATTR_IS_ACTIVE(vap, va_data_size) && !vnode_islnk(vp)) { | |
b0d623f7 A |
1465 | #if HFS_COMPRESSION |
1466 | /* keep the compressed state locked until we're done truncating the file */ | |
1467 | decmpfs_cnode *dp = VTOCMP(vp); | |
1468 | if (!dp) { | |
1469 | /* | |
1470 | * call hfs_lazy_init_decmpfs_cnode() to make sure that the decmpfs_cnode | |
1471 | * is filled in; we need a decmpfs_cnode to lock out decmpfs state changes | |
1472 | * on this file while it's truncating | |
1473 | */ | |
1474 | dp = hfs_lazy_init_decmpfs_cnode(VTOC(vp)); | |
1475 | if (!dp) { | |
1476 | /* failed to allocate a decmpfs_cnode */ | |
1477 | return ENOMEM; /* what should this be? */ | |
1478 | } | |
1479 | } | |
1480 | ||
6d2010ae A |
1481 | check_for_tracked_file(vp, orig_ctime, vap->va_data_size == 0 ? NAMESPACE_HANDLER_TRUNCATE_OP|NAMESPACE_HANDLER_DELETE_OP : NAMESPACE_HANDLER_TRUNCATE_OP, NULL); |
1482 | ||
b0d623f7 A |
1483 | decmpfs_lock_compressed_data(dp, 1); |
1484 | if (hfs_file_is_compressed(VTOC(vp), 1)) { | |
1485 | error = decmpfs_decompress_file(vp, dp, -1/*vap->va_data_size*/, 0, 1); | |
1486 | if (error != 0) { | |
1487 | decmpfs_unlock_compressed_data(dp, 1); | |
1488 | return error; | |
1489 | } | |
1490 | } | |
1491 | #endif | |
55e303ae | 1492 | |
fe8ab488 | 1493 | // Take truncate lock |
39236c6e | 1494 | hfs_lock_truncate(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
2d21ac55 | 1495 | |
fe8ab488 A |
1496 | // hfs_truncate will deal with the cnode lock |
1497 | error = hfs_truncate(vp, vap->va_data_size, vap->va_vaflags & 0xffff, | |
1498 | 0, ap->a_context); | |
55e303ae | 1499 | |
fe8ab488 | 1500 | hfs_unlock_truncate(VTOC(vp), HFS_LOCK_DEFAULT); |
b0d623f7 A |
1501 | #if HFS_COMPRESSION |
1502 | decmpfs_unlock_compressed_data(dp, 1); | |
1503 | #endif | |
91447636 | 1504 | if (error) |
fe8ab488 | 1505 | return error; |
91447636 A |
1506 | } |
1507 | if (cp == NULL) { | |
39236c6e | 1508 | if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) |
9bccf70c | 1509 | return (error); |
91447636 | 1510 | cp = VTOC(vp); |
9bccf70c A |
1511 | } |
1512 | ||
2d21ac55 A |
1513 | /* |
1514 | * If it is just an access time update request by itself | |
1515 | * we know the request is from kernel level code, and we | |
1516 | * can delay it without being as worried about consistency. | |
1517 | * This change speeds up mmaps, in the rare case that they | |
1518 | * get caught behind a sync. | |
1519 | */ | |
1520 | ||
1521 | if (vap->va_active == VNODE_ATTR_va_access_time) { | |
1522 | cp->c_touch_acctime=TRUE; | |
1523 | goto out; | |
1524 | } | |
1525 | ||
1526 | ||
1527 | ||
91447636 A |
1528 | /* |
1529 | * Owner/group change request. | |
1530 | * We are guaranteed that the new owner/group is valid and legal. | |
1531 | */ | |
1532 | VATTR_SET_SUPPORTED(vap, va_uid); | |
1533 | VATTR_SET_SUPPORTED(vap, va_gid); | |
1534 | nuid = VATTR_IS_ACTIVE(vap, va_uid) ? vap->va_uid : (uid_t)VNOVAL; | |
1535 | ngid = VATTR_IS_ACTIVE(vap, va_gid) ? vap->va_gid : (gid_t)VNOVAL; | |
1536 | if (((nuid != (uid_t)VNOVAL) || (ngid != (gid_t)VNOVAL)) && | |
1537 | ((error = hfs_chown(vp, nuid, ngid, cred, p)) != 0)) | |
1538 | goto out; | |
b4c24cb9 | 1539 | |
91447636 A |
1540 | /* |
1541 | * Mode change request. | |
1542 | * We are guaranteed that the mode value is valid and that in | |
1543 | * conjunction with the owner and group, this change is legal. | |
b0d623f7 | 1544 | */ |
91447636 A |
1545 | VATTR_SET_SUPPORTED(vap, va_mode); |
1546 | if (VATTR_IS_ACTIVE(vap, va_mode) && | |
1547 | ((error = hfs_chmod(vp, (int)vap->va_mode, cred, p)) != 0)) | |
1548 | goto out; | |
b4c24cb9 | 1549 | |
91447636 A |
1550 | /* |
1551 | * File flags change. | |
1552 | * We are guaranteed that only flags allowed to change given the | |
1553 | * current securelevel are being changed. | |
1554 | */ | |
1555 | VATTR_SET_SUPPORTED(vap, va_flags); | |
cc9f6e38 | 1556 | if (VATTR_IS_ACTIVE(vap, va_flags)) { |
2d21ac55 A |
1557 | u_int16_t *fdFlags; |
1558 | ||
b0d623f7 | 1559 | #if HFS_COMPRESSION |
316670eb | 1560 | if ((cp->c_bsdflags ^ vap->va_flags) & UF_COMPRESSED) { |
b0d623f7 A |
1561 | /* |
1562 | * the UF_COMPRESSED was toggled, so reset our cached compressed state | |
1563 | * but we don't want to actually do the update until we've released the cnode lock down below | |
1564 | * NOTE: turning the flag off doesn't actually decompress the file, so that we can | |
1565 | * turn off the flag and look at the "raw" file for debugging purposes | |
1566 | */ | |
1567 | decmpfs_reset_state = 1; | |
1568 | } | |
1569 | #endif | |
22ba694c A |
1570 | if ((vap->va_flags & UF_TRACKED) && !(cp->c_bsdflags & UF_TRACKED)) { |
1571 | struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); | |
1572 | ||
1573 | // | |
1574 | // we're marking this item UF_TRACKED. if the document_id is | |
1575 | // not set, get a new one and put it on the file. | |
1576 | // | |
1577 | if (fip->document_id == 0) { | |
1578 | if (document_id != 0) { | |
1579 | // printf("SETATTR: assigning doc-id %d to %s (ino %d)\n", document_id, vp->v_name, cp->c_desc.cd_cnid); | |
1580 | fip->document_id = (uint32_t)document_id; | |
1581 | #if CONFIG_FSE | |
1582 | add_fsevent(FSE_DOCID_CHANGED, ap->a_context, | |
1583 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
1584 | FSE_ARG_INO, (ino64_t)0, // src inode # | |
1585 | FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # | |
1586 | FSE_ARG_INT32, document_id, | |
1587 | FSE_ARG_DONE); | |
1588 | #endif | |
1589 | } else { | |
1590 | // printf("hfs: could not acquire a new document_id for %s (ino %d)\n", vp->v_name, cp->c_desc.cd_cnid); | |
1591 | } | |
1592 | } | |
1593 | ||
1594 | } else if (!(vap->va_flags & UF_TRACKED) && (cp->c_bsdflags & UF_TRACKED)) { | |
1595 | // | |
1596 | // UF_TRACKED is being cleared so clear the document_id | |
1597 | // | |
1598 | struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); | |
1599 | if (fip->document_id) { | |
1600 | // printf("SETATTR: clearing doc-id %d from %s (ino %d)\n", fip->document_id, vp->v_name, cp->c_desc.cd_cnid); | |
1601 | #if CONFIG_FSE | |
1602 | add_fsevent(FSE_DOCID_CHANGED, ap->a_context, | |
1603 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
1604 | FSE_ARG_INO, (ino64_t)cp->c_fileid, // src inode # | |
1605 | FSE_ARG_INO, (ino64_t)0, // dst inode # | |
1606 | FSE_ARG_INT32, fip->document_id, // document id | |
1607 | FSE_ARG_DONE); | |
1608 | #endif | |
1609 | fip->document_id = 0; | |
1610 | cp->c_bsdflags &= ~UF_TRACKED; | |
1611 | } | |
1612 | } | |
b0d623f7 | 1613 | |
316670eb | 1614 | cp->c_bsdflags = vap->va_flags; |
cc9f6e38 | 1615 | cp->c_touch_chgtime = TRUE; |
22ba694c | 1616 | |
2d21ac55 A |
1617 | |
1618 | /* | |
1619 | * Mirror the UF_HIDDEN flag to the invisible bit of the Finder Info. | |
1620 | * | |
1621 | * The fdFlags for files and frFlags for folders are both 8 bytes | |
1622 | * into the userInfo (the first 16 bytes of the Finder Info). They | |
1623 | * are both 16-bit fields. | |
1624 | */ | |
1625 | fdFlags = (u_int16_t *) &cp->c_finderinfo[8]; | |
1626 | if (vap->va_flags & UF_HIDDEN) | |
1627 | *fdFlags |= OSSwapHostToBigConstInt16(kFinderInvisibleMask); | |
1628 | else | |
1629 | *fdFlags &= ~OSSwapHostToBigConstInt16(kFinderInvisibleMask); | |
9bccf70c | 1630 | } |
91447636 A |
1631 | |
1632 | /* | |
1633 | * Timestamp updates. | |
1634 | */ | |
1635 | VATTR_SET_SUPPORTED(vap, va_create_time); | |
1636 | VATTR_SET_SUPPORTED(vap, va_access_time); | |
1637 | VATTR_SET_SUPPORTED(vap, va_modify_time); | |
1638 | VATTR_SET_SUPPORTED(vap, va_backup_time); | |
1639 | VATTR_SET_SUPPORTED(vap, va_change_time); | |
1640 | if (VATTR_IS_ACTIVE(vap, va_create_time) || | |
1641 | VATTR_IS_ACTIVE(vap, va_access_time) || | |
1642 | VATTR_IS_ACTIVE(vap, va_modify_time) || | |
1643 | VATTR_IS_ACTIVE(vap, va_backup_time)) { | |
91447636 A |
1644 | if (VATTR_IS_ACTIVE(vap, va_create_time)) |
1645 | cp->c_itime = vap->va_create_time.tv_sec; | |
1646 | if (VATTR_IS_ACTIVE(vap, va_access_time)) { | |
1647 | cp->c_atime = vap->va_access_time.tv_sec; | |
1648 | cp->c_touch_acctime = FALSE; | |
9bccf70c | 1649 | } |
91447636 A |
1650 | if (VATTR_IS_ACTIVE(vap, va_modify_time)) { |
1651 | cp->c_mtime = vap->va_modify_time.tv_sec; | |
1652 | cp->c_touch_modtime = FALSE; | |
1653 | cp->c_touch_chgtime = TRUE; | |
1654 | ||
fe8ab488 A |
1655 | hfs_clear_might_be_dirty_flag(cp); |
1656 | ||
9bccf70c A |
1657 | /* |
1658 | * The utimes system call can reset the modification | |
1659 | * time but it doesn't know about HFS create times. | |
91447636 | 1660 | * So we need to ensure that the creation time is |
9bccf70c A |
1661 | * always at least as old as the modification time. |
1662 | */ | |
1663 | if ((VTOVCB(vp)->vcbSigWord == kHFSPlusSigWord) && | |
91447636 A |
1664 | (cp->c_cnid != kHFSRootFolderID) && |
1665 | (cp->c_mtime < cp->c_itime)) { | |
1666 | cp->c_itime = cp->c_mtime; | |
9bccf70c A |
1667 | } |
1668 | } | |
91447636 A |
1669 | if (VATTR_IS_ACTIVE(vap, va_backup_time)) |
1670 | cp->c_btime = vap->va_backup_time.tv_sec; | |
1671 | cp->c_flag |= C_MODIFIED; | |
9bccf70c | 1672 | } |
91447636 A |
1673 | |
1674 | /* | |
1675 | * Set name encoding. | |
1676 | */ | |
1677 | VATTR_SET_SUPPORTED(vap, va_encoding); | |
1678 | if (VATTR_IS_ACTIVE(vap, va_encoding)) { | |
1679 | cp->c_encoding = vap->va_encoding; | |
1680 | hfs_setencodingbits(hfsmp, cp->c_encoding); | |
9bccf70c | 1681 | } |
91447636 | 1682 | |
91447636 | 1683 | if ((error = hfs_update(vp, TRUE)) != 0) |
2d21ac55 | 1684 | goto out; |
91447636 | 1685 | out: |
b0d623f7 A |
1686 | if (cp) { |
1687 | /* Purge origin cache for cnode, since caller now has correct link ID for it | |
1688 | * We purge it here since it was acquired for us during lookup, and we no longer need it. | |
1689 | */ | |
1690 | if ((cp->c_flag & C_HARDLINK) && (vp->v_type != VDIR)){ | |
1691 | hfs_relorigin(cp, 0); | |
1692 | } | |
1693 | ||
91447636 | 1694 | hfs_unlock(cp); |
b0d623f7 A |
1695 | #if HFS_COMPRESSION |
1696 | if (decmpfs_reset_state) { | |
1697 | /* | |
1698 | * we've changed the UF_COMPRESSED flag, so reset the decmpfs state for this cnode | |
1699 | * but don't do it while holding the hfs cnode lock | |
1700 | */ | |
1701 | decmpfs_cnode *dp = VTOCMP(vp); | |
1702 | if (!dp) { | |
1703 | /* | |
1704 | * call hfs_lazy_init_decmpfs_cnode() to make sure that the decmpfs_cnode | |
1705 | * is filled in; we need a decmpfs_cnode to prevent decmpfs state changes | |
1706 | * on this file if it's locked | |
1707 | */ | |
1708 | dp = hfs_lazy_init_decmpfs_cnode(VTOC(vp)); | |
1709 | if (!dp) { | |
1710 | /* failed to allocate a decmpfs_cnode */ | |
1711 | return ENOMEM; /* what should this be? */ | |
1712 | } | |
1713 | } | |
1714 | decmpfs_cnode_set_vnode_state(dp, FILE_TYPE_UNKNOWN, 0); | |
1715 | } | |
1716 | #endif | |
1717 | } | |
9bccf70c A |
1718 | return (error); |
1719 | } | |
1720 | ||
1721 | ||
1722 | /* | |
1723 | * Change the mode on a file. | |
1724 | * cnode must be locked before calling. | |
1725 | */ | |
1726 | int | |
2d21ac55 | 1727 | hfs_chmod(struct vnode *vp, int mode, __unused kauth_cred_t cred, __unused struct proc *p) |
9bccf70c A |
1728 | { |
1729 | register struct cnode *cp = VTOC(vp); | |
9bccf70c A |
1730 | |
1731 | if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord) | |
1732 | return (0); | |
1733 | ||
316670eb A |
1734 | // Don't allow modification of the journal or journal_info_block |
1735 | if (hfs_is_journal_file(VTOHFS(vp), cp)) { | |
1736 | return EPERM; | |
b4c24cb9 A |
1737 | } |
1738 | ||
9bccf70c | 1739 | #if OVERRIDE_UNKNOWN_PERMISSIONS |
91447636 | 1740 | if (((unsigned int)vfs_flags(VTOVFS(vp))) & MNT_UNKNOWNPERMISSIONS) { |
9bccf70c A |
1741 | return (0); |
1742 | }; | |
1743 | #endif | |
9bccf70c A |
1744 | cp->c_mode &= ~ALLPERMS; |
1745 | cp->c_mode |= (mode & ALLPERMS); | |
91447636 | 1746 | cp->c_touch_chgtime = TRUE; |
9bccf70c A |
1747 | return (0); |
1748 | } | |
1749 | ||
1750 | ||
1751 | int | |
91447636 | 1752 | hfs_write_access(struct vnode *vp, kauth_cred_t cred, struct proc *p, Boolean considerFlags) |
9bccf70c A |
1753 | { |
1754 | struct cnode *cp = VTOC(vp); | |
9bccf70c | 1755 | int retval = 0; |
91447636 | 1756 | int is_member; |
9bccf70c A |
1757 | |
1758 | /* | |
1759 | * Disallow write attempts on read-only file systems; | |
1760 | * unless the file is a socket, fifo, or a block or | |
1761 | * character device resident on the file system. | |
1762 | */ | |
91447636 | 1763 | switch (vnode_vtype(vp)) { |
9bccf70c A |
1764 | case VDIR: |
1765 | case VLNK: | |
1766 | case VREG: | |
55e303ae | 1767 | if (VTOHFS(vp)->hfs_flags & HFS_READ_ONLY) |
9bccf70c | 1768 | return (EROFS); |
55e303ae | 1769 | break; |
9bccf70c A |
1770 | default: |
1771 | break; | |
1772 | } | |
1773 | ||
1774 | /* If immutable bit set, nobody gets to write it. */ | |
316670eb | 1775 | if (considerFlags && (cp->c_bsdflags & IMMUTABLE)) |
9bccf70c A |
1776 | return (EPERM); |
1777 | ||
1778 | /* Otherwise, user id 0 always gets access. */ | |
91447636 | 1779 | if (!suser(cred, NULL)) |
9bccf70c A |
1780 | return (0); |
1781 | ||
1782 | /* Otherwise, check the owner. */ | |
1783 | if ((retval = hfs_owner_rights(VTOHFS(vp), cp->c_uid, cred, p, false)) == 0) | |
1784 | return ((cp->c_mode & S_IWUSR) == S_IWUSR ? 0 : EACCES); | |
1785 | ||
1786 | /* Otherwise, check the groups. */ | |
91447636 A |
1787 | if (kauth_cred_ismember_gid(cred, cp->c_gid, &is_member) == 0 && is_member) { |
1788 | return ((cp->c_mode & S_IWGRP) == S_IWGRP ? 0 : EACCES); | |
9bccf70c A |
1789 | } |
1790 | ||
1791 | /* Otherwise, check everyone else. */ | |
1792 | return ((cp->c_mode & S_IWOTH) == S_IWOTH ? 0 : EACCES); | |
1793 | } | |
1794 | ||
1795 | ||
9bccf70c A |
1796 | /* |
1797 | * Perform chown operation on cnode cp; | |
1798 | * code must be locked prior to call. | |
1799 | */ | |
1800 | int | |
2d21ac55 A |
1801 | #if !QUOTA |
1802 | hfs_chown(struct vnode *vp, uid_t uid, gid_t gid, __unused kauth_cred_t cred, | |
1803 | __unused struct proc *p) | |
1804 | #else | |
91447636 | 1805 | hfs_chown(struct vnode *vp, uid_t uid, gid_t gid, kauth_cred_t cred, |
2d21ac55 A |
1806 | __unused struct proc *p) |
1807 | #endif | |
9bccf70c A |
1808 | { |
1809 | register struct cnode *cp = VTOC(vp); | |
1810 | uid_t ouid; | |
1811 | gid_t ogid; | |
9bccf70c | 1812 | #if QUOTA |
2d21ac55 | 1813 | int error = 0; |
9bccf70c A |
1814 | register int i; |
1815 | int64_t change; | |
1816 | #endif /* QUOTA */ | |
1817 | ||
1818 | if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord) | |
91447636 | 1819 | return (ENOTSUP); |
9bccf70c | 1820 | |
91447636 | 1821 | if (((unsigned int)vfs_flags(VTOVFS(vp))) & MNT_UNKNOWNPERMISSIONS) |
9bccf70c A |
1822 | return (0); |
1823 | ||
1824 | if (uid == (uid_t)VNOVAL) | |
1825 | uid = cp->c_uid; | |
1826 | if (gid == (gid_t)VNOVAL) | |
1827 | gid = cp->c_gid; | |
91447636 A |
1828 | |
1829 | #if 0 /* we are guaranteed that this is already the case */ | |
9bccf70c A |
1830 | /* |
1831 | * If we don't own the file, are trying to change the owner | |
1832 | * of the file, or are not a member of the target group, | |
1833 | * the caller must be superuser or the call fails. | |
1834 | */ | |
91447636 A |
1835 | if ((kauth_cred_getuid(cred) != cp->c_uid || uid != cp->c_uid || |
1836 | (gid != cp->c_gid && | |
1837 | (kauth_cred_ismember_gid(cred, gid, &is_member) || !is_member))) && | |
1838 | (error = suser(cred, 0))) | |
9bccf70c | 1839 | return (error); |
91447636 | 1840 | #endif |
9bccf70c A |
1841 | |
1842 | ogid = cp->c_gid; | |
1843 | ouid = cp->c_uid; | |
1844 | #if QUOTA | |
1845 | if ((error = hfs_getinoquota(cp))) | |
1846 | return (error); | |
1847 | if (ouid == uid) { | |
91447636 | 1848 | dqrele(cp->c_dquot[USRQUOTA]); |
9bccf70c A |
1849 | cp->c_dquot[USRQUOTA] = NODQUOT; |
1850 | } | |
1851 | if (ogid == gid) { | |
91447636 | 1852 | dqrele(cp->c_dquot[GRPQUOTA]); |
9bccf70c A |
1853 | cp->c_dquot[GRPQUOTA] = NODQUOT; |
1854 | } | |
1855 | ||
1856 | /* | |
1857 | * Eventually need to account for (fake) a block per directory | |
91447636 A |
1858 | * if (vnode_isdir(vp)) |
1859 | * change = VTOHFS(vp)->blockSize; | |
1860 | * else | |
9bccf70c A |
1861 | */ |
1862 | ||
1863 | change = (int64_t)(cp->c_blocks) * (int64_t)VTOVCB(vp)->blockSize; | |
1864 | (void) hfs_chkdq(cp, -change, cred, CHOWN); | |
1865 | (void) hfs_chkiq(cp, -1, cred, CHOWN); | |
1866 | for (i = 0; i < MAXQUOTAS; i++) { | |
91447636 | 1867 | dqrele(cp->c_dquot[i]); |
9bccf70c A |
1868 | cp->c_dquot[i] = NODQUOT; |
1869 | } | |
1870 | #endif /* QUOTA */ | |
1871 | cp->c_gid = gid; | |
1872 | cp->c_uid = uid; | |
1873 | #if QUOTA | |
1874 | if ((error = hfs_getinoquota(cp)) == 0) { | |
1875 | if (ouid == uid) { | |
91447636 | 1876 | dqrele(cp->c_dquot[USRQUOTA]); |
9bccf70c A |
1877 | cp->c_dquot[USRQUOTA] = NODQUOT; |
1878 | } | |
1879 | if (ogid == gid) { | |
91447636 | 1880 | dqrele(cp->c_dquot[GRPQUOTA]); |
9bccf70c A |
1881 | cp->c_dquot[GRPQUOTA] = NODQUOT; |
1882 | } | |
1883 | if ((error = hfs_chkdq(cp, change, cred, CHOWN)) == 0) { | |
1884 | if ((error = hfs_chkiq(cp, 1, cred, CHOWN)) == 0) | |
1885 | goto good; | |
1886 | else | |
1887 | (void) hfs_chkdq(cp, -change, cred, CHOWN|FORCE); | |
1888 | } | |
1889 | for (i = 0; i < MAXQUOTAS; i++) { | |
91447636 | 1890 | dqrele(cp->c_dquot[i]); |
9bccf70c A |
1891 | cp->c_dquot[i] = NODQUOT; |
1892 | } | |
1893 | } | |
1894 | cp->c_gid = ogid; | |
1895 | cp->c_uid = ouid; | |
1896 | if (hfs_getinoquota(cp) == 0) { | |
1897 | if (ouid == uid) { | |
91447636 | 1898 | dqrele(cp->c_dquot[USRQUOTA]); |
9bccf70c A |
1899 | cp->c_dquot[USRQUOTA] = NODQUOT; |
1900 | } | |
1901 | if (ogid == gid) { | |
91447636 | 1902 | dqrele(cp->c_dquot[GRPQUOTA]); |
9bccf70c A |
1903 | cp->c_dquot[GRPQUOTA] = NODQUOT; |
1904 | } | |
1905 | (void) hfs_chkdq(cp, change, cred, FORCE|CHOWN); | |
1906 | (void) hfs_chkiq(cp, 1, cred, FORCE|CHOWN); | |
1907 | (void) hfs_getinoquota(cp); | |
1908 | } | |
1909 | return (error); | |
1910 | good: | |
1911 | if (hfs_getinoquota(cp)) | |
1912 | panic("hfs_chown: lost quota"); | |
1913 | #endif /* QUOTA */ | |
1914 | ||
2d21ac55 A |
1915 | |
1916 | /* | |
1917 | According to the SUSv3 Standard, chown() shall mark | |
1918 | for update the st_ctime field of the file. | |
1919 | (No exceptions mentioned) | |
1920 | */ | |
91447636 | 1921 | cp->c_touch_chgtime = TRUE; |
9bccf70c A |
1922 | return (0); |
1923 | } | |
1924 | ||
fe8ab488 A |
1925 | #if HFS_COMPRESSION |
1926 | /* | |
1927 | * Flush the resource fork if it exists. vp is the data fork and has | |
1928 | * an iocount. | |
1929 | */ | |
1930 | static int hfs_flush_rsrc(vnode_t vp, vfs_context_t ctx) | |
1931 | { | |
1932 | cnode_t *cp = VTOC(vp); | |
1933 | ||
1934 | hfs_lock(cp, HFS_SHARED_LOCK, 0); | |
1935 | ||
1936 | vnode_t rvp = cp->c_rsrc_vp; | |
1937 | ||
1938 | if (!rvp) { | |
1939 | hfs_unlock(cp); | |
1940 | return 0; | |
1941 | } | |
1942 | ||
1943 | int vid = vnode_vid(rvp); | |
1944 | ||
1945 | hfs_unlock(cp); | |
1946 | ||
1947 | int error = vnode_getwithvid(rvp, vid); | |
1948 | ||
1949 | if (error) | |
1950 | return error == ENOENT ? 0 : error; | |
1951 | ||
1952 | hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, 0); | |
1953 | hfs_lock_always(cp, HFS_EXCLUSIVE_LOCK); | |
1954 | hfs_filedone(rvp, ctx, HFS_FILE_DONE_NO_SYNC); | |
1955 | hfs_unlock(cp); | |
1956 | hfs_unlock_truncate(cp, 0); | |
1957 | ||
1958 | error = ubc_msync(rvp, 0, ubc_getsize(rvp), NULL, | |
1959 | UBC_PUSHALL | UBC_SYNC); | |
1960 | ||
1961 | vnode_put(rvp); | |
1962 | ||
1963 | return error; | |
1964 | } | |
1965 | #endif // HFS_COMPRESSION | |
9bccf70c A |
1966 | |
1967 | /* | |
39236c6e A |
1968 | * hfs_vnop_exchange: |
1969 | * | |
1970 | * Inputs: | |
1971 | * 'from' vnode/cnode | |
1972 | * 'to' vnode/cnode | |
1973 | * options flag bits | |
1974 | * vfs_context | |
1975 | * | |
1976 | * Discussion: | |
1977 | * hfs_vnop_exchange is used to service the exchangedata(2) system call. | |
1978 | * Per the requirements of that system call, this function "swaps" some | |
1979 | * of the information that lives in one catalog record for some that | |
1980 | * lives in another. Note that not everything is swapped; in particular, | |
1981 | * the extent information stored in each cnode is kept local to that | |
1982 | * cnode. This allows existing file descriptor references to continue | |
1983 | * to operate on the same content, regardless of the location in the | |
1984 | * namespace that the file may have moved to. See inline comments | |
1985 | * in the function for more information. | |
9bccf70c | 1986 | */ |
6d2010ae | 1987 | int |
91447636 A |
1988 | hfs_vnop_exchange(ap) |
1989 | struct vnop_exchange_args /* { | |
9bccf70c A |
1990 | struct vnode *a_fvp; |
1991 | struct vnode *a_tvp; | |
91447636 A |
1992 | int a_options; |
1993 | vfs_context_t a_context; | |
9bccf70c A |
1994 | } */ *ap; |
1995 | { | |
1996 | struct vnode *from_vp = ap->a_fvp; | |
1997 | struct vnode *to_vp = ap->a_tvp; | |
91447636 A |
1998 | struct cnode *from_cp; |
1999 | struct cnode *to_cp; | |
2000 | struct hfsmount *hfsmp; | |
9bccf70c A |
2001 | struct cat_desc tempdesc; |
2002 | struct cat_attr tempattr; | |
2d21ac55 A |
2003 | const unsigned char *from_nameptr; |
2004 | const unsigned char *to_nameptr; | |
2005 | char from_iname[32]; | |
2006 | char to_iname[32]; | |
db609669 A |
2007 | uint32_t to_flag_special; |
2008 | uint32_t from_flag_special; | |
2d21ac55 A |
2009 | cnid_t from_parid; |
2010 | cnid_t to_parid; | |
91447636 A |
2011 | int lockflags; |
2012 | int error = 0, started_tr = 0, got_cookie = 0; | |
2013 | cat_cookie_t cookie; | |
6d2010ae | 2014 | time_t orig_from_ctime, orig_to_ctime; |
fe8ab488 | 2015 | bool have_cnode_locks = false, have_from_trunc_lock = false, have_to_trunc_lock = false; |
9bccf70c | 2016 | |
39236c6e A |
2017 | /* |
2018 | * VFS does the following checks: | |
2019 | * 1. Validate that both are files. | |
2020 | * 2. Validate that both are on the same mount. | |
2021 | * 3. Validate that they're not the same vnode. | |
2022 | */ | |
91447636 | 2023 | |
fe8ab488 A |
2024 | from_cp = VTOC(from_vp); |
2025 | to_cp = VTOC(to_vp); | |
2026 | hfsmp = VTOHFS(from_vp); | |
6d2010ae | 2027 | |
fe8ab488 A |
2028 | orig_from_ctime = from_cp->c_ctime; |
2029 | orig_to_ctime = to_cp->c_ctime; | |
316670eb A |
2030 | |
2031 | #if CONFIG_PROTECT | |
2032 | /* | |
2033 | * Do not allow exchangedata/F_MOVEDATAEXTENTS on data-protected filesystems | |
2034 | * because the EAs will not be swapped. As a result, the persistent keys would not | |
2035 | * match and the files will be garbage. | |
2036 | */ | |
2037 | if (cp_fs_protected (vnode_mount(from_vp))) { | |
2038 | return EINVAL; | |
2039 | } | |
2040 | #endif | |
2041 | ||
b0d623f7 | 2042 | #if HFS_COMPRESSION |
fe8ab488 A |
2043 | if (!ISSET(ap->a_options, FSOPT_EXCHANGE_DATA_ONLY)) { |
2044 | if ( hfs_file_is_compressed(from_cp, 0) ) { | |
2045 | if ( 0 != ( error = decmpfs_decompress_file(from_vp, VTOCMP(from_vp), -1, 0, 1) ) ) { | |
2046 | return error; | |
2047 | } | |
b0d623f7 | 2048 | } |
fe8ab488 A |
2049 | |
2050 | if ( hfs_file_is_compressed(to_cp, 0) ) { | |
2051 | if ( 0 != ( error = decmpfs_decompress_file(to_vp, VTOCMP(to_vp), -1, 0, 1) ) ) { | |
2052 | return error; | |
2053 | } | |
b0d623f7 A |
2054 | } |
2055 | } | |
2056 | #endif // HFS_COMPRESSION | |
fe8ab488 A |
2057 | |
2058 | // Resource forks cannot be exchanged. | |
2059 | if (VNODE_IS_RSRC(from_vp) || VNODE_IS_RSRC(to_vp)) | |
2060 | return EINVAL; | |
2061 | ||
6d2010ae A |
2062 | /* |
2063 | * Normally, we want to notify the user handlers about the event, | |
2064 | * except if it's a handler driving the event. | |
2065 | */ | |
2066 | if ((ap->a_options & FSOPT_EXCHANGE_DATA_ONLY) == 0) { | |
2067 | check_for_tracked_file(from_vp, orig_from_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL); | |
2068 | check_for_tracked_file(to_vp, orig_to_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL); | |
316670eb | 2069 | } else { |
fe8ab488 A |
2070 | /* |
2071 | * This is currently used by mtmd so we should tidy up the | |
2072 | * file now because the data won't be used again in the | |
2073 | * destination file. | |
6d2010ae | 2074 | */ |
fe8ab488 A |
2075 | hfs_lock_truncate(from_cp, HFS_EXCLUSIVE_LOCK, 0); |
2076 | hfs_lock_always(from_cp, HFS_EXCLUSIVE_LOCK); | |
2077 | hfs_filedone(from_vp, ap->a_context, HFS_FILE_DONE_NO_SYNC); | |
2078 | hfs_unlock(from_cp); | |
2079 | hfs_unlock_truncate(from_cp, 0); | |
2080 | ||
2081 | // Flush all the data from the source file | |
2082 | error = ubc_msync(from_vp, 0, ubc_getsize(from_vp), NULL, | |
2083 | UBC_PUSHALL | UBC_SYNC); | |
2084 | if (error) | |
2085 | goto exit; | |
6d2010ae | 2086 | |
fe8ab488 A |
2087 | #if HFS_COMPRESSION |
2088 | /* | |
2089 | * If this is a compressed file, we need to do the same for | |
2090 | * the resource fork. | |
2091 | */ | |
2092 | if (ISSET(from_cp->c_bsdflags, UF_COMPRESSED)) { | |
2093 | error = hfs_flush_rsrc(from_vp, ap->a_context); | |
2094 | if (error) | |
2095 | goto exit; | |
6d2010ae | 2096 | } |
fe8ab488 | 2097 | #endif |
6d2010ae | 2098 | |
fe8ab488 A |
2099 | /* |
2100 | * We're doing a data-swap so we need to take the truncate | |
2101 | * lock exclusively. We need an exclusive lock because we | |
2102 | * will be completely truncating the source file and we must | |
2103 | * make sure nobody else sneaks in and trys to issue I/O | |
2104 | * whilst we don't have the cnode lock. | |
2105 | * | |
2106 | * After taking the truncate lock we do a quick check to | |
2107 | * verify there are no other references (including mmap | |
2108 | * references), but we must remember that this does not stop | |
2109 | * anybody coming in later and taking a reference. We will | |
2110 | * have the truncate lock exclusively so that will prevent | |
2111 | * them from issuing any I/O. | |
2112 | */ | |
6d2010ae | 2113 | |
fe8ab488 A |
2114 | if (to_cp < from_cp) { |
2115 | hfs_lock_truncate(to_cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
2116 | have_to_trunc_lock = true; | |
6d2010ae | 2117 | } |
6d2010ae | 2118 | |
fe8ab488 A |
2119 | hfs_lock_truncate(from_cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
2120 | have_from_trunc_lock = true; | |
91447636 | 2121 | |
fe8ab488 A |
2122 | /* |
2123 | * Do an early check to verify the source is not in use by | |
2124 | * anyone. We should be called from an FD opened as F_EVTONLY | |
2125 | * so that doesn't count as a reference. | |
2126 | */ | |
2127 | if (vnode_isinuse(from_vp, 0)) { | |
2128 | error = EBUSY; | |
2129 | goto exit; | |
2130 | } | |
91447636 | 2131 | |
fe8ab488 A |
2132 | if (to_cp >= from_cp) { |
2133 | hfs_lock_truncate(to_cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
2134 | have_to_trunc_lock = true; | |
2135 | } | |
91447636 | 2136 | } |
9bccf70c | 2137 | |
fe8ab488 A |
2138 | if ((error = hfs_lockpair(from_cp, to_cp, HFS_EXCLUSIVE_LOCK))) |
2139 | goto exit; | |
2140 | have_cnode_locks = true; | |
2141 | ||
316670eb A |
2142 | // Don't allow modification of the journal or journal_info_block |
2143 | if (hfs_is_journal_file(hfsmp, from_cp) || | |
2144 | hfs_is_journal_file(hfsmp, to_cp)) { | |
2145 | error = EPERM; | |
2146 | goto exit; | |
b4c24cb9 | 2147 | } |
fe8ab488 | 2148 | |
6d2010ae A |
2149 | /* |
2150 | * Ok, now that all of the pre-flighting is done, call the underlying | |
2151 | * function if needed. | |
2152 | */ | |
fe8ab488 A |
2153 | if (ISSET(ap->a_options, FSOPT_EXCHANGE_DATA_ONLY)) { |
2154 | #if HFS_COMPRESSION | |
2155 | if (ISSET(from_cp->c_bsdflags, UF_COMPRESSED)) { | |
2156 | error = hfs_move_compressed(from_cp, to_cp); | |
2157 | goto exit; | |
2158 | } | |
2159 | #endif | |
2160 | ||
2161 | error = hfs_move_data(from_cp, to_cp, 0); | |
6d2010ae A |
2162 | goto exit; |
2163 | } | |
316670eb | 2164 | |
91447636 A |
2165 | if ((error = hfs_start_transaction(hfsmp)) != 0) { |
2166 | goto exit; | |
b4c24cb9 | 2167 | } |
91447636 | 2168 | started_tr = 1; |
b4c24cb9 | 2169 | |
55e303ae A |
2170 | /* |
2171 | * Reserve some space in the Catalog file. | |
2172 | */ | |
91447636 A |
2173 | if ((error = cat_preflight(hfsmp, CAT_EXCHANGE, &cookie, vfs_context_proc(ap->a_context)))) { |
2174 | goto exit; | |
55e303ae | 2175 | } |
91447636 | 2176 | got_cookie = 1; |
9bccf70c A |
2177 | |
2178 | /* The backend code always tries to delete the virtual | |
2d21ac55 | 2179 | * extent id for exchanging files so we need to lock |
9bccf70c A |
2180 | * the extents b-tree. |
2181 | */ | |
91447636 | 2182 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); |
9bccf70c | 2183 | |
2d21ac55 A |
2184 | /* Account for the location of the catalog objects. */ |
2185 | if (from_cp->c_flag & C_HARDLINK) { | |
2186 | MAKE_INODE_NAME(from_iname, sizeof(from_iname), | |
2187 | from_cp->c_attr.ca_linkref); | |
2188 | from_nameptr = (unsigned char *)from_iname; | |
2189 | from_parid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid; | |
2190 | from_cp->c_hint = 0; | |
2191 | } else { | |
2192 | from_nameptr = from_cp->c_desc.cd_nameptr; | |
2193 | from_parid = from_cp->c_parentcnid; | |
2194 | } | |
2195 | if (to_cp->c_flag & C_HARDLINK) { | |
2196 | MAKE_INODE_NAME(to_iname, sizeof(to_iname), | |
2197 | to_cp->c_attr.ca_linkref); | |
2198 | to_nameptr = (unsigned char *)to_iname; | |
2199 | to_parid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid; | |
2200 | to_cp->c_hint = 0; | |
2201 | } else { | |
2202 | to_nameptr = to_cp->c_desc.cd_nameptr; | |
2203 | to_parid = to_cp->c_parentcnid; | |
2204 | } | |
2205 | ||
39236c6e | 2206 | /* |
fe8ab488 A |
2207 | * ExchangeFileIDs swaps the on-disk, or in-BTree extent information |
2208 | * attached to two different file IDs. It also swaps the extent | |
2209 | * information that may live in the extents-overflow B-Tree. | |
39236c6e A |
2210 | * |
2211 | * We do this in a transaction as this may require a lot of B-Tree nodes | |
2212 | * to do completely, particularly if one of the files in question | |
fe8ab488 | 2213 | * has a lot of extents. |
39236c6e A |
2214 | * |
2215 | * For example, assume "file1" has fileID 50, and "file2" has fileID 52. | |
2216 | * For the on-disk records, which are assumed to be synced, we will | |
2217 | * first swap the resident inline-8 extents as part of the catalog records. | |
2218 | * Then we will swap any extents overflow records for each file. | |
2219 | * | |
fe8ab488 A |
2220 | * When ExchangeFileIDs returns successfully, "file1" will have fileID 52, |
2221 | * and "file2" will have fileID 50. However, note that this is only | |
2222 | * approximately half of the work that exchangedata(2) will need to | |
2223 | * accomplish. In other words, we swap "too much" of the information | |
2224 | * because if we only called ExchangeFileIDs, both the fileID and extent | |
2225 | * information would be the invariants of this operation. We don't | |
2226 | * actually want that; we want to conclude with "file1" having | |
2227 | * file ID 50, and "file2" having fileID 52. | |
2228 | * | |
2229 | * The remainder of hfs_vnop_exchange will swap the file ID and other cnode | |
2230 | * data back to the proper ownership, while still allowing the cnode to remain | |
2231 | * pointing at the same set of extents that it did originally. | |
39236c6e | 2232 | */ |
2d21ac55 A |
2233 | error = ExchangeFileIDs(hfsmp, from_nameptr, to_nameptr, from_parid, |
2234 | to_parid, from_cp->c_hint, to_cp->c_hint); | |
91447636 | 2235 | hfs_systemfile_unlock(hfsmp, lockflags); |
9bccf70c | 2236 | |
91447636 A |
2237 | /* |
2238 | * Note that we don't need to exchange any extended attributes | |
2239 | * since the attributes are keyed by file ID. | |
2240 | */ | |
9bccf70c A |
2241 | |
2242 | if (error != E_NONE) { | |
91447636 A |
2243 | error = MacToVFSError(error); |
2244 | goto exit; | |
9bccf70c A |
2245 | } |
2246 | ||
2247 | /* Purge the vnodes from the name cache */ | |
2248 | if (from_vp) | |
2249 | cache_purge(from_vp); | |
2250 | if (to_vp) | |
2251 | cache_purge(to_vp); | |
2252 | ||
39236c6e A |
2253 | /* Bump both source and destination write counts before any swaps. */ |
2254 | { | |
2255 | hfs_incr_gencount (from_cp); | |
2256 | hfs_incr_gencount (to_cp); | |
2257 | } | |
2258 | ||
39236c6e | 2259 | /* Save a copy of "from" attributes before swapping. */ |
9bccf70c A |
2260 | bcopy(&from_cp->c_desc, &tempdesc, sizeof(struct cat_desc)); |
2261 | bcopy(&from_cp->c_attr, &tempattr, sizeof(struct cat_attr)); | |
db609669 A |
2262 | |
2263 | /* Save whether or not each cnode is a hardlink or has EAs */ | |
2264 | from_flag_special = from_cp->c_flag & (C_HARDLINK | C_HASXATTRS); | |
2265 | to_flag_special = to_cp->c_flag & (C_HARDLINK | C_HASXATTRS); | |
2266 | ||
2267 | /* Drop the special bits from each cnode */ | |
2268 | from_cp->c_flag &= ~(C_HARDLINK | C_HASXATTRS); | |
2269 | to_cp->c_flag &= ~(C_HARDLINK | C_HASXATTRS); | |
9bccf70c A |
2270 | |
2271 | /* | |
fe8ab488 | 2272 | * Now complete the in-memory portion of the copy. |
39236c6e A |
2273 | * |
2274 | * ExchangeFileIDs swaps the on-disk records involved. We complete the | |
2275 | * operation by swapping the in-memory contents of the two files here. | |
2276 | * We swap the cnode descriptors, which contain name, BSD attributes, | |
2277 | * timestamps, etc, about the file. | |
2278 | * | |
2279 | * NOTE: We do *NOT* swap the fileforks of the two cnodes. We have | |
2280 | * already swapped the on-disk extent information. As long as we swap the | |
2281 | * IDs, the in-line resident 8 extents that live in the filefork data | |
2282 | * structure will point to the right data for the new file ID if we leave | |
2283 | * them alone. | |
2284 | * | |
2285 | * As a result, any file descriptor that points to a particular | |
2286 | * vnode (even though it should change names), will continue | |
2287 | * to point to the same content. | |
9bccf70c | 2288 | */ |
39236c6e A |
2289 | |
2290 | /* Copy the "to" -> "from" cnode */ | |
9bccf70c | 2291 | bcopy(&to_cp->c_desc, &from_cp->c_desc, sizeof(struct cat_desc)); |
b4c24cb9 | 2292 | |
9bccf70c | 2293 | from_cp->c_hint = 0; |
db609669 A |
2294 | /* |
2295 | * If 'to' was a hardlink, then we copied over its link ID/CNID/(namespace ID) | |
39236c6e A |
2296 | * when we bcopy'd the descriptor above. However, the cnode attributes |
2297 | * are not bcopied. As a result, make sure to swap the file IDs of each item. | |
2298 | * | |
2299 | * Further, other hardlink attributes must be moved along in this swap: | |
2300 | * the linkcount, the linkref, and the firstlink all need to move | |
2301 | * along with the file IDs. See note below regarding the flags and | |
2302 | * what moves vs. what does not. | |
2303 | * | |
2304 | * For Reference: | |
2305 | * linkcount == total # of hardlinks. | |
2306 | * linkref == the indirect inode pointer. | |
2307 | * firstlink == the first hardlink in the chain (written to the raw inode). | |
2308 | * These three are tied to the fileID and must move along with the rest of the data. | |
db609669 A |
2309 | */ |
2310 | from_cp->c_fileid = to_cp->c_attr.ca_fileid; | |
39236c6e | 2311 | |
9bccf70c A |
2312 | from_cp->c_itime = to_cp->c_itime; |
2313 | from_cp->c_btime = to_cp->c_btime; | |
2314 | from_cp->c_atime = to_cp->c_atime; | |
2315 | from_cp->c_ctime = to_cp->c_ctime; | |
2316 | from_cp->c_gid = to_cp->c_gid; | |
2317 | from_cp->c_uid = to_cp->c_uid; | |
316670eb | 2318 | from_cp->c_bsdflags = to_cp->c_bsdflags; |
9bccf70c | 2319 | from_cp->c_mode = to_cp->c_mode; |
2d21ac55 | 2320 | from_cp->c_linkcount = to_cp->c_linkcount; |
db609669 A |
2321 | from_cp->c_attr.ca_linkref = to_cp->c_attr.ca_linkref; |
2322 | from_cp->c_attr.ca_firstlink = to_cp->c_attr.ca_firstlink; | |
2323 | ||
2324 | /* | |
2325 | * The cnode flags need to stay with the cnode and not get transferred | |
2326 | * over along with everything else because they describe the content; they are | |
2327 | * not attributes that reflect changes specific to the file ID. In general, | |
2328 | * fields that are tied to the file ID are the ones that will move. | |
2329 | * | |
2330 | * This reflects the fact that the file may have borrowed blocks, dirty metadata, | |
2331 | * or other extents, which may not yet have been written to the catalog. If | |
2332 | * they were, they would have been transferred above in the ExchangeFileIDs call above... | |
2333 | * | |
2334 | * The flags that are special are: | |
2335 | * C_HARDLINK, C_HASXATTRS | |
2336 | * | |
2337 | * These flags move with the item and file ID in the namespace since their | |
2338 | * state is tied to that of the file ID. | |
2339 | * | |
2340 | * So to transfer the flags, we have to take the following steps | |
2341 | * 1) Store in a localvar whether or not the special bits are set. | |
2342 | * 2) Drop the special bits from the current flags | |
2343 | * 3) swap the special flag bits to their destination | |
2344 | */ | |
2345 | from_cp->c_flag |= to_flag_special; | |
3a60a9f5 | 2346 | from_cp->c_attr.ca_recflags = to_cp->c_attr.ca_recflags; |
9bccf70c A |
2347 | bcopy(to_cp->c_finderinfo, from_cp->c_finderinfo, 32); |
2348 | ||
39236c6e A |
2349 | |
2350 | /* Copy the "from" -> "to" cnode */ | |
9bccf70c A |
2351 | bcopy(&tempdesc, &to_cp->c_desc, sizeof(struct cat_desc)); |
2352 | to_cp->c_hint = 0; | |
db609669 A |
2353 | /* |
2354 | * Pull the file ID from the tempattr we copied above. We can't assume | |
2355 | * it is the same as the CNID. | |
2356 | */ | |
2357 | to_cp->c_fileid = tempattr.ca_fileid; | |
9bccf70c A |
2358 | to_cp->c_itime = tempattr.ca_itime; |
2359 | to_cp->c_btime = tempattr.ca_btime; | |
2360 | to_cp->c_atime = tempattr.ca_atime; | |
2361 | to_cp->c_ctime = tempattr.ca_ctime; | |
2362 | to_cp->c_gid = tempattr.ca_gid; | |
2363 | to_cp->c_uid = tempattr.ca_uid; | |
316670eb | 2364 | to_cp->c_bsdflags = tempattr.ca_flags; |
9bccf70c | 2365 | to_cp->c_mode = tempattr.ca_mode; |
2d21ac55 | 2366 | to_cp->c_linkcount = tempattr.ca_linkcount; |
db609669 A |
2367 | to_cp->c_attr.ca_linkref = tempattr.ca_linkref; |
2368 | to_cp->c_attr.ca_firstlink = tempattr.ca_firstlink; | |
2369 | ||
2370 | /* | |
2371 | * Only OR in the "from" flags into our cnode flags below. | |
2372 | * Leave the rest of the flags alone. | |
2373 | */ | |
2374 | to_cp->c_flag |= from_flag_special; | |
2375 | ||
3a60a9f5 | 2376 | to_cp->c_attr.ca_recflags = tempattr.ca_recflags; |
9bccf70c A |
2377 | bcopy(tempattr.ca_finderinfo, to_cp->c_finderinfo, 32); |
2378 | ||
39236c6e | 2379 | |
91447636 | 2380 | /* Rehash the cnodes using their new file IDs */ |
b0d623f7 | 2381 | hfs_chash_rehash(hfsmp, from_cp, to_cp); |
90556fb8 A |
2382 | |
2383 | /* | |
2384 | * When a file moves out of "Cleanup At Startup" | |
2385 | * we can drop its NODUMP status. | |
2386 | */ | |
316670eb | 2387 | if ((from_cp->c_bsdflags & UF_NODUMP) && |
90556fb8 | 2388 | (from_cp->c_parentcnid != to_cp->c_parentcnid)) { |
316670eb | 2389 | from_cp->c_bsdflags &= ~UF_NODUMP; |
91447636 | 2390 | from_cp->c_touch_chgtime = TRUE; |
90556fb8 | 2391 | } |
316670eb | 2392 | if ((to_cp->c_bsdflags & UF_NODUMP) && |
90556fb8 | 2393 | (to_cp->c_parentcnid != from_cp->c_parentcnid)) { |
316670eb | 2394 | to_cp->c_bsdflags &= ~UF_NODUMP; |
91447636 | 2395 | to_cp->c_touch_chgtime = TRUE; |
90556fb8 A |
2396 | } |
2397 | ||
91447636 A |
2398 | exit: |
2399 | if (got_cookie) { | |
fe8ab488 | 2400 | cat_postflight(hfsmp, &cookie, vfs_context_proc(ap->a_context)); |
b4c24cb9 | 2401 | } |
91447636 A |
2402 | if (started_tr) { |
2403 | hfs_end_transaction(hfsmp); | |
b4c24cb9 A |
2404 | } |
2405 | ||
fe8ab488 A |
2406 | if (have_from_trunc_lock) |
2407 | hfs_unlock_truncate(from_cp, 0); | |
2408 | ||
2409 | if (have_to_trunc_lock) | |
2410 | hfs_unlock_truncate(to_cp, 0); | |
2411 | ||
2412 | if (have_cnode_locks) | |
2413 | hfs_unlockpair(from_cp, to_cp); | |
2414 | ||
2415 | return (error); | |
9bccf70c A |
2416 | } |
2417 | ||
fe8ab488 A |
2418 | #if HFS_COMPRESSION |
2419 | /* | |
2420 | * This function is used specifically for the case when a namespace | |
2421 | * handler is trying to steal data before it's deleted. Note that we | |
2422 | * don't bother deleting the xattr from the source because it will get | |
2423 | * deleted a short time later anyway. | |
2424 | * | |
2425 | * cnodes must be locked | |
2426 | */ | |
2427 | static int hfs_move_compressed(cnode_t *from_cp, cnode_t *to_cp) | |
2428 | { | |
2429 | int ret; | |
2430 | void *data = NULL; | |
2431 | ||
2432 | CLR(from_cp->c_bsdflags, UF_COMPRESSED); | |
2433 | SET(from_cp->c_flag, C_MODIFIED); | |
2434 | ||
2435 | ret = hfs_move_data(from_cp, to_cp, HFS_MOVE_DATA_INCLUDE_RSRC); | |
2436 | if (ret) | |
2437 | goto exit; | |
2438 | ||
2439 | /* | |
2440 | * Transfer the xattr that decmpfs uses. Ideally, this code | |
2441 | * should be with the other decmpfs code but it's file system | |
2442 | * agnostic and this path is currently, and likely to remain, HFS+ | |
2443 | * specific. It's easier and more performant if we implement it | |
2444 | * here. | |
2445 | */ | |
2446 | ||
2447 | size_t size = MAX_DECMPFS_XATTR_SIZE; | |
2448 | MALLOC(data, void *, size, M_TEMP, M_WAITOK); | |
2449 | ||
2450 | ret = hfs_xattr_read(from_cp->c_vp, DECMPFS_XATTR_NAME, data, &size); | |
2451 | if (ret) | |
2452 | goto exit; | |
2453 | ||
2454 | ret = hfs_xattr_write(to_cp->c_vp, DECMPFS_XATTR_NAME, data, size); | |
2455 | if (ret) | |
2456 | goto exit; | |
2457 | ||
2458 | SET(to_cp->c_bsdflags, UF_COMPRESSED); | |
2459 | SET(to_cp->c_flag, C_MODIFIED); | |
2460 | ||
2461 | exit: | |
2462 | if (data) | |
2463 | FREE(data, M_TEMP); | |
2464 | ||
2465 | return ret; | |
2466 | } | |
2467 | #endif // HFS_COMPRESSION | |
2468 | ||
6d2010ae A |
2469 | int |
2470 | hfs_vnop_mmap(struct vnop_mmap_args *ap) | |
2471 | { | |
2472 | struct vnode *vp = ap->a_vp; | |
fe8ab488 | 2473 | cnode_t *cp = VTOC(vp); |
6d2010ae A |
2474 | int error; |
2475 | ||
2476 | if (VNODE_IS_RSRC(vp)) { | |
2477 | /* allow pageins of the resource fork */ | |
2478 | } else { | |
fe8ab488 A |
2479 | int compressed = hfs_file_is_compressed(cp, 1); /* 1 == don't take the cnode lock */ |
2480 | time_t orig_ctime = cp->c_ctime; | |
6d2010ae | 2481 | |
fe8ab488 | 2482 | if (!compressed && (cp->c_bsdflags & UF_COMPRESSED)) { |
6d2010ae A |
2483 | error = check_for_dataless_file(vp, NAMESPACE_HANDLER_READ_OP); |
2484 | if (error != 0) { | |
2485 | return error; | |
2486 | } | |
2487 | } | |
2488 | ||
2489 | if (ap->a_fflags & PROT_WRITE) { | |
2490 | check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL); | |
2491 | } | |
2492 | } | |
fe8ab488 | 2493 | |
6d2010ae A |
2494 | // |
2495 | // NOTE: we return ENOTSUP because we want the cluster layer | |
2496 | // to actually do all the real work. | |
2497 | // | |
2498 | return (ENOTSUP); | |
2499 | } | |
2500 | ||
fe8ab488 A |
2501 | static errno_t hfs_vnop_mnomap(struct vnop_mnomap_args *ap) |
2502 | { | |
2503 | vnode_t vp = ap->a_vp; | |
2504 | ||
2505 | /* | |
2506 | * Whilst the file was mapped, there may not have been any | |
2507 | * page-outs so we need to increment the generation counter now. | |
2508 | * Unfortunately this may lead to a change in the generation | |
2509 | * counter when no actual change has been made, but there is | |
2510 | * little we can do about that with our current architecture. | |
2511 | */ | |
2512 | if (ubc_is_mapped_writable(vp)) { | |
2513 | cnode_t *cp = VTOC(vp); | |
2514 | hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); | |
2515 | hfs_incr_gencount(cp); | |
2516 | ||
2517 | /* | |
2518 | * We don't want to set the modification time here since a | |
2519 | * change to that is not acceptable if no changes were made. | |
2520 | * Instead we set a flag so that if we get any page-outs we | |
2521 | * know to update the modification time. It's possible that | |
2522 | * they weren't actually because of changes made whilst the | |
2523 | * file was mapped but that's not easy to fix now. | |
2524 | */ | |
2525 | SET(cp->c_flag, C_MIGHT_BE_DIRTY_FROM_MAPPING); | |
2526 | ||
2527 | hfs_unlock(cp); | |
2528 | } | |
2529 | ||
2530 | return 0; | |
2531 | } | |
2532 | ||
6d2010ae | 2533 | /* |
fe8ab488 A |
2534 | * Mark the resource fork as needing a ubc_setsize when we drop the |
2535 | * cnode lock later. | |
2536 | */ | |
2537 | static void hfs_rsrc_setsize(cnode_t *cp) | |
2538 | { | |
2539 | /* | |
2540 | * We need to take an iocount if we don't have one. vnode_get | |
2541 | * will return ENOENT if the vnode is terminating which is what we | |
2542 | * want as it's not safe to call ubc_setsize in that case. | |
2543 | */ | |
2544 | if (cp->c_rsrc_vp && !vnode_get(cp->c_rsrc_vp)) { | |
2545 | // Shouldn't happen, but better safe... | |
2546 | if (ISSET(cp->c_flag, C_NEED_RVNODE_PUT)) | |
2547 | vnode_put(cp->c_rsrc_vp); | |
2548 | SET(cp->c_flag, C_NEED_RVNODE_PUT | C_NEED_RSRC_SETSIZE); | |
2549 | } | |
2550 | } | |
2551 | ||
2552 | /* | |
2553 | * hfs_move_data | |
6d2010ae A |
2554 | * |
2555 | * This is a non-symmetric variant of exchangedata. In this function, | |
fe8ab488 A |
2556 | * the contents of the data fork (and optionally the resource fork) |
2557 | * are moved from from_cp to to_cp. | |
6d2010ae | 2558 | * |
fe8ab488 | 2559 | * The cnodes must be locked. |
6d2010ae | 2560 | * |
fe8ab488 A |
2561 | * The cnode pointed to by 'to_cp' *must* be empty prior to invoking |
2562 | * this function. We impose this restriction because we may not be | |
2563 | * able to fully delete the entire file's contents in a single | |
2564 | * transaction, particularly if it has a lot of extents. In the | |
2565 | * normal file deletion codepath, the file is screened for two | |
2566 | * conditions: 1) bigger than 400MB, and 2) more than 8 extents. If | |
2567 | * so, the file is relocated to the hidden directory and the deletion | |
2568 | * is broken up into multiple truncates. We can't do that here | |
2569 | * because both files need to exist in the namespace. The main reason | |
2570 | * this is imposed is that we may have to touch a whole lot of bitmap | |
2571 | * blocks if there are many extents. | |
6d2010ae | 2572 | * |
fe8ab488 A |
2573 | * Any data written to 'from_cp' after this call completes is not |
2574 | * guaranteed to be moved. | |
6d2010ae A |
2575 | * |
2576 | * Arguments: | |
fe8ab488 A |
2577 | * cnode_t *from_cp : source file |
2578 | * cnode_t *to_cp : destination file; must be empty | |
6d2010ae A |
2579 | * |
2580 | * Returns: | |
fe8ab488 A |
2581 | * |
2582 | * EBUSY - File has been deleted or is in use | |
6d2010ae | 2583 | * EFBIG - Destination file was not empty |
fe8ab488 A |
2584 | * EIO - An I/O error |
2585 | * 0 - success | |
2586 | * other - Other errors that can be returned from called functions | |
6d2010ae | 2587 | */ |
fe8ab488 A |
2588 | int hfs_move_data(cnode_t *from_cp, cnode_t *to_cp, |
2589 | hfs_move_data_options_t options) | |
2590 | { | |
2591 | hfsmount_t *hfsmp = VTOHFS(from_cp->c_vp); | |
2592 | int error = 0; | |
2593 | int lockflags = 0; | |
2594 | bool return_EIO_on_error = false; | |
2595 | const bool include_rsrc = ISSET(options, HFS_MOVE_DATA_INCLUDE_RSRC); | |
6d2010ae | 2596 | |
fe8ab488 A |
2597 | /* Verify that neither source/dest file is open-unlinked */ |
2598 | if (ISSET(from_cp->c_flag, C_DELETED | C_NOEXISTS) | |
2599 | || ISSET(to_cp->c_flag, C_DELETED | C_NOEXISTS)) { | |
2600 | return EBUSY; | |
6d2010ae A |
2601 | } |
2602 | ||
2603 | /* | |
2604 | * Verify the source file is not in use by anyone besides us. | |
2605 | * | |
2606 | * This function is typically invoked by a namespace handler | |
2607 | * process responding to a temporarily stalled system call. | |
2608 | * The FD that it is working off of is opened O_EVTONLY, so | |
2609 | * it really has no active usecounts (the kusecount from O_EVTONLY | |
2610 | * is subtracted from the total usecounts). | |
2611 | * | |
2612 | * As a result, we shouldn't have any active usecounts against | |
2613 | * this vnode when we go to check it below. | |
2614 | */ | |
fe8ab488 A |
2615 | if (vnode_isinuse(from_cp->c_vp, 0)) |
2616 | return EBUSY; | |
6d2010ae | 2617 | |
fe8ab488 A |
2618 | if (include_rsrc && from_cp->c_rsrc_vp) { |
2619 | if (vnode_isinuse(from_cp->c_rsrc_vp, 0)) | |
2620 | return EBUSY; | |
2621 | ||
2622 | /* | |
2623 | * In the code below, if the destination file doesn't have a | |
2624 | * c_rsrcfork then we don't create it which means we we cannot | |
2625 | * transfer the ff_invalidranges and cf_vblocks fields. These | |
2626 | * shouldn't be set because we flush the resource fork before | |
2627 | * calling this function but there is a tiny window when we | |
2628 | * did not have any locks... | |
2629 | */ | |
2630 | if (!to_cp->c_rsrcfork | |
2631 | && (!TAILQ_EMPTY(&from_cp->c_rsrcfork->ff_invalidranges) | |
2632 | || from_cp->c_rsrcfork->ff_unallocblocks)) { | |
2633 | /* | |
2634 | * The file isn't really busy now but something did slip | |
2635 | * in and tinker with the file while we didn't have any | |
2636 | * locks, so this is the most meaningful return code for | |
2637 | * the caller. | |
2638 | */ | |
2639 | return EBUSY; | |
6d2010ae A |
2640 | } |
2641 | } | |
fe8ab488 A |
2642 | |
2643 | // Check the destination file is empty | |
2644 | if (to_cp->c_datafork->ff_blocks | |
2645 | || to_cp->c_datafork->ff_size | |
2646 | || (include_rsrc | |
2647 | && (to_cp->c_blocks | |
2648 | || (to_cp->c_rsrcfork && to_cp->c_rsrcfork->ff_size)))) { | |
2649 | return EFBIG; | |
6d2010ae | 2650 | } |
fe8ab488 A |
2651 | |
2652 | if ((error = hfs_start_transaction (hfsmp))) | |
2653 | return error; | |
2654 | ||
2655 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS | SFL_ATTRIBUTE, | |
2656 | HFS_EXCLUSIVE_LOCK); | |
2657 | ||
2658 | // filefork_t is 128 bytes which should be OK | |
2659 | filefork_t rfork_buf, *from_rfork = NULL; | |
2660 | ||
2661 | if (include_rsrc) { | |
2662 | from_rfork = from_cp->c_rsrcfork; | |
2663 | ||
2664 | /* | |
2665 | * Creating resource fork vnodes is expensive, so just get get | |
2666 | * the fork data if we need it. | |
2667 | */ | |
2668 | if (!from_rfork && hfs_has_rsrc(from_cp)) { | |
2669 | from_rfork = &rfork_buf; | |
2670 | ||
2671 | from_rfork->ff_cp = from_cp; | |
2672 | TAILQ_INIT(&from_rfork->ff_invalidranges); | |
2673 | ||
2674 | error = cat_idlookup(hfsmp, from_cp->c_fileid, 0, 1, NULL, NULL, | |
2675 | &from_rfork->ff_data); | |
2676 | ||
2677 | if (error) | |
2678 | goto exit; | |
6d2010ae A |
2679 | } |
2680 | } | |
fe8ab488 A |
2681 | |
2682 | /* | |
2683 | * From here on, any failures mean that we might be leaving things | |
2684 | * in a weird or inconsistent state. Ideally, we should back out | |
2685 | * all the changes, but to do that properly we need to fix | |
2686 | * MoveData. We'll save fixing that for another time. For now, | |
2687 | * just return EIO in all cases to the caller so that they know. | |
2688 | */ | |
2689 | return_EIO_on_error = true; | |
2690 | ||
2691 | bool data_overflow_extents = overflow_extents(from_cp->c_datafork); | |
2692 | ||
2693 | // Move the data fork | |
2694 | if ((error = hfs_move_fork (from_cp->c_datafork, from_cp, | |
2695 | to_cp->c_datafork, to_cp))) { | |
2696 | goto exit; | |
6d2010ae | 2697 | } |
fe8ab488 A |
2698 | |
2699 | SET(from_cp->c_flag, C_NEED_DATA_SETSIZE); | |
2700 | SET(to_cp->c_flag, C_NEED_DATA_SETSIZE); | |
2701 | ||
2702 | // We move the resource fork later | |
2703 | ||
6d2010ae | 2704 | /* |
fe8ab488 A |
2705 | * Note that because all we're doing is moving the extents around, |
2706 | * we can probably do this in a single transaction: Each extent | |
2707 | * record (group of 8) is 64 bytes. A extent overflow B-Tree node | |
2708 | * is typically 4k. This means each node can hold roughly ~60 | |
2709 | * extent records == (480 extents). | |
6d2010ae | 2710 | * |
fe8ab488 A |
2711 | * If a file was massively fragmented and had 20k extents, this |
2712 | * means we'd roughly touch 20k/480 == 41 to 42 nodes, plus the | |
2713 | * index nodes, for half of the operation. (inserting or | |
2714 | * deleting). So if we're manipulating 80-100 nodes, this is | |
2715 | * basically 320k of data to write to the journal in a bad case. | |
6d2010ae | 2716 | */ |
fe8ab488 A |
2717 | if (data_overflow_extents) { |
2718 | if ((error = MoveData(hfsmp, from_cp->c_cnid, to_cp->c_cnid, 0))) | |
2719 | goto exit; | |
6d2010ae | 2720 | } |
fe8ab488 A |
2721 | |
2722 | if (from_rfork && overflow_extents(from_rfork)) { | |
2723 | if ((error = MoveData(hfsmp, from_cp->c_cnid, to_cp->c_cnid, 1))) | |
2724 | goto exit; | |
2725 | } | |
2726 | ||
2727 | // Touch times | |
2728 | from_cp->c_touch_acctime = TRUE; | |
2729 | from_cp->c_touch_chgtime = TRUE; | |
2730 | from_cp->c_touch_modtime = TRUE; | |
2731 | hfs_touchtimes(hfsmp, from_cp); | |
2732 | ||
2733 | to_cp->c_touch_acctime = TRUE; | |
2734 | to_cp->c_touch_chgtime = TRUE; | |
2735 | to_cp->c_touch_modtime = TRUE; | |
2736 | hfs_touchtimes(hfsmp, to_cp); | |
2737 | ||
2738 | struct cat_fork dfork_buf; | |
2739 | const struct cat_fork *dfork, *rfork; | |
2740 | ||
2741 | dfork = hfs_prepare_fork_for_update(to_cp->c_datafork, &dfork_buf, | |
2742 | hfsmp->blockSize); | |
2743 | rfork = hfs_prepare_fork_for_update(from_rfork, &rfork_buf.ff_data, | |
2744 | hfsmp->blockSize); | |
2745 | ||
2746 | // Update the catalog nodes, to_cp first | |
2747 | if ((error = cat_update(hfsmp, &to_cp->c_desc, &to_cp->c_attr, | |
2748 | dfork, rfork))) { | |
2749 | goto exit; | |
6d2010ae | 2750 | } |
fe8ab488 A |
2751 | |
2752 | CLR(to_cp->c_flag, C_MODIFIED); | |
2753 | ||
2754 | // Update in-memory resource fork data here | |
2755 | if (from_rfork) { | |
2756 | // Update c_blocks | |
2757 | uint32_t moving = from_rfork->ff_blocks + from_rfork->ff_unallocblocks; | |
2758 | ||
2759 | from_cp->c_blocks -= moving; | |
2760 | to_cp->c_blocks += moving; | |
2761 | ||
2762 | // Update to_cp's resource data if it has it | |
2763 | filefork_t *to_rfork = to_cp->c_rsrcfork; | |
2764 | if (to_rfork) { | |
2765 | to_rfork->ff_invalidranges = from_rfork->ff_invalidranges; | |
2766 | to_rfork->ff_data = from_rfork->ff_data; | |
2767 | ||
2768 | // Deal with ubc_setsize | |
2769 | hfs_rsrc_setsize(to_cp); | |
6d2010ae | 2770 | } |
fe8ab488 A |
2771 | |
2772 | // Wipe out the resource fork in from_cp | |
2773 | rl_init(&from_rfork->ff_invalidranges); | |
2774 | bzero(&from_rfork->ff_data, sizeof(from_rfork->ff_data)); | |
2775 | ||
2776 | // Deal with ubc_setsize | |
2777 | hfs_rsrc_setsize(from_cp); | |
6d2010ae | 2778 | } |
fe8ab488 A |
2779 | |
2780 | // Currently unnecessary, but might be useful in future... | |
2781 | dfork = hfs_prepare_fork_for_update(from_cp->c_datafork, &dfork_buf, | |
2782 | hfsmp->blockSize); | |
2783 | rfork = hfs_prepare_fork_for_update(from_rfork, &rfork_buf.ff_data, | |
2784 | hfsmp->blockSize); | |
2785 | ||
2786 | // Update from_cp | |
2787 | if ((error = cat_update(hfsmp, &from_cp->c_desc, &from_cp->c_attr, | |
2788 | dfork, rfork))) { | |
2789 | goto exit; | |
2790 | } | |
2791 | ||
2792 | CLR(from_cp->c_flag, C_MODIFIED); | |
2793 | ||
2794 | exit: | |
2795 | if (lockflags) { | |
2796 | hfs_systemfile_unlock(hfsmp, lockflags); | |
6d2010ae A |
2797 | hfs_end_transaction(hfsmp); |
2798 | } | |
fe8ab488 A |
2799 | |
2800 | if (error && error != EIO && return_EIO_on_error) { | |
2801 | printf("hfs_move_data: encountered error %d\n", error); | |
2802 | error = EIO; | |
2803 | } | |
2804 | ||
6d2010ae | 2805 | return error; |
fe8ab488 | 2806 | } |
6d2010ae A |
2807 | |
2808 | /* | |
fe8ab488 | 2809 | * Move all of the catalog and runtime data in srcfork to dstfork. |
6d2010ae | 2810 | * |
fe8ab488 A |
2811 | * This allows us to maintain the invalid ranges across the move data |
2812 | * operation so we don't need to force all of the pending IO right | |
2813 | * now. In addition, we move all non overflow-extent extents into the | |
2814 | * destination here. | |
2815 | * | |
2816 | * The destination fork must be empty and should have been checked | |
2817 | * prior to calling this. | |
6d2010ae | 2818 | */ |
fe8ab488 A |
2819 | static int hfs_move_fork(filefork_t *srcfork, cnode_t *src_cp, |
2820 | filefork_t *dstfork, cnode_t *dst_cp) | |
2821 | { | |
2822 | // Move the invalid ranges | |
2823 | dstfork->ff_invalidranges = srcfork->ff_invalidranges; | |
2824 | rl_init(&srcfork->ff_invalidranges); | |
2825 | ||
2826 | // Move the fork data (copy whole structure) | |
2827 | dstfork->ff_data = srcfork->ff_data; | |
2828 | bzero(&srcfork->ff_data, sizeof(srcfork->ff_data)); | |
2829 | ||
2830 | // Update c_blocks | |
2831 | src_cp->c_blocks -= dstfork->ff_blocks + dstfork->ff_unallocblocks; | |
2832 | dst_cp->c_blocks += dstfork->ff_blocks + dstfork->ff_unallocblocks; | |
2833 | ||
6d2010ae A |
2834 | return 0; |
2835 | } | |
fe8ab488 A |
2836 | |
2837 | ||
2838 | #include <i386/panic_hooks.h> | |
2839 | ||
2840 | struct hfs_fsync_panic_hook { | |
2841 | panic_hook_t hook; | |
2842 | struct cnode *cp; | |
2843 | }; | |
2844 | ||
2845 | static void hfs_fsync_panic_hook(panic_hook_t *hook_) | |
2846 | { | |
2847 | struct hfs_fsync_panic_hook *hook = (struct hfs_fsync_panic_hook *)hook_; | |
2848 | extern int kdb_log(const char *fmt, ...); | |
2849 | ||
2850 | // Get the physical region just before cp | |
2851 | panic_phys_range_t range; | |
2852 | uint64_t phys; | |
2853 | ||
2854 | if (panic_phys_range_before(hook->cp, &phys, &range)) { | |
2855 | kdb_log("cp = %p, phys = %p, prev (%p: %p-%p)\n", | |
2856 | hook->cp, phys, range.type, range.phys_start, | |
2857 | range.phys_start + range.len); | |
2858 | } else | |
2859 | kdb_log("cp = %p, phys = %p, prev (!)\n", hook->cp, phys); | |
2860 | ||
2861 | panic_dump_mem((void *)(((vm_offset_t)hook->cp - 4096) & ~4095), 12288); | |
2862 | ||
2863 | kdb_log("\n"); | |
2864 | } | |
2865 | ||
9bccf70c A |
2866 | |
2867 | /* | |
91447636 A |
2868 | * cnode must be locked |
2869 | */ | |
91447636 A |
2870 | int |
2871 | hfs_fsync(struct vnode *vp, int waitfor, int fullsync, struct proc *p) | |
9bccf70c | 2872 | { |
9bccf70c A |
2873 | struct cnode *cp = VTOC(vp); |
2874 | struct filefork *fp = NULL; | |
2875 | int retval = 0; | |
91447636 | 2876 | struct hfsmount *hfsmp = VTOHFS(vp); |
b0d623f7 | 2877 | struct rl_entry *invalid_range; |
9bccf70c | 2878 | struct timeval tv; |
b0d623f7 A |
2879 | int waitdata; /* attributes necessary for data retrieval */ |
2880 | int wait; /* all other attributes (e.g. atime, etc.) */ | |
91447636 A |
2881 | int lockflag; |
2882 | int took_trunc_lock = 0; | |
316670eb | 2883 | int locked_buffers = 0; |
9bccf70c | 2884 | |
b0d623f7 A |
2885 | /* |
2886 | * Applications which only care about data integrity rather than full | |
2887 | * file integrity may opt out of (delay) expensive metadata update | |
2888 | * operations as a performance optimization. | |
2889 | */ | |
91447636 | 2890 | wait = (waitfor == MNT_WAIT); |
b0d623f7 | 2891 | waitdata = (waitfor == MNT_DWAIT) | wait; |
2d21ac55 A |
2892 | if (always_do_fullfsync) |
2893 | fullsync = 1; | |
2894 | ||
9bccf70c | 2895 | /* HFS directories don't have any data blocks. */ |
91447636 | 2896 | if (vnode_isdir(vp)) |
9bccf70c | 2897 | goto metasync; |
b0d623f7 | 2898 | fp = VTOF(vp); |
9bccf70c A |
2899 | |
2900 | /* | |
2901 | * For system files flush the B-tree header and | |
2902 | * for regular files write out any clusters | |
2903 | */ | |
91447636 | 2904 | if (vnode_issystem(vp)) { |
b4c24cb9 A |
2905 | if (VTOF(vp)->fcbBTCBPtr != NULL) { |
2906 | // XXXdbg | |
55e303ae | 2907 | if (hfsmp->jnl == NULL) { |
b4c24cb9 A |
2908 | BTFlushPath(VTOF(vp)); |
2909 | } | |
2910 | } | |
91447636 A |
2911 | } else if (UBCINFOEXISTS(vp)) { |
2912 | hfs_unlock(cp); | |
39236c6e | 2913 | hfs_lock_truncate(cp, HFS_SHARED_LOCK, HFS_LOCK_DEFAULT); |
91447636 | 2914 | took_trunc_lock = 1; |
9bccf70c | 2915 | |
fe8ab488 A |
2916 | struct hfs_fsync_panic_hook hook; |
2917 | hook.cp = cp; | |
2918 | panic_hook(&hook.hook, hfs_fsync_panic_hook); | |
2919 | ||
b0d623f7 | 2920 | if (fp->ff_unallocblocks != 0) { |
39236c6e | 2921 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
b0d623f7 | 2922 | |
39236c6e | 2923 | hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
b0d623f7 | 2924 | } |
fe8ab488 A |
2925 | |
2926 | panic_unhook(&hook.hook); | |
2927 | ||
91447636 | 2928 | /* Don't hold cnode lock when calling into cluster layer. */ |
b0d623f7 | 2929 | (void) cluster_push(vp, waitdata ? IO_SYNC : 0); |
91447636 | 2930 | |
39236c6e | 2931 | hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); |
91447636 | 2932 | } |
9bccf70c A |
2933 | /* |
2934 | * When MNT_WAIT is requested and the zero fill timeout | |
2935 | * has expired then we must explicitly zero out any areas | |
2936 | * that are currently marked invalid (holes). | |
90556fb8 A |
2937 | * |
2938 | * Files with NODUMP can bypass zero filling here. | |
9bccf70c | 2939 | */ |
b0d623f7 A |
2940 | if (fp && (((cp->c_flag & C_ALWAYS_ZEROFILL) && !TAILQ_EMPTY(&fp->ff_invalidranges)) || |
2941 | ((wait || (cp->c_flag & C_ZFWANTSYNC)) && | |
316670eb | 2942 | ((cp->c_bsdflags & UF_NODUMP) == 0) && |
b0d623f7 A |
2943 | UBCINFOEXISTS(vp) && (vnode_issystem(vp) ==0) && |
2944 | cp->c_zftimeout != 0))) { | |
2945 | ||
91447636 | 2946 | microuptime(&tv); |
b0d623f7 | 2947 | if ((cp->c_flag & C_ALWAYS_ZEROFILL) == 0 && !fullsync && tv.tv_sec < (long)cp->c_zftimeout) { |
9bccf70c A |
2948 | /* Remember that a force sync was requested. */ |
2949 | cp->c_flag |= C_ZFWANTSYNC; | |
91447636 A |
2950 | goto datasync; |
2951 | } | |
b0d623f7 | 2952 | if (!TAILQ_EMPTY(&fp->ff_invalidranges)) { |
6d2010ae | 2953 | if (!took_trunc_lock || (cp->c_truncatelockowner == HFS_SHARED_OWNER)) { |
b0d623f7 | 2954 | hfs_unlock(cp); |
6d2010ae | 2955 | if (took_trunc_lock) { |
39236c6e | 2956 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
6d2010ae | 2957 | } |
39236c6e A |
2958 | hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
2959 | hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); | |
b0d623f7 A |
2960 | took_trunc_lock = 1; |
2961 | } | |
2962 | while ((invalid_range = TAILQ_FIRST(&fp->ff_invalidranges))) { | |
2963 | off_t start = invalid_range->rl_start; | |
2964 | off_t end = invalid_range->rl_end; | |
9bccf70c | 2965 | |
b0d623f7 A |
2966 | /* The range about to be written must be validated |
2967 | * first, so that VNOP_BLOCKMAP() will return the | |
2968 | * appropriate mapping for the cluster code: | |
2969 | */ | |
2970 | rl_remove(start, end, &fp->ff_invalidranges); | |
9bccf70c | 2971 | |
b0d623f7 A |
2972 | /* Don't hold cnode lock when calling into cluster layer. */ |
2973 | hfs_unlock(cp); | |
2974 | (void) cluster_write(vp, (struct uio *) 0, | |
2975 | fp->ff_size, end + 1, start, (off_t)0, | |
2976 | IO_HEADZEROFILL | IO_NOZERODIRTY | IO_NOCACHE); | |
39236c6e | 2977 | hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); |
b0d623f7 A |
2978 | cp->c_flag |= C_MODIFIED; |
2979 | } | |
91447636 | 2980 | hfs_unlock(cp); |
b0d623f7 | 2981 | (void) cluster_push(vp, waitdata ? IO_SYNC : 0); |
39236c6e | 2982 | hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); |
9bccf70c | 2983 | } |
9bccf70c A |
2984 | cp->c_flag &= ~C_ZFWANTSYNC; |
2985 | cp->c_zftimeout = 0; | |
2986 | } | |
91447636 | 2987 | datasync: |
b0d623f7 | 2988 | if (took_trunc_lock) { |
39236c6e | 2989 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
b0d623f7 A |
2990 | took_trunc_lock = 0; |
2991 | } | |
91447636 A |
2992 | /* |
2993 | * if we have a journal and if journal_active() returns != 0 then the | |
2994 | * we shouldn't do anything to a locked block (because it is part | |
2995 | * of a transaction). otherwise we'll just go through the normal | |
2996 | * code path and flush the buffer. note journal_active() can return | |
2997 | * -1 if the journal is invalid -- however we still need to skip any | |
2998 | * locked blocks as they get cleaned up when we finish the transaction | |
2999 | * or close the journal. | |
3000 | */ | |
3001 | // if (hfsmp->jnl && journal_active(hfsmp->jnl) >= 0) | |
3002 | if (hfsmp->jnl) | |
3003 | lockflag = BUF_SKIP_LOCKED; | |
3004 | else | |
3005 | lockflag = 0; | |
9bccf70c A |
3006 | |
3007 | /* | |
3008 | * Flush all dirty buffers associated with a vnode. | |
316670eb | 3009 | * Record how many of them were dirty AND locked (if necessary). |
9bccf70c | 3010 | */ |
316670eb A |
3011 | locked_buffers = buf_flushdirtyblks_skipinfo(vp, waitdata, lockflag, "hfs_fsync"); |
3012 | if ((lockflag & BUF_SKIP_LOCKED) && (locked_buffers) && (vnode_vtype(vp) == VLNK)) { | |
3013 | /* | |
3014 | * If there are dirty symlink buffers, then we may need to take action | |
3015 | * to prevent issues later on if we are journaled. If we're fsyncing a | |
3016 | * symlink vnode then we are in one of three cases: | |
3017 | * | |
3018 | * 1) automatic sync has fired. In this case, we don't want the behavior to change. | |
3019 | * | |
3020 | * 2) Someone has opened the FD for the symlink (not what it points to) | |
3021 | * and has issued an fsync against it. This should be rare, and we don't | |
3022 | * want the behavior to change. | |
3023 | * | |
3024 | * 3) We are being called by a vclean which is trying to reclaim this | |
3025 | * symlink vnode. If this is the case, then allowing this fsync to | |
3026 | * proceed WITHOUT flushing the journal could result in the vclean | |
3027 | * invalidating the buffer's blocks before the journal transaction is | |
3028 | * written to disk. To prevent this, we force a journal flush | |
3029 | * if the vnode is in the middle of a recycle (VL_TERMINATE or VL_DEAD is set). | |
3030 | */ | |
3031 | if (vnode_isrecycled(vp)) { | |
3032 | fullsync = 1; | |
3033 | } | |
3034 | } | |
9bccf70c A |
3035 | |
3036 | metasync: | |
91447636 A |
3037 | if (vnode_isreg(vp) && vnode_issystem(vp)) { |
3038 | if (VTOF(vp)->fcbBTCBPtr != NULL) { | |
3039 | microuptime(&tv); | |
9bccf70c | 3040 | BTSetLastSync(VTOF(vp), tv.tv_sec); |
91447636 A |
3041 | } |
3042 | cp->c_touch_acctime = FALSE; | |
3043 | cp->c_touch_chgtime = FALSE; | |
3044 | cp->c_touch_modtime = FALSE; | |
3a60a9f5 | 3045 | } else if ( !(vp->v_flag & VSWAP) ) /* User file */ { |
91447636 | 3046 | retval = hfs_update(vp, wait); |
9bccf70c | 3047 | |
2d21ac55 A |
3048 | /* |
3049 | * When MNT_WAIT is requested push out the catalog record for | |
3050 | * this file. If they asked for a full fsync, we can skip this | |
3051 | * because the journal_flush or hfs_metasync_all will push out | |
3052 | * all of the metadata changes. | |
3053 | */ | |
3054 | if ((retval == 0) && wait && !fullsync && cp->c_hint && | |
9bccf70c | 3055 | !ISSET(cp->c_flag, C_DELETED | C_NOEXISTS)) { |
91447636 | 3056 | hfs_metasync(VTOHFS(vp), (daddr64_t)cp->c_hint, p); |
2d21ac55 | 3057 | } |
55e303ae | 3058 | |
2d21ac55 A |
3059 | /* |
3060 | * If this was a full fsync, make sure all metadata | |
3061 | * changes get to stable storage. | |
3062 | */ | |
3063 | if (fullsync) { | |
6d2010ae A |
3064 | if (hfsmp->jnl) { |
3065 | hfs_journal_flush(hfsmp, FALSE); | |
3066 | ||
3067 | if (journal_uses_fua(hfsmp->jnl)) { | |
3068 | /* | |
3069 | * the journal_flush did NOT issue a sync track cache command, | |
3070 | * and the fullsync indicates we are supposed to flush all cached | |
3071 | * data to the media, so issue the sync track cache command | |
3072 | * explicitly | |
3073 | */ | |
3074 | VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, NULL); | |
3075 | } | |
3076 | } else { | |
3077 | retval = hfs_metasync_all(hfsmp); | |
3078 | /* XXX need to pass context! */ | |
3079 | VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCSYNCHRONIZECACHE, NULL, FWRITE, NULL); | |
3080 | } | |
55e303ae | 3081 | } |
9bccf70c A |
3082 | } |
3083 | ||
3084 | return (retval); | |
3085 | } | |
3086 | ||
91447636 | 3087 | |
9bccf70c | 3088 | /* Sync an hfs catalog b-tree node */ |
6d2010ae | 3089 | int |
2d21ac55 | 3090 | hfs_metasync(struct hfsmount *hfsmp, daddr64_t node, __unused struct proc *p) |
9bccf70c | 3091 | { |
91447636 A |
3092 | vnode_t vp; |
3093 | buf_t bp; | |
3094 | int lockflags; | |
9bccf70c A |
3095 | |
3096 | vp = HFSTOVCB(hfsmp)->catalogRefNum; | |
3097 | ||
b4c24cb9 A |
3098 | // XXXdbg - don't need to do this on a journaled volume |
3099 | if (hfsmp->jnl) { | |
3100 | return 0; | |
3101 | } | |
3102 | ||
91447636 | 3103 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK); |
9bccf70c A |
3104 | /* |
3105 | * Look for a matching node that has been delayed | |
3106 | * but is not part of a set (B_LOCKED). | |
91447636 A |
3107 | * |
3108 | * BLK_ONLYVALID causes buf_getblk to return a | |
3109 | * buf_t for the daddr64_t specified only if it's | |
3110 | * currently resident in the cache... the size | |
3111 | * parameter to buf_getblk is ignored when this flag | |
3112 | * is set | |
9bccf70c | 3113 | */ |
91447636 A |
3114 | bp = buf_getblk(vp, node, 0, 0, 0, BLK_META | BLK_ONLYVALID); |
3115 | ||
3116 | if (bp) { | |
3117 | if ((buf_flags(bp) & (B_LOCKED | B_DELWRI)) == B_DELWRI) | |
3118 | (void) VNOP_BWRITE(bp); | |
3119 | else | |
3120 | buf_brelse(bp); | |
9bccf70c | 3121 | } |
91447636 A |
3122 | |
3123 | hfs_systemfile_unlock(hfsmp, lockflags); | |
9bccf70c A |
3124 | |
3125 | return (0); | |
3126 | } | |
3127 | ||
91447636 | 3128 | |
2d21ac55 A |
3129 | /* |
3130 | * Sync all hfs B-trees. Use this instead of journal_flush for a volume | |
3131 | * without a journal. Note that the volume bitmap does not get written; | |
3132 | * we rely on fsck_hfs to fix that up (which it can do without any loss | |
3133 | * of data). | |
3134 | */ | |
6d2010ae | 3135 | int |
2d21ac55 A |
3136 | hfs_metasync_all(struct hfsmount *hfsmp) |
3137 | { | |
3138 | int lockflags; | |
3139 | ||
3140 | /* Lock all of the B-trees so we get a mutually consistent state */ | |
3141 | lockflags = hfs_systemfile_lock(hfsmp, | |
3142 | SFL_CATALOG|SFL_EXTENTS|SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); | |
3143 | ||
3144 | /* Sync each of the B-trees */ | |
3145 | if (hfsmp->hfs_catalog_vp) | |
3146 | hfs_btsync(hfsmp->hfs_catalog_vp, 0); | |
3147 | if (hfsmp->hfs_extents_vp) | |
3148 | hfs_btsync(hfsmp->hfs_extents_vp, 0); | |
3149 | if (hfsmp->hfs_attribute_vp) | |
3150 | hfs_btsync(hfsmp->hfs_attribute_vp, 0); | |
3151 | ||
3152 | /* Wait for all of the writes to complete */ | |
3153 | if (hfsmp->hfs_catalog_vp) | |
3154 | vnode_waitforwrites(hfsmp->hfs_catalog_vp, 0, 0, 0, "hfs_metasync_all"); | |
3155 | if (hfsmp->hfs_extents_vp) | |
3156 | vnode_waitforwrites(hfsmp->hfs_extents_vp, 0, 0, 0, "hfs_metasync_all"); | |
3157 | if (hfsmp->hfs_attribute_vp) | |
3158 | vnode_waitforwrites(hfsmp->hfs_attribute_vp, 0, 0, 0, "hfs_metasync_all"); | |
3159 | ||
3160 | hfs_systemfile_unlock(hfsmp, lockflags); | |
3161 | ||
3162 | return 0; | |
3163 | } | |
3164 | ||
3165 | ||
91447636 A |
3166 | /*ARGSUSED 1*/ |
3167 | static int | |
2d21ac55 | 3168 | hfs_btsync_callback(struct buf *bp, __unused void *dummy) |
91447636 A |
3169 | { |
3170 | buf_clearflags(bp, B_LOCKED); | |
3171 | (void) buf_bawrite(bp); | |
3172 | ||
3173 | return(BUF_CLAIMED); | |
3174 | } | |
3175 | ||
3176 | ||
9bccf70c A |
3177 | int |
3178 | hfs_btsync(struct vnode *vp, int sync_transaction) | |
3179 | { | |
3180 | struct cnode *cp = VTOC(vp); | |
9bccf70c | 3181 | struct timeval tv; |
91447636 | 3182 | int flags = 0; |
9bccf70c | 3183 | |
91447636 A |
3184 | if (sync_transaction) |
3185 | flags |= BUF_SKIP_NONLOCKED; | |
9bccf70c A |
3186 | /* |
3187 | * Flush all dirty buffers associated with b-tree. | |
3188 | */ | |
91447636 | 3189 | buf_iterate(vp, hfs_btsync_callback, flags, 0); |
b4c24cb9 | 3190 | |
91447636 A |
3191 | microuptime(&tv); |
3192 | if (vnode_issystem(vp) && (VTOF(vp)->fcbBTCBPtr != NULL)) | |
9bccf70c | 3193 | (void) BTSetLastSync(VTOF(vp), tv.tv_sec); |
91447636 A |
3194 | cp->c_touch_acctime = FALSE; |
3195 | cp->c_touch_chgtime = FALSE; | |
3196 | cp->c_touch_modtime = FALSE; | |
9bccf70c A |
3197 | |
3198 | return 0; | |
3199 | } | |
3200 | ||
3201 | /* | |
91447636 | 3202 | * Remove a directory. |
9bccf70c | 3203 | */ |
6d2010ae | 3204 | int |
91447636 A |
3205 | hfs_vnop_rmdir(ap) |
3206 | struct vnop_rmdir_args /* { | |
9bccf70c A |
3207 | struct vnode *a_dvp; |
3208 | struct vnode *a_vp; | |
3209 | struct componentname *a_cnp; | |
91447636 | 3210 | vfs_context_t a_context; |
9bccf70c A |
3211 | } */ *ap; |
3212 | { | |
91447636 A |
3213 | struct vnode *dvp = ap->a_dvp; |
3214 | struct vnode *vp = ap->a_vp; | |
2d21ac55 A |
3215 | struct cnode *dcp = VTOC(dvp); |
3216 | struct cnode *cp = VTOC(vp); | |
91447636 | 3217 | int error; |
6d2010ae A |
3218 | time_t orig_ctime; |
3219 | ||
3220 | orig_ctime = VTOC(vp)->c_ctime; | |
91447636 | 3221 | |
2d21ac55 | 3222 | if (!S_ISDIR(cp->c_mode)) { |
91447636 A |
3223 | return (ENOTDIR); |
3224 | } | |
3225 | if (dvp == vp) { | |
3226 | return (EINVAL); | |
3227 | } | |
6d2010ae A |
3228 | |
3229 | check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_DELETE_OP, NULL); | |
3230 | cp = VTOC(vp); | |
3231 | ||
2d21ac55 | 3232 | if ((error = hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK))) { |
91447636 | 3233 | return (error); |
2d21ac55 | 3234 | } |
b0d623f7 A |
3235 | |
3236 | /* Check for a race with rmdir on the parent directory */ | |
3237 | if (dcp->c_flag & (C_DELETED | C_NOEXISTS)) { | |
3238 | hfs_unlockpair (dcp, cp); | |
3239 | return ENOENT; | |
3240 | } | |
22ba694c A |
3241 | |
3242 | // | |
3243 | // if the item is tracked but doesn't have a document_id, assign one and generate an fsevent for it | |
3244 | // | |
3245 | if ((cp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id == 0) { | |
3246 | uint32_t newid; | |
3247 | ||
3248 | hfs_unlockpair(dcp, cp); | |
3249 | ||
3250 | if (hfs_generate_document_id(VTOHFS(vp), &newid) == 0) { | |
3251 | hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); | |
3252 | ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id = newid; | |
3253 | #if CONFIG_FSE | |
3254 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
3255 | FSE_ARG_DEV, VTOHFS(vp)->hfs_raw_dev, | |
3256 | FSE_ARG_INO, (ino64_t)0, // src inode # | |
3257 | FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # | |
3258 | FSE_ARG_INT32, newid, | |
3259 | FSE_ARG_DONE); | |
3260 | #endif | |
3261 | } else { | |
3262 | // XXXdbg - couldn't get a new docid... what to do? can't really fail the rm... | |
3263 | hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); | |
3264 | } | |
3265 | } | |
3266 | ||
6d2010ae | 3267 | error = hfs_removedir(dvp, vp, ap->a_cnp, 0, 0); |
b0d623f7 | 3268 | |
2d21ac55 | 3269 | hfs_unlockpair(dcp, cp); |
91447636 A |
3270 | |
3271 | return (error); | |
55e303ae A |
3272 | } |
3273 | ||
3274 | /* | |
91447636 A |
3275 | * Remove a directory |
3276 | * | |
3277 | * Both dvp and vp cnodes are locked | |
55e303ae | 3278 | */ |
6d2010ae | 3279 | int |
91447636 | 3280 | hfs_removedir(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, |
6d2010ae | 3281 | int skip_reserve, int only_unlink) |
55e303ae | 3282 | { |
9bccf70c A |
3283 | struct cnode *cp; |
3284 | struct cnode *dcp; | |
3285 | struct hfsmount * hfsmp; | |
91447636 | 3286 | struct cat_desc desc; |
91447636 | 3287 | int lockflags; |
2d21ac55 | 3288 | int error = 0, started_tr = 0; |
9bccf70c A |
3289 | |
3290 | cp = VTOC(vp); | |
3291 | dcp = VTOC(dvp); | |
3292 | hfsmp = VTOHFS(vp); | |
3293 | ||
2d21ac55 | 3294 | if (dcp == cp) { |
9bccf70c | 3295 | return (EINVAL); /* cannot remove "." */ |
2d21ac55 A |
3296 | } |
3297 | if (cp->c_flag & (C_NOEXISTS | C_DELETED)) { | |
3298 | return (0); | |
3299 | } | |
3300 | if (cp->c_entries != 0) { | |
3301 | return (ENOTEMPTY); | |
3302 | } | |
6d2010ae A |
3303 | |
3304 | /* | |
3305 | * If the directory is open or in use (e.g. opendir() or current working | |
3306 | * directory for some process); wait for inactive/reclaim to actually | |
3307 | * remove cnode from the catalog. Both inactive and reclaim codepaths are capable | |
3308 | * of removing open-unlinked directories from the catalog, as well as getting rid | |
3309 | * of EAs still on the element. So change only_unlink to true, so that it will get | |
3310 | * cleaned up below. | |
3311 | * | |
3312 | * Otherwise, we can get into a weird old mess where the directory has C_DELETED, | |
3313 | * but it really means C_NOEXISTS because the item was actually removed from the | |
3314 | * catalog. Then when we try to remove the entry from the catalog later on, it won't | |
3315 | * really be there anymore. | |
3316 | */ | |
3317 | if (vnode_isinuse(vp, 0)) { | |
3318 | only_unlink = 1; | |
3319 | } | |
2d21ac55 | 3320 | |
6d2010ae | 3321 | /* Deal with directory hardlinks */ |
2d21ac55 | 3322 | if (cp->c_flag & C_HARDLINK) { |
6d2010ae A |
3323 | /* |
3324 | * Note that if we have a directory which was a hardlink at any point, | |
3325 | * its actual directory data is stored in the directory inode in the hidden | |
3326 | * directory rather than the leaf element(s) present in the namespace. | |
3327 | * | |
3328 | * If there are still other hardlinks to this directory, | |
3329 | * then we'll just eliminate this particular link and the vnode will still exist. | |
3330 | * If this is the last link to an empty directory, then we'll open-unlink the | |
3331 | * directory and it will be only tagged with C_DELETED (as opposed to C_NOEXISTS). | |
3332 | * | |
3333 | * We could also return EBUSY here. | |
3334 | */ | |
3335 | ||
2d21ac55 A |
3336 | return hfs_unlink(hfsmp, dvp, vp, cnp, skip_reserve); |
3337 | } | |
b0d623f7 A |
3338 | |
3339 | /* | |
6d2010ae A |
3340 | * In a few cases, we may want to allow the directory to persist in an |
3341 | * open-unlinked state. If the directory is being open-unlinked (still has usecount | |
3342 | * references), or if it has EAs, or if it was being deleted as part of a rename, | |
3343 | * then we go ahead and move it to the hidden directory. | |
3344 | * | |
3345 | * If the directory is being open-unlinked, then we want to keep the catalog entry | |
3346 | * alive so that future EA calls and fchmod/fstat etc. do not cause issues later. | |
3347 | * | |
3348 | * If the directory had EAs, then we want to use the open-unlink trick so that the | |
3349 | * EA removal is not done in one giant transaction. Otherwise, it could cause a panic | |
3350 | * due to overflowing the journal. | |
3351 | * | |
3352 | * Finally, if it was deleted as part of a rename, we move it to the hidden directory | |
3353 | * in order to maintain rename atomicity. | |
3354 | * | |
3355 | * Note that the allow_dirs argument to hfs_removefile specifies that it is | |
3356 | * supposed to handle directories for this case. | |
3357 | */ | |
3358 | ||
3359 | if (((hfsmp->hfs_attribute_vp != NULL) && | |
3360 | ((cp->c_attr.ca_recflags & kHFSHasAttributesMask) != 0)) || | |
3361 | (only_unlink != 0)) { | |
3362 | ||
3363 | int ret = hfs_removefile(dvp, vp, cnp, 0, 0, 1, NULL, only_unlink); | |
3364 | /* | |
3365 | * Even though hfs_vnop_rename calls vnode_recycle for us on tvp we call | |
3366 | * it here just in case we were invoked by rmdir() on a directory that had | |
3367 | * EAs. To ensure that we start reclaiming the space as soon as possible, | |
3368 | * we call vnode_recycle on the directory. | |
3369 | */ | |
3370 | vnode_recycle(vp); | |
3371 | ||
3372 | return ret; | |
3373 | ||
2d21ac55 A |
3374 | } |
3375 | ||
3376 | dcp->c_flag |= C_DIR_MODIFICATION; | |
b4c24cb9 | 3377 | |
d7e50217 | 3378 | #if QUOTA |
2d21ac55 A |
3379 | if (hfsmp->hfs_flags & HFS_QUOTAS) |
3380 | (void)hfs_getinoquota(cp); | |
d7e50217 | 3381 | #endif |
91447636 A |
3382 | if ((error = hfs_start_transaction(hfsmp)) != 0) { |
3383 | goto out; | |
b4c24cb9 | 3384 | } |
91447636 | 3385 | started_tr = 1; |
b4c24cb9 | 3386 | |
9bccf70c A |
3387 | /* |
3388 | * Verify the directory is empty (and valid). | |
3389 | * (Rmdir ".." won't be valid since | |
3390 | * ".." will contain a reference to | |
3391 | * the current directory and thus be | |
3392 | * non-empty.) | |
3393 | */ | |
316670eb | 3394 | if ((dcp->c_bsdflags & APPEND) || (cp->c_bsdflags & (IMMUTABLE | APPEND))) { |
9bccf70c A |
3395 | error = EPERM; |
3396 | goto out; | |
3397 | } | |
3398 | ||
3399 | /* Remove the entry from the namei cache: */ | |
3400 | cache_purge(vp); | |
3401 | ||
91447636 A |
3402 | /* |
3403 | * Protect against a race with rename by using the component | |
3404 | * name passed in and parent id from dvp (instead of using | |
3405 | * the cp->c_desc which may have changed). | |
3406 | */ | |
2d21ac55 | 3407 | desc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr; |
91447636 | 3408 | desc.cd_namelen = cnp->cn_namelen; |
2d21ac55 | 3409 | desc.cd_parentcnid = dcp->c_fileid; |
91447636 | 3410 | desc.cd_cnid = cp->c_cnid; |
2d21ac55 A |
3411 | desc.cd_flags = CD_ISDIR; |
3412 | desc.cd_encoding = cp->c_encoding; | |
3413 | desc.cd_hint = 0; | |
3414 | ||
6d2010ae | 3415 | if (!hfs_valid_cnode(hfsmp, dvp, cnp, cp->c_fileid, NULL, &error)) { |
2d21ac55 A |
3416 | error = 0; |
3417 | goto out; | |
3418 | } | |
9bccf70c | 3419 | |
9bccf70c | 3420 | /* Remove entry from catalog */ |
2d21ac55 A |
3421 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); |
3422 | ||
3423 | if (!skip_reserve) { | |
3424 | /* | |
3425 | * Reserve some space in the Catalog file. | |
3426 | */ | |
3427 | if ((error = cat_preflight(hfsmp, CAT_DELETE, NULL, 0))) { | |
3428 | hfs_systemfile_unlock(hfsmp, lockflags); | |
3429 | goto out; | |
3430 | } | |
3431 | } | |
3432 | ||
91447636 | 3433 | error = cat_delete(hfsmp, &desc, &cp->c_attr); |
22ba694c A |
3434 | |
3435 | if (!error) { | |
3436 | // | |
3437 | // if skip_reserve == 1 then we're being called from hfs_vnop_rename() and thus | |
3438 | // we don't need to touch the document_id as it's handled by the rename code. | |
3439 | // otherwise it's a normal remove and we need to save the document id in the | |
3440 | // per thread struct and clear it from the cnode. | |
3441 | // | |
3442 | struct doc_tombstone *ut; | |
3443 | ut = get_uthread_doc_tombstone(); | |
3444 | if (!skip_reserve && (cp->c_bsdflags & UF_TRACKED) && should_save_docid_tombstone(ut, vp, cnp)) { | |
3445 | ||
3446 | if (ut->t_lastop_document_id) { | |
3447 | clear_tombstone_docid(ut, hfsmp, NULL); | |
3448 | } | |
3449 | save_tombstone(hfsmp, dvp, vp, cnp, 1); | |
3450 | ||
3451 | } | |
3452 | ||
2d21ac55 A |
3453 | /* The parent lost a child */ |
3454 | if (dcp->c_entries > 0) | |
3455 | dcp->c_entries--; | |
3456 | DEC_FOLDERCOUNT(hfsmp, dcp->c_attr); | |
3457 | dcp->c_dirchangecnt++; | |
fe8ab488 A |
3458 | hfs_incr_gencount(dcp); |
3459 | ||
2d21ac55 A |
3460 | dcp->c_touch_chgtime = TRUE; |
3461 | dcp->c_touch_modtime = TRUE; | |
3462 | hfs_touchtimes(hfsmp, cp); | |
3463 | (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); | |
3464 | cp->c_flag &= ~(C_MODIFIED | C_FORCEUPDATE); | |
91447636 | 3465 | } |
2d21ac55 | 3466 | |
91447636 | 3467 | hfs_systemfile_unlock(hfsmp, lockflags); |
9bccf70c | 3468 | |
91447636 A |
3469 | if (error) |
3470 | goto out; | |
9bccf70c A |
3471 | |
3472 | #if QUOTA | |
2d21ac55 A |
3473 | if (hfsmp->hfs_flags & HFS_QUOTAS) |
3474 | (void)hfs_chkiq(cp, -1, NOCRED, 0); | |
9bccf70c A |
3475 | #endif /* QUOTA */ |
3476 | ||
9bccf70c A |
3477 | hfs_volupdate(hfsmp, VOL_RMDIR, (dcp->c_cnid == kHFSRootFolderID)); |
3478 | ||
6d2010ae A |
3479 | /* Mark C_NOEXISTS since the catalog entry is now gone */ |
3480 | cp->c_flag |= C_NOEXISTS; | |
fe8ab488 | 3481 | |
9bccf70c | 3482 | out: |
2d21ac55 A |
3483 | dcp->c_flag &= ~C_DIR_MODIFICATION; |
3484 | wakeup((caddr_t)&dcp->c_flag); | |
3485 | ||
b4c24cb9 | 3486 | if (started_tr) { |
91447636 | 3487 | hfs_end_transaction(hfsmp); |
b4c24cb9 A |
3488 | } |
3489 | ||
9bccf70c A |
3490 | return (error); |
3491 | } | |
3492 | ||
9bccf70c | 3493 | |
91447636 A |
3494 | /* |
3495 | * Remove a file or link. | |
3496 | */ | |
6d2010ae | 3497 | int |
91447636 A |
3498 | hfs_vnop_remove(ap) |
3499 | struct vnop_remove_args /* { | |
9bccf70c A |
3500 | struct vnode *a_dvp; |
3501 | struct vnode *a_vp; | |
3502 | struct componentname *a_cnp; | |
91447636 A |
3503 | int a_flags; |
3504 | vfs_context_t a_context; | |
9bccf70c A |
3505 | } */ *ap; |
3506 | { | |
91447636 A |
3507 | struct vnode *dvp = ap->a_dvp; |
3508 | struct vnode *vp = ap->a_vp; | |
2d21ac55 | 3509 | struct cnode *dcp = VTOC(dvp); |
6d2010ae | 3510 | struct cnode *cp; |
b0d623f7 | 3511 | struct vnode *rvp = NULL; |
b0d623f7 | 3512 | int error=0, recycle_rsrc=0; |
39236c6e | 3513 | int recycle_vnode = 0; |
316670eb | 3514 | uint32_t rsrc_vid = 0; |
39236c6e | 3515 | time_t orig_ctime; |
91447636 A |
3516 | |
3517 | if (dvp == vp) { | |
3518 | return (EINVAL); | |
3519 | } | |
3520 | ||
6d2010ae | 3521 | orig_ctime = VTOC(vp)->c_ctime; |
39236c6e | 3522 | if (!vnode_isnamedstream(vp) && ((ap->a_flags & VNODE_REMOVE_SKIP_NAMESPACE_EVENT) == 0)) { |
6d2010ae A |
3523 | error = check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_DELETE_OP, NULL); |
3524 | if (error) { | |
3525 | // XXXdbg - decide on a policy for handling namespace handler failures! | |
3526 | // for now we just let them proceed. | |
3527 | } | |
3528 | } | |
3529 | error = 0; | |
3530 | ||
3531 | cp = VTOC(vp); | |
3532 | ||
316670eb A |
3533 | relock: |
3534 | ||
39236c6e | 3535 | hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
316670eb A |
3536 | |
3537 | if ((error = hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK))) { | |
39236c6e | 3538 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
316670eb A |
3539 | if (rvp) { |
3540 | vnode_put (rvp); | |
3541 | } | |
3542 | return (error); | |
3543 | } | |
22ba694c A |
3544 | // |
3545 | // if the item is tracked but doesn't have a document_id, assign one and generate an fsevent for it | |
3546 | // | |
3547 | if ((cp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id == 0) { | |
3548 | uint32_t newid; | |
3549 | ||
3550 | hfs_unlockpair(dcp, cp); | |
3551 | ||
3552 | if (hfs_generate_document_id(VTOHFS(vp), &newid) == 0) { | |
3553 | hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); | |
3554 | ((struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16))->document_id = newid; | |
3555 | #if CONFIG_FSE | |
3556 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
3557 | FSE_ARG_DEV, VTOHFS(vp)->hfs_raw_dev, | |
3558 | FSE_ARG_INO, (ino64_t)0, // src inode # | |
3559 | FSE_ARG_INO, (ino64_t)cp->c_fileid, // dst inode # | |
3560 | FSE_ARG_INT32, newid, | |
3561 | FSE_ARG_DONE); | |
3562 | #endif | |
3563 | } else { | |
3564 | // XXXdbg - couldn't get a new docid... what to do? can't really fail the rm... | |
3565 | hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK); | |
3566 | } | |
3567 | } | |
316670eb A |
3568 | |
3569 | /* | |
3570 | * Lazily respond to determining if there is a valid resource fork | |
3571 | * vnode attached to 'cp' if it is a regular file or symlink. | |
3572 | * If the vnode does not exist, then we may proceed without having to | |
3573 | * create it. | |
3574 | * | |
3575 | * If, however, it does exist, then we need to acquire an iocount on the | |
3576 | * vnode after acquiring its vid. This ensures that if we have to do I/O | |
3577 | * against it, it can't get recycled from underneath us in the middle | |
3578 | * of this call. | |
3579 | * | |
b0d623f7 A |
3580 | * Note: this function may be invoked for directory hardlinks, so just skip these |
3581 | * steps if 'vp' is a directory. | |
3582 | */ | |
3583 | ||
b0d623f7 | 3584 | if ((vp->v_type == VLNK) || (vp->v_type == VREG)) { |
316670eb A |
3585 | if ((cp->c_rsrc_vp) && (rvp == NULL)) { |
3586 | /* We need to acquire the rsrc vnode */ | |
3587 | rvp = cp->c_rsrc_vp; | |
3588 | rsrc_vid = vnode_vid (rvp); | |
39236c6e | 3589 | |
316670eb | 3590 | /* Unlock everything to acquire iocount on the rsrc vnode */ |
39236c6e | 3591 | hfs_unlock_truncate (cp, HFS_LOCK_DEFAULT); |
316670eb | 3592 | hfs_unlockpair (dcp, cp); |
316670eb A |
3593 | /* Use the vid to maintain identity on rvp */ |
3594 | if (vnode_getwithvid(rvp, rsrc_vid)) { | |
3595 | /* | |
3596 | * If this fails, then it was recycled or | |
3597 | * reclaimed in the interim. Reset fields and | |
3598 | * start over. | |
3599 | */ | |
b7266188 | 3600 | rvp = NULL; |
316670eb | 3601 | rsrc_vid = 0; |
b7266188 | 3602 | } |
316670eb | 3603 | goto relock; |
b0d623f7 | 3604 | } |
4a3eedf9 | 3605 | } |
b0d623f7 A |
3606 | |
3607 | /* | |
3608 | * Check to see if we raced rmdir for the parent directory | |
3609 | * hfs_removefile already checks for a race on vp/cp | |
3610 | */ | |
3611 | if (dcp->c_flag & (C_DELETED | C_NOEXISTS)) { | |
3612 | error = ENOENT; | |
3613 | goto rm_done; | |
3614 | } | |
3615 | ||
316670eb | 3616 | error = hfs_removefile(dvp, vp, ap->a_cnp, ap->a_flags, 0, 0, NULL, 0); |
6d2010ae A |
3617 | |
3618 | /* | |
3619 | * If the remove succeeded in deleting the file, then we may need to mark | |
3620 | * the resource fork for recycle so that it is reclaimed as quickly | |
3621 | * as possible. If it were not recycled quickly, then this resource fork | |
3622 | * vnode could keep a v_parent reference on the data fork, which prevents it | |
3623 | * from going through reclaim (by giving it extra usecounts), except in the force- | |
3624 | * unmount case. | |
3625 | * | |
3626 | * However, a caveat: we need to continue to supply resource fork | |
3627 | * access to open-unlinked files even if the resource fork is not open. This is | |
3628 | * a requirement for the compressed files work. Luckily, hfs_vgetrsrc will handle | |
3629 | * this already if the data fork has been re-parented to the hidden directory. | |
3630 | * | |
3631 | * As a result, all we really need to do here is mark the resource fork vnode | |
3632 | * for recycle. If it goes out of core, it can be brought in again if needed. | |
3633 | * If the cnode was instead marked C_NOEXISTS, then there wouldn't be any | |
3634 | * more work. | |
3635 | */ | |
39236c6e A |
3636 | if (error == 0) { |
3637 | if (rvp) { | |
3638 | recycle_rsrc = 1; | |
3639 | } | |
3640 | /* | |
3641 | * If the target was actually removed from the catalog schedule it for | |
3642 | * full reclamation/inactivation. We hold an iocount on it so it should just | |
3643 | * get marked with MARKTERM | |
3644 | */ | |
3645 | if (cp->c_flag & C_NOEXISTS) { | |
3646 | recycle_vnode = 1; | |
3647 | } | |
2d21ac55 | 3648 | } |
91447636 | 3649 | |
39236c6e | 3650 | |
4a3eedf9 A |
3651 | /* |
3652 | * Drop the truncate lock before unlocking the cnode | |
3653 | * (which can potentially perform a vnode_put and | |
3654 | * recycle the vnode which in turn might require the | |
3655 | * truncate lock) | |
3656 | */ | |
b0d623f7 | 3657 | rm_done: |
4a3eedf9 | 3658 | hfs_unlockpair(dcp, cp); |
04b8595b | 3659 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
2d21ac55 | 3660 | |
b0d623f7 | 3661 | if (recycle_rsrc) { |
6d2010ae | 3662 | /* inactive or reclaim on rvp will clean up the blocks from the rsrc fork */ |
2d21ac55 | 3663 | vnode_recycle(rvp); |
2d21ac55 | 3664 | } |
39236c6e A |
3665 | if (recycle_vnode) { |
3666 | vnode_recycle (vp); | |
3667 | } | |
3668 | ||
316670eb | 3669 | if (rvp) { |
b0d623f7 A |
3670 | /* drop iocount on rsrc fork, was obtained at beginning of fxn */ |
3671 | vnode_put(rvp); | |
3672 | } | |
3673 | ||
91447636 | 3674 | return (error); |
55e303ae A |
3675 | } |
3676 | ||
3677 | ||
316670eb | 3678 | int |
91447636 A |
3679 | hfs_removefile_callback(struct buf *bp, void *hfsmp) { |
3680 | ||
3681 | if ( !(buf_flags(bp) & B_META)) | |
2d21ac55 | 3682 | panic("hfs: symlink bp @ %p is not marked meta-data!\n", bp); |
91447636 A |
3683 | /* |
3684 | * it's part of the current transaction, kill it. | |
3685 | */ | |
3686 | journal_kill_block(((struct hfsmount *)hfsmp)->jnl, bp); | |
3687 | ||
3688 | return (BUF_CLAIMED); | |
3689 | } | |
55e303ae A |
3690 | |
3691 | /* | |
3692 | * hfs_removefile | |
3693 | * | |
91447636 | 3694 | * Similar to hfs_vnop_remove except there are additional options. |
b0d623f7 A |
3695 | * This function may be used to remove directories if they have |
3696 | * lots of EA's -- note the 'allow_dirs' argument. | |
3697 | * | |
316670eb A |
3698 | * This function is able to delete blocks & fork data for the resource |
3699 | * fork even if it does not exist in core (and have a backing vnode). | |
3700 | * It should infer the correct behavior based on the number of blocks | |
3701 | * in the cnode and whether or not the resource fork pointer exists or | |
3702 | * not. As a result, one only need pass in the 'vp' corresponding to the | |
3703 | * data fork of this file (or main vnode in the case of a directory). | |
3704 | * Passing in a resource fork will result in an error. | |
3705 | * | |
3706 | * Because we do not create any vnodes in this function, we are not at | |
3707 | * risk of deadlocking against ourselves by double-locking. | |
91447636 A |
3708 | * |
3709 | * Requires cnode and truncate locks to be held. | |
55e303ae | 3710 | */ |
6d2010ae | 3711 | int |
91447636 | 3712 | hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp, |
6d2010ae | 3713 | int flags, int skip_reserve, int allow_dirs, |
316670eb | 3714 | __unused struct vnode *rvp, int only_unlink) |
55e303ae | 3715 | { |
9bccf70c A |
3716 | struct cnode *cp; |
3717 | struct cnode *dcp; | |
316670eb | 3718 | struct vnode *rsrc_vp = NULL; |
9bccf70c | 3719 | struct hfsmount *hfsmp; |
91447636 A |
3720 | struct cat_desc desc; |
3721 | struct timeval tv; | |
9bccf70c A |
3722 | int dataforkbusy = 0; |
3723 | int rsrcforkbusy = 0; | |
91447636 | 3724 | int lockflags; |
9bccf70c | 3725 | int error = 0; |
2d21ac55 | 3726 | int started_tr = 0; |
4a3eedf9 | 3727 | int isbigfile = 0, defer_remove=0, isdir=0; |
6d2010ae | 3728 | int update_vh = 0; |
316670eb | 3729 | |
9bccf70c A |
3730 | cp = VTOC(vp); |
3731 | dcp = VTOC(dvp); | |
3732 | hfsmp = VTOHFS(vp); | |
91447636 | 3733 | |
2d21ac55 | 3734 | /* Check if we lost a race post lookup. */ |
91447636 | 3735 | if (cp->c_flag & (C_NOEXISTS | C_DELETED)) { |
2d21ac55 | 3736 | return (0); |
91447636 | 3737 | } |
2d21ac55 | 3738 | |
6d2010ae | 3739 | if (!hfs_valid_cnode(hfsmp, dvp, cnp, cp->c_fileid, NULL, &error)) { |
2d21ac55 | 3740 | return 0; |
9bccf70c A |
3741 | } |
3742 | ||
3743 | /* Make sure a remove is permitted */ | |
91447636 | 3744 | if (VNODE_IS_RSRC(vp)) { |
2d21ac55 | 3745 | return (EPERM); |
b4c24cb9 | 3746 | } |
316670eb A |
3747 | else { |
3748 | /* | |
3749 | * We know it's a data fork. | |
3750 | * Probe the cnode to see if we have a valid resource fork | |
3751 | * in hand or not. | |
3752 | */ | |
3753 | rsrc_vp = cp->c_rsrc_vp; | |
3754 | } | |
3755 | ||
2d21ac55 | 3756 | /* Don't allow deleting the journal or journal_info_block. */ |
316670eb | 3757 | if (hfs_is_journal_file(hfsmp, cp)) { |
2d21ac55 A |
3758 | return (EPERM); |
3759 | } | |
316670eb | 3760 | |
39236c6e | 3761 | /* |
316670eb A |
3762 | * If removing a symlink, then we need to ensure that the |
3763 | * data blocks for the symlink are not still in-flight or pending. | |
3764 | * If so, we will unlink the symlink here, making its blocks | |
3765 | * available for re-allocation by a subsequent transaction. That is OK, but | |
3766 | * then the I/O for the data blocks could then go out before the journal | |
3767 | * transaction that created it was flushed, leading to I/O ordering issues. | |
3768 | */ | |
3769 | if (vp->v_type == VLNK) { | |
3770 | /* | |
3771 | * This will block if the asynchronous journal flush is in progress. | |
3772 | * If this symlink is not being renamed over and doesn't have any open FDs, | |
3773 | * then we'll remove it from the journal's bufs below in kill_block. | |
3774 | */ | |
3775 | buf_wait_for_shadow_io (vp, 0); | |
3776 | } | |
3777 | ||
2d21ac55 A |
3778 | /* |
3779 | * Hard links require special handling. | |
3780 | */ | |
3781 | if (cp->c_flag & C_HARDLINK) { | |
3782 | if ((flags & VNODE_REMOVE_NODELETEBUSY) && vnode_isinuse(vp, 0)) { | |
3783 | return (EBUSY); | |
3784 | } else { | |
3785 | /* A directory hard link with a link count of one is | |
3786 | * treated as a regular directory. Therefore it should | |
3787 | * only be removed using rmdir(). | |
3788 | */ | |
3789 | if ((vnode_isdir(vp) == 1) && (cp->c_linkcount == 1) && | |
3790 | (allow_dirs == 0)) { | |
3791 | return (EPERM); | |
3792 | } | |
3793 | return hfs_unlink(hfsmp, dvp, vp, cnp, skip_reserve); | |
3794 | } | |
3795 | } | |
316670eb | 3796 | |
2d21ac55 A |
3797 | /* Directories should call hfs_rmdir! (unless they have a lot of attributes) */ |
3798 | if (vnode_isdir(vp)) { | |
3799 | if (allow_dirs == 0) | |
3800 | return (EPERM); /* POSIX */ | |
3801 | isdir = 1; | |
3802 | } | |
3803 | /* Sanity check the parent ids. */ | |
3804 | if ((cp->c_parentcnid != hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) && | |
3805 | (cp->c_parentcnid != dcp->c_fileid)) { | |
3806 | return (EINVAL); | |
3807 | } | |
3808 | ||
3809 | dcp->c_flag |= C_DIR_MODIFICATION; | |
3810 | ||
3811 | // this guy is going away so mark him as such | |
3812 | cp->c_flag |= C_DELETED; | |
3813 | ||
3814 | ||
3815 | /* Remove our entry from the namei cache. */ | |
3816 | cache_purge(vp); | |
316670eb | 3817 | |
9bccf70c | 3818 | /* |
316670eb A |
3819 | * If the caller was operating on a file (as opposed to a |
3820 | * directory with EAs), then we need to figure out | |
3821 | * whether or not it has a valid resource fork vnode. | |
3822 | * | |
3823 | * If there was a valid resource fork vnode, then we need | |
3824 | * to use hfs_truncate to eliminate its data. If there is | |
3825 | * no vnode, then we hold the cnode lock which would | |
3826 | * prevent it from being created. As a result, | |
3827 | * we can use the data deletion functions which do not | |
3828 | * require that a cnode/vnode pair exist. | |
9bccf70c | 3829 | */ |
b0d623f7 | 3830 | |
2d21ac55 A |
3831 | /* Check if this file is being used. */ |
3832 | if (isdir == 0) { | |
3833 | dataforkbusy = vnode_isinuse(vp, 0); | |
316670eb A |
3834 | /* |
3835 | * At this point, we know that 'vp' points to the | |
3836 | * a data fork because we checked it up front. And if | |
3837 | * there is no rsrc fork, rsrc_vp will be NULL. | |
3838 | */ | |
3839 | if (rsrc_vp && (cp->c_blocks - VTOF(vp)->ff_blocks)) { | |
3840 | rsrcforkbusy = vnode_isinuse(rsrc_vp, 0); | |
b0d623f7 | 3841 | } |
2d21ac55 A |
3842 | } |
3843 | ||
3844 | /* Check if we have to break the deletion into multiple pieces. */ | |
fe8ab488 A |
3845 | if (isdir == 0) |
3846 | isbigfile = cp->c_datafork->ff_size >= HFS_BIGFILE_SIZE; | |
b4c24cb9 | 3847 | |
2d21ac55 A |
3848 | /* Check if the file has xattrs. If it does we'll have to delete them in |
3849 | individual transactions in case there are too many */ | |
3850 | if ((hfsmp->hfs_attribute_vp != NULL) && | |
3851 | (cp->c_attr.ca_recflags & kHFSHasAttributesMask) != 0) { | |
4a3eedf9 | 3852 | defer_remove = 1; |
2d21ac55 | 3853 | } |
6d2010ae A |
3854 | |
3855 | /* If we are explicitly told to only unlink item and move to hidden dir, then do it */ | |
3856 | if (only_unlink) { | |
3857 | defer_remove = 1; | |
3858 | } | |
55e303ae | 3859 | |
9bccf70c A |
3860 | /* |
3861 | * Carbon semantics prohibit deleting busy files. | |
91447636 | 3862 | * (enforced when VNODE_REMOVE_NODELETEBUSY is requested) |
9bccf70c | 3863 | */ |
91447636 A |
3864 | if (dataforkbusy || rsrcforkbusy) { |
3865 | if ((flags & VNODE_REMOVE_NODELETEBUSY) || | |
2d21ac55 | 3866 | (hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid == 0)) { |
91447636 A |
3867 | error = EBUSY; |
3868 | goto out; | |
3869 | } | |
9bccf70c A |
3870 | } |
3871 | ||
d7e50217 | 3872 | #if QUOTA |
2d21ac55 A |
3873 | if (hfsmp->hfs_flags & HFS_QUOTAS) |
3874 | (void)hfs_getinoquota(cp); | |
d7e50217 | 3875 | #endif /* QUOTA */ |
6d2010ae A |
3876 | |
3877 | /* | |
3878 | * Do a ubc_setsize to indicate we need to wipe contents if: | |
3879 | * 1) item is a regular file. | |
3880 | * 2) Neither fork is busy AND we are not told to unlink this. | |
3881 | * | |
3882 | * We need to check for the defer_remove since it can be set without | |
3883 | * having a busy data or rsrc fork | |
3884 | */ | |
3885 | if (isdir == 0 && (!dataforkbusy || !rsrcforkbusy) && (defer_remove == 0)) { | |
91447636 | 3886 | /* |
0c530ab8 A |
3887 | * A ubc_setsize can cause a pagein so defer it |
3888 | * until after the cnode lock is dropped. The | |
3889 | * cnode lock cannot be dropped/reacquired here | |
3890 | * since we might already hold the journal lock. | |
91447636 | 3891 | */ |
91447636 | 3892 | if (!dataforkbusy && cp->c_datafork->ff_blocks && !isbigfile) { |
0c530ab8 | 3893 | cp->c_flag |= C_NEED_DATA_SETSIZE; |
91447636 | 3894 | } |
316670eb | 3895 | if (!rsrcforkbusy && rsrc_vp) { |
0c530ab8 | 3896 | cp->c_flag |= C_NEED_RSRC_SETSIZE; |
91447636 | 3897 | } |
91447636 A |
3898 | } |
3899 | ||
3900 | if ((error = hfs_start_transaction(hfsmp)) != 0) { | |
3901 | goto out; | |
b4c24cb9 | 3902 | } |
91447636 | 3903 | started_tr = 1; |
b4c24cb9 | 3904 | |
b4c24cb9 | 3905 | // XXXdbg - if we're journaled, kill any dirty symlink buffers |
6d2010ae | 3906 | if (hfsmp->jnl && vnode_islnk(vp) && (defer_remove == 0)) { |
91447636 | 3907 | buf_iterate(vp, hfs_removefile_callback, BUF_SKIP_NONLOCKED, (void *)hfsmp); |
6d2010ae | 3908 | } |
b4c24cb9 | 3909 | |
9bccf70c | 3910 | /* |
6d2010ae | 3911 | * Prepare to truncate any non-busy forks. Busy forks will |
2d21ac55 | 3912 | * get truncated when their vnode goes inactive. |
b0d623f7 A |
3913 | * Note that we will only enter this region if we |
3914 | * can avoid creating an open-unlinked file. If | |
3915 | * either region is busy, we will have to create an open | |
3916 | * unlinked file. | |
6d2010ae A |
3917 | * |
3918 | * Since we are deleting the file, we need to stagger the runtime | |
3919 | * modifications to do things in such a way that a crash won't | |
3920 | * result in us getting overlapped extents or any other | |
3921 | * bad inconsistencies. As such, we call prepare_release_storage | |
3922 | * which updates the UBC, updates quota information, and releases | |
3923 | * any loaned blocks that belong to this file. No actual | |
3924 | * truncation or bitmap manipulation is done until *AFTER* | |
3925 | * the catalog record is removed. | |
9bccf70c | 3926 | */ |
6d2010ae A |
3927 | if (isdir == 0 && (!dataforkbusy && !rsrcforkbusy) && (only_unlink == 0)) { |
3928 | ||
55e303ae | 3929 | if (!dataforkbusy && !isbigfile && cp->c_datafork->ff_blocks != 0) { |
6d2010ae A |
3930 | |
3931 | error = hfs_prepare_release_storage (hfsmp, vp); | |
3932 | if (error) { | |
9bccf70c | 3933 | goto out; |
6d2010ae A |
3934 | } |
3935 | update_vh = 1; | |
9bccf70c | 3936 | } |
316670eb A |
3937 | |
3938 | /* | |
3939 | * If the resource fork vnode does not exist, we can skip this step. | |
3940 | */ | |
3941 | if (!rsrcforkbusy && rsrc_vp) { | |
3942 | error = hfs_prepare_release_storage (hfsmp, rsrc_vp); | |
6d2010ae | 3943 | if (error) { |
9bccf70c | 3944 | goto out; |
6d2010ae A |
3945 | } |
3946 | update_vh = 1; | |
9bccf70c A |
3947 | } |
3948 | } | |
6d2010ae | 3949 | |
91447636 A |
3950 | /* |
3951 | * Protect against a race with rename by using the component | |
3952 | * name passed in and parent id from dvp (instead of using | |
b0d623f7 A |
3953 | * the cp->c_desc which may have changed). Also, be aware that |
3954 | * because we allow directories to be passed in, we need to special case | |
3955 | * this temporary descriptor in case we were handed a directory. | |
91447636 | 3956 | */ |
b0d623f7 A |
3957 | if (isdir) { |
3958 | desc.cd_flags = CD_ISDIR; | |
3959 | } | |
3960 | else { | |
3961 | desc.cd_flags = 0; | |
3962 | } | |
91447636 | 3963 | desc.cd_encoding = cp->c_desc.cd_encoding; |
2d21ac55 | 3964 | desc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr; |
91447636 | 3965 | desc.cd_namelen = cnp->cn_namelen; |
2d21ac55 | 3966 | desc.cd_parentcnid = dcp->c_fileid; |
91447636 | 3967 | desc.cd_hint = cp->c_desc.cd_hint; |
2d21ac55 | 3968 | desc.cd_cnid = cp->c_cnid; |
91447636 A |
3969 | microtime(&tv); |
3970 | ||
9bccf70c | 3971 | /* |
2d21ac55 | 3972 | * There are two cases to consider: |
b0d623f7 | 3973 | * 1. File/Dir is busy/big/defer_remove ==> move/rename the file/dir |
2d21ac55 | 3974 | * 2. File is not in use ==> remove the file |
b0d623f7 A |
3975 | * |
3976 | * We can get a directory in case 1 because it may have had lots of attributes, | |
3977 | * which need to get removed here. | |
9bccf70c | 3978 | */ |
4a3eedf9 | 3979 | if (dataforkbusy || rsrcforkbusy || isbigfile || defer_remove) { |
9bccf70c A |
3980 | char delname[32]; |
3981 | struct cat_desc to_desc; | |
3982 | struct cat_desc todir_desc; | |
3983 | ||
3984 | /* | |
b0d623f7 A |
3985 | * Orphan this file or directory (move to hidden directory). |
3986 | * Again, we need to take care that we treat directories as directories, | |
3987 | * and files as files. Because directories with attributes can be passed in | |
3988 | * check to make sure that we have a directory or a file before filling in the | |
3989 | * temporary descriptor's flags. We keep orphaned directories AND files in | |
3990 | * the FILE_HARDLINKS private directory since we're generalizing over all | |
3991 | * orphaned filesystem objects. | |
9bccf70c A |
3992 | */ |
3993 | bzero(&todir_desc, sizeof(todir_desc)); | |
3994 | todir_desc.cd_parentcnid = 2; | |
3995 | ||
2d21ac55 | 3996 | MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid); |
9bccf70c | 3997 | bzero(&to_desc, sizeof(to_desc)); |
2d21ac55 | 3998 | to_desc.cd_nameptr = (const u_int8_t *)delname; |
9bccf70c | 3999 | to_desc.cd_namelen = strlen(delname); |
2d21ac55 | 4000 | to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid; |
b0d623f7 A |
4001 | if (isdir) { |
4002 | to_desc.cd_flags = CD_ISDIR; | |
4003 | } | |
4004 | else { | |
4005 | to_desc.cd_flags = 0; | |
4006 | } | |
9bccf70c A |
4007 | to_desc.cd_cnid = cp->c_cnid; |
4008 | ||
91447636 | 4009 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK); |
2d21ac55 A |
4010 | if (!skip_reserve) { |
4011 | if ((error = cat_preflight(hfsmp, CAT_RENAME, NULL, 0))) { | |
4012 | hfs_systemfile_unlock(hfsmp, lockflags); | |
4013 | goto out; | |
4014 | } | |
4015 | } | |
9bccf70c | 4016 | |
91447636 | 4017 | error = cat_rename(hfsmp, &desc, &todir_desc, |
9bccf70c A |
4018 | &to_desc, (struct cat_desc *)NULL); |
4019 | ||
b4c24cb9 | 4020 | if (error == 0) { |
2d21ac55 A |
4021 | hfsmp->hfs_private_attr[FILE_HARDLINKS].ca_entries++; |
4022 | if (isdir == 1) { | |
4023 | INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[FILE_HARDLINKS]); | |
4024 | } | |
4025 | (void) cat_update(hfsmp, &hfsmp->hfs_private_desc[FILE_HARDLINKS], | |
4026 | &hfsmp->hfs_private_attr[FILE_HARDLINKS], NULL, NULL); | |
91447636 A |
4027 | |
4028 | /* Update the parent directory */ | |
4029 | if (dcp->c_entries > 0) | |
4030 | dcp->c_entries--; | |
2d21ac55 A |
4031 | if (isdir == 1) { |
4032 | DEC_FOLDERCOUNT(hfsmp, dcp->c_attr); | |
4033 | } | |
4034 | dcp->c_dirchangecnt++; | |
fe8ab488 A |
4035 | hfs_incr_gencount(dcp); |
4036 | ||
91447636 A |
4037 | dcp->c_ctime = tv.tv_sec; |
4038 | dcp->c_mtime = tv.tv_sec; | |
4039 | (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); | |
4040 | ||
b0d623f7 | 4041 | /* Update the file or directory's state */ |
91447636 A |
4042 | cp->c_flag |= C_DELETED; |
4043 | cp->c_ctime = tv.tv_sec; | |
2d21ac55 | 4044 | --cp->c_linkcount; |
91447636 | 4045 | (void) cat_update(hfsmp, &to_desc, &cp->c_attr, NULL, NULL); |
b4c24cb9 | 4046 | } |
91447636 A |
4047 | hfs_systemfile_unlock(hfsmp, lockflags); |
4048 | if (error) | |
4049 | goto out; | |
9bccf70c | 4050 | |
6d2010ae | 4051 | } |
316670eb A |
4052 | else { |
4053 | /* | |
4054 | * Nobody is using this item; we can safely remove everything. | |
4055 | */ | |
4056 | struct filefork *temp_rsrc_fork = NULL; | |
6d2010ae A |
4057 | #if QUOTA |
4058 | off_t savedbytes; | |
4059 | int blksize = hfsmp->blockSize; | |
4060 | #endif | |
4061 | u_int32_t fileid = cp->c_fileid; | |
316670eb A |
4062 | |
4063 | /* | |
4064 | * Figure out if we need to read the resource fork data into | |
4065 | * core before wiping out the catalog record. | |
4066 | * | |
4067 | * 1) Must not be a directory | |
4068 | * 2) cnode's c_rsrcfork ptr must be NULL. | |
4069 | * 3) rsrc fork must have actual blocks | |
4070 | */ | |
4071 | if ((isdir == 0) && (cp->c_rsrcfork == NULL) && | |
4072 | (cp->c_blocks - VTOF(vp)->ff_blocks)) { | |
4073 | /* | |
4074 | * The resource fork vnode & filefork did not exist. | |
4075 | * Create a temporary one for use in this function only. | |
4076 | */ | |
4077 | MALLOC_ZONE (temp_rsrc_fork, struct filefork *, sizeof (struct filefork), M_HFSFORK, M_WAITOK); | |
4078 | bzero(temp_rsrc_fork, sizeof(struct filefork)); | |
4079 | temp_rsrc_fork->ff_cp = cp; | |
4080 | rl_init(&temp_rsrc_fork->ff_invalidranges); | |
4081 | } | |
4082 | ||
2d21ac55 | 4083 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); |
316670eb A |
4084 | |
4085 | /* Look up the resource fork first, if necessary */ | |
4086 | if (temp_rsrc_fork) { | |
39236c6e | 4087 | error = cat_lookup (hfsmp, &desc, 1, 0, (struct cat_desc*) NULL, |
316670eb A |
4088 | (struct cat_attr*) NULL, &temp_rsrc_fork->ff_data, NULL); |
4089 | if (error) { | |
4090 | FREE_ZONE (temp_rsrc_fork, sizeof(struct filefork), M_HFSFORK); | |
4091 | hfs_systemfile_unlock (hfsmp, lockflags); | |
4092 | goto out; | |
4093 | } | |
4094 | } | |
4095 | ||
2d21ac55 A |
4096 | if (!skip_reserve) { |
4097 | if ((error = cat_preflight(hfsmp, CAT_DELETE, NULL, 0))) { | |
316670eb A |
4098 | if (temp_rsrc_fork) { |
4099 | FREE_ZONE (temp_rsrc_fork, sizeof(struct filefork), M_HFSFORK); | |
4100 | } | |
2d21ac55 A |
4101 | hfs_systemfile_unlock(hfsmp, lockflags); |
4102 | goto out; | |
4103 | } | |
4104 | } | |
6d2010ae | 4105 | |
91447636 | 4106 | error = cat_delete(hfsmp, &desc, &cp->c_attr); |
6d2010ae A |
4107 | |
4108 | if (error && error != ENXIO && error != ENOENT) { | |
39236c6e A |
4109 | printf("hfs_removefile: deleting file %s (id=%d) vol=%s err=%d\n", |
4110 | cp->c_desc.cd_nameptr, cp->c_attr.ca_fileid, hfsmp->vcbVN, error); | |
b4c24cb9 | 4111 | } |
6d2010ae | 4112 | |
91447636 | 4113 | if (error == 0) { |
91447636 A |
4114 | /* Update the parent directory */ |
4115 | if (dcp->c_entries > 0) | |
4116 | dcp->c_entries--; | |
2d21ac55 | 4117 | dcp->c_dirchangecnt++; |
fe8ab488 A |
4118 | hfs_incr_gencount(dcp); |
4119 | ||
91447636 A |
4120 | dcp->c_ctime = tv.tv_sec; |
4121 | dcp->c_mtime = tv.tv_sec; | |
4122 | (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); | |
4123 | } | |
4124 | hfs_systemfile_unlock(hfsmp, lockflags); | |
316670eb | 4125 | |
6d2010ae | 4126 | if (error) { |
316670eb A |
4127 | if (temp_rsrc_fork) { |
4128 | FREE_ZONE (temp_rsrc_fork, sizeof(struct filefork), M_HFSFORK); | |
4129 | } | |
91447636 | 4130 | goto out; |
6d2010ae A |
4131 | } |
4132 | ||
4133 | /* | |
4134 | * Now that we've wiped out the catalog record, the file effectively doesn't | |
4135 | * exist anymore. So update the quota records to reflect the loss of the | |
4136 | * data fork and the resource fork. | |
4137 | */ | |
9bccf70c | 4138 | #if QUOTA |
6d2010ae A |
4139 | if (cp->c_datafork->ff_blocks > 0) { |
4140 | savedbytes = ((off_t)cp->c_datafork->ff_blocks * (off_t)blksize); | |
4141 | (void) hfs_chkdq(cp, (int64_t)-(savedbytes), NOCRED, 0); | |
4142 | } | |
4143 | ||
316670eb A |
4144 | /* |
4145 | * We may have just deleted the catalog record for a resource fork even | |
4146 | * though it did not exist in core as a vnode. However, just because there | |
4147 | * was a resource fork pointer in the cnode does not mean that it had any blocks. | |
4148 | */ | |
4149 | if (temp_rsrc_fork || cp->c_rsrcfork) { | |
4150 | if (cp->c_rsrcfork) { | |
4151 | if (cp->c_rsrcfork->ff_blocks > 0) { | |
4152 | savedbytes = ((off_t)cp->c_rsrcfork->ff_blocks * (off_t)blksize); | |
4153 | (void) hfs_chkdq(cp, (int64_t)-(savedbytes), NOCRED, 0); | |
4154 | } | |
4155 | } | |
4156 | else { | |
4157 | /* we must have used a temporary fork */ | |
4158 | savedbytes = ((off_t)temp_rsrc_fork->ff_blocks * (off_t)blksize); | |
4159 | (void) hfs_chkdq(cp, (int64_t)-(savedbytes), NOCRED, 0); | |
4160 | } | |
6d2010ae A |
4161 | } |
4162 | ||
4163 | if (hfsmp->hfs_flags & HFS_QUOTAS) { | |
2d21ac55 | 4164 | (void)hfs_chkiq(cp, -1, NOCRED, 0); |
6d2010ae A |
4165 | } |
4166 | #endif | |
4167 | ||
6d2010ae A |
4168 | /* |
4169 | * If we didn't get any errors deleting the catalog entry, then go ahead | |
4170 | * and release the backing store now. The filefork pointers are still valid. | |
316670eb A |
4171 | */ |
4172 | if (temp_rsrc_fork) { | |
4173 | error = hfs_release_storage (hfsmp, cp->c_datafork, temp_rsrc_fork, fileid); | |
4174 | } | |
4175 | else { | |
4176 | /* if cp->c_rsrcfork == NULL, hfs_release_storage will skip over it. */ | |
4177 | error = hfs_release_storage (hfsmp, cp->c_datafork, cp->c_rsrcfork, fileid); | |
4178 | } | |
6d2010ae A |
4179 | if (error) { |
4180 | /* | |
4181 | * If we encountered an error updating the extents and bitmap, | |
4182 | * mark the volume inconsistent. At this point, the catalog record has | |
4183 | * already been deleted, so we can't recover it at this point. We need | |
4184 | * to proceed and update the volume header and mark the cnode C_NOEXISTS. | |
4185 | * The subsequent fsck should be able to recover the free space for us. | |
4186 | */ | |
fe8ab488 | 4187 | hfs_mark_inconsistent(hfsmp, HFS_OP_INCOMPLETE); |
6d2010ae A |
4188 | } |
4189 | else { | |
4190 | /* reset update_vh to 0, since hfs_release_storage should have done it for us */ | |
4191 | update_vh = 0; | |
4192 | } | |
316670eb A |
4193 | |
4194 | /* Get rid of the temporary rsrc fork */ | |
4195 | if (temp_rsrc_fork) { | |
4196 | FREE_ZONE (temp_rsrc_fork, sizeof(struct filefork), M_HFSFORK); | |
4197 | } | |
4198 | ||
91447636 | 4199 | cp->c_flag |= C_NOEXISTS; |
2d21ac55 | 4200 | cp->c_flag &= ~C_DELETED; |
6d2010ae | 4201 | |
91447636 | 4202 | cp->c_touch_chgtime = TRUE; /* XXX needed ? */ |
2d21ac55 | 4203 | --cp->c_linkcount; |
6d2010ae | 4204 | |
b0d623f7 A |
4205 | /* |
4206 | * We must never get a directory if we're in this else block. We could | |
4207 | * accidentally drop the number of files in the volume header if we did. | |
4208 | */ | |
9bccf70c | 4209 | hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID)); |
6d2010ae | 4210 | |
9bccf70c A |
4211 | } |
4212 | ||
22ba694c A |
4213 | // |
4214 | // if skip_reserve == 1 then we're being called from hfs_vnop_rename() and thus | |
4215 | // we don't need to touch the document_id as it's handled by the rename code. | |
4216 | // otherwise it's a normal remove and we need to save the document id in the | |
4217 | // per thread struct and clear it from the cnode. | |
4218 | // | |
4219 | struct doc_tombstone *ut; | |
4220 | ut = get_uthread_doc_tombstone(); | |
4221 | if (!error && !skip_reserve && (cp->c_bsdflags & UF_TRACKED) && should_save_docid_tombstone(ut, vp, cnp)) { | |
4222 | ||
4223 | if (ut->t_lastop_document_id) { | |
4224 | clear_tombstone_docid(ut, hfsmp, NULL); | |
4225 | } | |
4226 | save_tombstone(hfsmp, dvp, vp, cnp, 1); | |
4227 | ||
4228 | } | |
4229 | ||
4230 | ||
9bccf70c A |
4231 | /* |
4232 | * All done with this cnode's descriptor... | |
4233 | * | |
2d21ac55 A |
4234 | * Note: all future catalog calls for this cnode must be by |
4235 | * fileid only. This is OK for HFS (which doesn't have file | |
4236 | * thread records) since HFS doesn't support the removal of | |
4237 | * busy files. | |
9bccf70c A |
4238 | */ |
4239 | cat_releasedesc(&cp->c_desc); | |
4240 | ||
55e303ae | 4241 | out: |
2d21ac55 A |
4242 | if (error) { |
4243 | cp->c_flag &= ~C_DELETED; | |
b4c24cb9 | 4244 | } |
6d2010ae A |
4245 | |
4246 | if (update_vh) { | |
4247 | /* | |
4248 | * If we bailed out earlier, we may need to update the volume header | |
4249 | * to deal with the borrowed blocks accounting. | |
4250 | */ | |
4251 | hfs_volupdate (hfsmp, VOL_UPDATE, 0); | |
4252 | } | |
b4c24cb9 | 4253 | |
b4c24cb9 | 4254 | if (started_tr) { |
91447636 | 4255 | hfs_end_transaction(hfsmp); |
b4c24cb9 A |
4256 | } |
4257 | ||
2d21ac55 A |
4258 | dcp->c_flag &= ~C_DIR_MODIFICATION; |
4259 | wakeup((caddr_t)&dcp->c_flag); | |
4260 | ||
9bccf70c A |
4261 | return (error); |
4262 | } | |
4263 | ||
4264 | ||
4265 | __private_extern__ void | |
4266 | replace_desc(struct cnode *cp, struct cat_desc *cdp) | |
4267 | { | |
2d21ac55 | 4268 | // fixes 4348457 and 4463138 |
0c530ab8 | 4269 | if (&cp->c_desc == cdp) { |
2d21ac55 | 4270 | return; |
0c530ab8 A |
4271 | } |
4272 | ||
9bccf70c A |
4273 | /* First release allocated name buffer */ |
4274 | if (cp->c_desc.cd_flags & CD_HASBUF && cp->c_desc.cd_nameptr != 0) { | |
2d21ac55 | 4275 | const u_int8_t *name = cp->c_desc.cd_nameptr; |
9bccf70c A |
4276 | |
4277 | cp->c_desc.cd_nameptr = 0; | |
4278 | cp->c_desc.cd_namelen = 0; | |
4279 | cp->c_desc.cd_flags &= ~CD_HASBUF; | |
2d21ac55 | 4280 | vfs_removename((const char *)name); |
9bccf70c A |
4281 | } |
4282 | bcopy(cdp, &cp->c_desc, sizeof(cp->c_desc)); | |
4283 | ||
4284 | /* Cnode now owns the name buffer */ | |
4285 | cdp->cd_nameptr = 0; | |
4286 | cdp->cd_namelen = 0; | |
4287 | cdp->cd_flags &= ~CD_HASBUF; | |
4288 | } | |
4289 | ||
316670eb | 4290 | |
9bccf70c A |
4291 | /* |
4292 | * Rename a cnode. | |
4293 | * | |
91447636 A |
4294 | * The VFS layer guarantees that: |
4295 | * - source and destination will either both be directories, or | |
4296 | * both not be directories. | |
4297 | * - all the vnodes are from the same file system | |
d7e50217 | 4298 | * |
91447636 | 4299 | * When the target is a directory, HFS must ensure that its empty. |
b0d623f7 A |
4300 | * |
4301 | * Note that this function requires up to 6 vnodes in order to work properly | |
4302 | * if it is operating on files (and not on directories). This is because only | |
4303 | * files can have resource forks, and we now require iocounts to be held on the | |
4304 | * vnodes corresponding to the resource forks (if applicable) as well as | |
4305 | * the files or directories undergoing rename. The problem with not holding | |
4306 | * iocounts on the resource fork vnodes is that it can lead to a deadlock | |
4307 | * situation: The rsrc fork of the source file may be recycled and reclaimed | |
4308 | * in order to provide a vnode for the destination file's rsrc fork. Since | |
4309 | * data and rsrc forks share the same cnode, we'd eventually try to lock the | |
4310 | * source file's cnode in order to sync its rsrc fork to disk, but it's already | |
4311 | * been locked. By taking the rsrc fork vnodes up front we ensure that they | |
4312 | * cannot be recycled, and that the situation mentioned above cannot happen. | |
9bccf70c | 4313 | */ |
6d2010ae | 4314 | int |
91447636 A |
4315 | hfs_vnop_rename(ap) |
4316 | struct vnop_rename_args /* { | |
9bccf70c A |
4317 | struct vnode *a_fdvp; |
4318 | struct vnode *a_fvp; | |
4319 | struct componentname *a_fcnp; | |
4320 | struct vnode *a_tdvp; | |
4321 | struct vnode *a_tvp; | |
4322 | struct componentname *a_tcnp; | |
91447636 | 4323 | vfs_context_t a_context; |
9bccf70c A |
4324 | } */ *ap; |
4325 | { | |
4326 | struct vnode *tvp = ap->a_tvp; | |
4327 | struct vnode *tdvp = ap->a_tdvp; | |
4328 | struct vnode *fvp = ap->a_fvp; | |
4329 | struct vnode *fdvp = ap->a_fdvp; | |
316670eb A |
4330 | /* |
4331 | * Note that we only need locals for the target/destination's | |
4332 | * resource fork vnode (and only if necessary). We don't care if the | |
4333 | * source has a resource fork vnode or not. | |
4334 | */ | |
39236c6e | 4335 | struct vnode *tvp_rsrc = NULLVP; |
316670eb | 4336 | uint32_t tvp_rsrc_vid = 0; |
9bccf70c A |
4337 | struct componentname *tcnp = ap->a_tcnp; |
4338 | struct componentname *fcnp = ap->a_fcnp; | |
91447636 A |
4339 | struct proc *p = vfs_context_proc(ap->a_context); |
4340 | struct cnode *fcp; | |
4341 | struct cnode *fdcp; | |
4342 | struct cnode *tdcp; | |
4343 | struct cnode *tcp; | |
b0d623f7 | 4344 | struct cnode *error_cnode; |
9bccf70c A |
4345 | struct cat_desc from_desc; |
4346 | struct cat_desc to_desc; | |
4347 | struct cat_desc out_desc; | |
91447636 A |
4348 | struct hfsmount *hfsmp; |
4349 | cat_cookie_t cookie; | |
4350 | int tvp_deleted = 0; | |
4351 | int started_tr = 0, got_cookie = 0; | |
4352 | int took_trunc_lock = 0; | |
4353 | int lockflags; | |
4354 | int error; | |
6d2010ae | 4355 | time_t orig_from_ctime, orig_to_ctime; |
7ee9d059 A |
4356 | int emit_rename = 1; |
4357 | int emit_delete = 1; | |
39236c6e | 4358 | int is_tracked = 0; |
22ba694c | 4359 | int unlocked; |
6d2010ae A |
4360 | |
4361 | orig_from_ctime = VTOC(fvp)->c_ctime; | |
4362 | if (tvp && VTOC(tvp)) { | |
4363 | orig_to_ctime = VTOC(tvp)->c_ctime; | |
4364 | } else { | |
4365 | orig_to_ctime = ~0; | |
4366 | } | |
b0d623f7 | 4367 | |
7ee9d059 A |
4368 | hfsmp = VTOHFS(tdvp); |
4369 | /* | |
4370 | * Do special case checks here. If fvp == tvp then we need to check the | |
4371 | * cnode with locks held. | |
4372 | */ | |
4373 | if (fvp == tvp) { | |
4374 | int is_hardlink = 0; | |
4375 | /* | |
4376 | * In this case, we do *NOT* ever emit a DELETE event. | |
4377 | * We may not necessarily emit a RENAME event | |
4378 | */ | |
4379 | emit_delete = 0; | |
39236c6e | 4380 | if ((error = hfs_lock(VTOC(fvp), HFS_SHARED_LOCK, HFS_LOCK_DEFAULT))) { |
7ee9d059 A |
4381 | return error; |
4382 | } | |
4383 | /* Check to see if the item is a hardlink or not */ | |
4384 | is_hardlink = (VTOC(fvp)->c_flag & C_HARDLINK); | |
4385 | hfs_unlock (VTOC(fvp)); | |
4386 | ||
4387 | /* | |
4388 | * If the item is not a hardlink, then case sensitivity must be off, otherwise | |
4389 | * two names should not resolve to the same cnode unless they were case variants. | |
4390 | */ | |
4391 | if (is_hardlink) { | |
4392 | emit_rename = 0; | |
4393 | /* | |
4394 | * Hardlinks are a little trickier. We only want to emit a rename event | |
4395 | * if the item is a hardlink, the parent directories are the same, case sensitivity | |
4396 | * is off, and the case folded names are the same. See the fvp == tvp case below for more | |
4397 | * info. | |
4398 | */ | |
4399 | ||
4400 | if ((fdvp == tdvp) && ((hfsmp->hfs_flags & HFS_CASE_SENSITIVE) == 0)) { | |
4401 | if (hfs_namecmp((const u_int8_t *)fcnp->cn_nameptr, fcnp->cn_namelen, | |
4402 | (const u_int8_t *)tcnp->cn_nameptr, tcnp->cn_namelen) == 0) { | |
4403 | /* Then in this case only it is ok to emit a rename */ | |
4404 | emit_rename = 1; | |
4405 | } | |
4406 | } | |
4407 | } | |
4408 | } | |
4409 | if (emit_rename) { | |
39236c6e A |
4410 | /* c_bsdflags should only be assessed while holding the cnode lock. |
4411 | * This is not done consistently throughout the code and can result | |
4412 | * in race. This will be fixed via rdar://12181064 | |
4413 | */ | |
4414 | if (VTOC(fvp)->c_bsdflags & UF_TRACKED) { | |
4415 | is_tracked = 1; | |
4416 | } | |
7ee9d059 A |
4417 | check_for_tracked_file(fvp, orig_from_ctime, NAMESPACE_HANDLER_RENAME_OP, NULL); |
4418 | } | |
b0d623f7 | 4419 | |
6d2010ae | 4420 | if (tvp && VTOC(tvp)) { |
7ee9d059 A |
4421 | if (emit_delete) { |
4422 | check_for_tracked_file(tvp, orig_to_ctime, NAMESPACE_HANDLER_DELETE_OP, NULL); | |
4423 | } | |
6d2010ae | 4424 | } |
39236c6e | 4425 | |
316670eb | 4426 | retry: |
b0d623f7 | 4427 | /* When tvp exists, take the truncate lock for hfs_removefile(). */ |
2d21ac55 | 4428 | if (tvp && (vnode_isreg(tvp) || vnode_islnk(tvp))) { |
39236c6e | 4429 | hfs_lock_truncate(VTOC(tvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
91447636 A |
4430 | took_trunc_lock = 1; |
4431 | } | |
9bccf70c | 4432 | |
22ba694c | 4433 | relock: |
91447636 | 4434 | error = hfs_lockfour(VTOC(fdvp), VTOC(fvp), VTOC(tdvp), tvp ? VTOC(tvp) : NULL, |
b0d623f7 | 4435 | HFS_EXCLUSIVE_LOCK, &error_cnode); |
91447636 | 4436 | if (error) { |
593a1d5f | 4437 | if (took_trunc_lock) { |
39236c6e | 4438 | hfs_unlock_truncate(VTOC(tvp), HFS_LOCK_DEFAULT); |
593a1d5f A |
4439 | took_trunc_lock = 0; |
4440 | } | |
316670eb A |
4441 | |
4442 | /* | |
4443 | * We hit an error path. If we were trying to re-acquire the locks | |
4444 | * after coming through here once, we might have already obtained | |
4445 | * an iocount on tvp's resource fork vnode. Drop that before dealing | |
4446 | * with the failure. Note this is safe -- since we are in an | |
4447 | * error handling path, we can't be holding the cnode locks. | |
4448 | */ | |
4449 | if (tvp_rsrc) { | |
4450 | vnode_put (tvp_rsrc); | |
4451 | tvp_rsrc_vid = 0; | |
4452 | tvp_rsrc = NULL; | |
4453 | } | |
4454 | ||
b0d623f7 A |
4455 | /* |
4456 | * tvp might no longer exist. If the cause of the lock failure | |
4457 | * was tvp, then we can try again with tvp/tcp set to NULL. | |
4458 | * This is ok because the vfs syscall will vnode_put the vnodes | |
4459 | * after we return from hfs_vnop_rename. | |
4460 | */ | |
4461 | if ((error == ENOENT) && (tvp != NULL) && (error_cnode == VTOC(tvp))) { | |
4462 | tcp = NULL; | |
4463 | tvp = NULL; | |
4464 | goto retry; | |
4465 | } | |
316670eb | 4466 | |
fe8ab488 A |
4467 | /* If we want to reintroduce notifications for failed renames, this |
4468 | is the place to do it. */ | |
4469 | ||
91447636 | 4470 | return (error); |
55e303ae A |
4471 | } |
4472 | ||
91447636 A |
4473 | fdcp = VTOC(fdvp); |
4474 | fcp = VTOC(fvp); | |
4475 | tdcp = VTOC(tdvp); | |
4476 | tcp = tvp ? VTOC(tvp) : NULL; | |
55e303ae | 4477 | |
22ba694c A |
4478 | // |
4479 | // if the item is tracked but doesn't have a document_id, assign one and generate an fsevent for it | |
4480 | // | |
4481 | unlocked = 0; | |
4482 | if ((fcp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16))->document_id == 0) { | |
4483 | uint32_t newid; | |
4484 | ||
4485 | hfs_unlockfour(VTOC(fdvp), VTOC(fvp), VTOC(tdvp), tvp ? VTOC(tvp) : NULL); | |
4486 | unlocked = 1; | |
4487 | ||
4488 | if (hfs_generate_document_id(hfsmp, &newid) == 0) { | |
4489 | hfs_lock(fcp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
4490 | ((struct FndrExtendedDirInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16))->document_id = newid; | |
4491 | #if CONFIG_FSE | |
4492 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
4493 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
4494 | FSE_ARG_INO, (ino64_t)0, // src inode # | |
4495 | FSE_ARG_INO, (ino64_t)fcp->c_fileid, // dst inode # | |
4496 | FSE_ARG_INT32, newid, | |
4497 | FSE_ARG_DONE); | |
4498 | #endif | |
4499 | hfs_unlock(fcp); | |
4500 | } else { | |
4501 | // XXXdbg - couldn't get a new docid... what to do? can't really fail the rename... | |
4502 | } | |
4503 | ||
4504 | // | |
4505 | // check if we're going to need to fix tcp as well. if we aren't, go back relock | |
4506 | // everything. otherwise continue on and fix up tcp as well before relocking. | |
4507 | // | |
4508 | if (tcp == NULL || !(tcp->c_bsdflags & UF_TRACKED) || ((struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16))->document_id != 0) { | |
4509 | goto relock; | |
4510 | } | |
4511 | } | |
4512 | ||
4513 | // | |
4514 | // same thing for tcp if it's set | |
4515 | // | |
4516 | if (tcp && (tcp->c_bsdflags & UF_TRACKED) && ((struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16))->document_id == 0) { | |
4517 | uint32_t newid; | |
4518 | ||
4519 | if (!unlocked) { | |
4520 | hfs_unlockfour(VTOC(fdvp), VTOC(fvp), VTOC(tdvp), tvp ? VTOC(tvp) : NULL); | |
4521 | unlocked = 1; | |
4522 | } | |
4523 | ||
4524 | if (hfs_generate_document_id(hfsmp, &newid) == 0) { | |
4525 | hfs_lock(tcp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); | |
4526 | ((struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16))->document_id = newid; | |
4527 | #if CONFIG_FSE | |
4528 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
4529 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
4530 | FSE_ARG_INO, (ino64_t)0, // src inode # | |
4531 | FSE_ARG_INO, (ino64_t)tcp->c_fileid, // dst inode # | |
4532 | FSE_ARG_INT32, newid, | |
4533 | FSE_ARG_DONE); | |
4534 | #endif | |
4535 | hfs_unlock(tcp); | |
4536 | } else { | |
4537 | // XXXdbg - couldn't get a new docid... what to do? can't really fail the rename... | |
4538 | } | |
4539 | ||
4540 | // go back up and relock everything. next time through the if statement won't be true | |
4541 | // and we'll skip over this block of code. | |
4542 | goto relock; | |
4543 | } | |
4544 | ||
4545 | ||
4546 | ||
316670eb A |
4547 | /* |
4548 | * Acquire iocounts on the destination's resource fork vnode | |
4549 | * if necessary. If dst/src are files and the dst has a resource | |
4550 | * fork vnode, then we need to try and acquire an iocount on the rsrc vnode. | |
4551 | * If it does not exist, then we don't care and can skip it. | |
4552 | */ | |
4553 | if ((vnode_isreg(fvp)) || (vnode_islnk(fvp))) { | |
4554 | if ((tvp) && (tcp->c_rsrc_vp) && (tvp_rsrc == NULL)) { | |
4555 | tvp_rsrc = tcp->c_rsrc_vp; | |
4556 | /* | |
4557 | * We can look at the vid here because we're holding the | |
4558 | * cnode lock on the underlying cnode for this rsrc vnode. | |
4559 | */ | |
4560 | tvp_rsrc_vid = vnode_vid (tvp_rsrc); | |
4561 | ||
4562 | /* Unlock everything to acquire iocount on this rsrc vnode */ | |
4563 | if (took_trunc_lock) { | |
39236c6e | 4564 | hfs_unlock_truncate (VTOC(tvp), HFS_LOCK_DEFAULT); |
316670eb A |
4565 | took_trunc_lock = 0; |
4566 | } | |
4567 | hfs_unlockfour(fdcp, fcp, tdcp, tcp); | |
4568 | ||
4569 | if (vnode_getwithvid (tvp_rsrc, tvp_rsrc_vid)) { | |
4570 | /* iocount acquisition failed. Reset fields and start over.. */ | |
4571 | tvp_rsrc_vid = 0; | |
4572 | tvp_rsrc = NULL; | |
4573 | } | |
4574 | goto retry; | |
4575 | } | |
4576 | } | |
4577 | ||
39236c6e A |
4578 | |
4579 | ||
b0d623f7 A |
4580 | /* Ensure we didn't race src or dst parent directories with rmdir. */ |
4581 | if (fdcp->c_flag & (C_NOEXISTS | C_DELETED)) { | |
4582 | error = ENOENT; | |
4583 | goto out; | |
4584 | } | |
4585 | ||
4586 | if (tdcp->c_flag & (C_NOEXISTS | C_DELETED)) { | |
4587 | error = ENOENT; | |
4588 | goto out; | |
4589 | } | |
4590 | ||
4591 | ||
4592 | /* Check for a race against unlink. The hfs_valid_cnode checks validate | |
4593 | * the parent/child relationship with fdcp and tdcp, as well as the | |
4594 | * component name of the target cnodes. | |
4595 | */ | |
6d2010ae | 4596 | if ((fcp->c_flag & (C_NOEXISTS | C_DELETED)) || !hfs_valid_cnode(hfsmp, fdvp, fcnp, fcp->c_fileid, NULL, &error)) { |
91447636 A |
4597 | error = ENOENT; |
4598 | goto out; | |
9bccf70c | 4599 | } |
91447636 | 4600 | |
6d2010ae | 4601 | if (tcp && ((tcp->c_flag & (C_NOEXISTS | C_DELETED)) || !hfs_valid_cnode(hfsmp, tdvp, tcnp, tcp->c_fileid, NULL, &error))) { |
2d21ac55 A |
4602 | // |
4603 | // hmm, the destination vnode isn't valid any more. | |
4604 | // in this case we can just drop him and pretend he | |
4605 | // never existed in the first place. | |
4606 | // | |
4607 | if (took_trunc_lock) { | |
39236c6e | 4608 | hfs_unlock_truncate(VTOC(tvp), HFS_LOCK_DEFAULT); |
6d2010ae | 4609 | took_trunc_lock = 0; |
2d21ac55 | 4610 | } |
6d2010ae | 4611 | error = 0; |
2d21ac55 A |
4612 | |
4613 | hfs_unlockfour(fdcp, fcp, tdcp, tcp); | |
4614 | ||
4615 | tcp = NULL; | |
4616 | tvp = NULL; | |
4617 | ||
4618 | // retry the locking with tvp null'ed out | |
4619 | goto retry; | |
4620 | } | |
4621 | ||
4622 | fdcp->c_flag |= C_DIR_MODIFICATION; | |
4623 | if (fdvp != tdvp) { | |
4624 | tdcp->c_flag |= C_DIR_MODIFICATION; | |
4625 | } | |
4626 | ||
4627 | /* | |
4628 | * Disallow renaming of a directory hard link if the source and | |
4629 | * destination parent directories are different, or a directory whose | |
4630 | * descendant is a directory hard link and the one of the ancestors | |
4631 | * of the destination directory is a directory hard link. | |
4632 | */ | |
4633 | if (vnode_isdir(fvp) && (fdvp != tdvp)) { | |
4634 | if (fcp->c_flag & C_HARDLINK) { | |
4635 | error = EPERM; | |
4636 | goto out; | |
4637 | } | |
4638 | if (fcp->c_attr.ca_recflags & kHFSHasChildLinkMask) { | |
4639 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); | |
4640 | if (cat_check_link_ancestry(hfsmp, tdcp->c_fileid, 0)) { | |
4641 | error = EPERM; | |
4642 | hfs_systemfile_unlock(hfsmp, lockflags); | |
4643 | goto out; | |
4644 | } | |
4645 | hfs_systemfile_unlock(hfsmp, lockflags); | |
4646 | } | |
4647 | } | |
4648 | ||
9bccf70c | 4649 | /* |
55e303ae A |
4650 | * The following edge case is caught here: |
4651 | * (to cannot be a descendent of from) | |
4652 | * | |
4653 | * o fdvp | |
4654 | * / | |
4655 | * / | |
4656 | * o fvp | |
4657 | * \ | |
4658 | * \ | |
4659 | * o tdvp | |
4660 | * / | |
4661 | * / | |
4662 | * o tvp | |
9bccf70c | 4663 | */ |
2d21ac55 | 4664 | if (tdcp->c_parentcnid == fcp->c_fileid) { |
55e303ae A |
4665 | error = EINVAL; |
4666 | goto out; | |
4667 | } | |
4668 | ||
4669 | /* | |
4670 | * The following two edge cases are caught here: | |
4671 | * (note tvp is not empty) | |
4672 | * | |
4673 | * o tdvp o tdvp | |
4674 | * / / | |
4675 | * / / | |
4676 | * o tvp tvp o fdvp | |
4677 | * \ \ | |
4678 | * \ \ | |
4679 | * o fdvp o fvp | |
4680 | * / | |
4681 | * / | |
4682 | * o fvp | |
4683 | */ | |
91447636 | 4684 | if (tvp && vnode_isdir(tvp) && (tcp->c_entries != 0) && fvp != tvp) { |
55e303ae A |
4685 | error = ENOTEMPTY; |
4686 | goto out; | |
4687 | } | |
4688 | ||
4689 | /* | |
4690 | * The following edge case is caught here: | |
4691 | * (the from child and parent are the same) | |
4692 | * | |
4693 | * o tdvp | |
4694 | * / | |
4695 | * / | |
4696 | * fdvp o fvp | |
4697 | */ | |
4698 | if (fdvp == fvp) { | |
4699 | error = EINVAL; | |
d7e50217 | 4700 | goto out; |
9bccf70c A |
4701 | } |
4702 | ||
4703 | /* | |
d7e50217 | 4704 | * Make sure "from" vnode and its parent are changeable. |
9bccf70c | 4705 | */ |
316670eb | 4706 | if ((fcp->c_bsdflags & (IMMUTABLE | APPEND)) || (fdcp->c_bsdflags & APPEND)) { |
d7e50217 A |
4707 | error = EPERM; |
4708 | goto out; | |
9bccf70c A |
4709 | } |
4710 | ||
9bccf70c | 4711 | /* |
d7e50217 A |
4712 | * If the destination parent directory is "sticky", then the |
4713 | * user must own the parent directory, or the destination of | |
4714 | * the rename, otherwise the destination may not be changed | |
4715 | * (except by root). This implements append-only directories. | |
4716 | * | |
55e303ae | 4717 | * Note that checks for immutable and write access are done |
91447636 | 4718 | * by the call to hfs_removefile. |
9bccf70c | 4719 | */ |
d7e50217 | 4720 | if (tvp && (tdcp->c_mode & S_ISTXT) && |
91447636 A |
4721 | (suser(vfs_context_ucred(tcnp->cn_context), NULL)) && |
4722 | (kauth_cred_getuid(vfs_context_ucred(tcnp->cn_context)) != tdcp->c_uid) && | |
4723 | (hfs_owner_rights(hfsmp, tcp->c_uid, vfs_context_ucred(tcnp->cn_context), p, false)) ) { | |
55e303ae A |
4724 | error = EPERM; |
4725 | goto out; | |
9bccf70c A |
4726 | } |
4727 | ||
316670eb A |
4728 | /* Don't allow modification of the journal or journal_info_block */ |
4729 | if (hfs_is_journal_file(hfsmp, fcp) || | |
4730 | (tcp && hfs_is_journal_file(hfsmp, tcp))) { | |
4731 | error = EPERM; | |
4732 | goto out; | |
4733 | } | |
4734 | ||
55e303ae A |
4735 | #if QUOTA |
4736 | if (tvp) | |
91447636 | 4737 | (void)hfs_getinoquota(tcp); |
55e303ae | 4738 | #endif |
91447636 | 4739 | /* Preflighting done, take fvp out of the name space. */ |
55e303ae A |
4740 | cache_purge(fvp); |
4741 | ||
fe8ab488 A |
4742 | #if CONFIG_SECLUDED_RENAME |
4743 | /* | |
4744 | * Check for "secure" rename that imposes additional restrictions on the | |
4745 | * source vnode. We wait until here to check in order to prevent a race | |
4746 | * with other threads that manage to look up fvp, but their open or link | |
4747 | * is blocked by our locks. At this point, with fvp out of the name cache, | |
4748 | * and holding the lock on fdvp, no other thread can find fvp. | |
4749 | * | |
4750 | * TODO: Do we need to limit these checks to regular files only? | |
4751 | */ | |
4752 | if (fcnp->cn_flags & CN_SECLUDE_RENAME) { | |
4753 | if (vnode_isdir(fvp)) { | |
4754 | error = EISDIR; | |
4755 | goto out; | |
4756 | } | |
4757 | ||
4758 | /* | |
4759 | * Neither fork of source may be open or memory mapped. | |
4760 | * We also don't want it in use by any other system call. | |
4761 | * The file must not have hard links. | |
4762 | * | |
4763 | * We can't simply use vnode_isinuse() because that does not | |
4764 | * count opens with O_EVTONLY. We don't want a malicious | |
4765 | * process using O_EVTONLY to subvert a secluded rename. | |
4766 | */ | |
4767 | if (fcp->c_linkcount != 1) { | |
4768 | error = EMLINK; | |
4769 | goto out; | |
4770 | } | |
4771 | ||
4772 | if (fcp->c_rsrc_vp && (fcp->c_rsrc_vp->v_usecount > 0 || | |
4773 | fcp->c_rsrc_vp->v_iocount > 0)) { | |
4774 | /* Resource fork is in use (including O_EVTONLY) */ | |
4775 | error = EBUSY; | |
4776 | goto out; | |
4777 | } | |
4778 | if (fcp->c_vp && (fcp->c_vp->v_usecount > (fcp->c_rsrc_vp ? 1 : 0) || | |
4779 | fcp->c_vp->v_iocount > 1)) { | |
4780 | /* | |
4781 | * Data fork is in use, including O_EVTONLY, but not | |
4782 | * including a reference from the resource fork. | |
4783 | */ | |
4784 | error = EBUSY; | |
4785 | goto out; | |
4786 | } | |
4787 | } | |
4788 | #endif | |
4789 | ||
9bccf70c | 4790 | bzero(&from_desc, sizeof(from_desc)); |
2d21ac55 | 4791 | from_desc.cd_nameptr = (const u_int8_t *)fcnp->cn_nameptr; |
9bccf70c | 4792 | from_desc.cd_namelen = fcnp->cn_namelen; |
2d21ac55 | 4793 | from_desc.cd_parentcnid = fdcp->c_fileid; |
9bccf70c A |
4794 | from_desc.cd_flags = fcp->c_desc.cd_flags & ~(CD_HASBUF | CD_DECOMPOSED); |
4795 | from_desc.cd_cnid = fcp->c_cnid; | |
d7e50217 | 4796 | |
9bccf70c | 4797 | bzero(&to_desc, sizeof(to_desc)); |
2d21ac55 | 4798 | to_desc.cd_nameptr = (const u_int8_t *)tcnp->cn_nameptr; |
9bccf70c | 4799 | to_desc.cd_namelen = tcnp->cn_namelen; |
2d21ac55 | 4800 | to_desc.cd_parentcnid = tdcp->c_fileid; |
9bccf70c A |
4801 | to_desc.cd_flags = fcp->c_desc.cd_flags & ~(CD_HASBUF | CD_DECOMPOSED); |
4802 | to_desc.cd_cnid = fcp->c_cnid; | |
4803 | ||
91447636 A |
4804 | if ((error = hfs_start_transaction(hfsmp)) != 0) { |
4805 | goto out; | |
4806 | } | |
4807 | started_tr = 1; | |
4808 | ||
2d21ac55 A |
4809 | /* hfs_vnop_link() and hfs_vnop_rename() set kHFSHasChildLinkMask |
4810 | * inside a journal transaction and without holding a cnode lock. | |
4811 | * As setting of this bit depends on being in journal transaction for | |
4812 | * concurrency, check this bit again after we start journal transaction for rename | |
4813 | * to ensure that this directory does not have any descendant that | |
4814 | * is a directory hard link. | |
4815 | */ | |
4816 | if (vnode_isdir(fvp) && (fdvp != tdvp)) { | |
4817 | if (fcp->c_attr.ca_recflags & kHFSHasChildLinkMask) { | |
4818 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); | |
4819 | if (cat_check_link_ancestry(hfsmp, tdcp->c_fileid, 0)) { | |
4820 | error = EPERM; | |
4821 | hfs_systemfile_unlock(hfsmp, lockflags); | |
4822 | goto out; | |
4823 | } | |
4824 | hfs_systemfile_unlock(hfsmp, lockflags); | |
4825 | } | |
4826 | } | |
4827 | ||
91447636 A |
4828 | // if it's a hardlink then re-lookup the name so |
4829 | // that we get the correct cnid in from_desc (see | |
4830 | // the comment in hfs_removefile for more details) | |
4831 | // | |
4832 | if (fcp->c_flag & C_HARDLINK) { | |
4833 | struct cat_desc tmpdesc; | |
4834 | cnid_t real_cnid; | |
4835 | ||
2d21ac55 | 4836 | tmpdesc.cd_nameptr = (const u_int8_t *)fcnp->cn_nameptr; |
91447636 | 4837 | tmpdesc.cd_namelen = fcnp->cn_namelen; |
2d21ac55 | 4838 | tmpdesc.cd_parentcnid = fdcp->c_fileid; |
91447636 | 4839 | tmpdesc.cd_hint = fdcp->c_childhint; |
2d21ac55 A |
4840 | tmpdesc.cd_flags = fcp->c_desc.cd_flags & CD_ISDIR; |
4841 | tmpdesc.cd_encoding = 0; | |
91447636 A |
4842 | |
4843 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); | |
4844 | ||
39236c6e | 4845 | if (cat_lookup(hfsmp, &tmpdesc, 0, 0, NULL, NULL, NULL, &real_cnid) != 0) { |
91447636 A |
4846 | hfs_systemfile_unlock(hfsmp, lockflags); |
4847 | goto out; | |
55e303ae | 4848 | } |
91447636 A |
4849 | |
4850 | // use the real cnid instead of whatever happened to be there | |
4851 | from_desc.cd_cnid = real_cnid; | |
4852 | hfs_systemfile_unlock(hfsmp, lockflags); | |
55e303ae A |
4853 | } |
4854 | ||
4855 | /* | |
4856 | * Reserve some space in the Catalog file. | |
4857 | */ | |
4858 | if ((error = cat_preflight(hfsmp, CAT_RENAME + CAT_DELETE, &cookie, p))) { | |
4859 | goto out; | |
4860 | } | |
91447636 | 4861 | got_cookie = 1; |
55e303ae A |
4862 | |
4863 | /* | |
6d2010ae A |
4864 | * If the destination exists then it may need to be removed. |
4865 | * | |
4866 | * Due to HFS's locking system, we should always move the | |
4867 | * existing 'tvp' element to the hidden directory in hfs_vnop_rename. | |
4868 | * Because the VNOP_LOOKUP call enters and exits the filesystem independently | |
4869 | * of the actual vnop that it was trying to do (stat, link, readlink), | |
4870 | * we must release the cnode lock of that element during the interim to | |
4871 | * do MAC checking, vnode authorization, and other calls. In that time, | |
4872 | * the item can be deleted (or renamed over). However, only in the rename | |
4873 | * case is it inappropriate to return ENOENT from any of those calls. Either | |
4874 | * the call should return information about the old element (stale), or get | |
4875 | * information about the newer element that we are about to write in its place. | |
4876 | * | |
4877 | * HFS lookup has been modified to detect a rename and re-drive its | |
4878 | * lookup internally. For other calls that have already succeeded in | |
4879 | * their lookup call and are waiting to acquire the cnode lock in order | |
4880 | * to proceed, that cnode lock will not fail due to the cnode being marked | |
4881 | * C_NOEXISTS, because it won't have been marked as such. It will only | |
4882 | * have C_DELETED. Thus, they will simply act on the stale open-unlinked | |
4883 | * element. All future callers will get the new element. | |
4884 | * | |
4885 | * To implement this behavior, we pass the "only_unlink" argument to | |
4886 | * hfs_removefile and hfs_removedir. This will result in the vnode acting | |
4887 | * as though it is open-unlinked. Additionally, when we are done moving the | |
4888 | * element to the hidden directory, we vnode_recycle the target so that it is | |
4889 | * reclaimed as soon as possible. Reclaim and inactive are both | |
4890 | * capable of clearing out unused blocks for an open-unlinked file or dir. | |
55e303ae | 4891 | */ |
55e303ae | 4892 | if (tvp) { |
22ba694c A |
4893 | // |
4894 | // if the destination has a document id, we need to preserve it | |
4895 | // | |
4896 | if (fvp != tvp) { | |
4897 | uint32_t document_id; | |
4898 | struct FndrExtendedDirInfo *ffip = (struct FndrExtendedDirInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16); | |
4899 | struct FndrExtendedDirInfo *tfip = (struct FndrExtendedDirInfo *)((char *)&tcp->c_attr.ca_finderinfo + 16); | |
4900 | ||
4901 | if (ffip->document_id && tfip->document_id) { | |
4902 | // both documents are tracked. only save a tombstone from tcp and do nothing else. | |
4903 | save_tombstone(hfsmp, tdvp, tvp, tcnp, 0); | |
4904 | } else { | |
4905 | struct doc_tombstone *ut; | |
4906 | ut = get_uthread_doc_tombstone(); | |
4907 | ||
4908 | document_id = tfip->document_id; | |
4909 | tfip->document_id = 0; | |
4910 | ||
4911 | if (document_id != 0) { | |
4912 | // clear UF_TRACKED as well since tcp is now no longer tracked | |
4913 | tcp->c_bsdflags &= ~UF_TRACKED; | |
4914 | (void) cat_update(hfsmp, &tcp->c_desc, &tcp->c_attr, NULL, NULL); | |
4915 | } | |
4916 | ||
4917 | if (ffip->document_id == 0 && document_id != 0) { | |
4918 | // printf("RENAME: preserving doc-id %d onto %s (from ino %d, to ino %d)\n", document_id, tcp->c_desc.cd_nameptr, tcp->c_desc.cd_cnid, fcp->c_desc.cd_cnid); | |
4919 | fcp->c_bsdflags |= UF_TRACKED; | |
4920 | ffip->document_id = document_id; | |
4921 | ||
4922 | (void) cat_update(hfsmp, &fcp->c_desc, &fcp->c_attr, NULL, NULL); | |
4923 | #if CONFIG_FSE | |
4924 | add_fsevent(FSE_DOCID_CHANGED, vfs_context_current(), | |
4925 | FSE_ARG_DEV, hfsmp->hfs_raw_dev, | |
4926 | FSE_ARG_INO, (ino64_t)tcp->c_fileid, // src inode # | |
4927 | FSE_ARG_INO, (ino64_t)fcp->c_fileid, // dst inode # | |
4928 | FSE_ARG_INT32, (uint32_t)ffip->document_id, | |
4929 | FSE_ARG_DONE); | |
4930 | #endif | |
4931 | } else if ((fcp->c_bsdflags & UF_TRACKED) && should_save_docid_tombstone(ut, fvp, fcnp)) { | |
4932 | ||
4933 | if (ut->t_lastop_document_id) { | |
4934 | clear_tombstone_docid(ut, hfsmp, NULL); | |
4935 | } | |
4936 | save_tombstone(hfsmp, fdvp, fvp, fcnp, 0); | |
4937 | ||
4938 | //printf("RENAME: (dest-exists): saving tombstone doc-id %lld @ %s (ino %d)\n", | |
4939 | // ut->t_lastop_document_id, ut->t_lastop_filename, fcp->c_desc.cd_cnid); | |
4940 | } | |
4941 | } | |
4942 | } | |
4943 | ||
55e303ae | 4944 | /* |
2d21ac55 A |
4945 | * When fvp matches tvp they could be case variants |
4946 | * or matching hard links. | |
55e303ae | 4947 | */ |
91447636 | 4948 | if (fvp == tvp) { |
2d21ac55 | 4949 | if (!(fcp->c_flag & C_HARDLINK)) { |
7ee9d059 A |
4950 | /* |
4951 | * If they're not hardlinks, then fvp == tvp must mean we | |
4952 | * are using case-insensitive HFS because case-sensitive would | |
4953 | * not use the same vnode for both. In this case we just update | |
4954 | * the catalog for: a -> A | |
4955 | */ | |
2d21ac55 A |
4956 | goto skip_rm; /* simple case variant */ |
4957 | ||
7ee9d059 A |
4958 | } |
4959 | /* For all cases below, we must be using hardlinks */ | |
4960 | else if ((fdvp != tdvp) || | |
2d21ac55 | 4961 | (hfsmp->hfs_flags & HFS_CASE_SENSITIVE)) { |
7ee9d059 A |
4962 | /* |
4963 | * If the parent directories are not the same, AND the two items | |
4964 | * are hardlinks, posix says to do nothing: | |
4965 | * dir1/fred <-> dir2/bob and the op was mv dir1/fred -> dir2/bob | |
4966 | * We just return 0 in this case. | |
4967 | * | |
4968 | * If case sensitivity is on, and we are using hardlinks | |
4969 | * then renaming is supposed to do nothing. | |
4970 | * dir1/fred <-> dir2/FRED, and op == mv dir1/fred -> dir2/FRED | |
4971 | */ | |
2d21ac55 A |
4972 | goto out; /* matching hardlinks, nothing to do */ |
4973 | ||
4974 | } else if (hfs_namecmp((const u_int8_t *)fcnp->cn_nameptr, fcnp->cn_namelen, | |
4975 | (const u_int8_t *)tcnp->cn_nameptr, tcnp->cn_namelen) == 0) { | |
7ee9d059 A |
4976 | /* |
4977 | * If we get here, then the following must be true: | |
4978 | * a) We are running case-insensitive HFS+. | |
4979 | * b) Both paths 'fvp' and 'tvp' are in the same parent directory. | |
4980 | * c) the two names are case-variants of each other. | |
4981 | * | |
4982 | * In this case, we are really only dealing with a single catalog record | |
4983 | * whose name is being updated. | |
4984 | * | |
4985 | * op is dir1/fred -> dir1/FRED | |
4986 | * | |
4987 | * We need to special case the name matching, because if | |
4988 | * dir1/fred <-> dir1/bob were the two links, and the | |
4989 | * op was dir1/fred -> dir1/bob | |
4990 | * That would fail/do nothing. | |
4991 | */ | |
2d21ac55 A |
4992 | goto skip_rm; /* case-variant hardlink in the same dir */ |
4993 | } else { | |
4994 | goto out; /* matching hardlink, nothing to do */ | |
91447636 | 4995 | } |
91447636 A |
4996 | } |
4997 | ||
6d2010ae A |
4998 | |
4999 | if (vnode_isdir(tvp)) { | |
5000 | /* | |
5001 | * hfs_removedir will eventually call hfs_removefile on the directory | |
5002 | * we're working on, because only hfs_removefile does the renaming of the | |
5003 | * item to the hidden directory. The directory will stay around in the | |
5004 | * hidden directory with C_DELETED until it gets an inactive or a reclaim. | |
5005 | * That way, we can destroy all of the EAs as needed and allow new ones to be | |
5006 | * written. | |
cf7d32b8 | 5007 | */ |
6d2010ae A |
5008 | error = hfs_removedir(tdvp, tvp, tcnp, HFSRM_SKIP_RESERVE, 1); |
5009 | } | |
5010 | else { | |
316670eb | 5011 | error = hfs_removefile(tdvp, tvp, tcnp, 0, HFSRM_SKIP_RESERVE, 0, NULL, 1); |
6d2010ae A |
5012 | |
5013 | /* | |
5014 | * If the destination file had a resource fork vnode, then we need to get rid of | |
5015 | * its blocks when there are no more references to it. Because the call to | |
5016 | * hfs_removefile above always open-unlinks things, we need to force an inactive/reclaim | |
5017 | * on the resource fork vnode, in order to prevent block leaks. Otherwise, | |
5018 | * the resource fork vnode could prevent the data fork vnode from going out of scope | |
5019 | * because it holds a v_parent reference on it. So we mark it for termination | |
5020 | * with a call to vnode_recycle. hfs_vnop_reclaim has been modified so that it | |
5021 | * can clean up the blocks of open-unlinked files and resource forks. | |
5022 | * | |
5023 | * We can safely call vnode_recycle on the resource fork because we took an iocount | |
5024 | * reference on it at the beginning of the function. | |
5025 | */ | |
5026 | ||
5027 | if ((error == 0) && (tcp->c_flag & C_DELETED) && (tvp_rsrc)) { | |
5028 | vnode_recycle(tvp_rsrc); | |
5029 | } | |
91447636 | 5030 | } |
55e303ae | 5031 | |
6d2010ae | 5032 | if (error) { |
55e303ae | 5033 | goto out; |
6d2010ae A |
5034 | } |
5035 | ||
91447636 | 5036 | tvp_deleted = 1; |
6d2010ae A |
5037 | |
5038 | /* Mark 'tcp' as being deleted due to a rename */ | |
5039 | tcp->c_flag |= C_RENAMED; | |
5040 | ||
5041 | /* | |
5042 | * Aggressively mark tvp/tcp for termination to ensure that we recover all blocks | |
5043 | * as quickly as possible. | |
5044 | */ | |
5045 | vnode_recycle(tvp); | |
22ba694c A |
5046 | } else { |
5047 | struct doc_tombstone *ut; | |
5048 | ut = get_uthread_doc_tombstone(); | |
5049 | ||
5050 | // | |
5051 | // There is nothing at the destination. If the file being renamed is | |
5052 | // tracked, save a "tombstone" of the document_id. If the file is | |
5053 | // not a tracked file, then see if it needs to inherit a tombstone. | |
5054 | // | |
5055 | // NOTE: we do not save a tombstone if the file being renamed begins | |
5056 | // with "atmp" which is done to work-around AutoCad's bizarre | |
5057 | // 5-step un-safe save behavior | |
5058 | // | |
5059 | if (fcp->c_bsdflags & UF_TRACKED) { | |
5060 | if (should_save_docid_tombstone(ut, fvp, fcnp)) { | |
5061 | save_tombstone(hfsmp, fdvp, fvp, fcnp, 0); | |
5062 | ||
5063 | //printf("RENAME: (no dest): saving tombstone doc-id %lld @ %s (ino %d)\n", | |
5064 | // ut->t_lastop_document_id, ut->t_lastop_filename, fcp->c_desc.cd_cnid); | |
5065 | } else { | |
5066 | // intentionally do nothing | |
5067 | } | |
5068 | } else if ( ut->t_lastop_document_id != 0 | |
5069 | && tdvp == ut->t_lastop_parent | |
5070 | && vnode_vid(tdvp) == ut->t_lastop_parent_vid | |
5071 | && strcmp((char *)ut->t_lastop_filename, (char *)tcnp->cn_nameptr) == 0) { | |
5072 | ||
5073 | //printf("RENAME: %s (ino %d) inheriting doc-id %lld\n", tcnp->cn_nameptr, fcp->c_desc.cd_cnid, ut->t_lastop_document_id); | |
5074 | struct FndrExtendedFileInfo *fip = (struct FndrExtendedFileInfo *)((char *)&fcp->c_attr.ca_finderinfo + 16); | |
5075 | fcp->c_bsdflags |= UF_TRACKED; | |
5076 | fip->document_id = ut->t_lastop_document_id; | |
5077 | cat_update(hfsmp, &fcp->c_desc, &fcp->c_attr, NULL, NULL); | |
5078 | ||
5079 | clear_tombstone_docid(ut, hfsmp, fcp); // will send the docid-changed fsevent | |
5080 | ||
5081 | } else if (ut->t_lastop_document_id && should_save_docid_tombstone(ut, fvp, fcnp) && should_save_docid_tombstone(ut, tvp, tcnp)) { | |
5082 | // no match, clear the tombstone | |
5083 | //printf("RENAME: clearing the tombstone %lld @ %s\n", ut->t_lastop_document_id, ut->t_lastop_filename); | |
5084 | clear_tombstone_docid(ut, hfsmp, NULL); | |
5085 | } | |
5086 | ||
55e303ae | 5087 | } |
2d21ac55 | 5088 | skip_rm: |
55e303ae | 5089 | /* |
b0d623f7 A |
5090 | * All done with tvp and fvp. |
5091 | * | |
e2fac8b1 A |
5092 | * We also jump to this point if there was no destination observed during lookup and namei. |
5093 | * However, because only iocounts are held at the VFS layer, there is nothing preventing a | |
5094 | * competing thread from racing us and creating a file or dir at the destination of this rename | |
5095 | * operation. If this occurs, it may cause us to get a spurious EEXIST out of the cat_rename | |
5096 | * call below. To preserve rename's atomicity, we need to signal VFS to re-drive the | |
5097 | * namei/lookup and restart the rename operation. EEXIST is an allowable errno to be bubbled | |
5098 | * out of the rename syscall, but not for this reason, since it is a synonym errno for ENOTEMPTY. | |
5099 | * To signal VFS, we return ERECYCLE (which is also used for lookup restarts). This errno | |
5100 | * will be swallowed and it will restart the operation. | |
55e303ae | 5101 | */ |
b0d623f7 | 5102 | |
91447636 | 5103 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK); |
d7e50217 | 5104 | error = cat_rename(hfsmp, &from_desc, &tdcp->c_desc, &to_desc, &out_desc); |
91447636 | 5105 | hfs_systemfile_unlock(hfsmp, lockflags); |
55e303ae A |
5106 | |
5107 | if (error) { | |
e2fac8b1 A |
5108 | if (error == EEXIST) { |
5109 | error = ERECYCLE; | |
5110 | } | |
d7e50217 | 5111 | goto out; |
55e303ae | 5112 | } |
9bccf70c | 5113 | |
91447636 | 5114 | /* Invalidate negative cache entries in the destination directory */ |
2d21ac55 | 5115 | if (tdcp->c_flag & C_NEG_ENTRIES) { |
91447636 | 5116 | cache_purge_negatives(tdvp); |
2d21ac55 A |
5117 | tdcp->c_flag &= ~C_NEG_ENTRIES; |
5118 | } | |
91447636 | 5119 | |
d7e50217 | 5120 | /* Update cnode's catalog descriptor */ |
91447636 | 5121 | replace_desc(fcp, &out_desc); |
2d21ac55 | 5122 | fcp->c_parentcnid = tdcp->c_fileid; |
91447636 | 5123 | fcp->c_hint = 0; |
316670eb | 5124 | |
6d2010ae A |
5125 | /* Now indicate this cnode needs to have date-added written to the finderinfo */ |
5126 | fcp->c_flag |= C_NEEDS_DATEADDED; | |
5127 | (void) hfs_update (fvp, 0); | |
5128 | ||
9bccf70c | 5129 | |
91447636 | 5130 | hfs_volupdate(hfsmp, vnode_isdir(fvp) ? VOL_RMDIR : VOL_RMFILE, |
d7e50217 | 5131 | (fdcp->c_cnid == kHFSRootFolderID)); |
91447636 | 5132 | hfs_volupdate(hfsmp, vnode_isdir(fvp) ? VOL_MKDIR : VOL_MKFILE, |
d7e50217 A |
5133 | (tdcp->c_cnid == kHFSRootFolderID)); |
5134 | ||
d7e50217 | 5135 | /* Update both parent directories. */ |
b4c24cb9 | 5136 | if (fdvp != tdvp) { |
2d21ac55 A |
5137 | if (vnode_isdir(fvp)) { |
5138 | /* If the source directory has directory hard link | |
5139 | * descendants, set the kHFSHasChildLinkBit in the | |
5140 | * destination parent hierarchy | |
5141 | */ | |
5142 | if ((fcp->c_attr.ca_recflags & kHFSHasChildLinkMask) && | |
5143 | !(tdcp->c_attr.ca_recflags & kHFSHasChildLinkMask)) { | |
5144 | ||
5145 | tdcp->c_attr.ca_recflags |= kHFSHasChildLinkMask; | |
5146 | ||
5147 | error = cat_set_childlinkbit(hfsmp, tdcp->c_parentcnid); | |
5148 | if (error) { | |
5149 | printf ("hfs_vnop_rename: error updating parent chain for %u\n", tdcp->c_cnid); | |
5150 | error = 0; | |
5151 | } | |
5152 | } | |
5153 | INC_FOLDERCOUNT(hfsmp, tdcp->c_attr); | |
5154 | DEC_FOLDERCOUNT(hfsmp, fdcp->c_attr); | |
5155 | } | |
d7e50217 | 5156 | tdcp->c_entries++; |
2d21ac55 | 5157 | tdcp->c_dirchangecnt++; |
fe8ab488 A |
5158 | hfs_incr_gencount(tdcp); |
5159 | ||
d7e50217 A |
5160 | if (fdcp->c_entries > 0) |
5161 | fdcp->c_entries--; | |
2d21ac55 | 5162 | fdcp->c_dirchangecnt++; |
91447636 A |
5163 | fdcp->c_touch_chgtime = TRUE; |
5164 | fdcp->c_touch_modtime = TRUE; | |
5165 | ||
5166 | fdcp->c_flag |= C_FORCEUPDATE; // XXXdbg - force it out! | |
5167 | (void) hfs_update(fdvp, 0); | |
b4c24cb9 | 5168 | } |
fe8ab488 | 5169 | hfs_incr_gencount(fdcp); |
22ba694c | 5170 | |
d7e50217 | 5171 | tdcp->c_childhint = out_desc.cd_hint; /* Cache directory's location */ |
91447636 A |
5172 | tdcp->c_touch_chgtime = TRUE; |
5173 | tdcp->c_touch_modtime = TRUE; | |
b4c24cb9 | 5174 | |
91447636 A |
5175 | tdcp->c_flag |= C_FORCEUPDATE; // XXXdbg - force it out! |
5176 | (void) hfs_update(tdvp, 0); | |
99c3a104 A |
5177 | |
5178 | /* Update the vnode's name now that the rename has completed. */ | |
5179 | vnode_update_identity(fvp, tdvp, tcnp->cn_nameptr, tcnp->cn_namelen, | |
5180 | tcnp->cn_hash, (VNODE_UPDATE_PARENT | VNODE_UPDATE_NAME)); | |
39236c6e | 5181 | |
99c3a104 A |
5182 | /* |
5183 | * At this point, we may have a resource fork vnode attached to the | |
5184 | * 'from' vnode. If it exists, we will want to update its name, because | |
5185 | * it contains the old name + _PATH_RSRCFORKSPEC. ("/..namedfork/rsrc"). | |
5186 | * | |
5187 | * Note that the only thing we need to update here is the name attached to | |
5188 | * the vnode, since a resource fork vnode does not have a separate resource | |
5189 | * cnode -- it's still 'fcp'. | |
5190 | */ | |
5191 | if (fcp->c_rsrc_vp) { | |
5192 | char* rsrc_path = NULL; | |
5193 | int len; | |
5194 | ||
5195 | /* Create a new temporary buffer that's going to hold the new name */ | |
5196 | MALLOC_ZONE (rsrc_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); | |
5197 | len = snprintf (rsrc_path, MAXPATHLEN, "%s%s", tcnp->cn_nameptr, _PATH_RSRCFORKSPEC); | |
5198 | len = MIN(len, MAXPATHLEN); | |
5199 | ||
5200 | /* | |
5201 | * vnode_update_identity will do the following for us: | |
5202 | * 1) release reference on the existing rsrc vnode's name. | |
5203 | * 2) copy/insert new name into the name cache | |
5204 | * 3) attach the new name to the resource vnode | |
5205 | * 4) update the vnode's vid | |
5206 | */ | |
5207 | vnode_update_identity (fcp->c_rsrc_vp, fvp, rsrc_path, len, 0, (VNODE_UPDATE_NAME | VNODE_UPDATE_CACHE)); | |
39236c6e | 5208 | |
99c3a104 A |
5209 | /* Free the memory associated with the resource fork's name */ |
5210 | FREE_ZONE (rsrc_path, MAXPATHLEN, M_NAMEI); | |
5211 | } | |
d7e50217 | 5212 | out: |
91447636 | 5213 | if (got_cookie) { |
55e303ae A |
5214 | cat_postflight(hfsmp, &cookie, p); |
5215 | } | |
b4c24cb9 | 5216 | if (started_tr) { |
91447636 | 5217 | hfs_end_transaction(hfsmp); |
b4c24cb9 A |
5218 | } |
5219 | ||
2d21ac55 A |
5220 | fdcp->c_flag &= ~C_DIR_MODIFICATION; |
5221 | wakeup((caddr_t)&fdcp->c_flag); | |
5222 | if (fdvp != tdvp) { | |
5223 | tdcp->c_flag &= ~C_DIR_MODIFICATION; | |
5224 | wakeup((caddr_t)&tdcp->c_flag); | |
5225 | } | |
5226 | ||
04b8595b A |
5227 | hfs_unlockfour(fdcp, fcp, tdcp, tcp); |
5228 | ||
6d2010ae | 5229 | if (took_trunc_lock) { |
39236c6e | 5230 | hfs_unlock_truncate(VTOC(tvp), HFS_LOCK_DEFAULT); |
6d2010ae | 5231 | } |
91447636 | 5232 | |
39236c6e | 5233 | /* Now vnode_put the resource forks vnodes if necessary */ |
b0d623f7 A |
5234 | if (tvp_rsrc) { |
5235 | vnode_put(tvp_rsrc); | |
39236c6e | 5236 | tvp_rsrc = NULL; |
b0d623f7 | 5237 | } |
cf7d32b8 | 5238 | |
d7e50217 | 5239 | /* After tvp is removed the only acceptable error is EIO */ |
55e303ae | 5240 | if (error && tvp_deleted) |
d7e50217 A |
5241 | error = EIO; |
5242 | ||
fe8ab488 A |
5243 | /* If we want to reintroduce notifications for renames, this is the |
5244 | place to do it. */ | |
39236c6e | 5245 | |
d7e50217 | 5246 | return (error); |
9bccf70c A |
5247 | } |
5248 | ||
5249 | ||
9bccf70c | 5250 | /* |
91447636 A |
5251 | * Make a directory. |
5252 | */ | |
6d2010ae | 5253 | int |
91447636 | 5254 | hfs_vnop_mkdir(struct vnop_mkdir_args *ap) |
9bccf70c | 5255 | { |
91447636 A |
5256 | /***** HACK ALERT ********/ |
5257 | ap->a_cnp->cn_flags |= MAKEENTRY; | |
5258 | return hfs_makenode(ap->a_dvp, ap->a_vpp, ap->a_cnp, ap->a_vap, ap->a_context); | |
9bccf70c A |
5259 | } |
5260 | ||
5261 | ||
5262 | /* | |
91447636 A |
5263 | * Create a symbolic link. |
5264 | */ | |
6d2010ae | 5265 | int |
91447636 | 5266 | hfs_vnop_symlink(struct vnop_symlink_args *ap) |
9bccf70c | 5267 | { |
91447636 A |
5268 | struct vnode **vpp = ap->a_vpp; |
5269 | struct vnode *dvp = ap->a_dvp; | |
5270 | struct vnode *vp = NULL; | |
2d21ac55 | 5271 | struct cnode *cp = NULL; |
b4c24cb9 | 5272 | struct hfsmount *hfsmp; |
9bccf70c | 5273 | struct filefork *fp; |
9bccf70c | 5274 | struct buf *bp = NULL; |
91447636 A |
5275 | char *datap; |
5276 | int started_tr = 0; | |
2d21ac55 A |
5277 | u_int32_t len; |
5278 | int error; | |
9bccf70c A |
5279 | |
5280 | /* HFS standard disks don't support symbolic links */ | |
91447636 A |
5281 | if (VTOVCB(dvp)->vcbSigWord != kHFSPlusSigWord) |
5282 | return (ENOTSUP); | |
9bccf70c A |
5283 | |
5284 | /* Check for empty target name */ | |
91447636 | 5285 | if (ap->a_target[0] == 0) |
9bccf70c | 5286 | return (EINVAL); |
b4c24cb9 | 5287 | |
2d21ac55 A |
5288 | hfsmp = VTOHFS(dvp); |
5289 | len = strlen(ap->a_target); | |
5290 | ||
5291 | /* Check for free space */ | |
5292 | if (((u_int64_t)hfs_freeblks(hfsmp, 0) * (u_int64_t)hfsmp->blockSize) < len) { | |
5293 | return (ENOSPC); | |
5294 | } | |
5295 | ||
9bccf70c | 5296 | /* Create the vnode */ |
91447636 A |
5297 | ap->a_vap->va_mode |= S_IFLNK; |
5298 | if ((error = hfs_makenode(dvp, vpp, ap->a_cnp, ap->a_vap, ap->a_context))) { | |
5299 | goto out; | |
b4c24cb9 | 5300 | } |
9bccf70c | 5301 | vp = *vpp; |
39236c6e | 5302 | if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { |
2d21ac55 A |
5303 | goto out; |
5304 | } | |
5305 | cp = VTOC(vp); | |
9bccf70c | 5306 | fp = VTOF(vp); |
2d21ac55 A |
5307 | |
5308 | if (cp->c_flag & (C_NOEXISTS | C_DELETED)) { | |
5309 | goto out; | |
5310 | } | |
9bccf70c | 5311 | |
d7e50217 | 5312 | #if QUOTA |
2d21ac55 | 5313 | (void)hfs_getinoquota(cp); |
d7e50217 A |
5314 | #endif /* QUOTA */ |
5315 | ||
91447636 A |
5316 | if ((error = hfs_start_transaction(hfsmp)) != 0) { |
5317 | goto out; | |
b4c24cb9 | 5318 | } |
91447636 | 5319 | started_tr = 1; |
b4c24cb9 | 5320 | |
91447636 A |
5321 | /* |
5322 | * Allocate space for the link. | |
5323 | * | |
5324 | * Since we're already inside a transaction, | |
91447636 A |
5325 | * |
5326 | * Don't need truncate lock since a symlink is treated as a system file. | |
5327 | */ | |
fe8ab488 | 5328 | error = hfs_truncate(vp, len, IO_NOZEROFILL, 0, ap->a_context); |
2d21ac55 A |
5329 | |
5330 | /* On errors, remove the symlink file */ | |
5331 | if (error) { | |
5332 | /* | |
5333 | * End the transaction so we don't re-take the cnode lock | |
5334 | * below while inside a transaction (lock order violation). | |
5335 | */ | |
5336 | hfs_end_transaction(hfsmp); | |
5337 | ||
5338 | /* hfs_removefile() requires holding the truncate lock */ | |
5339 | hfs_unlock(cp); | |
39236c6e A |
5340 | hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
5341 | hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS); | |
2d21ac55 A |
5342 | |
5343 | if (hfs_start_transaction(hfsmp) != 0) { | |
5344 | started_tr = 0; | |
39236c6e | 5345 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
2d21ac55 A |
5346 | goto out; |
5347 | } | |
5348 | ||
6d2010ae | 5349 | (void) hfs_removefile(dvp, vp, ap->a_cnp, 0, 0, 0, NULL, 0); |
39236c6e | 5350 | hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT); |
2d21ac55 A |
5351 | goto out; |
5352 | } | |
9bccf70c A |
5353 | |
5354 | /* Write the link to disk */ | |
593a1d5f | 5355 | bp = buf_getblk(vp, (daddr64_t)0, roundup((int)fp->ff_size, hfsmp->hfs_physical_block_size), |
9bccf70c | 5356 | 0, 0, BLK_META); |
b4c24cb9 A |
5357 | if (hfsmp->jnl) { |
5358 | journal_modify_block_start(hfsmp->jnl, bp); | |
5359 | } | |
91447636 A |
5360 | datap = (char *)buf_dataptr(bp); |
5361 | bzero(datap, buf_size(bp)); | |
5362 | bcopy(ap->a_target, datap, len); | |
5363 | ||
b4c24cb9 | 5364 | if (hfsmp->jnl) { |
2d21ac55 | 5365 | journal_modify_block_end(hfsmp->jnl, bp, NULL, NULL); |
b4c24cb9 | 5366 | } else { |
91447636 | 5367 | buf_bawrite(bp); |
b4c24cb9 | 5368 | } |
9bccf70c | 5369 | out: |
91447636 A |
5370 | if (started_tr) |
5371 | hfs_end_transaction(hfsmp); | |
2d21ac55 A |
5372 | if ((cp != NULL) && (vp != NULL)) { |
5373 | hfs_unlock(cp); | |
5374 | } | |
5375 | if (error) { | |
5376 | if (vp) { | |
5377 | vnode_put(vp); | |
5378 | } | |
5379 | *vpp = NULL; | |
b4c24cb9 | 5380 | } |
9bccf70c A |
5381 | return (error); |
5382 | } | |
5383 | ||
5384 | ||
91447636 A |
5385 | /* structures to hold a "." or ".." directory entry */ |
5386 | struct hfs_stddotentry { | |
5387 | u_int32_t d_fileno; /* unique file number */ | |
5388 | u_int16_t d_reclen; /* length of this structure */ | |
5389 | u_int8_t d_type; /* dirent file type */ | |
5390 | u_int8_t d_namlen; /* len of filename */ | |
5391 | char d_name[4]; /* "." or ".." */ | |
9bccf70c A |
5392 | }; |
5393 | ||
91447636 A |
5394 | struct hfs_extdotentry { |
5395 | u_int64_t d_fileno; /* unique file number */ | |
5396 | u_int64_t d_seekoff; /* seek offset (optional, used by servers) */ | |
5397 | u_int16_t d_reclen; /* length of this structure */ | |
5398 | u_int16_t d_namlen; /* len of filename */ | |
5399 | u_int8_t d_type; /* dirent file type */ | |
5400 | u_char d_name[3]; /* "." or ".." */ | |
5401 | }; | |
9bccf70c | 5402 | |
91447636 A |
5403 | typedef union { |
5404 | struct hfs_stddotentry std; | |
5405 | struct hfs_extdotentry ext; | |
5406 | } hfs_dotentry_t; | |
9bccf70c A |
5407 | |
5408 | /* | |
91447636 A |
5409 | * hfs_vnop_readdir reads directory entries into the buffer pointed |
5410 | * to by uio, in a filesystem independent format. Up to uio_resid | |
5411 | * bytes of data can be transferred. The data in the buffer is a | |
5412 | * series of packed dirent structures where each one contains the | |
5413 | * following entries: | |
5414 | * | |
5415 | * u_int32_t d_fileno; // file number of entry | |
5416 | * u_int16_t d_reclen; // length of this record | |
5417 | * u_int8_t d_type; // file type | |
5418 | * u_int8_t d_namlen; // length of string in d_name | |
5419 | * char d_name[MAXNAMELEN+1]; // null terminated file name | |
5420 | * | |
5421 | * The current position (uio_offset) refers to the next block of | |
5422 | * entries. The offset can only be set to a value previously | |
5423 | * returned by hfs_vnop_readdir or zero. This offset does not have | |
5424 | * to match the number of bytes returned (in uio_resid). | |
5425 | * | |
5426 | * In fact, the offset used by HFS is essentially an index (26 bits) | |
5427 | * with a tag (6 bits). The tag is for associating the next request | |
22ba694c | 5428 | * with the current request. This enables us to have multiple threads |
91447636 A |
5429 | * reading the directory while the directory is also being modified. |
5430 | * | |
5431 | * Each tag/index pair is tied to a unique directory hint. The hint | |
5432 | * contains information (filename) needed to build the catalog b-tree | |
5433 | * key for finding the next set of entries. | |
2d21ac55 A |
5434 | * |
5435 | * If the directory is marked as deleted-but-in-use (cp->c_flag & C_DELETED), | |
5436 | * do NOT synthesize entries for "." and "..". | |
9bccf70c | 5437 | */ |
6d2010ae | 5438 | int |
91447636 A |
5439 | hfs_vnop_readdir(ap) |
5440 | struct vnop_readdir_args /* { | |
5441 | vnode_t a_vp; | |
5442 | uio_t a_uio; | |
5443 | int a_flags; | |
5444 | int *a_eofflag; | |
5445 | int *a_numdirent; | |
5446 | vfs_context_t a_context; | |
9bccf70c A |
5447 | } */ *ap; |
5448 | { | |
91447636 A |
5449 | struct vnode *vp = ap->a_vp; |
5450 | uio_t uio = ap->a_uio; | |
5451 | struct cnode *cp; | |
5452 | struct hfsmount *hfsmp; | |
5453 | directoryhint_t *dirhint = NULL; | |
5454 | directoryhint_t localhint; | |
5455 | off_t offset; | |
5456 | off_t startoffset; | |
5457 | int error = 0; | |
9bccf70c | 5458 | int eofflag = 0; |
91447636 A |
5459 | user_addr_t user_start = 0; |
5460 | user_size_t user_len = 0; | |
5461 | int index; | |
5462 | unsigned int tag; | |
5463 | int items; | |
5464 | int lockflags; | |
5465 | int extended; | |
5466 | int nfs_cookies; | |
91447636 | 5467 | cnid_t cnid_hint = 0; |
fe8ab488 | 5468 | int bump_valence = 0; |
91447636 A |
5469 | |
5470 | items = 0; | |
5471 | startoffset = offset = uio_offset(uio); | |
91447636 A |
5472 | extended = (ap->a_flags & VNODE_READDIR_EXTENDED); |
5473 | nfs_cookies = extended && (ap->a_flags & VNODE_READDIR_REQSEEKOFF); | |
5474 | ||
5475 | /* Sanity check the uio data. */ | |
b0d623f7 | 5476 | if (uio_iovcnt(uio) > 1) |
91447636 | 5477 | return (EINVAL); |
6d2010ae | 5478 | |
316670eb | 5479 | if (VTOC(vp)->c_bsdflags & UF_COMPRESSED) { |
6d2010ae A |
5480 | int compressed = hfs_file_is_compressed(VTOC(vp), 0); /* 0 == take the cnode lock */ |
5481 | if (VTOCMP(vp) != NULL && !compressed) { | |
5482 | error = check_for_dataless_file(vp, NAMESPACE_HANDLER_READ_OP); | |
5483 | if (error) { | |
5484 | return error; | |
5485 | } | |
5486 | } | |
5487 | } | |
5488 | ||
5489 | cp = VTOC(vp); | |
5490 | hfsmp = VTOHFS(vp); | |
5491 | ||
91447636 | 5492 | /* Note that the dirhint calls require an exclusive lock. */ |
39236c6e | 5493 | if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) |
91447636 | 5494 | return (error); |
55e303ae | 5495 | |
91447636 A |
5496 | /* Pick up cnid hint (if any). */ |
5497 | if (nfs_cookies) { | |
5498 | cnid_hint = (cnid_t)(uio_offset(uio) >> 32); | |
5499 | uio_setoffset(uio, uio_offset(uio) & 0x00000000ffffffffLL); | |
3a60a9f5 A |
5500 | if (cnid_hint == INT_MAX) { /* searching pass the last item */ |
5501 | eofflag = 1; | |
5502 | goto out; | |
5503 | } | |
91447636 A |
5504 | } |
5505 | /* | |
2d21ac55 A |
5506 | * Synthesize entries for "." and "..", unless the directory has |
5507 | * been deleted, but not closed yet (lazy delete in progress). | |
91447636 | 5508 | */ |
2d21ac55 | 5509 | if (offset == 0 && !(cp->c_flag & C_DELETED)) { |
91447636 A |
5510 | hfs_dotentry_t dotentry[2]; |
5511 | size_t uiosize; | |
5512 | ||
5513 | if (extended) { | |
5514 | struct hfs_extdotentry *entry = &dotentry[0].ext; | |
5515 | ||
5516 | entry->d_fileno = cp->c_cnid; | |
5517 | entry->d_reclen = sizeof(struct hfs_extdotentry); | |
5518 | entry->d_type = DT_DIR; | |
5519 | entry->d_namlen = 1; | |
5520 | entry->d_name[0] = '.'; | |
5521 | entry->d_name[1] = '\0'; | |
5522 | entry->d_name[2] = '\0'; | |
5523 | entry->d_seekoff = 1; | |
5524 | ||
5525 | ++entry; | |
5526 | entry->d_fileno = cp->c_parentcnid; | |
5527 | entry->d_reclen = sizeof(struct hfs_extdotentry); | |
5528 | entry->d_type = DT_DIR; | |
5529 | entry->d_namlen = 2; | |
5530 | entry->d_name[0] = '.'; | |
5531 | entry->d_name[1] = '.'; | |
5532 | entry->d_name[2] = '\0'; | |
5533 | entry->d_seekoff = 2; | |
5534 | uiosize = 2 * sizeof(struct hfs_extdotentry); | |
5535 | } else { | |
5536 | struct hfs_stddotentry *entry = &dotentry[0].std; | |
5537 | ||
5538 | entry->d_fileno = cp->c_cnid; | |
5539 | entry->d_reclen = sizeof(struct hfs_stddotentry); | |
5540 | entry->d_type = DT_DIR; | |
5541 | entry->d_namlen = 1; | |
5542 | *(int *)&entry->d_name[0] = 0; | |
5543 | entry->d_name[0] = '.'; | |
5544 | ||
5545 | ++entry; | |
5546 | entry->d_fileno = cp->c_parentcnid; | |
5547 | entry->d_reclen = sizeof(struct hfs_stddotentry); | |
5548 | entry->d_type = DT_DIR; | |
5549 | entry->d_namlen = 2; | |
5550 | *(int *)&entry->d_name[0] = 0; | |
5551 | entry->d_name[0] = '.'; | |
5552 | entry->d_name[1] = '.'; | |
5553 | uiosize = 2 * sizeof(struct hfs_stddotentry); | |
5554 | } | |
5555 | if ((error = uiomove((caddr_t)&dotentry, uiosize, uio))) { | |
5556 | goto out; | |
5557 | } | |
5558 | offset += 2; | |
5559 | } | |
9bccf70c | 5560 | |
fe8ab488 A |
5561 | /* |
5562 | * Intentionally avoid checking the valence here. If we | |
5563 | * have FS corruption that reports the valence is 0, even though it | |
5564 | * has contents, we might artificially skip over iterating | |
5565 | * this directory. | |
5566 | */ | |
5567 | ||
91447636 | 5568 | // |
b4c24cb9 A |
5569 | // We have to lock the user's buffer here so that we won't |
5570 | // fault on it after we've acquired a shared lock on the | |
5571 | // catalog file. The issue is that you can get a 3-way | |
5572 | // deadlock if someone else starts a transaction and then | |
5573 | // tries to lock the catalog file but can't because we're | |
5574 | // here and we can't service our page fault because VM is | |
5575 | // blocked trying to start a transaction as a result of | |
5576 | // trying to free up pages for our page fault. It's messy | |
2d21ac55 | 5577 | // but it does happen on dual-processors that are paging |
b4c24cb9 A |
5578 | // heavily (see radar 3082639 for more info). By locking |
5579 | // the buffer up-front we prevent ourselves from faulting | |
5580 | // while holding the shared catalog file lock. | |
5581 | // | |
5582 | // Fortunately this and hfs_search() are the only two places | |
5583 | // currently (10/30/02) that can fault on user data with a | |
5584 | // shared lock on the catalog file. | |
5585 | // | |
91447636 A |
5586 | if (hfsmp->jnl && uio_isuserspace(uio)) { |
5587 | user_start = uio_curriovbase(uio); | |
5588 | user_len = uio_curriovlen(uio); | |
b4c24cb9 | 5589 | |
91447636 A |
5590 | if ((error = vslock(user_start, user_len)) != 0) { |
5591 | user_start = 0; | |
5592 | goto out; | |
b4c24cb9 A |
5593 | } |
5594 | } | |
91447636 A |
5595 | /* Convert offset into a catalog directory index. */ |
5596 | index = (offset & HFS_INDEX_MASK) - 2; | |
5597 | tag = offset & ~HFS_INDEX_MASK; | |
5598 | ||
5599 | /* Lock catalog during cat_findname and cat_getdirentries. */ | |
5600 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); | |
5601 | ||
5602 | /* When called from NFS, try and resolve a cnid hint. */ | |
5603 | if (nfs_cookies && cnid_hint != 0) { | |
5604 | if (cat_findname(hfsmp, cnid_hint, &localhint.dh_desc) == 0) { | |
2d21ac55 | 5605 | if ( localhint.dh_desc.cd_parentcnid == cp->c_fileid) { |
91447636 A |
5606 | localhint.dh_index = index - 1; |
5607 | localhint.dh_time = 0; | |
b36670ce | 5608 | bzero(&localhint.dh_link, sizeof(localhint.dh_link)); |
91447636 A |
5609 | dirhint = &localhint; /* don't forget to release the descriptor */ |
5610 | } else { | |
5611 | cat_releasedesc(&localhint.dh_desc); | |
5612 | } | |
9bccf70c | 5613 | } |
9bccf70c A |
5614 | } |
5615 | ||
91447636 A |
5616 | /* Get a directory hint (cnode must be locked exclusive) */ |
5617 | if (dirhint == NULL) { | |
2d21ac55 | 5618 | dirhint = hfs_getdirhint(cp, ((index - 1) & HFS_INDEX_MASK) | tag, 0); |
55e303ae | 5619 | |
91447636 A |
5620 | /* Hide tag from catalog layer. */ |
5621 | dirhint->dh_index &= HFS_INDEX_MASK; | |
5622 | if (dirhint->dh_index == HFS_INDEX_MASK) { | |
5623 | dirhint->dh_index = -1; | |
ccc36f2f | 5624 | } |
55e303ae | 5625 | } |
91447636 | 5626 | |
2d21ac55 A |
5627 | if (index == 0) { |
5628 | dirhint->dh_threadhint = cp->c_dirthreadhint; | |
6d2010ae | 5629 | } |
7e4a7d39 A |
5630 | else { |
5631 | /* | |
5632 | * If we have a non-zero index, there is a possibility that during the last | |
5633 | * call to hfs_vnop_readdir we hit EOF for this directory. If that is the case | |
5634 | * then we don't want to return any new entries for the caller. Just return 0 | |
5635 | * items, mark the eofflag, and bail out. Because we won't have done any work, the | |
5636 | * code at the end of the function will release the dirhint for us. | |
5637 | * | |
5638 | * Don't forget to unlock the catalog lock on the way out, too. | |
5639 | */ | |
5640 | if (dirhint->dh_desc.cd_flags & CD_EOF) { | |
5641 | error = 0; | |
5642 | eofflag = 1; | |
5643 | uio_setoffset(uio, startoffset); | |
5644 | hfs_systemfile_unlock (hfsmp, lockflags); | |
5645 | ||
5646 | goto seekoffcalc; | |
5647 | } | |
5648 | } | |
2d21ac55 | 5649 | |
91447636 | 5650 | /* Pack the buffer with dirent entries. */ |
316670eb | 5651 | error = cat_getdirentries(hfsmp, cp->c_entries, dirhint, uio, ap->a_flags, &items, &eofflag); |
55e303ae | 5652 | |
2d21ac55 A |
5653 | if (index == 0 && error == 0) { |
5654 | cp->c_dirthreadhint = dirhint->dh_threadhint; | |
5655 | } | |
5656 | ||
91447636 A |
5657 | hfs_systemfile_unlock(hfsmp, lockflags); |
5658 | ||
5659 | if (error != 0) { | |
5660 | goto out; | |
5661 | } | |
5662 | ||
5663 | /* Get index to the next item */ | |
5664 | index += items; | |
5665 | ||
5666 | if (items >= (int)cp->c_entries) { | |
9bccf70c | 5667 | eofflag = 1; |
9bccf70c | 5668 | } |
fe8ab488 A |
5669 | |
5670 | /* | |
5671 | * Detect valence FS corruption. | |
5672 | * | |
5673 | * We are holding the cnode lock exclusive, so there should not be | |
5674 | * anybody modifying the valence field of this cnode. If we enter | |
5675 | * this block, that means we observed filesystem corruption, because | |
5676 | * this directory reported a valence of 0, yet we found at least one | |
5677 | * item. In this case, we need to minimally self-heal this | |
5678 | * directory to prevent userland from tripping over a directory | |
5679 | * that appears empty (getattr of valence reports 0), but actually | |
5680 | * has contents. | |
5681 | * | |
5682 | * We'll force the cnode update at the end of the function after | |
5683 | * completing all of the normal getdirentries steps. | |
5684 | */ | |
5685 | if ((cp->c_entries == 0) && (items > 0)) { | |
5686 | /* disk corruption */ | |
5687 | cp->c_entries++; | |
5688 | /* Mark the cnode as dirty. */ | |
5689 | cp->c_flag |= (C_MODIFIED | C_FORCEUPDATE); | |
5690 | printf("hfs_vnop_readdir: repairing valence to non-zero! \n"); | |
5691 | bump_valence++; | |
5692 | } | |
5693 | ||
9bccf70c | 5694 | |
91447636 A |
5695 | /* Convert catalog directory index back into an offset. */ |
5696 | while (tag == 0) | |
5697 | tag = (++cp->c_dirhinttag) << HFS_INDEX_BITS; | |
5698 | uio_setoffset(uio, (index + 2) | tag); | |
5699 | dirhint->dh_index |= tag; | |
9bccf70c | 5700 | |
91447636 A |
5701 | seekoffcalc: |
5702 | cp->c_touch_acctime = TRUE; | |
9bccf70c | 5703 | |
91447636 A |
5704 | if (ap->a_numdirent) { |
5705 | if (startoffset == 0) | |
5706 | items += 2; | |
5707 | *ap->a_numdirent = items; | |
9bccf70c | 5708 | } |
9bccf70c | 5709 | |
91447636 | 5710 | out: |
d1ecb069 | 5711 | if (user_start) { |
b4c24cb9 A |
5712 | vsunlock(user_start, user_len, TRUE); |
5713 | } | |
91447636 A |
5714 | /* If we didn't do anything then go ahead and dump the hint. */ |
5715 | if ((dirhint != NULL) && | |
5716 | (dirhint != &localhint) && | |
5717 | (uio_offset(uio) == startoffset)) { | |
5718 | hfs_reldirhint(cp, dirhint); | |
5719 | eofflag = 1; | |
5720 | } | |
5721 | if (ap->a_eofflag) { | |
9bccf70c | 5722 | *ap->a_eofflag = eofflag; |
91447636 A |
5723 | } |
5724 | if (dirhint == &localhint) { | |
5725 | cat_releasedesc(&localhint.dh_desc); | |
5726 | } | |
fe8ab488 A |
5727 | |
5728 | if (bump_valence) { | |
5729 | /* force the update before dropping the cnode lock*/ | |
5730 | hfs_update(vp, 0); | |
5731 | } | |
5732 | ||
91447636 | 5733 | hfs_unlock(cp); |
fe8ab488 | 5734 | |
91447636 | 5735 | return (error); |
9bccf70c A |
5736 | } |
5737 | ||
5738 | ||
5739 | /* | |
91447636 A |
5740 | * Read contents of a symbolic link. |
5741 | */ | |
6d2010ae | 5742 | int |
91447636 A |
5743 | hfs_vnop_readlink(ap) |
5744 | struct vnop_readlink_args /* { | |
9bccf70c A |
5745 | struct vnode *a_vp; |
5746 | struct uio *a_uio; | |
91447636 | 5747 | vfs_context_t a_context; |
9bccf70c A |
5748 | } */ *ap; |
5749 | { | |
9bccf70c A |
5750 | struct vnode *vp = ap->a_vp; |
5751 | struct cnode *cp; | |
5752 | struct filefork *fp; | |
91447636 | 5753 | int error; |
9bccf70c | 5754 | |
91447636 | 5755 | if (!vnode_islnk(vp)) |
9bccf70c A |
5756 | return (EINVAL); |
5757 | ||
39236c6e | 5758 | if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) |
91447636 | 5759 | return (error); |
9bccf70c A |
5760 | cp = VTOC(vp); |
5761 | fp = VTOF(vp); | |
5762 | ||
5763 | /* Zero length sym links are not allowed */ | |
5764 | if (fp->ff_size == 0 || fp->ff_size > MAXPATHLEN) { | |
91447636 A |
5765 | error = EINVAL; |
5766 | goto exit; | |
9bccf70c A |
5767 | } |
5768 | ||
5769 | /* Cache the path so we don't waste buffer cache resources */ | |
5770 | if (fp->ff_symlinkptr == NULL) { | |
5771 | struct buf *bp = NULL; | |
5772 | ||
5773 | MALLOC(fp->ff_symlinkptr, char *, fp->ff_size, M_TEMP, M_WAITOK); | |
b0d623f7 A |
5774 | if (fp->ff_symlinkptr == NULL) { |
5775 | error = ENOMEM; | |
5776 | goto exit; | |
5777 | } | |
91447636 | 5778 | error = (int)buf_meta_bread(vp, (daddr64_t)0, |
593a1d5f | 5779 | roundup((int)fp->ff_size, VTOHFS(vp)->hfs_physical_block_size), |
91447636 A |
5780 | vfs_context_ucred(ap->a_context), &bp); |
5781 | if (error) { | |
9bccf70c | 5782 | if (bp) |
91447636 | 5783 | buf_brelse(bp); |
9bccf70c A |
5784 | if (fp->ff_symlinkptr) { |
5785 | FREE(fp->ff_symlinkptr, M_TEMP); | |
5786 | fp->ff_symlinkptr = NULL; | |
5787 | } | |
91447636 | 5788 | goto exit; |
9bccf70c | 5789 | } |
91447636 | 5790 | bcopy((char *)buf_dataptr(bp), fp->ff_symlinkptr, (size_t)fp->ff_size); |
9bccf70c | 5791 | |
91447636 A |
5792 | if (VTOHFS(vp)->jnl && (buf_flags(bp) & B_LOCKED) == 0) { |
5793 | buf_markinvalid(bp); /* data no longer needed */ | |
5794 | } | |
5795 | buf_brelse(bp); | |
55e303ae | 5796 | } |
91447636 | 5797 | error = uiomove((caddr_t)fp->ff_symlinkptr, (int)fp->ff_size, ap->a_uio); |
9bccf70c | 5798 | |
91447636 A |
5799 | /* |
5800 | * Keep track blocks read | |
5801 | */ | |
5802 | if ((VTOHFS(vp)->hfc_stage == HFC_RECORDING) && (error == 0)) { | |
5803 | ||
5804 | /* | |
5805 | * If this file hasn't been seen since the start of | |
5806 | * the current sampling period then start over. | |
5807 | */ | |
5808 | if (cp->c_atime < VTOHFS(vp)->hfc_timebase) | |
5809 | VTOF(vp)->ff_bytesread = fp->ff_size; | |
5810 | else | |
5811 | VTOF(vp)->ff_bytesread += fp->ff_size; | |
5812 | ||
5813 | // if (VTOF(vp)->ff_bytesread > fp->ff_size) | |
5814 | // cp->c_touch_acctime = TRUE; | |
5815 | } | |
9bccf70c | 5816 | |
91447636 A |
5817 | exit: |
5818 | hfs_unlock(cp); | |
5819 | return (error); | |
9bccf70c A |
5820 | } |
5821 | ||
5822 | ||
5823 | /* | |
91447636 A |
5824 | * Get configurable pathname variables. |
5825 | */ | |
6d2010ae | 5826 | int |
91447636 A |
5827 | hfs_vnop_pathconf(ap) |
5828 | struct vnop_pathconf_args /* { | |
9bccf70c A |
5829 | struct vnode *a_vp; |
5830 | int a_name; | |
5831 | int *a_retval; | |
91447636 | 5832 | vfs_context_t a_context; |
9bccf70c A |
5833 | } */ *ap; |
5834 | { | |
39236c6e A |
5835 | |
5836 | int std_hfs = (VTOHFS(ap->a_vp)->hfs_flags & HFS_STANDARD); | |
9bccf70c A |
5837 | switch (ap->a_name) { |
5838 | case _PC_LINK_MAX: | |
39236c6e | 5839 | if (std_hfs == 0){ |
91447636 | 5840 | *ap->a_retval = HFS_LINK_MAX; |
39236c6e A |
5841 | } |
5842 | #if CONFIG_HFS_STD | |
5843 | else { | |
5844 | *ap->a_retval = 1; | |
5845 | } | |
5846 | #endif | |
9bccf70c A |
5847 | break; |
5848 | case _PC_NAME_MAX: | |
39236c6e | 5849 | if (std_hfs == 0) { |
6d2010ae | 5850 | *ap->a_retval = kHFSPlusMaxFileNameChars; /* 255 */ |
39236c6e A |
5851 | } |
5852 | #if CONFIG_HFS_STD | |
5853 | else { | |
5854 | *ap->a_retval = kHFSMaxFileNameChars; /* 31 */ | |
5855 | } | |
5856 | #endif | |
9bccf70c A |
5857 | break; |
5858 | case _PC_PATH_MAX: | |
91447636 | 5859 | *ap->a_retval = PATH_MAX; /* 1024 */ |
9bccf70c | 5860 | break; |
55e303ae A |
5861 | case _PC_PIPE_BUF: |
5862 | *ap->a_retval = PIPE_BUF; | |
5863 | break; | |
9bccf70c | 5864 | case _PC_CHOWN_RESTRICTED: |
2d21ac55 | 5865 | *ap->a_retval = 200112; /* _POSIX_CHOWN_RESTRICTED */ |
9bccf70c A |
5866 | break; |
5867 | case _PC_NO_TRUNC: | |
2d21ac55 | 5868 | *ap->a_retval = 200112; /* _POSIX_NO_TRUNC */ |
9bccf70c A |
5869 | break; |
5870 | case _PC_NAME_CHARS_MAX: | |
39236c6e | 5871 | if (std_hfs == 0) { |
6d2010ae | 5872 | *ap->a_retval = kHFSPlusMaxFileNameChars; /* 255 */ |
39236c6e A |
5873 | } |
5874 | #if CONFIG_HFS_STD | |
5875 | else { | |
5876 | *ap->a_retval = kHFSMaxFileNameChars; /* 31 */ | |
5877 | } | |
5878 | #endif | |
9bccf70c A |
5879 | break; |
5880 | case _PC_CASE_SENSITIVE: | |
55e303ae A |
5881 | if (VTOHFS(ap->a_vp)->hfs_flags & HFS_CASE_SENSITIVE) |
5882 | *ap->a_retval = 1; | |
5883 | else | |
5884 | *ap->a_retval = 0; | |
9bccf70c A |
5885 | break; |
5886 | case _PC_CASE_PRESERVING: | |
5887 | *ap->a_retval = 1; | |
5888 | break; | |
2d21ac55 | 5889 | case _PC_FILESIZEBITS: |
39236c6e A |
5890 | /* number of bits to store max file size */ |
5891 | if (std_hfs == 0) { | |
5892 | *ap->a_retval = 64; | |
5893 | } | |
5894 | #if CONFIG_HFS_STD | |
5895 | else { | |
b0d623f7 | 5896 | *ap->a_retval = 32; |
39236c6e A |
5897 | } |
5898 | #endif | |
2d21ac55 | 5899 | break; |
6d2010ae A |
5900 | case _PC_XATTR_SIZE_BITS: |
5901 | /* Number of bits to store maximum extended attribute size */ | |
5902 | *ap->a_retval = HFS_XATTR_SIZE_BITS; | |
5903 | break; | |
9bccf70c | 5904 | default: |
55e303ae | 5905 | return (EINVAL); |
9bccf70c A |
5906 | } |
5907 | ||
91447636 | 5908 | return (0); |
9bccf70c A |
5909 | } |
5910 | ||
fe8ab488 A |
5911 | /* |
5912 | * Prepares a fork for cat_update by making sure ff_size and ff_blocks | |
5913 | * are no bigger than the valid data on disk thus reducing the chance | |
5914 | * of exposing unitialised data in the event of a non clean unmount. | |
5915 | * fork_buf is where to put the temporary copy if required. (It can | |
5916 | * be inside pfork.) | |
5917 | */ | |
5918 | static const struct cat_fork * | |
5919 | hfs_prepare_fork_for_update(const filefork_t *pfork, | |
5920 | struct cat_fork *fork_buf, | |
5921 | uint32_t block_size) | |
5922 | { | |
5923 | if (!pfork) | |
5924 | return NULL; | |
5925 | ||
5926 | off_t max_size = pfork->ff_size; | |
5927 | ||
5928 | // Check first invalid range | |
5929 | if (!TAILQ_EMPTY(&pfork->ff_invalidranges)) | |
5930 | max_size = TAILQ_FIRST(&pfork->ff_invalidranges)->rl_start; | |
5931 | ||
5932 | if (!pfork->ff_unallocblocks && pfork->ff_size <= max_size) | |
5933 | return &pfork->ff_data; // Nothing to do | |
5934 | ||
5935 | if (pfork->ff_blocks < pfork->ff_unallocblocks) { | |
5936 | panic("hfs: ff_blocks %d is less than unalloc blocks %d\n", | |
5937 | pfork->ff_blocks, pfork->ff_unallocblocks); | |
5938 | } | |
5939 | ||
5940 | struct cat_fork *out = fork_buf; | |
5941 | ||
5942 | if (out != &pfork->ff_data) | |
5943 | bcopy(&pfork->ff_data, out, sizeof(*out)); | |
5944 | ||
5945 | // Adjust cf_blocks for cf_vblocks | |
5946 | out->cf_blocks -= out->cf_vblocks; | |
5947 | ||
5948 | /* | |
5949 | * We have to trim the size with the updated cf_blocks. You might | |
5950 | * think that this is unnecessary because the invalid ranges | |
5951 | * should catch this, but we update invalid ranges *before* we do | |
5952 | * I/O whereas cf_vblocks is updated later in hfs_vnop_blockmap. | |
5953 | * There might still be a chance that we will be exposing | |
5954 | * unitialised data because the metadata takes a different path to | |
5955 | * data but the window should be tiny (if it exists at all). | |
5956 | */ | |
5957 | off_t alloc_bytes = hfs_blk_to_bytes(out->cf_blocks, block_size); | |
5958 | if (out->cf_size > alloc_bytes) | |
5959 | out->cf_size = alloc_bytes; | |
5960 | ||
5961 | // Trim cf_size to first invalid range | |
5962 | if (out->cf_size > max_size) | |
5963 | out->cf_size = max_size; | |
5964 | ||
5965 | return out; | |
5966 | } | |
9bccf70c | 5967 | |
9bccf70c | 5968 | /* |
91447636 A |
5969 | * Update a cnode's on-disk metadata. |
5970 | * | |
5971 | * If waitfor is set, then wait for the disk write of | |
5972 | * the node to complete. | |
5973 | * | |
5974 | * The cnode must be locked exclusive | |
9bccf70c | 5975 | */ |
91447636 A |
5976 | int |
5977 | hfs_update(struct vnode *vp, __unused int waitfor) | |
9bccf70c | 5978 | { |
91447636 | 5979 | struct cnode *cp = VTOC(vp); |
9bccf70c | 5980 | struct proc *p; |
fe8ab488 A |
5981 | const struct cat_fork *dataforkp = NULL; |
5982 | const struct cat_fork *rsrcforkp = NULL; | |
9bccf70c | 5983 | struct cat_fork datafork; |
4a3eedf9 | 5984 | struct cat_fork rsrcfork; |
b4c24cb9 | 5985 | struct hfsmount *hfsmp; |
91447636 | 5986 | int lockflags; |
9bccf70c | 5987 | int error; |
39236c6e | 5988 | uint32_t tstate = 0; |
9bccf70c | 5989 | |
91447636 | 5990 | p = current_proc(); |
b4c24cb9 A |
5991 | hfsmp = VTOHFS(vp); |
5992 | ||
2d21ac55 A |
5993 | if (((vnode_issystem(vp) && (cp->c_cnid < kHFSFirstUserCatalogNodeID))) || |
5994 | hfsmp->hfs_catalog_vp == NULL){ | |
91447636 A |
5995 | return (0); |
5996 | } | |
5997 | if ((hfsmp->hfs_flags & HFS_READ_ONLY) || (cp->c_mode == 0)) { | |
5998 | cp->c_flag &= ~C_MODIFIED; | |
5999 | cp->c_touch_acctime = 0; | |
6000 | cp->c_touch_chgtime = 0; | |
6001 | cp->c_touch_modtime = 0; | |
9bccf70c A |
6002 | return (0); |
6003 | } | |
39236c6e A |
6004 | if (kdebug_enable) { |
6005 | if (cp->c_touch_acctime) | |
6006 | tstate |= DBG_HFS_UPDATE_ACCTIME; | |
6007 | if (cp->c_touch_modtime) | |
6008 | tstate |= DBG_HFS_UPDATE_MODTIME; | |
6009 | if (cp->c_touch_chgtime) | |
6010 | tstate |= DBG_HFS_UPDATE_CHGTIME; | |
6011 | ||
6012 | if (cp->c_flag & C_MODIFIED) | |
6013 | tstate |= DBG_HFS_UPDATE_MODIFIED; | |
6014 | if (cp->c_flag & C_FORCEUPDATE) | |
6015 | tstate |= DBG_HFS_UPDATE_FORCE; | |
6016 | if (cp->c_flag & C_NEEDS_DATEADDED) | |
6017 | tstate |= DBG_HFS_UPDATE_DATEADDED; | |
6018 | } | |
91447636 | 6019 | hfs_touchtimes(hfsmp, cp); |
9bccf70c A |
6020 | |
6021 | /* Nothing to update. */ | |
91447636 | 6022 | if ((cp->c_flag & (C_MODIFIED | C_FORCEUPDATE)) == 0) { |
9bccf70c | 6023 | return (0); |
b4c24cb9 | 6024 | } |
9bccf70c A |
6025 | |
6026 | if (cp->c_datafork) | |
6027 | dataforkp = &cp->c_datafork->ff_data; | |
6028 | if (cp->c_rsrcfork) | |
6029 | rsrcforkp = &cp->c_rsrcfork->ff_data; | |
6030 | ||
9bccf70c A |
6031 | /* |
6032 | * For delayed allocations updates are | |
6033 | * postponed until an fsync or the file | |
6034 | * gets written to disk. | |
6035 | * | |
6036 | * Deleted files can defer meta data updates until inactive. | |
55e303ae A |
6037 | * |
6038 | * If we're ever called with the C_FORCEUPDATE flag though | |
6039 | * we have to do the update. | |
9bccf70c | 6040 | */ |
55e303ae | 6041 | if (ISSET(cp->c_flag, C_FORCEUPDATE) == 0 && |
2d21ac55 | 6042 | (ISSET(cp->c_flag, C_DELETED) || |
9bccf70c | 6043 | (dataforkp && cp->c_datafork->ff_unallocblocks) || |
55e303ae | 6044 | (rsrcforkp && cp->c_rsrcfork->ff_unallocblocks))) { |
91447636 | 6045 | // cp->c_flag &= ~(C_ACCESS | C_CHANGE | C_UPDATE); |
9bccf70c | 6046 | cp->c_flag |= C_MODIFIED; |
b4c24cb9 | 6047 | |
9bccf70c A |
6048 | return (0); |
6049 | } | |
6050 | ||
fe8ab488 | 6051 | KERNEL_DEBUG_CONSTANT(HFSDBG_UPDATE | DBG_FUNC_START, VM_KERNEL_ADDRPERM(vp), tstate, 0, 0, 0); |
39236c6e | 6052 | |
91447636 | 6053 | if ((error = hfs_start_transaction(hfsmp)) != 0) { |
fe8ab488 | 6054 | KERNEL_DEBUG_CONSTANT(HFSDBG_UPDATE | DBG_FUNC_END, VM_KERNEL_ADDRPERM(vp), tstate, error, -1, 0); |
91447636 | 6055 | return error; |
b4c24cb9 | 6056 | } |
b4c24cb9 | 6057 | |
316670eb A |
6058 | /* |
6059 | * Modify the values passed to cat_update based on whether or not | |
6060 | * the file has invalid ranges or borrowed blocks. | |
6061 | */ | |
fe8ab488 A |
6062 | dataforkp = hfs_prepare_fork_for_update(cp->c_datafork, &datafork, hfsmp->blockSize); |
6063 | rsrcforkp = hfs_prepare_fork_for_update(cp->c_rsrcfork, &rsrcfork, hfsmp->blockSize); | |
6064 | ||
39236c6e A |
6065 | if (kdebug_enable) { |
6066 | long dbg_parms[NUMPARMS]; | |
6067 | int dbg_namelen; | |
6068 | ||
6069 | dbg_namelen = NUMPARMS * sizeof(long); | |
6070 | vn_getpath(vp, (char *)dbg_parms, &dbg_namelen); | |
6071 | ||
6072 | if (dbg_namelen < (int)sizeof(dbg_parms)) | |
6073 | memset((char *)dbg_parms + dbg_namelen, 0, sizeof(dbg_parms) - dbg_namelen); | |
6074 | ||
6075 | kdebug_lookup_gen_events(dbg_parms, dbg_namelen, (void *)vp, TRUE); | |
6076 | } | |
4a3eedf9 | 6077 | |
9bccf70c A |
6078 | /* |
6079 | * Lock the Catalog b-tree file. | |
9bccf70c | 6080 | */ |
2d21ac55 | 6081 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK); |
9bccf70c A |
6082 | |
6083 | /* XXX - waitfor is not enforced */ | |
b4c24cb9 | 6084 | error = cat_update(hfsmp, &cp->c_desc, &cp->c_attr, dataforkp, rsrcforkp); |
9bccf70c | 6085 | |
91447636 | 6086 | hfs_systemfile_unlock(hfsmp, lockflags); |
b4c24cb9 | 6087 | |
55e303ae | 6088 | /* After the updates are finished, clear the flags */ |
91447636 | 6089 | cp->c_flag &= ~(C_MODIFIED | C_FORCEUPDATE); |
55e303ae | 6090 | |
91447636 | 6091 | hfs_end_transaction(hfsmp); |
9bccf70c | 6092 | |
fe8ab488 | 6093 | KERNEL_DEBUG_CONSTANT(HFSDBG_UPDATE | DBG_FUNC_END, VM_KERNEL_ADDRPERM(vp), tstate, error, 0, 0); |
39236c6e | 6094 | |
9bccf70c A |
6095 | return (error); |
6096 | } | |
6097 | ||
6098 | /* | |
6099 | * Allocate a new node | |
9bccf70c | 6100 | */ |
6d2010ae | 6101 | int |
91447636 A |
6102 | hfs_makenode(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, |
6103 | struct vnode_attr *vap, vfs_context_t ctx) | |
9bccf70c | 6104 | { |
91447636 | 6105 | struct cnode *cp = NULL; |
b0d623f7 | 6106 | struct cnode *dcp = NULL; |
9bccf70c A |
6107 | struct vnode *tvp; |
6108 | struct hfsmount *hfsmp; | |
9bccf70c A |
6109 | struct cat_desc in_desc, out_desc; |
6110 | struct cat_attr attr; | |
91447636 | 6111 | struct timeval tv; |
91447636 | 6112 | int lockflags; |
2d21ac55 | 6113 | int error, started_tr = 0; |
9bccf70c | 6114 | enum vtype vnodetype; |
91447636 | 6115 | int mode; |
6d2010ae | 6116 | int newvnode_flags = 0; |
6d2010ae | 6117 | u_int32_t gnv_flags = 0; |
316670eb | 6118 | int protectable_target = 0; |
39236c6e | 6119 | int nocache = 0; |
316670eb A |
6120 | |
6121 | #if CONFIG_PROTECT | |
6122 | struct cprotect *entry = NULL; | |
39236c6e | 6123 | int32_t cp_class = -1; |
fe8ab488 A |
6124 | |
6125 | /* | |
6126 | * By default, it's OK for AKS to overrride our target class preferences. | |
6127 | */ | |
6128 | uint32_t keywrap_flags = CP_KEYWRAP_DIFFCLASS; | |
6129 | ||
316670eb | 6130 | if (VATTR_IS_ACTIVE(vap, va_dataprotect_class)) { |
39236c6e | 6131 | cp_class = (int32_t)vap->va_dataprotect_class; |
fe8ab488 A |
6132 | /* |
6133 | * Since the user specifically requested this target class be used, | |
6134 | * we want to fail this creation operation if we cannot wrap to their | |
6135 | * target class. The CP_KEYWRAP_DIFFCLASS bit says that it is OK to | |
6136 | * use a different class than the one specified, so we turn that off | |
6137 | * now. | |
6138 | */ | |
6139 | keywrap_flags &= ~CP_KEYWRAP_DIFFCLASS; | |
316670eb A |
6140 | } |
6141 | int protected_mount = 0; | |
6142 | #endif | |
6143 | ||
9bccf70c | 6144 | |
39236c6e | 6145 | if ((error = hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) |
2d21ac55 A |
6146 | return (error); |
6147 | ||
b0d623f7 A |
6148 | /* set the cnode pointer only after successfully acquiring lock */ |
6149 | dcp = VTOC(dvp); | |
6d2010ae | 6150 | |
0b4c1975 | 6151 | /* Don't allow creation of new entries in open-unlinked directories */ |
6d2010ae A |
6152 | if ((error = hfs_checkdeleted(dcp))) { |
6153 | hfs_unlock(dcp); | |
0b4c1975 A |
6154 | return error; |
6155 | } | |
6156 | ||
2d21ac55 | 6157 | dcp->c_flag |= C_DIR_MODIFICATION; |
316670eb | 6158 | |
9bccf70c | 6159 | hfsmp = VTOHFS(dvp); |
316670eb | 6160 | |
9bccf70c A |
6161 | *vpp = NULL; |
6162 | tvp = NULL; | |
91447636 A |
6163 | out_desc.cd_flags = 0; |
6164 | out_desc.cd_nameptr = NULL; | |
6165 | ||
2d21ac55 A |
6166 | vnodetype = vap->va_type; |
6167 | if (vnodetype == VNON) | |
6168 | vnodetype = VREG; | |
6169 | mode = MAKEIMODE(vnodetype, vap->va_mode); | |
9bccf70c | 6170 | |
316670eb A |
6171 | if (S_ISDIR (mode) || S_ISREG (mode)) { |
6172 | protectable_target = 1; | |
6d2010ae | 6173 | } |
6d2010ae | 6174 | |
316670eb | 6175 | |
9bccf70c | 6176 | /* Check if were out of usable disk space. */ |
2d21ac55 | 6177 | if ((hfs_freeblks(hfsmp, 1) == 0) && (vfs_context_suser(ctx) != 0)) { |
9bccf70c A |
6178 | error = ENOSPC; |
6179 | goto exit; | |
6180 | } | |
6181 | ||
91447636 A |
6182 | microtime(&tv); |
6183 | ||
9bccf70c A |
6184 | /* Setup the default attributes */ |
6185 | bzero(&attr, sizeof(attr)); | |
6186 | attr.ca_mode = mode; | |
2d21ac55 A |
6187 | attr.ca_linkcount = 1; |
6188 | if (VATTR_IS_ACTIVE(vap, va_rdev)) { | |
6189 | attr.ca_rdev = vap->va_rdev; | |
6190 | } | |
6191 | if (VATTR_IS_ACTIVE(vap, va_create_time)) { | |
6192 | VATTR_SET_SUPPORTED(vap, va_create_time); | |
6193 | attr.ca_itime = vap->va_create_time.tv_sec; | |
6194 | } else { | |
6195 | attr.ca_itime = tv.tv_sec; | |
6196 | } | |
39236c6e | 6197 | #if CONFIG_HFS_STD |
2d21ac55 A |
6198 | if ((hfsmp->hfs_flags & HFS_STANDARD) && gTimeZone.tz_dsttime) { |
6199 | attr.ca_itime += 3600; /* Same as what hfs_update does */ | |
9bccf70c | 6200 | } |
39236c6e | 6201 | #endif |
2d21ac55 | 6202 | attr.ca_atime = attr.ca_ctime = attr.ca_mtime = attr.ca_itime; |
91447636 | 6203 | attr.ca_atimeondisk = attr.ca_atime; |
2d21ac55 A |
6204 | if (VATTR_IS_ACTIVE(vap, va_flags)) { |
6205 | VATTR_SET_SUPPORTED(vap, va_flags); | |
6206 | attr.ca_flags = vap->va_flags; | |
6207 | } | |
316670eb | 6208 | |
2d21ac55 A |
6209 | /* |
6210 | * HFS+ only: all files get ThreadExists | |
6211 | * HFSX only: dirs get HasFolderCount | |
6212 | */ | |
6213 | if (!(hfsmp->hfs_flags & HFS_STANDARD)) { | |
6214 | if (vnodetype == VDIR) { | |
6215 | if (hfsmp->hfs_flags & HFS_FOLDERCOUNT) | |
6216 | attr.ca_recflags = kHFSHasFolderCountMask; | |
6217 | } else { | |
6218 | attr.ca_recflags = kHFSThreadExistsMask; | |
6219 | } | |
6220 | } | |
91447636 | 6221 | |
316670eb A |
6222 | #if CONFIG_PROTECT |
6223 | if (cp_fs_protected(hfsmp->hfs_mp)) { | |
6224 | protected_mount = 1; | |
6225 | } | |
6226 | /* | |
6227 | * On a content-protected HFS+/HFSX filesystem, files and directories | |
6228 | * cannot be created without atomically setting/creating the EA that | |
6229 | * contains the protection class metadata and keys at the same time, in | |
6230 | * the same transaction. As a result, pre-set the "EAs exist" flag | |
6231 | * on the cat_attr for protectable catalog record creations. This will | |
6232 | * cause the cnode creation routine in hfs_getnewvnode to mark the cnode | |
6233 | * as having EAs. | |
6234 | */ | |
6235 | if ((protected_mount) && (protectable_target)) { | |
6236 | attr.ca_recflags |= kHFSHasAttributesMask; | |
39236c6e A |
6237 | /* delay entering in the namecache */ |
6238 | nocache = 1; | |
316670eb A |
6239 | } |
6240 | #endif | |
6241 | ||
6242 | ||
6243 | /* | |
6244 | * Add the date added to the item. See above, as | |
6245 | * all of the dates are set to the itime. | |
6246 | */ | |
6d2010ae A |
6247 | hfs_write_dateadded (&attr, attr.ca_atime); |
6248 | ||
39236c6e A |
6249 | /* Initialize the gen counter to 1 */ |
6250 | hfs_write_gencount(&attr, (uint32_t)1); | |
6251 | ||
91447636 A |
6252 | attr.ca_uid = vap->va_uid; |
6253 | attr.ca_gid = vap->va_gid; | |
6254 | VATTR_SET_SUPPORTED(vap, va_mode); | |
6255 | VATTR_SET_SUPPORTED(vap, va_uid); | |
6256 | VATTR_SET_SUPPORTED(vap, va_gid); | |
9bccf70c | 6257 | |
b0d623f7 A |
6258 | #if QUOTA |
6259 | /* check to see if this node's creation would cause us to go over | |
6260 | * quota. If so, abort this operation. | |
6261 | */ | |
6262 | if (hfsmp->hfs_flags & HFS_QUOTAS) { | |
6263 | if ((error = hfs_quotacheck(hfsmp, 1, attr.ca_uid, attr.ca_gid, | |
6264 | vfs_context_ucred(ctx)))) { | |
6265 | goto exit; | |
6266 | } | |
6267 | } | |
6268 | #endif | |
6269 | ||
6270 | ||
9bccf70c A |
6271 | /* Tag symlinks with a type and creator. */ |
6272 | if (vnodetype == VLNK) { | |
6273 | struct FndrFileInfo *fip; | |
6274 | ||
6275 | fip = (struct FndrFileInfo *)&attr.ca_finderinfo; | |
6276 | fip->fdType = SWAP_BE32(kSymLinkFileType); | |
6277 | fip->fdCreator = SWAP_BE32(kSymLinkCreator); | |
6278 | } | |
9bccf70c A |
6279 | |
6280 | /* Setup the descriptor */ | |
2d21ac55 | 6281 | in_desc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr; |
9bccf70c | 6282 | in_desc.cd_namelen = cnp->cn_namelen; |
2d21ac55 | 6283 | in_desc.cd_parentcnid = dcp->c_fileid; |
9bccf70c | 6284 | in_desc.cd_flags = S_ISDIR(mode) ? CD_ISDIR : 0; |
91447636 A |
6285 | in_desc.cd_hint = dcp->c_childhint; |
6286 | in_desc.cd_encoding = 0; | |
9bccf70c | 6287 | |
316670eb A |
6288 | #if CONFIG_PROTECT |
6289 | /* | |
6290 | * To preserve file creation atomicity with regards to the content protection EA, | |
39236c6e A |
6291 | * we must create the file in the catalog and then write out its EA in the same |
6292 | * transaction. | |
6293 | * | |
6294 | * We only denote the target class in this EA; key generation is not completed | |
6295 | * until the file has been inserted into the catalog and will be done | |
6296 | * in a separate transaction. | |
316670eb A |
6297 | */ |
6298 | if ((protected_mount) && (protectable_target)) { | |
39236c6e | 6299 | error = cp_setup_newentry(hfsmp, dcp, cp_class, attr.ca_mode, &entry); |
316670eb A |
6300 | if (error) { |
6301 | goto exit; | |
6302 | } | |
6303 | } | |
6304 | #endif | |
6305 | ||
91447636 A |
6306 | if ((error = hfs_start_transaction(hfsmp)) != 0) { |
6307 | goto exit; | |
b4c24cb9 | 6308 | } |
91447636 | 6309 | started_tr = 1; |
b4c24cb9 | 6310 | |
2d21ac55 A |
6311 | // have to also lock the attribute file because cat_create() needs |
6312 | // to check that any fileID it wants to use does not have orphaned | |
6313 | // attributes in it. | |
6314 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK); | |
39236c6e | 6315 | cnid_t new_id; |
2d21ac55 A |
6316 | |
6317 | /* Reserve some space in the Catalog file. */ | |
6318 | if ((error = cat_preflight(hfsmp, CAT_CREATE, NULL, 0))) { | |
6319 | hfs_systemfile_unlock(hfsmp, lockflags); | |
55e303ae A |
6320 | goto exit; |
6321 | } | |
39236c6e A |
6322 | |
6323 | if ((error = cat_acquire_cnid(hfsmp, &new_id))) { | |
6324 | hfs_systemfile_unlock (hfsmp, lockflags); | |
6325 | goto exit; | |
6326 | } | |
6327 | ||
6328 | error = cat_create(hfsmp, new_id, &in_desc, &attr, &out_desc); | |
91447636 A |
6329 | if (error == 0) { |
6330 | /* Update the parent directory */ | |
6331 | dcp->c_childhint = out_desc.cd_hint; /* Cache directory's location */ | |
91447636 | 6332 | dcp->c_entries++; |
fe8ab488 | 6333 | |
2d21ac55 A |
6334 | if (vnodetype == VDIR) { |
6335 | INC_FOLDERCOUNT(hfsmp, dcp->c_attr); | |
6336 | } | |
6337 | dcp->c_dirchangecnt++; | |
fe8ab488 A |
6338 | hfs_incr_gencount(dcp); |
6339 | ||
91447636 A |
6340 | dcp->c_ctime = tv.tv_sec; |
6341 | dcp->c_mtime = tv.tv_sec; | |
6342 | (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); | |
316670eb A |
6343 | |
6344 | #if CONFIG_PROTECT | |
6345 | /* | |
6346 | * If we are creating a content protected file, now is when | |
6347 | * we create the EA. We must create it in the same transaction | |
6348 | * that creates the file. We can also guarantee that the file | |
6349 | * MUST exist because we are still holding the catalog lock | |
6350 | * at this point. | |
6351 | */ | |
6352 | if ((attr.ca_fileid != 0) && (protected_mount) && (protectable_target)) { | |
6353 | error = cp_setxattr (NULL, entry, hfsmp, attr.ca_fileid, XATTR_CREATE); | |
6354 | ||
6355 | if (error) { | |
6356 | int delete_err; | |
6357 | /* | |
6358 | * If we fail the EA creation, then we need to delete the file. | |
6359 | * Luckily, we are still holding all of the right locks. | |
6360 | */ | |
6361 | delete_err = cat_delete (hfsmp, &out_desc, &attr); | |
6362 | if (delete_err == 0) { | |
6363 | /* Update the parent directory */ | |
6364 | if (dcp->c_entries > 0) | |
6365 | dcp->c_entries--; | |
6366 | dcp->c_dirchangecnt++; | |
6367 | dcp->c_ctime = tv.tv_sec; | |
6368 | dcp->c_mtime = tv.tv_sec; | |
6369 | (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL); | |
6370 | } | |
6371 | ||
6372 | /* Emit EINVAL if we fail to create EA*/ | |
6373 | error = EINVAL; | |
6374 | } | |
6375 | } | |
6376 | #endif | |
91447636 A |
6377 | } |
6378 | hfs_systemfile_unlock(hfsmp, lockflags); | |
9bccf70c A |
6379 | if (error) |
6380 | goto exit; | |
6381 | ||
91447636 | 6382 | /* Invalidate negative cache entries in the directory */ |
2d21ac55 | 6383 | if (dcp->c_flag & C_NEG_ENTRIES) { |
91447636 | 6384 | cache_purge_negatives(dvp); |
2d21ac55 A |
6385 | dcp->c_flag &= ~C_NEG_ENTRIES; |
6386 | } | |
91447636 | 6387 | |
9bccf70c A |
6388 | hfs_volupdate(hfsmp, vnodetype == VDIR ? VOL_MKDIR : VOL_MKFILE, |
6389 | (dcp->c_cnid == kHFSRootFolderID)); | |
6390 | ||
b4c24cb9 A |
6391 | // XXXdbg |
6392 | // have to end the transaction here before we call hfs_getnewvnode() | |
6393 | // because that can cause us to try and reclaim a vnode on a different | |
6394 | // file system which could cause us to start a transaction which can | |
6395 | // deadlock with someone on that other file system (since we could be | |
6396 | // holding two transaction locks as well as various vnodes and we did | |
6397 | // not obtain the locks on them in the proper order). | |
91447636 | 6398 | // |
b4c24cb9 A |
6399 | // NOTE: this means that if the quota check fails or we have to update |
6400 | // the change time on a block-special device that those changes | |
6401 | // will happen as part of independent transactions. | |
6402 | // | |
6403 | if (started_tr) { | |
91447636 A |
6404 | hfs_end_transaction(hfsmp); |
6405 | started_tr = 0; | |
b4c24cb9 A |
6406 | } |
6407 | ||
316670eb A |
6408 | #if CONFIG_PROTECT |
6409 | /* | |
6410 | * At this point, we must have encountered success with writing the EA. | |
39236c6e | 6411 | * Destroy our temporary cprotect (which had no keys). |
316670eb A |
6412 | */ |
6413 | ||
6414 | if ((attr.ca_fileid != 0) && (protected_mount) && (protectable_target)) { | |
39236c6e A |
6415 | cp_entry_destroy (entry); |
6416 | entry = NULL; | |
316670eb A |
6417 | } |
6418 | #endif | |
6d2010ae | 6419 | gnv_flags |= GNV_CREATE; |
39236c6e A |
6420 | if (nocache) { |
6421 | gnv_flags |= GNV_NOCACHE; | |
6422 | } | |
2d21ac55 | 6423 | |
91447636 A |
6424 | /* |
6425 | * Create a vnode for the object just created. | |
b0d623f7 | 6426 | * |
7e4a7d39 A |
6427 | * NOTE: Maintaining the cnode lock on the parent directory is important, |
6428 | * as it prevents race conditions where other threads want to look up entries | |
6429 | * in the directory and/or add things as we are in the process of creating | |
6430 | * the vnode below. However, this has the potential for causing a | |
6431 | * double lock panic when dealing with shadow files on a HFS boot partition. | |
6432 | * The panic could occur if we are not cleaning up after ourselves properly | |
6433 | * when done with a shadow file or in the error cases. The error would occur if we | |
6434 | * try to create a new vnode, and then end up reclaiming another shadow vnode to | |
6435 | * create the new one. However, if everything is working properly, this should | |
6436 | * be a non-issue as we would never enter that reclaim codepath. | |
6d2010ae | 6437 | * |
91447636 A |
6438 | * The cnode is locked on successful return. |
6439 | */ | |
6d2010ae A |
6440 | error = hfs_getnewvnode(hfsmp, dvp, cnp, &out_desc, gnv_flags, &attr, |
6441 | NULL, &tvp, &newvnode_flags); | |
9bccf70c A |
6442 | if (error) |
6443 | goto exit; | |
6444 | ||
9bccf70c | 6445 | cp = VTOC(tvp); |
22ba694c A |
6446 | |
6447 | struct doc_tombstone *ut; | |
6448 | ut = get_uthread_doc_tombstone(); | |
6449 | if ( ut->t_lastop_document_id != 0 | |
6450 | && ut->t_lastop_parent == dvp | |
6451 | && ut->t_lastop_parent_vid == vnode_vid(dvp) | |
6452 | && strcmp((char *)ut->t_lastop_filename, (char *)cp->c_desc.cd_nameptr) == 0) { | |
6453 | struct FndrExtendedDirInfo *fip = (struct FndrExtendedDirInfo *)((char *)&cp->c_attr.ca_finderinfo + 16); | |
6454 | ||
6455 | //printf("CREATE: preserving doc-id %lld on %s\n", ut->t_lastop_document_id, ut->t_lastop_filename); | |
6456 | fip->document_id = (uint32_t)(ut->t_lastop_document_id & 0xffffffff); | |
6457 | ||
6458 | cp->c_bsdflags |= UF_TRACKED; | |
6459 | // mark the cnode dirty | |
6460 | cp->c_flag |= C_MODIFIED | C_FORCEUPDATE; | |
6461 | ||
6462 | if ((error = hfs_start_transaction(hfsmp)) == 0) { | |
6463 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK); | |
6464 | ||
6465 | (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL); | |
6466 | ||
6467 | hfs_systemfile_unlock (hfsmp, lockflags); | |
6468 | (void) hfs_end_transaction(hfsmp); | |
6469 | } | |
6470 | ||
6471 | clear_tombstone_docid(ut, hfsmp, cp); // will send the docid-changed fsevent | |
6472 | } else if (ut->t_lastop_document_id != 0) { | |
6473 | int len = cnp->cn_namelen; | |
6474 | if (len == 0) { | |
6475 | len = strlen(cnp->cn_nameptr); | |
6476 | } | |
6477 | ||
6478 | if (is_ignorable_temp_name(cnp->cn_nameptr, cnp->cn_namelen)) { | |
6479 | // printf("CREATE: not clearing tombstone because %s is a temp name.\n", cnp->cn_nameptr); | |
6480 | } else { | |
6481 | // Clear the tombstone because the thread is not recreating the same path | |
6482 | // printf("CREATE: clearing tombstone because %s is NOT a temp name.\n", cnp->cn_nameptr); | |
6483 | clear_tombstone_docid(ut, hfsmp, NULL); | |
6484 | } | |
6485 | } | |
6486 | ||
9bccf70c | 6487 | *vpp = tvp; |
6d2010ae | 6488 | |
39236c6e A |
6489 | #if CONFIG_PROTECT |
6490 | /* | |
6491 | * Now that we have a vnode-in-hand, generate keys for this namespace item. | |
6492 | * If we fail to create the keys, then attempt to delete the item from the | |
6493 | * namespace. If we can't delete the item, that's not desirable but also not fatal.. | |
6494 | * All of the places which deal with restoring/unwrapping keys must also be | |
6495 | * prepared to encounter an entry that does not have keys. | |
6496 | */ | |
6497 | if ((protectable_target) && (protected_mount)) { | |
6498 | struct cprotect *keyed_entry = NULL; | |
6499 | ||
6500 | if (cp->c_cpentry == NULL) { | |
6501 | panic ("hfs_makenode: no cpentry for cnode (%p)", cp); | |
6502 | } | |
6503 | ||
fe8ab488 | 6504 | error = cp_generate_keys (hfsmp, cp, CP_CLASS(cp->c_cpentry->cp_pclass), keywrap_flags, &keyed_entry); |
39236c6e A |
6505 | if (error == 0) { |
6506 | /* | |
6507 | * Upon success, the keys were generated and written out. | |
6508 | * Update the cp pointer in the cnode. | |
6509 | */ | |
6510 | cp_replace_entry (cp, keyed_entry); | |
6511 | if (nocache) { | |
6512 | cache_enter (dvp, tvp, cnp); | |
6513 | } | |
6514 | } | |
6515 | else { | |
6516 | /* If key creation OR the setxattr failed, emit EPERM to userland */ | |
6517 | error = EPERM; | |
6518 | ||
6519 | /* | |
6520 | * Beware! This slightly violates the lock ordering for the | |
6521 | * cnode/vnode 'tvp'. Ordinarily, you must acquire the truncate lock | |
6522 | * which guards file size changes before acquiring the normal cnode lock | |
6523 | * and calling hfs_removefile on an item. | |
6524 | * | |
6525 | * However, in this case, we are still holding the directory lock so | |
6526 | * 'tvp' is not lookup-able and it was a newly created vnode so it | |
6527 | * cannot have any content yet. The only reason we are initiating | |
6528 | * the removefile is because we could not generate content protection keys | |
6529 | * for this namespace item. Note also that we pass a '1' in the allow_dirs | |
6530 | * argument for hfs_removefile because we may be creating a directory here. | |
6531 | * | |
6532 | * All this to say that while it is technically a violation it is | |
6533 | * impossible to race with another thread for this cnode so it is safe. | |
6534 | */ | |
6535 | int err = hfs_removefile (dvp, tvp, cnp, 0, 0, 1, NULL, 0); | |
6536 | if (err) { | |
6537 | printf("hfs_makenode: removefile failed (%d) for CP entry %p\n", err, tvp); | |
6538 | } | |
6539 | ||
6540 | /* Release the cnode lock and mark the vnode for termination */ | |
6541 | hfs_unlock (cp); | |
6542 | err = vnode_recycle (tvp); | |
6543 | if (err) { | |
6544 | printf("hfs_makenode: vnode_recycle failed (%d) for CP entry %p\n", err, tvp); | |
6545 | } | |
6546 | ||
6547 | /* Drop the iocount on the new vnode to force reclamation/recycling */ | |
6548 | vnode_put (tvp); | |
6549 | cp = NULL; | |
6550 | *vpp = NULL; | |
6551 | } | |
6552 | } | |
6553 | #endif | |
6554 | ||
6d2010ae A |
6555 | #if QUOTA |
6556 | /* | |
6557 | * Once we create this vnode, we need to initialize its quota data | |
6558 | * structures, if necessary. We know that it is OK to just go ahead and | |
6559 | * initialize because we've already validated earlier (through the hfs_quotacheck | |
6560 | * function) to see if creating this cnode/vnode would cause us to go over quota. | |
6561 | */ | |
6562 | if (hfsmp->hfs_flags & HFS_QUOTAS) { | |
39236c6e A |
6563 | if (cp) { |
6564 | /* cp could have been zeroed earlier */ | |
6565 | (void) hfs_getinoquota(cp); | |
6566 | } | |
6d2010ae A |
6567 | } |
6568 | #endif | |
6569 | ||
9bccf70c A |
6570 | exit: |
6571 | cat_releasedesc(&out_desc); | |
b0d623f7 | 6572 | |
316670eb A |
6573 | #if CONFIG_PROTECT |
6574 | /* | |
6575 | * We may have jumped here in error-handling various situations above. | |
6576 | * If we haven't already dumped the temporary CP used to initialize | |
6577 | * the file atomically, then free it now. cp_entry_destroy should null | |
6578 | * out the pointer if it was called already. | |
6579 | */ | |
6580 | if (entry) { | |
39236c6e A |
6581 | cp_entry_destroy (entry); |
6582 | entry = NULL; | |
316670eb A |
6583 | } |
6584 | #endif | |
6585 | ||
90556fb8 | 6586 | /* |
7e4a7d39 | 6587 | * Make sure we release cnode lock on dcp. |
90556fb8 | 6588 | */ |
b0d623f7 | 6589 | if (dcp) { |
2d21ac55 A |
6590 | dcp->c_flag &= ~C_DIR_MODIFICATION; |
6591 | wakeup((caddr_t)&dcp->c_flag); | |
6592 | ||
91447636 A |
6593 | hfs_unlock(dcp); |
6594 | } | |
6595 | if (error == 0 && cp != NULL) { | |
6596 | hfs_unlock(cp); | |
b4c24cb9 | 6597 | } |
91447636 A |
6598 | if (started_tr) { |
6599 | hfs_end_transaction(hfsmp); | |
6600 | started_tr = 0; | |
b4c24cb9 A |
6601 | } |
6602 | ||
9bccf70c A |
6603 | return (error); |
6604 | } | |
6605 | ||
6606 | ||
6d2010ae | 6607 | /* |
fe8ab488 A |
6608 | * hfs_vgetrsrc acquires a resource fork vnode corresponding to the |
6609 | * cnode that is found in 'vp'. The cnode should be locked upon entry | |
6610 | * and will be returned locked, but it may be dropped temporarily. | |
b7266188 | 6611 | * |
fe8ab488 A |
6612 | * On success, *rvpp wlll hold the resource fork vnode with an |
6613 | * iocount. *Don't* forget the vnode_put. | |
91447636 | 6614 | */ |
91447636 | 6615 | int |
fe8ab488 | 6616 | hfs_vgetrsrc(struct hfsmount *hfsmp, struct vnode *vp, struct vnode **rvpp) |
9bccf70c A |
6617 | { |
6618 | struct vnode *rvp; | |
cf7d32b8 | 6619 | struct vnode *dvp = NULLVP; |
9bccf70c A |
6620 | struct cnode *cp = VTOC(vp); |
6621 | int error; | |
91447636 | 6622 | int vid; |
b7266188 | 6623 | |
6d2010ae A |
6624 | if (vnode_vtype(vp) == VDIR) { |
6625 | return EINVAL; | |
6626 | } | |
6627 | ||
4a3eedf9 | 6628 | restart: |
6d2010ae | 6629 | /* Attempt to use existing vnode */ |
9bccf70c | 6630 | if ((rvp = cp->c_rsrc_vp)) { |
fe8ab488 | 6631 | vid = vnode_vid(rvp); |
91447636 | 6632 | |
fe8ab488 A |
6633 | // vnode_getwithvid can block so we need to drop the cnode lock |
6634 | hfs_unlock(cp); | |
2d21ac55 | 6635 | |
91447636 | 6636 | error = vnode_getwithvid(rvp, vid); |
2d21ac55 | 6637 | |
fe8ab488 | 6638 | hfs_lock_always(cp, HFS_EXCLUSIVE_LOCK); |
b7266188 | 6639 | |
fe8ab488 A |
6640 | /* |
6641 | * When our lock was relinquished, the resource fork | |
6642 | * could have been recycled. Check for this and try | |
6643 | * again. | |
6644 | */ | |
6645 | if (error == ENOENT) | |
6646 | goto restart; | |
b7266188 | 6647 | |
9bccf70c | 6648 | if (error) { |
2d21ac55 | 6649 | const char * name = (const char *)VTOC(vp)->c_desc.cd_nameptr; |
9bccf70c A |
6650 | |
6651 | if (name) | |
4a3eedf9 | 6652 | printf("hfs_vgetrsrc: couldn't get resource" |
39236c6e | 6653 | " fork for %s, vol=%s, err=%d\n", name, hfsmp->vcbVN, error); |
9bccf70c A |
6654 | return (error); |
6655 | } | |
6656 | } else { | |
6657 | struct cat_fork rsrcfork; | |
91447636 | 6658 | struct componentname cn; |
b0d623f7 A |
6659 | struct cat_desc *descptr = NULL; |
6660 | struct cat_desc to_desc; | |
6661 | char delname[32]; | |
91447636 | 6662 | int lockflags; |
6d2010ae | 6663 | int newvnode_flags = 0; |
fe8ab488 | 6664 | |
743b1565 A |
6665 | /* |
6666 | * Make sure cnode lock is exclusive, if not upgrade it. | |
6667 | * | |
6668 | * We assume that we were called from a read-only VNOP (getattr) | |
6669 | * and that its safe to have the cnode lock dropped and reacquired. | |
6670 | */ | |
6671 | if (cp->c_lockowner != current_thread()) { | |
6672 | /* | |
b7266188 | 6673 | * If the upgrade fails we lose the lock and |
743b1565 A |
6674 | * have to take the exclusive lock on our own. |
6675 | */ | |
2d21ac55 | 6676 | if (lck_rw_lock_shared_to_exclusive(&cp->c_rwlock) == FALSE) |
743b1565 A |
6677 | lck_rw_lock_exclusive(&cp->c_rwlock); |
6678 | cp->c_lockowner = current_thread(); | |
6679 | } | |
6680 | ||
b0d623f7 A |
6681 | /* |
6682 | * hfs_vgetsrc may be invoked for a cnode that has already been marked | |
6683 | * C_DELETED. This is because we need to continue to provide rsrc | |
6684 | * fork access to open-unlinked files. In this case, build a fake descriptor | |
6685 | * like in hfs_removefile. If we don't do this, buildkey will fail in | |
fe8ab488 | 6686 | * cat_lookup because this cnode has no name in its descriptor. |
b0d623f7 | 6687 | */ |
b0d623f7 A |
6688 | if ((cp->c_flag & C_DELETED ) && (cp->c_desc.cd_namelen == 0)) { |
6689 | bzero (&to_desc, sizeof(to_desc)); | |
6690 | bzero (delname, 32); | |
6691 | MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid); | |
6692 | to_desc.cd_nameptr = (const u_int8_t*) delname; | |
6693 | to_desc.cd_namelen = strlen(delname); | |
6694 | to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid; | |
6695 | to_desc.cd_flags = 0; | |
6696 | to_desc.cd_cnid = cp->c_cnid; | |
6697 | ||
6698 | descptr = &to_desc; | |
6699 | } | |
6700 | else { | |
6701 | descptr = &cp->c_desc; | |
6702 | } | |
6703 | ||
6704 | ||
91447636 | 6705 | lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK); |
9bccf70c | 6706 | |
39236c6e | 6707 | /* |
db609669 A |
6708 | * We call cat_idlookup (instead of cat_lookup) below because we can't |
6709 | * trust the descriptor in the provided cnode for lookups at this point. | |
6710 | * Between the time of the original lookup of this vnode and now, the | |
6711 | * descriptor could have gotten swapped or replaced. If this occurred, | |
6712 | * the parent/name combo originally desired may not necessarily be provided | |
6713 | * if we use the descriptor. Even worse, if the vnode represents | |
6714 | * a hardlink, we could have removed one of the links from the namespace | |
6715 | * but left the descriptor alone, since hfs_unlink does not invalidate | |
6716 | * the descriptor in the cnode if other links still point to the inode. | |
6717 | * | |
6718 | * Consider the following (slightly contrived) scenario: | |
6719 | * /tmp/a <--> /tmp/b (hardlinks). | |
6720 | * 1. Thread A: open rsrc fork on /tmp/b. | |
6721 | * 1a. Thread A: does lookup, goes out to lunch right before calling getnamedstream. | |
6722 | * 2. Thread B does 'mv /foo/b /tmp/b' | |
6723 | * 2. Thread B succeeds. | |
6724 | * 3. Thread A comes back and wants rsrc fork info for /tmp/b. | |
6725 | * | |
6726 | * Even though the hardlink backing /tmp/b is now eliminated, the descriptor | |
6727 | * is not removed/updated during the unlink process. So, if you were to | |
6728 | * do a lookup on /tmp/b, you'd acquire an entirely different record's resource | |
6729 | * fork. | |
6730 | * | |
39236c6e A |
6731 | * As a result, we use the fileid, which should be invariant for the lifetime |
6732 | * of the cnode (possibly barring calls to exchangedata). | |
4b17d6b6 A |
6733 | * |
6734 | * Addendum: We can't do the above for HFS standard since we aren't guaranteed to | |
6735 | * have thread records for files. They were only required for directories. So | |
6736 | * we need to do the lookup with the catalog name. This is OK since hardlinks were | |
6737 | * never allowed on HFS standard. | |
db609669 A |
6738 | */ |
6739 | ||
39236c6e A |
6740 | /* Get resource fork data */ |
6741 | if ((hfsmp->hfs_flags & HFS_STANDARD) == 0) { | |
6742 | error = cat_idlookup (hfsmp, cp->c_fileid, 0, 1, NULL, NULL, &rsrcfork); | |
6743 | } | |
6744 | #if CONFIG_HFS_STD | |
6745 | else { | |
4b17d6b6 A |
6746 | /* |
6747 | * HFS standard only: | |
6748 | * | |
39236c6e A |
6749 | * Get the resource fork for this item with a cat_lookup call, but do not |
6750 | * force a case lookup since HFS standard is case-insensitive only. We | |
6751 | * don't want the descriptor; just the fork data here. If we tried to | |
6752 | * do a ID lookup (via thread record -> catalog record), then we might fail | |
6753 | * prematurely since, as noted above, thread records were not strictly required | |
6754 | * on files in HFS. | |
4b17d6b6 | 6755 | */ |
39236c6e | 6756 | error = cat_lookup (hfsmp, descptr, 1, 0, (struct cat_desc*)NULL, |
4b17d6b6 A |
6757 | (struct cat_attr*)NULL, &rsrcfork, NULL); |
6758 | } | |
39236c6e | 6759 | #endif |
9bccf70c | 6760 | |
91447636 | 6761 | hfs_systemfile_unlock(hfsmp, lockflags); |
b0d623f7 | 6762 | if (error) { |
9bccf70c | 6763 | return (error); |
b0d623f7 | 6764 | } |
91447636 A |
6765 | /* |
6766 | * Supply hfs_getnewvnode with a component name. | |
6767 | */ | |
6768 | cn.cn_pnbuf = NULL; | |
b0d623f7 | 6769 | if (descptr->cd_nameptr) { |
91447636 A |
6770 | MALLOC_ZONE(cn.cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); |
6771 | cn.cn_nameiop = LOOKUP; | |
6772 | cn.cn_flags = ISLASTCN | HASBUF; | |
6773 | cn.cn_context = NULL; | |
6774 | cn.cn_pnlen = MAXPATHLEN; | |
6775 | cn.cn_nameptr = cn.cn_pnbuf; | |
6776 | cn.cn_hash = 0; | |
6777 | cn.cn_consume = 0; | |
2d21ac55 | 6778 | cn.cn_namelen = snprintf(cn.cn_nameptr, MAXPATHLEN, |
b0d623f7 | 6779 | "%s%s", descptr->cd_nameptr, |
2d21ac55 | 6780 | _PATH_RSRCFORKSPEC); |
fe8ab488 A |
6781 | // Should never happen because cn.cn_nameptr won't ever be long... |
6782 | if (cn.cn_namelen >= MAXPATHLEN) { | |
6783 | FREE_ZONE(cn.cn_pnbuf, cn.cn_pnlen, M_NAMEI); | |
6784 | return ENAMETOOLONG; | |
6785 | } | |
91447636 | 6786 | } |
cf7d32b8 A |
6787 | dvp = vnode_getparent(vp); |
6788 | error = hfs_getnewvnode(hfsmp, dvp, cn.cn_pnbuf ? &cn : NULL, | |
b0d623f7 | 6789 | descptr, GNV_WANTRSRC | GNV_SKIPLOCK, &cp->c_attr, |
6d2010ae | 6790 | &rsrcfork, &rvp, &newvnode_flags); |
cf7d32b8 A |
6791 | if (dvp) |
6792 | vnode_put(dvp); | |
91447636 A |
6793 | if (cn.cn_pnbuf) |
6794 | FREE_ZONE(cn.cn_pnbuf, cn.cn_pnlen, M_NAMEI); | |
9bccf70c A |
6795 | if (error) |
6796 | return (error); | |
6797 | } | |
6798 | ||
6799 | *rvpp = rvp; | |
6800 | return (0); | |
6801 | } | |
6802 | ||
9bccf70c A |
6803 | /* |
6804 | * Wrapper for special device reads | |
6805 | */ | |
6d2010ae | 6806 | int |
9bccf70c | 6807 | hfsspec_read(ap) |
91447636 | 6808 | struct vnop_read_args /* { |
9bccf70c A |
6809 | struct vnode *a_vp; |
6810 | struct uio *a_uio; | |
6811 | int a_ioflag; | |
91447636 | 6812 | vfs_context_t a_context; |
9bccf70c A |
6813 | } */ *ap; |
6814 | { | |
6815 | /* | |
6816 | * Set access flag. | |
6817 | */ | |
91447636 A |
6818 | VTOC(ap->a_vp)->c_touch_acctime = TRUE; |
6819 | return (VOCALL (spec_vnodeop_p, VOFFSET(vnop_read), ap)); | |
9bccf70c A |
6820 | } |
6821 | ||
6822 | /* | |
6823 | * Wrapper for special device writes | |
6824 | */ | |
6d2010ae | 6825 | int |
9bccf70c | 6826 | hfsspec_write(ap) |
91447636 | 6827 | struct vnop_write_args /* { |
9bccf70c A |
6828 | struct vnode *a_vp; |
6829 | struct uio *a_uio; | |
6830 | int a_ioflag; | |
91447636 | 6831 | vfs_context_t a_context; |
9bccf70c A |
6832 | } */ *ap; |
6833 | { | |
6834 | /* | |
6835 | * Set update and change flags. | |
6836 | */ | |
91447636 A |
6837 | VTOC(ap->a_vp)->c_touch_chgtime = TRUE; |
6838 | VTOC(ap->a_vp)->c_touch_modtime = TRUE; | |
6839 | return (VOCALL (spec_vnodeop_p, VOFFSET(vnop_write), ap)); | |
9bccf70c A |
6840 | } |
6841 | ||
6842 | /* | |
6843 | * Wrapper for special device close | |
6844 | * | |
6845 | * Update the times on the cnode then do device close. | |
6846 | */ | |
6d2010ae | 6847 | int |
9bccf70c | 6848 | hfsspec_close(ap) |
91447636 | 6849 | struct vnop_close_args /* { |
9bccf70c A |
6850 | struct vnode *a_vp; |
6851 | int a_fflag; | |
91447636 | 6852 | vfs_context_t a_context; |
9bccf70c A |
6853 | } */ *ap; |
6854 | { | |
6855 | struct vnode *vp = ap->a_vp; | |
91447636 | 6856 | struct cnode *cp; |
9bccf70c | 6857 | |
b0d623f7 | 6858 | if (vnode_isinuse(ap->a_vp, 0)) { |
39236c6e | 6859 | if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) == 0) { |
91447636 A |
6860 | cp = VTOC(vp); |
6861 | hfs_touchtimes(VTOHFS(vp), cp); | |
6862 | hfs_unlock(cp); | |
6863 | } | |
6864 | } | |
6865 | return (VOCALL (spec_vnodeop_p, VOFFSET(vnop_close), ap)); | |
9bccf70c A |
6866 | } |
6867 | ||
6868 | #if FIFO | |
6869 | /* | |
6870 | * Wrapper for fifo reads | |
6871 | */ | |
6872 | static int | |
6873 | hfsfifo_read(ap) | |
91447636 | 6874 | struct vnop_read_args /* { |
9bccf70c A |
6875 | struct vnode *a_vp; |
6876 | struct uio *a_uio; | |
6877 | int a_ioflag; | |
91447636 | 6878 | vfs_context_t a_context; |
9bccf70c A |
6879 | } */ *ap; |
6880 | { | |
9bccf70c A |
6881 | /* |
6882 | * Set access flag. | |
6883 | */ | |
91447636 A |
6884 | VTOC(ap->a_vp)->c_touch_acctime = TRUE; |
6885 | return (VOCALL (fifo_vnodeop_p, VOFFSET(vnop_read), ap)); | |
9bccf70c A |
6886 | } |
6887 | ||
6888 | /* | |
6889 | * Wrapper for fifo writes | |
6890 | */ | |
6891 | static int | |
6892 | hfsfifo_write(ap) | |
91447636 | 6893 | struct vnop_write_args /* { |
9bccf70c A |
6894 | struct vnode *a_vp; |
6895 | struct uio *a_uio; | |
6896 | int a_ioflag; | |
91447636 | 6897 | vfs_context_t a_context; |
9bccf70c A |
6898 | } */ *ap; |
6899 | { | |
9bccf70c A |
6900 | /* |
6901 | * Set update and change flags. | |
6902 | */ | |
91447636 A |
6903 | VTOC(ap->a_vp)->c_touch_chgtime = TRUE; |
6904 | VTOC(ap->a_vp)->c_touch_modtime = TRUE; | |
6905 | return (VOCALL (fifo_vnodeop_p, VOFFSET(vnop_write), ap)); | |
9bccf70c A |
6906 | } |
6907 | ||
6908 | /* | |
6909 | * Wrapper for fifo close | |
6910 | * | |
6911 | * Update the times on the cnode then do device close. | |
6912 | */ | |
6913 | static int | |
6914 | hfsfifo_close(ap) | |
91447636 | 6915 | struct vnop_close_args /* { |
9bccf70c A |
6916 | struct vnode *a_vp; |
6917 | int a_fflag; | |
91447636 | 6918 | vfs_context_t a_context; |
9bccf70c A |
6919 | } */ *ap; |
6920 | { | |
9bccf70c | 6921 | struct vnode *vp = ap->a_vp; |
91447636 | 6922 | struct cnode *cp; |
9bccf70c | 6923 | |
91447636 | 6924 | if (vnode_isinuse(ap->a_vp, 1)) { |
39236c6e | 6925 | if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) == 0) { |
91447636 A |
6926 | cp = VTOC(vp); |
6927 | hfs_touchtimes(VTOHFS(vp), cp); | |
6928 | hfs_unlock(cp); | |
6929 | } | |
6930 | } | |
6931 | return (VOCALL (fifo_vnodeop_p, VOFFSET(vnop_close), ap)); | |
9bccf70c | 6932 | } |
55e303ae | 6933 | |
55e303ae | 6934 | |
9bccf70c A |
6935 | #endif /* FIFO */ |
6936 | ||
22ba694c A |
6937 | /* |
6938 | * Getter for the document_id | |
6939 | * the document_id is stored in FndrExtendedFileInfo/FndrExtendedDirInfo | |
6940 | */ | |
6941 | static u_int32_t | |
6942 | hfs_get_document_id_internal(const uint8_t *finderinfo, mode_t mode) | |
6943 | { | |
6944 | u_int8_t *finfo = NULL; | |
6945 | u_int32_t doc_id = 0; | |
6946 | ||
6947 | /* overlay the FinderInfo to the correct pointer, and advance */ | |
6948 | finfo = ((uint8_t *)finderinfo) + 16; | |
6949 | ||
6950 | if (S_ISDIR(mode) || S_ISREG(mode)) { | |
6951 | struct FndrExtendedFileInfo *extinfo = (struct FndrExtendedFileInfo *)finfo; | |
6952 | doc_id = extinfo->document_id; | |
6953 | } else if (S_ISDIR(mode)) { | |
6954 | struct FndrExtendedDirInfo *extinfo = (struct FndrExtendedDirInfo *)((u_int8_t*)finderinfo + 16); | |
6955 | doc_id = extinfo->document_id; | |
6956 | } | |
6957 | ||
6958 | return doc_id; | |
6959 | } | |
6960 | ||
6961 | ||
6962 | /* getter(s) for document id */ | |
6963 | u_int32_t | |
6964 | hfs_get_document_id(struct cnode *cp) | |
6965 | { | |
6966 | return (hfs_get_document_id_internal((u_int8_t*)cp->c_finderinfo, | |
6967 | cp->c_attr.ca_mode)); | |
6968 | } | |
6969 | ||
6970 | /* If you have finderinfo and mode, you can use this */ | |
6971 | u_int32_t | |
6972 | hfs_get_document_id_from_blob(const uint8_t *finderinfo, mode_t mode) | |
6973 | { | |
6974 | return (hfs_get_document_id_internal(finderinfo, mode)); | |
6975 | } | |
6976 | ||
91447636 A |
6977 | /* |
6978 | * Synchronize a file's in-core state with that on disk. | |
6979 | */ | |
6d2010ae | 6980 | int |
91447636 A |
6981 | hfs_vnop_fsync(ap) |
6982 | struct vnop_fsync_args /* { | |
6983 | struct vnode *a_vp; | |
6984 | int a_waitfor; | |
6985 | vfs_context_t a_context; | |
6986 | } */ *ap; | |
6987 | { | |
6988 | struct vnode* vp = ap->a_vp; | |
6989 | int error; | |
6990 | ||
6d2010ae A |
6991 | /* Note: We check hfs flags instead of vfs mount flag because during |
6992 | * read-write update, hfs marks itself read-write much earlier than | |
6993 | * the vfs, and hence won't result in skipping of certain writes like | |
6994 | * zero'ing out of unused nodes, creation of hotfiles btree, etc. | |
6995 | */ | |
6996 | if (VTOHFS(vp)->hfs_flags & HFS_READ_ONLY) { | |
6997 | return 0; | |
6998 | } | |
6999 | ||
91447636 | 7000 | /* |
fe8ab488 A |
7001 | * No need to call cp_handle_vnop to resolve fsync(). Any dirty data |
7002 | * should have caused the keys to be unwrapped at the time the data was | |
7003 | * put into the UBC, either at mmap/pagein/read-write. If we did manage | |
7004 | * to let this by, then strategy will auto-resolve for us. | |
7005 | * | |
7006 | * We also need to allow ENOENT lock errors since unlink | |
7007 | * system call can call VNOP_FSYNC during vclean. | |
91447636 | 7008 | */ |
39236c6e | 7009 | error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT); |
91447636 A |
7010 | if (error) |
7011 | return (0); | |
7012 | ||
7013 | error = hfs_fsync(vp, ap->a_waitfor, 0, vfs_context_proc(ap->a_context)); | |
7014 | ||
7015 | hfs_unlock(VTOC(vp)); | |
7016 | return (error); | |
7017 | } | |
9bccf70c | 7018 | |
9bccf70c A |
7019 | int (**hfs_vnodeop_p)(void *); |
7020 | ||
7021 | #define VOPFUNC int (*)(void *) | |
7022 | ||
39236c6e A |
7023 | |
7024 | #if CONFIG_HFS_STD | |
7025 | int (**hfs_std_vnodeop_p) (void *); | |
b0d623f7 A |
7026 | static int hfs_readonly_op (__unused void* ap) { return (EROFS); } |
7027 | ||
7028 | /* | |
7029 | * In 10.6 and forward, HFS Standard is read-only and deprecated. The vnop table below | |
7030 | * is for use with HFS standard to block out operations that would modify the file system | |
7031 | */ | |
7032 | ||
7033 | struct vnodeopv_entry_desc hfs_standard_vnodeop_entries[] = { | |
7034 | { &vnop_default_desc, (VOPFUNC)vn_default_error }, | |
7035 | { &vnop_lookup_desc, (VOPFUNC)hfs_vnop_lookup }, /* lookup */ | |
7036 | { &vnop_create_desc, (VOPFUNC)hfs_readonly_op }, /* create (READONLY) */ | |
7037 | { &vnop_mknod_desc, (VOPFUNC)hfs_readonly_op }, /* mknod (READONLY) */ | |
7038 | { &vnop_open_desc, (VOPFUNC)hfs_vnop_open }, /* open */ | |
7039 | { &vnop_close_desc, (VOPFUNC)hfs_vnop_close }, /* close */ | |
7040 | { &vnop_getattr_desc, (VOPFUNC)hfs_vnop_getattr }, /* getattr */ | |
7041 | { &vnop_setattr_desc, (VOPFUNC)hfs_readonly_op }, /* setattr */ | |
7042 | { &vnop_read_desc, (VOPFUNC)hfs_vnop_read }, /* read */ | |
7043 | { &vnop_write_desc, (VOPFUNC)hfs_readonly_op }, /* write (READONLY) */ | |
7044 | { &vnop_ioctl_desc, (VOPFUNC)hfs_vnop_ioctl }, /* ioctl */ | |
7045 | { &vnop_select_desc, (VOPFUNC)hfs_vnop_select }, /* select */ | |
7046 | { &vnop_revoke_desc, (VOPFUNC)nop_revoke }, /* revoke */ | |
7047 | { &vnop_exchange_desc, (VOPFUNC)hfs_readonly_op }, /* exchange (READONLY)*/ | |
7048 | { &vnop_mmap_desc, (VOPFUNC)err_mmap }, /* mmap */ | |
7049 | { &vnop_fsync_desc, (VOPFUNC)hfs_readonly_op}, /* fsync (READONLY) */ | |
7050 | { &vnop_remove_desc, (VOPFUNC)hfs_readonly_op }, /* remove (READONLY) */ | |
7051 | { &vnop_link_desc, (VOPFUNC)hfs_readonly_op }, /* link ( READONLLY) */ | |
7052 | { &vnop_rename_desc, (VOPFUNC)hfs_readonly_op }, /* rename (READONLY)*/ | |
7053 | { &vnop_mkdir_desc, (VOPFUNC)hfs_readonly_op }, /* mkdir (READONLY) */ | |
7054 | { &vnop_rmdir_desc, (VOPFUNC)hfs_readonly_op }, /* rmdir (READONLY) */ | |
7055 | { &vnop_symlink_desc, (VOPFUNC)hfs_readonly_op }, /* symlink (READONLY) */ | |
7056 | { &vnop_readdir_desc, (VOPFUNC)hfs_vnop_readdir }, /* readdir */ | |
7057 | { &vnop_readdirattr_desc, (VOPFUNC)hfs_vnop_readdirattr }, /* readdirattr */ | |
7058 | { &vnop_readlink_desc, (VOPFUNC)hfs_vnop_readlink }, /* readlink */ | |
7059 | { &vnop_inactive_desc, (VOPFUNC)hfs_vnop_inactive }, /* inactive */ | |
7060 | { &vnop_reclaim_desc, (VOPFUNC)hfs_vnop_reclaim }, /* reclaim */ | |
7061 | { &vnop_strategy_desc, (VOPFUNC)hfs_vnop_strategy }, /* strategy */ | |
7062 | { &vnop_pathconf_desc, (VOPFUNC)hfs_vnop_pathconf }, /* pathconf */ | |
7063 | { &vnop_advlock_desc, (VOPFUNC)err_advlock }, /* advlock */ | |
7064 | { &vnop_allocate_desc, (VOPFUNC)hfs_readonly_op }, /* allocate (READONLY) */ | |
316670eb | 7065 | #if CONFIG_SEARCHFS |
b0d623f7 | 7066 | { &vnop_searchfs_desc, (VOPFUNC)hfs_vnop_search }, /* search fs */ |
316670eb A |
7067 | #else |
7068 | { &vnop_searchfs_desc, (VOPFUNC)err_searchfs }, /* search fs */ | |
7069 | #endif | |
b0d623f7 A |
7070 | { &vnop_bwrite_desc, (VOPFUNC)hfs_readonly_op }, /* bwrite (READONLY) */ |
7071 | { &vnop_pagein_desc, (VOPFUNC)hfs_vnop_pagein }, /* pagein */ | |
7072 | { &vnop_pageout_desc,(VOPFUNC) hfs_readonly_op }, /* pageout (READONLY) */ | |
7073 | { &vnop_copyfile_desc, (VOPFUNC)hfs_readonly_op }, /* copyfile (READONLY)*/ | |
7074 | { &vnop_blktooff_desc, (VOPFUNC)hfs_vnop_blktooff }, /* blktooff */ | |
7075 | { &vnop_offtoblk_desc, (VOPFUNC)hfs_vnop_offtoblk }, /* offtoblk */ | |
7076 | { &vnop_blockmap_desc, (VOPFUNC)hfs_vnop_blockmap }, /* blockmap */ | |
7077 | { &vnop_getxattr_desc, (VOPFUNC)hfs_vnop_getxattr}, | |
7078 | { &vnop_setxattr_desc, (VOPFUNC)hfs_readonly_op}, /* set xattr (READONLY) */ | |
7079 | { &vnop_removexattr_desc, (VOPFUNC)hfs_readonly_op}, /* remove xattr (READONLY) */ | |
7080 | { &vnop_listxattr_desc, (VOPFUNC)hfs_vnop_listxattr}, | |
b0d623f7 A |
7081 | #if NAMEDSTREAMS |
7082 | { &vnop_getnamedstream_desc, (VOPFUNC)hfs_vnop_getnamedstream }, | |
7083 | { &vnop_makenamedstream_desc, (VOPFUNC)hfs_readonly_op }, | |
7084 | { &vnop_removenamedstream_desc, (VOPFUNC)hfs_readonly_op }, | |
7085 | #endif | |
fe8ab488 | 7086 | { &vnop_getattrlistbulk_desc, (VOPFUNC)hfs_vnop_getattrlistbulk }, /* getattrlistbulk */ |
b0d623f7 A |
7087 | { NULL, (VOPFUNC)NULL } |
7088 | }; | |
7089 | ||
7090 | struct vnodeopv_desc hfs_std_vnodeop_opv_desc = | |
7091 | { &hfs_std_vnodeop_p, hfs_standard_vnodeop_entries }; | |
39236c6e | 7092 | #endif |
b0d623f7 A |
7093 | |
7094 | /* VNOP table for HFS+ */ | |
9bccf70c | 7095 | struct vnodeopv_entry_desc hfs_vnodeop_entries[] = { |
91447636 A |
7096 | { &vnop_default_desc, (VOPFUNC)vn_default_error }, |
7097 | { &vnop_lookup_desc, (VOPFUNC)hfs_vnop_lookup }, /* lookup */ | |
7098 | { &vnop_create_desc, (VOPFUNC)hfs_vnop_create }, /* create */ | |
7099 | { &vnop_mknod_desc, (VOPFUNC)hfs_vnop_mknod }, /* mknod */ | |
7100 | { &vnop_open_desc, (VOPFUNC)hfs_vnop_open }, /* open */ | |
7101 | { &vnop_close_desc, (VOPFUNC)hfs_vnop_close }, /* close */ | |
7102 | { &vnop_getattr_desc, (VOPFUNC)hfs_vnop_getattr }, /* getattr */ | |
7103 | { &vnop_setattr_desc, (VOPFUNC)hfs_vnop_setattr }, /* setattr */ | |
7104 | { &vnop_read_desc, (VOPFUNC)hfs_vnop_read }, /* read */ | |
7105 | { &vnop_write_desc, (VOPFUNC)hfs_vnop_write }, /* write */ | |
7106 | { &vnop_ioctl_desc, (VOPFUNC)hfs_vnop_ioctl }, /* ioctl */ | |
7107 | { &vnop_select_desc, (VOPFUNC)hfs_vnop_select }, /* select */ | |
7108 | { &vnop_revoke_desc, (VOPFUNC)nop_revoke }, /* revoke */ | |
7109 | { &vnop_exchange_desc, (VOPFUNC)hfs_vnop_exchange }, /* exchange */ | |
6d2010ae | 7110 | { &vnop_mmap_desc, (VOPFUNC)hfs_vnop_mmap }, /* mmap */ |
91447636 A |
7111 | { &vnop_fsync_desc, (VOPFUNC)hfs_vnop_fsync }, /* fsync */ |
7112 | { &vnop_remove_desc, (VOPFUNC)hfs_vnop_remove }, /* remove */ | |
7113 | { &vnop_link_desc, (VOPFUNC)hfs_vnop_link }, /* link */ | |
7114 | { &vnop_rename_desc, (VOPFUNC)hfs_vnop_rename }, /* rename */ | |
7115 | { &vnop_mkdir_desc, (VOPFUNC)hfs_vnop_mkdir }, /* mkdir */ | |
7116 | { &vnop_rmdir_desc, (VOPFUNC)hfs_vnop_rmdir }, /* rmdir */ | |
7117 | { &vnop_symlink_desc, (VOPFUNC)hfs_vnop_symlink }, /* symlink */ | |
7118 | { &vnop_readdir_desc, (VOPFUNC)hfs_vnop_readdir }, /* readdir */ | |
7119 | { &vnop_readdirattr_desc, (VOPFUNC)hfs_vnop_readdirattr }, /* readdirattr */ | |
7120 | { &vnop_readlink_desc, (VOPFUNC)hfs_vnop_readlink }, /* readlink */ | |
7121 | { &vnop_inactive_desc, (VOPFUNC)hfs_vnop_inactive }, /* inactive */ | |
7122 | { &vnop_reclaim_desc, (VOPFUNC)hfs_vnop_reclaim }, /* reclaim */ | |
7123 | { &vnop_strategy_desc, (VOPFUNC)hfs_vnop_strategy }, /* strategy */ | |
7124 | { &vnop_pathconf_desc, (VOPFUNC)hfs_vnop_pathconf }, /* pathconf */ | |
7125 | { &vnop_advlock_desc, (VOPFUNC)err_advlock }, /* advlock */ | |
7126 | { &vnop_allocate_desc, (VOPFUNC)hfs_vnop_allocate }, /* allocate */ | |
316670eb | 7127 | #if CONFIG_SEARCHFS |
91447636 | 7128 | { &vnop_searchfs_desc, (VOPFUNC)hfs_vnop_search }, /* search fs */ |
316670eb A |
7129 | #else |
7130 | { &vnop_searchfs_desc, (VOPFUNC)err_searchfs }, /* search fs */ | |
7131 | #endif | |
91447636 A |
7132 | { &vnop_bwrite_desc, (VOPFUNC)hfs_vnop_bwrite }, /* bwrite */ |
7133 | { &vnop_pagein_desc, (VOPFUNC)hfs_vnop_pagein }, /* pagein */ | |
7134 | { &vnop_pageout_desc,(VOPFUNC) hfs_vnop_pageout }, /* pageout */ | |
7135 | { &vnop_copyfile_desc, (VOPFUNC)err_copyfile }, /* copyfile */ | |
7136 | { &vnop_blktooff_desc, (VOPFUNC)hfs_vnop_blktooff }, /* blktooff */ | |
7137 | { &vnop_offtoblk_desc, (VOPFUNC)hfs_vnop_offtoblk }, /* offtoblk */ | |
7138 | { &vnop_blockmap_desc, (VOPFUNC)hfs_vnop_blockmap }, /* blockmap */ | |
91447636 A |
7139 | { &vnop_getxattr_desc, (VOPFUNC)hfs_vnop_getxattr}, |
7140 | { &vnop_setxattr_desc, (VOPFUNC)hfs_vnop_setxattr}, | |
7141 | { &vnop_removexattr_desc, (VOPFUNC)hfs_vnop_removexattr}, | |
7142 | { &vnop_listxattr_desc, (VOPFUNC)hfs_vnop_listxattr}, | |
2d21ac55 A |
7143 | #if NAMEDSTREAMS |
7144 | { &vnop_getnamedstream_desc, (VOPFUNC)hfs_vnop_getnamedstream }, | |
7145 | { &vnop_makenamedstream_desc, (VOPFUNC)hfs_vnop_makenamedstream }, | |
7146 | { &vnop_removenamedstream_desc, (VOPFUNC)hfs_vnop_removenamedstream }, | |
7147 | #endif | |
fe8ab488 A |
7148 | { &vnop_getattrlistbulk_desc, (VOPFUNC)hfs_vnop_getattrlistbulk }, /* getattrlistbulk */ |
7149 | { &vnop_mnomap_desc, (VOPFUNC)hfs_vnop_mnomap }, | |
9bccf70c A |
7150 | { NULL, (VOPFUNC)NULL } |
7151 | }; | |
7152 | ||
7153 | struct vnodeopv_desc hfs_vnodeop_opv_desc = | |
7154 | { &hfs_vnodeop_p, hfs_vnodeop_entries }; | |
7155 | ||
b0d623f7 A |
7156 | |
7157 | /* Spec Op vnop table for HFS+ */ | |
9bccf70c A |
7158 | int (**hfs_specop_p)(void *); |
7159 | struct vnodeopv_entry_desc hfs_specop_entries[] = { | |
91447636 A |
7160 | { &vnop_default_desc, (VOPFUNC)vn_default_error }, |
7161 | { &vnop_lookup_desc, (VOPFUNC)spec_lookup }, /* lookup */ | |
7162 | { &vnop_create_desc, (VOPFUNC)spec_create }, /* create */ | |
7163 | { &vnop_mknod_desc, (VOPFUNC)spec_mknod }, /* mknod */ | |
7164 | { &vnop_open_desc, (VOPFUNC)spec_open }, /* open */ | |
7165 | { &vnop_close_desc, (VOPFUNC)hfsspec_close }, /* close */ | |
7166 | { &vnop_getattr_desc, (VOPFUNC)hfs_vnop_getattr }, /* getattr */ | |
7167 | { &vnop_setattr_desc, (VOPFUNC)hfs_vnop_setattr }, /* setattr */ | |
7168 | { &vnop_read_desc, (VOPFUNC)hfsspec_read }, /* read */ | |
7169 | { &vnop_write_desc, (VOPFUNC)hfsspec_write }, /* write */ | |
7170 | { &vnop_ioctl_desc, (VOPFUNC)spec_ioctl }, /* ioctl */ | |
7171 | { &vnop_select_desc, (VOPFUNC)spec_select }, /* select */ | |
7172 | { &vnop_revoke_desc, (VOPFUNC)spec_revoke }, /* revoke */ | |
7173 | { &vnop_mmap_desc, (VOPFUNC)spec_mmap }, /* mmap */ | |
7174 | { &vnop_fsync_desc, (VOPFUNC)hfs_vnop_fsync }, /* fsync */ | |
7175 | { &vnop_remove_desc, (VOPFUNC)spec_remove }, /* remove */ | |
7176 | { &vnop_link_desc, (VOPFUNC)spec_link }, /* link */ | |
7177 | { &vnop_rename_desc, (VOPFUNC)spec_rename }, /* rename */ | |
7178 | { &vnop_mkdir_desc, (VOPFUNC)spec_mkdir }, /* mkdir */ | |
7179 | { &vnop_rmdir_desc, (VOPFUNC)spec_rmdir }, /* rmdir */ | |
7180 | { &vnop_symlink_desc, (VOPFUNC)spec_symlink }, /* symlink */ | |
7181 | { &vnop_readdir_desc, (VOPFUNC)spec_readdir }, /* readdir */ | |
7182 | { &vnop_readlink_desc, (VOPFUNC)spec_readlink }, /* readlink */ | |
7183 | { &vnop_inactive_desc, (VOPFUNC)hfs_vnop_inactive }, /* inactive */ | |
7184 | { &vnop_reclaim_desc, (VOPFUNC)hfs_vnop_reclaim }, /* reclaim */ | |
7185 | { &vnop_strategy_desc, (VOPFUNC)spec_strategy }, /* strategy */ | |
7186 | { &vnop_pathconf_desc, (VOPFUNC)spec_pathconf }, /* pathconf */ | |
7187 | { &vnop_advlock_desc, (VOPFUNC)err_advlock }, /* advlock */ | |
7188 | { &vnop_bwrite_desc, (VOPFUNC)hfs_vnop_bwrite }, | |
91447636 A |
7189 | { &vnop_pagein_desc, (VOPFUNC)hfs_vnop_pagein }, /* Pagein */ |
7190 | { &vnop_pageout_desc, (VOPFUNC)hfs_vnop_pageout }, /* Pageout */ | |
b0d623f7 | 7191 | { &vnop_copyfile_desc, (VOPFUNC)err_copyfile }, /* copyfile */ |
91447636 A |
7192 | { &vnop_blktooff_desc, (VOPFUNC)hfs_vnop_blktooff }, /* blktooff */ |
7193 | { &vnop_offtoblk_desc, (VOPFUNC)hfs_vnop_offtoblk }, /* offtoblk */ | |
39236c6e A |
7194 | { &vnop_getxattr_desc, (VOPFUNC)hfs_vnop_getxattr}, |
7195 | { &vnop_setxattr_desc, (VOPFUNC)hfs_vnop_setxattr}, | |
7196 | { &vnop_removexattr_desc, (VOPFUNC)hfs_vnop_removexattr}, | |
7197 | { &vnop_listxattr_desc, (VOPFUNC)hfs_vnop_listxattr}, | |
9bccf70c A |
7198 | { (struct vnodeop_desc*)NULL, (VOPFUNC)NULL } |
7199 | }; | |
7200 | struct vnodeopv_desc hfs_specop_opv_desc = | |
7201 | { &hfs_specop_p, hfs_specop_entries }; | |
7202 | ||
7203 | #if FIFO | |
b0d623f7 | 7204 | /* HFS+ FIFO VNOP table */ |
9bccf70c A |
7205 | int (**hfs_fifoop_p)(void *); |
7206 | struct vnodeopv_entry_desc hfs_fifoop_entries[] = { | |
91447636 A |
7207 | { &vnop_default_desc, (VOPFUNC)vn_default_error }, |
7208 | { &vnop_lookup_desc, (VOPFUNC)fifo_lookup }, /* lookup */ | |
7209 | { &vnop_create_desc, (VOPFUNC)fifo_create }, /* create */ | |
7210 | { &vnop_mknod_desc, (VOPFUNC)fifo_mknod }, /* mknod */ | |
7211 | { &vnop_open_desc, (VOPFUNC)fifo_open }, /* open */ | |
7212 | { &vnop_close_desc, (VOPFUNC)hfsfifo_close }, /* close */ | |
7213 | { &vnop_getattr_desc, (VOPFUNC)hfs_vnop_getattr }, /* getattr */ | |
7214 | { &vnop_setattr_desc, (VOPFUNC)hfs_vnop_setattr }, /* setattr */ | |
7215 | { &vnop_read_desc, (VOPFUNC)hfsfifo_read }, /* read */ | |
7216 | { &vnop_write_desc, (VOPFUNC)hfsfifo_write }, /* write */ | |
7217 | { &vnop_ioctl_desc, (VOPFUNC)fifo_ioctl }, /* ioctl */ | |
7218 | { &vnop_select_desc, (VOPFUNC)fifo_select }, /* select */ | |
7219 | { &vnop_revoke_desc, (VOPFUNC)fifo_revoke }, /* revoke */ | |
7220 | { &vnop_mmap_desc, (VOPFUNC)fifo_mmap }, /* mmap */ | |
7221 | { &vnop_fsync_desc, (VOPFUNC)hfs_vnop_fsync }, /* fsync */ | |
7222 | { &vnop_remove_desc, (VOPFUNC)fifo_remove }, /* remove */ | |
7223 | { &vnop_link_desc, (VOPFUNC)fifo_link }, /* link */ | |
7224 | { &vnop_rename_desc, (VOPFUNC)fifo_rename }, /* rename */ | |
7225 | { &vnop_mkdir_desc, (VOPFUNC)fifo_mkdir }, /* mkdir */ | |
7226 | { &vnop_rmdir_desc, (VOPFUNC)fifo_rmdir }, /* rmdir */ | |
7227 | { &vnop_symlink_desc, (VOPFUNC)fifo_symlink }, /* symlink */ | |
7228 | { &vnop_readdir_desc, (VOPFUNC)fifo_readdir }, /* readdir */ | |
7229 | { &vnop_readlink_desc, (VOPFUNC)fifo_readlink }, /* readlink */ | |
7230 | { &vnop_inactive_desc, (VOPFUNC)hfs_vnop_inactive }, /* inactive */ | |
7231 | { &vnop_reclaim_desc, (VOPFUNC)hfs_vnop_reclaim }, /* reclaim */ | |
7232 | { &vnop_strategy_desc, (VOPFUNC)fifo_strategy }, /* strategy */ | |
7233 | { &vnop_pathconf_desc, (VOPFUNC)fifo_pathconf }, /* pathconf */ | |
7234 | { &vnop_advlock_desc, (VOPFUNC)err_advlock }, /* advlock */ | |
7235 | { &vnop_bwrite_desc, (VOPFUNC)hfs_vnop_bwrite }, | |
7236 | { &vnop_pagein_desc, (VOPFUNC)hfs_vnop_pagein }, /* Pagein */ | |
7237 | { &vnop_pageout_desc, (VOPFUNC)hfs_vnop_pageout }, /* Pageout */ | |
7238 | { &vnop_copyfile_desc, (VOPFUNC)err_copyfile }, /* copyfile */ | |
7239 | { &vnop_blktooff_desc, (VOPFUNC)hfs_vnop_blktooff }, /* blktooff */ | |
7240 | { &vnop_offtoblk_desc, (VOPFUNC)hfs_vnop_offtoblk }, /* offtoblk */ | |
7241 | { &vnop_blockmap_desc, (VOPFUNC)hfs_vnop_blockmap }, /* blockmap */ | |
39236c6e A |
7242 | { &vnop_getxattr_desc, (VOPFUNC)hfs_vnop_getxattr}, |
7243 | { &vnop_setxattr_desc, (VOPFUNC)hfs_vnop_setxattr}, | |
7244 | { &vnop_removexattr_desc, (VOPFUNC)hfs_vnop_removexattr}, | |
7245 | { &vnop_listxattr_desc, (VOPFUNC)hfs_vnop_listxattr}, | |
9bccf70c A |
7246 | { (struct vnodeop_desc*)NULL, (VOPFUNC)NULL } |
7247 | }; | |
7248 | struct vnodeopv_desc hfs_fifoop_opv_desc = | |
7249 | { &hfs_fifoop_p, hfs_fifoop_entries }; | |
7250 | #endif /* FIFO */ | |
7251 | ||
7252 | ||
7253 |