+ if (vnode_vtype(vp) == VDIR) {
+ fp->fp_glob->fg_vn_data = fg_vn_data_alloc();
+ } else {
+ fp->fp_glob->fg_vn_data = NULL;
+ }
+
+ vnode_put(vp);
+
+ /*
+ * The first terminal open (without a O_NOCTTY) by a session leader
+ * results in it being set as the controlling terminal.
+ */
+ if (vnode_istty(vp) && !(p->p_flag & P_CONTROLT) &&
+ !(flags & O_NOCTTY)) {
+ int tmp = 0;
+
+ (void)(*fp->fp_glob->fg_ops->fo_ioctl)(fp, (int)TIOCSCTTY,
+ (caddr_t)&tmp, ctx);
+ }
+
+ proc_fdlock(p);
+ if (flags & O_CLOEXEC) {
+ *fdflags(p, indx) |= UF_EXCLOSE;
+ }
+ if (flags & O_CLOFORK) {
+ *fdflags(p, indx) |= UF_FORKCLOSE;
+ }
+ procfdtbl_releasefd(p, indx, NULL);
+
+#if CONFIG_SECLUDED_MEMORY
+ if (secluded_for_filecache &&
+ FILEGLOB_DTYPE(fp->fp_glob) == DTYPE_VNODE &&
+ vnode_vtype(vp) == VREG) {
+ memory_object_control_t moc;
+
+ moc = ubc_getobject(vp, UBC_FLAGS_NONE);
+
+ if (moc == MEMORY_OBJECT_CONTROL_NULL) {
+ /* nothing to do... */
+ } else if (fp->fp_glob->fg_flag & FWRITE) {
+ /* writable -> no longer eligible for secluded pages */
+ memory_object_mark_eligible_for_secluded(moc,
+ FALSE);
+ } else if (secluded_for_filecache == 1) {
+ char pathname[32] = { 0, };
+ size_t copied;
+ /* XXX FBDP: better way to detect /Applications/ ? */
+ if (UIO_SEG_IS_USER_SPACE(ndp->ni_segflg)) {
+ (void)copyinstr(ndp->ni_dirp,
+ pathname,
+ sizeof(pathname),
+ &copied);
+ } else {
+ copystr(CAST_DOWN(void *, ndp->ni_dirp),
+ pathname,
+ sizeof(pathname),
+ &copied);
+ }
+ pathname[sizeof(pathname) - 1] = '\0';
+ if (strncmp(pathname,
+ "/Applications/",
+ strlen("/Applications/")) == 0 &&
+ strncmp(pathname,
+ "/Applications/Camera.app/",
+ strlen("/Applications/Camera.app/")) != 0) {
+ /*
+ * not writable
+ * AND from "/Applications/"
+ * AND not from "/Applications/Camera.app/"
+ * ==> eligible for secluded
+ */
+ memory_object_mark_eligible_for_secluded(moc,
+ TRUE);
+ }
+ } else if (secluded_for_filecache == 2) {
+#if __arm64__
+#define DYLD_SHARED_CACHE_NAME "dyld_shared_cache_arm64"
+#elif __arm__
+#define DYLD_SHARED_CACHE_NAME "dyld_shared_cache_armv7"
+#else
+/* not implemented... */
+#endif
+ size_t len = strlen(vp->v_name);
+ if (!strncmp(vp->v_name, DYLD_SHARED_CACHE_NAME, len) ||
+ !strncmp(vp->v_name, "dyld", len) ||
+ !strncmp(vp->v_name, "launchd", len) ||
+ !strncmp(vp->v_name, "Camera", len) ||
+ !strncmp(vp->v_name, "mediaserverd", len) ||
+ !strncmp(vp->v_name, "SpringBoard", len) ||
+ !strncmp(vp->v_name, "backboardd", len)) {
+ /*
+ * This file matters when launching Camera:
+ * do not store its contents in the secluded
+ * pool that will be drained on Camera launch.
+ */
+ memory_object_mark_eligible_for_secluded(moc,
+ FALSE);
+ }
+ }
+ }
+#endif /* CONFIG_SECLUDED_MEMORY */
+
+ fp_drop(p, indx, fp, 1);
+ proc_fdunlock(p);
+
+ *retval = indx;
+
+ return 0;
+bad:
+ context = *vfs_context_current();
+ context.vc_ucred = fp->fp_glob->fg_cred;
+
+ if ((fp->fp_glob->fg_flag & FWASLOCKED) &&
+ (FILEGLOB_DTYPE(fp->fp_glob) == DTYPE_VNODE)) {
+ struct flock lf = {
+ .l_whence = SEEK_SET,
+ .l_type = F_UNLCK,
+ };
+
+ (void)VNOP_ADVLOCK(
+ vp, (caddr_t)fp->fp_glob, F_UNLCK, &lf, F_FLOCK, ctx, NULL);
+ }
+
+ vn_close(vp, fp->fp_glob->fg_flag, &context);
+ vnode_put(vp);
+ fp_free(p, indx, fp);
+
+ return error;
+}
+
+/*
+ * While most of the *at syscall handlers can call nameiat() which
+ * is a wrapper around namei, the use of namei and initialisation
+ * of nameidata are far removed and in different functions - namei
+ * gets called in vn_open_auth for open1. So we'll just do here what
+ * nameiat() does.
+ */
+static int
+open1at(vfs_context_t ctx, struct nameidata *ndp, int uflags,
+ struct vnode_attr *vap, fp_allocfn_t fp_zalloc, void *cra, int32_t *retval,
+ int dirfd)
+{
+ if ((dirfd != AT_FDCWD) && !(ndp->ni_cnd.cn_flags & USEDVP)) {
+ int error;
+ char c;
+
+ if (UIO_SEG_IS_USER_SPACE(ndp->ni_segflg)) {
+ error = copyin(ndp->ni_dirp, &c, sizeof(char));
+ if (error) {
+ return error;
+ }
+ } else {
+ c = *((char *)(ndp->ni_dirp));
+ }
+
+ if (c != '/') {
+ vnode_t dvp_at;
+
+ error = vnode_getfromfd(ndp->ni_cnd.cn_context, dirfd,
+ &dvp_at);
+ if (error) {
+ return error;
+ }
+
+ if (vnode_vtype(dvp_at) != VDIR) {
+ vnode_put(dvp_at);
+ return ENOTDIR;
+ }
+
+ ndp->ni_dvp = dvp_at;
+ ndp->ni_cnd.cn_flags |= USEDVP;
+ error = open1(ctx, ndp, uflags, vap, fp_zalloc, cra,
+ retval);
+ vnode_put(dvp_at);
+ return error;
+ }
+ }
+
+ return open1(ctx, ndp, uflags, vap, fp_zalloc, cra, retval);
+}
+
+/*
+ * open_extended: open a file given a path name; with extended argument list (including extended security (ACL)).
+ *
+ * Parameters: p Process requesting the open
+ * uap User argument descriptor (see below)
+ * retval Pointer to an area to receive the
+ * return calue from the system call
+ *
+ * Indirect: uap->path Path to open (same as 'open')
+ * uap->flags Flags to open (same as 'open'
+ * uap->uid UID to set, if creating
+ * uap->gid GID to set, if creating
+ * uap->mode File mode, if creating (same as 'open')
+ * uap->xsecurity ACL to set, if creating
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ * Notes: The kauth_filesec_t in 'va', if any, is in host byte order.
+ *
+ * XXX: We should enummerate the possible errno values here, and where
+ * in the code they originated.
+ */
+int
+open_extended(proc_t p, struct open_extended_args *uap, int32_t *retval)
+{
+ struct filedesc *fdp = p->p_fd;
+ int ciferror;
+ kauth_filesec_t xsecdst;
+ struct vnode_attr va;
+ struct nameidata nd;
+ int cmode;
+
+ AUDIT_ARG(owner, uap->uid, uap->gid);
+
+ xsecdst = NULL;
+ if ((uap->xsecurity != USER_ADDR_NULL) &&
+ ((ciferror = kauth_copyinfilesec(uap->xsecurity, &xsecdst)) != 0)) {
+ return ciferror;
+ }
+
+ VATTR_INIT(&va);
+ cmode = ((uap->mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT;
+ VATTR_SET(&va, va_mode, cmode & ACCESSPERMS);
+ if (uap->uid != KAUTH_UID_NONE) {
+ VATTR_SET(&va, va_uid, uap->uid);
+ }
+ if (uap->gid != KAUTH_GID_NONE) {
+ VATTR_SET(&va, va_gid, uap->gid);
+ }
+ if (xsecdst != NULL) {
+ VATTR_SET(&va, va_acl, &xsecdst->fsec_acl);
+ }
+
+ NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1, UIO_USERSPACE,
+ uap->path, vfs_context_current());
+
+ ciferror = open1(vfs_context_current(), &nd, uap->flags, &va,
+ fileproc_alloc_init, NULL, retval);
+ if (xsecdst != NULL) {
+ kauth_filesec_free(xsecdst);
+ }
+
+ return ciferror;
+}
+
+/*
+ * Go through the data-protected atomically controlled open (2)
+ *
+ * int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode)
+ */
+int
+open_dprotected_np(__unused proc_t p, struct open_dprotected_np_args *uap, int32_t *retval)
+{
+ int flags = uap->flags;
+ int class = uap->class;
+ int dpflags = uap->dpflags;
+
+ /*
+ * Follow the same path as normal open(2)
+ * Look up the item if it exists, and acquire the vnode.
+ */
+ struct filedesc *fdp = p->p_fd;
+ struct vnode_attr va;
+ struct nameidata nd;
+ int cmode;
+ int error;
+
+ VATTR_INIT(&va);
+ /* Mask off all but regular access permissions */
+ cmode = ((uap->mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT;
+ VATTR_SET(&va, va_mode, cmode & ACCESSPERMS);
+
+ NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1, UIO_USERSPACE,
+ uap->path, vfs_context_current());
+
+ /*
+ * Initialize the extra fields in vnode_attr to pass down our
+ * extra fields.
+ * 1. target cprotect class.
+ * 2. set a flag to mark it as requiring open-raw-encrypted semantics.
+ */
+ if (flags & O_CREAT) {
+ /* lower level kernel code validates that the class is valid before applying it. */
+ if (class != PROTECTION_CLASS_DEFAULT) {
+ /*
+ * PROTECTION_CLASS_DEFAULT implies that we make the class for this
+ * file behave the same as open (2)
+ */
+ VATTR_SET(&va, va_dataprotect_class, class);
+ }
+ }
+
+ if (dpflags & (O_DP_GETRAWENCRYPTED | O_DP_GETRAWUNENCRYPTED)) {
+ if (flags & (O_RDWR | O_WRONLY)) {
+ /* Not allowed to write raw encrypted bytes */
+ return EINVAL;
+ }
+ if (uap->dpflags & O_DP_GETRAWENCRYPTED) {
+ VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWENCRYPTED);
+ }
+ if (uap->dpflags & O_DP_GETRAWUNENCRYPTED) {
+ VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWUNENCRYPTED);
+ }
+ }
+
+ error = open1(vfs_context_current(), &nd, uap->flags, &va,
+ fileproc_alloc_init, NULL, retval);
+
+ return error;
+}
+
+static int
+openat_internal(vfs_context_t ctx, user_addr_t path, int flags, int mode,
+ int fd, enum uio_seg segflg, int *retval)
+{
+ struct filedesc *fdp = (vfs_context_proc(ctx))->p_fd;
+ struct {
+ struct vnode_attr va;
+ struct nameidata nd;
+ } *__open_data;
+ struct vnode_attr *vap;
+ struct nameidata *ndp;
+ int cmode;
+ int error;
+
+ __open_data = kheap_alloc(KHEAP_TEMP, sizeof(*__open_data), Z_WAITOK);
+ vap = &__open_data->va;
+ ndp = &__open_data->nd;
+
+ VATTR_INIT(vap);
+ /* Mask off all but regular access permissions */
+ cmode = ((mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT;
+ VATTR_SET(vap, va_mode, cmode & ACCESSPERMS);
+
+ NDINIT(ndp, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1,
+ segflg, path, ctx);
+
+ error = open1at(ctx, ndp, flags, vap, fileproc_alloc_init, NULL,
+ retval, fd);
+
+ kheap_free(KHEAP_TEMP, __open_data, sizeof(*__open_data));
+
+ return error;
+}
+
+int
+open(proc_t p, struct open_args *uap, int32_t *retval)
+{
+ __pthread_testcancel(1);
+ return open_nocancel(p, (struct open_nocancel_args *)uap, retval);
+}
+
+int
+open_nocancel(__unused proc_t p, struct open_nocancel_args *uap,
+ int32_t *retval)
+{
+ return openat_internal(vfs_context_current(), uap->path, uap->flags,
+ uap->mode, AT_FDCWD, UIO_USERSPACE, retval);
+}
+
+int
+openat_nocancel(__unused proc_t p, struct openat_nocancel_args *uap,
+ int32_t *retval)
+{
+ return openat_internal(vfs_context_current(), uap->path, uap->flags,
+ uap->mode, uap->fd, UIO_USERSPACE, retval);
+}
+
+int
+openat(proc_t p, struct openat_args *uap, int32_t *retval)
+{
+ __pthread_testcancel(1);
+ return openat_nocancel(p, (struct openat_nocancel_args *)uap, retval);
+}
+
+/*
+ * openbyid_np: open a file given a file system id and a file system object id
+ * the hfs file system object id is an fsobj_id_t {uint32, uint32}
+ * file systems that don't support object ids it is a node id (uint64_t).
+ *
+ * Parameters: p Process requesting the open
+ * uap User argument descriptor (see below)
+ * retval Pointer to an area to receive the
+ * return calue from the system call
+ *
+ * Indirect: uap->path Path to open (same as 'open')
+ *
+ * uap->fsid id of target file system
+ * uap->objid id of target file system object
+ * uap->flags Flags to open (same as 'open')
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ *
+ * XXX: We should enummerate the possible errno values here, and where
+ * in the code they originated.
+ */
+int
+openbyid_np(__unused proc_t p, struct openbyid_np_args *uap, int *retval)
+{
+ fsid_t fsid;
+ uint64_t objid;
+ int error;
+ char *buf = NULL;
+ int buflen = MAXPATHLEN;
+ int pathlen = 0;
+ vfs_context_t ctx = vfs_context_current();
+
+ if ((error = priv_check_cred(vfs_context_ucred(ctx), PRIV_VFS_OPEN_BY_ID, 0))) {
+ return error;
+ }
+
+ if ((error = copyin(uap->fsid, (caddr_t)&fsid, sizeof(fsid)))) {
+ return error;
+ }
+
+ /*uap->obj is an fsobj_id_t defined as struct {uint32_t, uint32_t} */
+ if ((error = copyin(uap->objid, (caddr_t)&objid, sizeof(uint64_t)))) {
+ return error;
+ }
+
+ AUDIT_ARG(value32, fsid.val[0]);
+ AUDIT_ARG(value64, objid);
+
+ /*resolve path from fsis, objid*/
+ do {
+ buf = kheap_alloc(KHEAP_TEMP, buflen + 1, Z_WAITOK);
+ if (buf == NULL) {
+ return ENOMEM;
+ }
+
+ error = fsgetpath_internal( ctx, fsid.val[0], objid, buflen,
+ buf, FSOPT_ISREALFSID, &pathlen);
+
+ if (error) {
+ kheap_free(KHEAP_TEMP, buf, buflen + 1);
+ buf = NULL;
+ }
+ } while (error == ENOSPC && (buflen += MAXPATHLEN));
+
+ if (error) {
+ return error;
+ }
+
+ buf[pathlen] = 0;
+
+ error = openat_internal(
+ ctx, (user_addr_t)buf, uap->oflags, 0, AT_FDCWD, UIO_SYSSPACE, retval);
+
+ kheap_free(KHEAP_TEMP, buf, buflen + 1);
+
+ return error;
+}
+
+
+/*
+ * Create a special file.
+ */
+static int mkfifo1(vfs_context_t ctx, user_addr_t upath, struct vnode_attr *vap);
+
+int
+mknod(proc_t p, struct mknod_args *uap, __unused int32_t *retval)
+{
+ struct vnode_attr va;
+ vfs_context_t ctx = vfs_context_current();
+ int error;
+ struct nameidata nd;
+ vnode_t vp, dvp;
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_mode, (uap->mode & ALLPERMS) & ~p->p_fd->fd_cmask);
+ VATTR_SET(&va, va_rdev, uap->dev);
+
+ /* If it's a mknod() of a FIFO, call mkfifo1() instead */
+ if ((uap->mode & S_IFMT) == S_IFIFO) {
+ return mkfifo1(ctx, uap->path, &va);
+ }
+
+ AUDIT_ARG(mode, (mode_t)uap->mode);
+ AUDIT_ARG(value32, uap->dev);
+
+ if ((error = suser(vfs_context_ucred(ctx), &p->p_acflag))) {
+ return error;
+ }
+ NDINIT(&nd, CREATE, OP_MKNOD, LOCKPARENT | AUDITVNPATH1,
+ UIO_USERSPACE, uap->path, ctx);
+ error = namei(&nd);
+ if (error) {
+ return error;
+ }
+ dvp = nd.ni_dvp;
+ vp = nd.ni_vp;
+
+ if (vp != NULL) {
+ error = EEXIST;
+ goto out;
+ }
+
+ switch (uap->mode & S_IFMT) {
+ case S_IFCHR:
+ VATTR_SET(&va, va_type, VCHR);
+ break;
+ case S_IFBLK:
+ VATTR_SET(&va, va_type, VBLK);
+ break;
+ default:
+ error = EINVAL;
+ goto out;
+ }
+
+#if CONFIG_MACF
+ error = mac_vnode_check_create(ctx,
+ nd.ni_dvp, &nd.ni_cnd, &va);
+ if (error) {
+ goto out;
+ }
+#endif
+
+ if ((error = vnode_authorize(dvp, NULL, KAUTH_VNODE_ADD_FILE, ctx)) != 0) {
+ goto out;
+ }
+
+ if ((error = vn_create(dvp, &vp, &nd, &va, 0, 0, NULL, ctx)) != 0) {
+ goto out;
+ }
+
+ if (vp) {
+ int update_flags = 0;
+
+ // Make sure the name & parent pointers are hooked up
+ if (vp->v_name == NULL) {
+ update_flags |= VNODE_UPDATE_NAME;
+ }
+ if (vp->v_parent == NULLVP) {
+ update_flags |= VNODE_UPDATE_PARENT;
+ }
+
+ if (update_flags) {
+ vnode_update_identity(vp, dvp, nd.ni_cnd.cn_nameptr, nd.ni_cnd.cn_namelen, nd.ni_cnd.cn_hash, update_flags);
+ }
+
+#if CONFIG_FSE
+ add_fsevent(FSE_CREATE_FILE, ctx,
+ FSE_ARG_VNODE, vp,
+ FSE_ARG_DONE);
+#endif
+ }
+
+out:
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&nd);
+
+ if (vp) {
+ vnode_put(vp);
+ }
+ vnode_put(dvp);
+
+ return error;
+}
+
+/*
+ * Create a named pipe.
+ *
+ * Returns: 0 Success
+ * EEXIST
+ * namei:???
+ * vnode_authorize:???
+ * vn_create:???
+ */
+static int
+mkfifo1(vfs_context_t ctx, user_addr_t upath, struct vnode_attr *vap)
+{
+ vnode_t vp, dvp;
+ int error;
+ struct nameidata nd;
+
+ NDINIT(&nd, CREATE, OP_MKFIFO, LOCKPARENT | AUDITVNPATH1,
+ UIO_USERSPACE, upath, ctx);
+ error = namei(&nd);
+ if (error) {
+ return error;
+ }
+ dvp = nd.ni_dvp;
+ vp = nd.ni_vp;
+
+ /* check that this is a new file and authorize addition */
+ if (vp != NULL) {
+ error = EEXIST;
+ goto out;
+ }
+ VATTR_SET(vap, va_type, VFIFO);
+
+ if ((error = vn_authorize_create(dvp, &nd.ni_cnd, vap, ctx, NULL)) != 0) {
+ goto out;
+ }
+
+ error = vn_create(dvp, &vp, &nd, vap, 0, 0, NULL, ctx);
+out:
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&nd);
+
+ if (vp) {
+ vnode_put(vp);
+ }
+ vnode_put(dvp);
+
+ return error;
+}
+
+
+/*
+ * mkfifo_extended: Create a named pipe; with extended argument list (including extended security (ACL)).
+ *
+ * Parameters: p Process requesting the open
+ * uap User argument descriptor (see below)
+ * retval (Ignored)
+ *
+ * Indirect: uap->path Path to fifo (same as 'mkfifo')
+ * uap->uid UID to set
+ * uap->gid GID to set
+ * uap->mode File mode to set (same as 'mkfifo')
+ * uap->xsecurity ACL to set, if creating
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ * Notes: The kauth_filesec_t in 'va', if any, is in host byte order.
+ *
+ * XXX: We should enummerate the possible errno values here, and where
+ * in the code they originated.
+ */
+int
+mkfifo_extended(proc_t p, struct mkfifo_extended_args *uap, __unused int32_t *retval)
+{
+ int ciferror;
+ kauth_filesec_t xsecdst;
+ struct vnode_attr va;
+
+ AUDIT_ARG(owner, uap->uid, uap->gid);
+
+ xsecdst = KAUTH_FILESEC_NONE;
+ if (uap->xsecurity != USER_ADDR_NULL) {
+ if ((ciferror = kauth_copyinfilesec(uap->xsecurity, &xsecdst)) != 0) {
+ return ciferror;
+ }
+ }
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_mode, (uap->mode & ALLPERMS) & ~p->p_fd->fd_cmask);
+ if (uap->uid != KAUTH_UID_NONE) {
+ VATTR_SET(&va, va_uid, uap->uid);
+ }
+ if (uap->gid != KAUTH_GID_NONE) {
+ VATTR_SET(&va, va_gid, uap->gid);
+ }
+ if (xsecdst != KAUTH_FILESEC_NONE) {
+ VATTR_SET(&va, va_acl, &xsecdst->fsec_acl);
+ }
+
+ ciferror = mkfifo1(vfs_context_current(), uap->path, &va);
+
+ if (xsecdst != KAUTH_FILESEC_NONE) {
+ kauth_filesec_free(xsecdst);
+ }
+ return ciferror;
+}
+
+/* ARGSUSED */
+int
+mkfifo(proc_t p, struct mkfifo_args *uap, __unused int32_t *retval)
+{
+ struct vnode_attr va;
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_mode, (uap->mode & ALLPERMS) & ~p->p_fd->fd_cmask);
+
+ return mkfifo1(vfs_context_current(), uap->path, &va);
+}
+
+extern int safe_getpath_new(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path, int firmlink);
+extern int safe_getpath(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path);
+extern int safe_getpath_no_firmlink(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path);
+
+int
+safe_getpath_new(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path, int firmlink)
+{
+ int ret, len = _len;
+
+ *truncated_path = 0;
+
+ if (firmlink) {
+ ret = vn_getpath(dvp, path, &len);
+ } else {
+ ret = vn_getpath_no_firmlink(dvp, path, &len);
+ }
+ if (ret == 0 && len < (MAXPATHLEN - 1)) {
+ if (leafname) {
+ path[len - 1] = '/';
+ len += strlcpy(&path[len], leafname, MAXPATHLEN - len) + 1;
+ if (len > MAXPATHLEN) {
+ char *ptr;
+
+ // the string got truncated!
+ *truncated_path = 1;
+ ptr = strrchr(path, '/');
+ if (ptr) {
+ *ptr = '\0'; // chop off the string at the last directory component
+ }
+ len = (int)strlen(path) + 1;
+ }
+ }
+ } else if (ret == 0) {
+ *truncated_path = 1;
+ } else if (ret != 0) {
+ struct vnode *mydvp = dvp;
+
+ if (ret != ENOSPC) {
+ printf("safe_getpath: failed to get the path for vp %p (%s) : err %d\n",
+ dvp, dvp->v_name ? dvp->v_name : "no-name", ret);
+ }
+ *truncated_path = 1;
+
+ do {
+ if (mydvp->v_parent != NULL) {
+ mydvp = mydvp->v_parent;
+ } else if (mydvp->v_mount) {
+ strlcpy(path, mydvp->v_mount->mnt_vfsstat.f_mntonname, _len);
+ break;
+ } else {
+ // no parent and no mount point? only thing is to punt and say "/" changed
+ strlcpy(path, "/", _len);
+ len = 2;
+ mydvp = NULL;
+ }
+
+ if (mydvp == NULL) {
+ break;
+ }
+
+ len = _len;
+ if (firmlink) {
+ ret = vn_getpath(mydvp, path, &len);
+ } else {
+ ret = vn_getpath_no_firmlink(mydvp, path, &len);
+ }
+ } while (ret == ENOSPC);
+ }
+
+ return len;
+}
+
+int
+safe_getpath(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path)
+{
+ return safe_getpath_new(dvp, leafname, path, _len, truncated_path, 1);
+}
+
+int
+safe_getpath_no_firmlink(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path)
+{
+ return safe_getpath_new(dvp, leafname, path, _len, truncated_path, 0);
+}
+
+/*
+ * Make a hard file link.
+ *
+ * Returns: 0 Success
+ * EPERM
+ * EEXIST
+ * EXDEV
+ * namei:???
+ * vnode_authorize:???
+ * VNOP_LINK:???
+ */
+/* ARGSUSED */
+static int
+linkat_internal(vfs_context_t ctx, int fd1, user_addr_t path, int fd2,
+ user_addr_t link, int flag, enum uio_seg segflg)
+{
+ vnode_t vp, pvp, dvp, lvp;
+ struct nameidata nd;
+ int follow;
+ int error;
+#if CONFIG_FSE
+ fse_info finfo;
+#endif
+ int need_event, has_listeners, need_kpath2;
+ char *target_path = NULL;
+ int truncated = 0;
+
+ vp = dvp = lvp = NULLVP;
+
+ /* look up the object we are linking to */
+ follow = (flag & AT_SYMLINK_FOLLOW) ? FOLLOW : NOFOLLOW;
+ NDINIT(&nd, LOOKUP, OP_LOOKUP, AUDITVNPATH1 | follow,
+ segflg, path, ctx);
+
+ error = nameiat(&nd, fd1);
+ if (error) {
+ return error;
+ }
+ vp = nd.ni_vp;
+
+ nameidone(&nd);
+
+ /*
+ * Normally, linking to directories is not supported.
+ * However, some file systems may have limited support.
+ */
+ if (vp->v_type == VDIR) {
+ if (!ISSET(vp->v_mount->mnt_kern_flag, MNTK_DIR_HARDLINKS)) {
+ error = EPERM; /* POSIX */
+ goto out;
+ }
+
+ /* Linking to a directory requires ownership. */
+ if (!kauth_cred_issuser(vfs_context_ucred(ctx))) {
+ struct vnode_attr dva;
+
+ VATTR_INIT(&dva);
+ VATTR_WANTED(&dva, va_uid);
+ if (vnode_getattr(vp, &dva, ctx) != 0 ||
+ !VATTR_IS_SUPPORTED(&dva, va_uid) ||
+ (dva.va_uid != kauth_cred_getuid(vfs_context_ucred(ctx)))) {
+ error = EACCES;
+ goto out;
+ }
+ }
+ }
+
+ /* lookup the target node */
+#if CONFIG_TRIGGERS
+ nd.ni_op = OP_LINK;
+#endif
+ nd.ni_cnd.cn_nameiop = CREATE;
+ nd.ni_cnd.cn_flags = LOCKPARENT | AUDITVNPATH2 | CN_NBMOUNTLOOK;
+ nd.ni_dirp = link;
+ error = nameiat(&nd, fd2);
+ if (error != 0) {
+ goto out;
+ }
+ dvp = nd.ni_dvp;
+ lvp = nd.ni_vp;
+
+#if CONFIG_MACF
+ if ((error = mac_vnode_check_link(ctx, dvp, vp, &nd.ni_cnd)) != 0) {
+ goto out2;
+ }
+#endif
+
+ /* or to anything that kauth doesn't want us to (eg. immutable items) */
+ if ((error = vnode_authorize(vp, NULL, KAUTH_VNODE_LINKTARGET, ctx)) != 0) {
+ goto out2;
+ }
+
+ /* target node must not exist */
+ if (lvp != NULLVP) {
+ error = EEXIST;
+ goto out2;
+ }
+ /* cannot link across mountpoints */
+ if (vnode_mount(vp) != vnode_mount(dvp)) {
+ error = EXDEV;
+ goto out2;
+ }
+
+ /* authorize creation of the target note */
+ if ((error = vnode_authorize(dvp, NULL, KAUTH_VNODE_ADD_FILE, ctx)) != 0) {
+ goto out2;
+ }
+
+ /* and finally make the link */
+ error = VNOP_LINK(vp, dvp, &nd.ni_cnd, ctx);
+ if (error) {
+ goto out2;
+ }
+
+#if CONFIG_MACF
+ (void)mac_vnode_notify_link(ctx, vp, dvp, &nd.ni_cnd);
+#endif
+
+#if CONFIG_FSE
+ need_event = need_fsevent(FSE_CREATE_FILE, dvp);
+#else
+ need_event = 0;
+#endif
+ has_listeners = kauth_authorize_fileop_has_listeners();
+
+ need_kpath2 = 0;
+#if CONFIG_AUDIT
+ if (AUDIT_RECORD_EXISTS()) {
+ need_kpath2 = 1;
+ }
+#endif
+
+ if (need_event || has_listeners || need_kpath2) {
+ char *link_to_path = NULL;
+ int len, link_name_len;
+
+ /* build the path to the new link file */
+ GET_PATH(target_path);
+
+ len = safe_getpath(dvp, nd.ni_cnd.cn_nameptr, target_path, MAXPATHLEN, &truncated);
+
+ AUDIT_ARG(kpath, target_path, ARG_KPATH2);
+
+ if (has_listeners) {
+ /* build the path to file we are linking to */
+ GET_PATH(link_to_path);
+
+ link_name_len = MAXPATHLEN;
+ if (vn_getpath(vp, link_to_path, &link_name_len) == 0) {
+ /*
+ * Call out to allow 3rd party notification of rename.
+ * Ignore result of kauth_authorize_fileop call.
+ */
+ kauth_authorize_fileop(vfs_context_ucred(ctx), KAUTH_FILEOP_LINK,
+ (uintptr_t)link_to_path,
+ (uintptr_t)target_path);
+ }
+ if (link_to_path != NULL) {
+ RELEASE_PATH(link_to_path);
+ }
+ }
+#if CONFIG_FSE
+ if (need_event) {
+ /* construct fsevent */
+ if (get_fse_info(vp, &finfo, ctx) == 0) {
+ if (truncated) {
+ finfo.mode |= FSE_TRUNCATED_PATH;
+ }
+
+ // build the path to the destination of the link
+ add_fsevent(FSE_CREATE_FILE, ctx,
+ FSE_ARG_STRING, len, target_path,
+ FSE_ARG_FINFO, &finfo,
+ FSE_ARG_DONE);
+ }
+
+ pvp = vp->v_parent;
+ // need an iocount on pvp in this case
+ if (pvp && pvp != dvp) {
+ error = vnode_get(pvp);
+ if (error) {
+ pvp = NULLVP;
+ error = 0;
+ }
+ }
+ if (pvp) {
+ add_fsevent(FSE_STAT_CHANGED, ctx,
+ FSE_ARG_VNODE, pvp, FSE_ARG_DONE);
+ }
+ if (pvp && pvp != dvp) {
+ vnode_put(pvp);
+ }
+ }
+#endif
+ }
+out2:
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&nd);
+ if (target_path != NULL) {
+ RELEASE_PATH(target_path);
+ }
+out:
+ if (lvp) {
+ vnode_put(lvp);
+ }
+ if (dvp) {
+ vnode_put(dvp);
+ }
+ vnode_put(vp);
+ return error;
+}
+
+int
+link(__unused proc_t p, struct link_args *uap, __unused int32_t *retval)
+{
+ return linkat_internal(vfs_context_current(), AT_FDCWD, uap->path,
+ AT_FDCWD, uap->link, AT_SYMLINK_FOLLOW, UIO_USERSPACE);
+}
+
+int
+linkat(__unused proc_t p, struct linkat_args *uap, __unused int32_t *retval)
+{
+ if (uap->flag & ~AT_SYMLINK_FOLLOW) {
+ return EINVAL;
+ }
+
+ return linkat_internal(vfs_context_current(), uap->fd1, uap->path,
+ uap->fd2, uap->link, uap->flag, UIO_USERSPACE);
+}
+
+/*
+ * Make a symbolic link.
+ *
+ * We could add support for ACLs here too...
+ */
+/* ARGSUSED */
+static int
+symlinkat_internal(vfs_context_t ctx, user_addr_t path_data, int fd,
+ user_addr_t link, enum uio_seg segflg)
+{
+ struct vnode_attr va;
+ char *path;
+ int error;
+ struct nameidata nd;
+ vnode_t vp, dvp;
+ size_t dummy = 0;
+ proc_t p;
+
+ error = 0;
+ if (UIO_SEG_IS_USER_SPACE(segflg)) {
+ path = zalloc(ZV_NAMEI);
+ error = copyinstr(path_data, path, MAXPATHLEN, &dummy);
+ } else {
+ path = (char *)path_data;
+ }
+ if (error) {
+ goto out;
+ }
+ AUDIT_ARG(text, path); /* This is the link string */
+
+ NDINIT(&nd, CREATE, OP_SYMLINK, LOCKPARENT | AUDITVNPATH1,
+ segflg, link, ctx);
+
+ error = nameiat(&nd, fd);
+ if (error) {
+ goto out;
+ }
+ dvp = nd.ni_dvp;
+ vp = nd.ni_vp;
+
+ p = vfs_context_proc(ctx);
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_type, VLNK);
+ VATTR_SET(&va, va_mode, ACCESSPERMS & ~p->p_fd->fd_cmask);
+
+#if CONFIG_MACF
+ error = mac_vnode_check_create(ctx,
+ dvp, &nd.ni_cnd, &va);
+#endif
+ if (error != 0) {
+ goto skipit;
+ }
+
+ if (vp != NULL) {
+ error = EEXIST;
+ goto skipit;
+ }
+
+ /* authorize */
+ if (error == 0) {
+ error = vnode_authorize(dvp, NULL, KAUTH_VNODE_ADD_FILE, ctx);
+ }
+ /* get default ownership, etc. */
+ if (error == 0) {
+ error = vnode_authattr_new(dvp, &va, 0, ctx);
+ }
+ if (error == 0) {
+ error = VNOP_SYMLINK(dvp, &vp, &nd.ni_cnd, &va, path, ctx);
+ }
+
+ /* do fallback attribute handling */
+ if (error == 0 && vp) {
+ error = vnode_setattr_fallback(vp, &va, ctx);
+ }
+
+#if CONFIG_MACF
+ if (error == 0 && vp) {
+ error = vnode_label(vnode_mount(vp), dvp, vp, &nd.ni_cnd, VNODE_LABEL_CREATE, ctx);
+ }
+#endif
+
+ if (error == 0) {
+ int update_flags = 0;
+
+ /*check if a new vnode was created, else try to get one*/
+ if (vp == NULL) {
+ nd.ni_cnd.cn_nameiop = LOOKUP;
+#if CONFIG_TRIGGERS
+ nd.ni_op = OP_LOOKUP;
+#endif
+ nd.ni_cnd.cn_flags = 0;
+ error = nameiat(&nd, fd);
+ vp = nd.ni_vp;
+
+ if (vp == NULL) {
+ goto skipit;
+ }
+ }
+
+#if 0 /* XXX - kauth_todo - is KAUTH_FILEOP_SYMLINK needed? */
+ /* call out to allow 3rd party notification of rename.
+ * Ignore result of kauth_authorize_fileop call.
+ */
+ if (kauth_authorize_fileop_has_listeners() &&
+ namei(&nd) == 0) {
+ char *new_link_path = NULL;
+ int len;
+
+ /* build the path to the new link file */
+ new_link_path = get_pathbuff();
+ len = MAXPATHLEN;
+ vn_getpath(dvp, new_link_path, &len);
+ if ((len + 1 + nd.ni_cnd.cn_namelen + 1) < MAXPATHLEN) {
+ new_link_path[len - 1] = '/';
+ strlcpy(&new_link_path[len], nd.ni_cnd.cn_nameptr, MAXPATHLEN - len);
+ }
+
+ kauth_authorize_fileop(vfs_context_ucred(ctx), KAUTH_FILEOP_SYMLINK,
+ (uintptr_t)path, (uintptr_t)new_link_path);
+ if (new_link_path != NULL) {
+ release_pathbuff(new_link_path);
+ }
+ }
+#endif
+ // Make sure the name & parent pointers are hooked up
+ if (vp->v_name == NULL) {
+ update_flags |= VNODE_UPDATE_NAME;
+ }
+ if (vp->v_parent == NULLVP) {
+ update_flags |= VNODE_UPDATE_PARENT;
+ }
+
+ if (update_flags) {
+ vnode_update_identity(vp, dvp, nd.ni_cnd.cn_nameptr, nd.ni_cnd.cn_namelen, nd.ni_cnd.cn_hash, update_flags);
+ }
+
+#if CONFIG_FSE
+ add_fsevent(FSE_CREATE_FILE, ctx,
+ FSE_ARG_VNODE, vp,
+ FSE_ARG_DONE);
+#endif
+ }
+
+skipit:
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&nd);
+
+ if (vp) {
+ vnode_put(vp);
+ }
+ vnode_put(dvp);
+out:
+ if (path && (path != (char *)path_data)) {
+ zfree(ZV_NAMEI, path);
+ }
+
+ return error;
+}
+
+int
+symlink(__unused proc_t p, struct symlink_args *uap, __unused int32_t *retval)
+{
+ return symlinkat_internal(vfs_context_current(), uap->path, AT_FDCWD,
+ uap->link, UIO_USERSPACE);
+}
+
+int
+symlinkat(__unused proc_t p, struct symlinkat_args *uap,
+ __unused int32_t *retval)
+{
+ return symlinkat_internal(vfs_context_current(), uap->path1, uap->fd,
+ uap->path2, UIO_USERSPACE);
+}
+
+/*
+ * Delete a whiteout from the filesystem.
+ * No longer supported.
+ */
+int
+undelete(__unused proc_t p, __unused struct undelete_args *uap, __unused int32_t *retval)
+{
+ return ENOTSUP;
+}
+
+/*
+ * Delete a name from the filesystem.
+ */
+/* ARGSUSED */
+static int
+unlinkat_internal(vfs_context_t ctx, int fd, vnode_t start_dvp,
+ user_addr_t path_arg, enum uio_seg segflg, int unlink_flags)
+{
+ struct nameidata nd;
+ vnode_t vp, dvp;
+ int error;
+ struct componentname *cnp;
+ char *path = NULL;
+ char *no_firmlink_path = NULL;
+ int len_path = 0;
+ int len_no_firmlink_path = 0;
+#if CONFIG_FSE
+ fse_info finfo;
+ struct vnode_attr va;
+#endif
+ int flags;
+ int need_event;
+ int has_listeners;
+ int truncated_path;
+ int truncated_no_firmlink_path;
+ int batched;
+ struct vnode_attr *vap;
+ int do_retry;
+ int retry_count = 0;
+ int cn_flags;
+
+ cn_flags = LOCKPARENT;
+ if (!(unlink_flags & VNODE_REMOVE_NO_AUDIT_PATH)) {
+ cn_flags |= AUDITVNPATH1;
+ }
+ /* If a starting dvp is passed, it trumps any fd passed. */
+ if (start_dvp) {
+ cn_flags |= USEDVP;
+ }
+
+#if NAMEDRSRCFORK
+ /* unlink or delete is allowed on rsrc forks and named streams */
+ cn_flags |= CN_ALLOWRSRCFORK;
+#endif
+
+retry:
+ do_retry = 0;
+ flags = 0;
+ need_event = 0;
+ has_listeners = 0;
+ truncated_path = 0;
+ truncated_no_firmlink_path = 0;
+ vap = NULL;
+
+ NDINIT(&nd, DELETE, OP_UNLINK, cn_flags, segflg, path_arg, ctx);
+
+ nd.ni_dvp = start_dvp;
+ nd.ni_flag |= NAMEI_COMPOUNDREMOVE;
+ cnp = &nd.ni_cnd;
+
+continue_lookup:
+ error = nameiat(&nd, fd);
+ if (error) {
+ return error;
+ }
+
+ dvp = nd.ni_dvp;
+ vp = nd.ni_vp;
+
+
+ /* With Carbon delete semantics, busy files cannot be deleted */
+ if (unlink_flags & VNODE_REMOVE_NODELETEBUSY) {
+ flags |= VNODE_REMOVE_NODELETEBUSY;
+ }
+
+ /* Skip any potential upcalls if told to. */
+ if (unlink_flags & VNODE_REMOVE_SKIP_NAMESPACE_EVENT) {
+ flags |= VNODE_REMOVE_SKIP_NAMESPACE_EVENT;
+ }
+
+ if (vp) {
+ batched = vnode_compound_remove_available(vp);
+ /*
+ * The root of a mounted filesystem cannot be deleted.
+ */
+ if ((vp->v_flag & VROOT) || (dvp->v_mount != vp->v_mount)) {
+ error = EBUSY;
+ goto out;
+ }
+
+#if DEVELOPMENT || DEBUG
+ /*
+ * XXX VSWAP: Check for entitlements or special flag here
+ * so we can restrict access appropriately.
+ */
+#else /* DEVELOPMENT || DEBUG */
+
+ if (vnode_isswap(vp) && (ctx != vfs_context_kernel())) {
+ error = EPERM;
+ goto out;
+ }
+#endif /* DEVELOPMENT || DEBUG */
+
+ if (!batched) {
+ error = vn_authorize_unlink(dvp, vp, cnp, ctx, NULL);
+ if (error) {
+ if (error == ENOENT) {
+ if (retry_count < MAX_AUTHORIZE_ENOENT_RETRIES) {
+ do_retry = 1;
+ retry_count++;
+ }
+ }
+ goto out;
+ }
+ }
+ } else {
+ batched = 1;
+
+ if (!vnode_compound_remove_available(dvp)) {
+ panic("No vp, but no compound remove?");
+ }
+ }
+
+#if CONFIG_FSE
+ need_event = need_fsevent(FSE_DELETE, dvp);
+ if (need_event) {
+ if (!batched) {
+ if ((vp->v_flag & VISHARDLINK) == 0) {
+ /* XXX need to get these data in batched VNOP */
+ get_fse_info(vp, &finfo, ctx);
+ }
+ } else {
+ error = vfs_get_notify_attributes(&va);
+ if (error) {
+ goto out;
+ }
+
+ vap = &va;
+ }
+ }
+#endif
+ has_listeners = kauth_authorize_fileop_has_listeners();
+ if (need_event || has_listeners) {
+ if (path == NULL) {
+ GET_PATH(path);
+ }
+ len_path = safe_getpath(dvp, nd.ni_cnd.cn_nameptr, path, MAXPATHLEN, &truncated_path);
+ if (no_firmlink_path == NULL) {
+ GET_PATH(no_firmlink_path);
+ }
+ len_no_firmlink_path = safe_getpath_no_firmlink(dvp, nd.ni_cnd.cn_nameptr, no_firmlink_path, MAXPATHLEN, &truncated_no_firmlink_path);
+ }
+
+#if NAMEDRSRCFORK
+ if (nd.ni_cnd.cn_flags & CN_WANTSRSRCFORK) {
+ error = vnode_removenamedstream(dvp, vp, XATTR_RESOURCEFORK_NAME, 0, ctx);
+ } else
+#endif
+ {
+ error = vn_remove(dvp, &nd.ni_vp, &nd, flags, vap, ctx);
+ vp = nd.ni_vp;
+ if (error == EKEEPLOOKING) {
+ if (!batched) {
+ panic("EKEEPLOOKING, but not a filesystem that supports compound VNOPs?");
+ }
+
+ if ((nd.ni_flag & NAMEI_CONTLOOKUP) == 0) {
+ panic("EKEEPLOOKING, but continue flag not set?");
+ }
+
+ if (vnode_isdir(vp)) {
+ error = EISDIR;
+ goto out;
+ }
+ goto continue_lookup;
+ } else if (error == ENOENT && batched) {
+ if (retry_count < MAX_AUTHORIZE_ENOENT_RETRIES) {
+ /*
+ * For compound VNOPs, the authorization callback may
+ * return ENOENT in case of racing hardlink lookups
+ * hitting the name cache, redrive the lookup.
+ */
+ do_retry = 1;
+ retry_count += 1;
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * Call out to allow 3rd party notification of delete.
+ * Ignore result of kauth_authorize_fileop call.
+ */
+ if (!error) {
+ if (has_listeners) {
+ kauth_authorize_fileop(vfs_context_ucred(ctx),
+ KAUTH_FILEOP_DELETE,
+ (uintptr_t)vp,
+ (uintptr_t)path);
+ }
+
+ if (vp->v_flag & VISHARDLINK) {
+ //
+ // if a hardlink gets deleted we want to blow away the
+ // v_parent link because the path that got us to this
+ // instance of the link is no longer valid. this will
+ // force the next call to get the path to ask the file
+ // system instead of just following the v_parent link.
+ //
+ vnode_update_identity(vp, NULL, NULL, 0, 0, VNODE_UPDATE_PARENT);
+ }
+
+#if CONFIG_FSE
+ if (need_event) {
+ if (vp->v_flag & VISHARDLINK) {
+ get_fse_info(vp, &finfo, ctx);
+ } else if (vap) {
+ vnode_get_fse_info_from_vap(vp, &finfo, vap);
+ }
+ if (truncated_path) {
+ finfo.mode |= FSE_TRUNCATED_PATH;
+ }
+ add_fsevent(FSE_DELETE, ctx,
+ FSE_ARG_STRING, len_no_firmlink_path, no_firmlink_path,
+ FSE_ARG_FINFO, &finfo,
+ FSE_ARG_DONE);
+ }
+#endif
+ }
+
+out:
+ if (path != NULL) {
+ RELEASE_PATH(path);
+ path = NULL;
+ }
+
+ if (no_firmlink_path != NULL) {
+ RELEASE_PATH(no_firmlink_path);
+ no_firmlink_path = NULL;
+ }
+#if NAMEDRSRCFORK
+ /* recycle the deleted rsrc fork vnode to force a reclaim, which
+ * will cause its shadow file to go away if necessary.
+ */
+ if (vp && (vnode_isnamedstream(vp)) &&
+ (vp->v_parent != NULLVP) &&
+ vnode_isshadow(vp)) {
+ vnode_recycle(vp);
+ }
+#endif
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&nd);
+ vnode_put(dvp);
+ if (vp) {
+ vnode_put(vp);
+ }
+
+ if (do_retry) {
+ goto retry;
+ }
+
+ return error;
+}
+
+int
+unlink1(vfs_context_t ctx, vnode_t start_dvp, user_addr_t path_arg,
+ enum uio_seg segflg, int unlink_flags)
+{
+ return unlinkat_internal(ctx, AT_FDCWD, start_dvp, path_arg, segflg,
+ unlink_flags);
+}
+
+/*
+ * Delete a name from the filesystem using Carbon semantics.
+ */
+int
+delete(__unused proc_t p, struct delete_args *uap, __unused int32_t *retval)
+{
+ return unlinkat_internal(vfs_context_current(), AT_FDCWD, NULLVP,
+ uap->path, UIO_USERSPACE, VNODE_REMOVE_NODELETEBUSY);
+}
+
+/*
+ * Delete a name from the filesystem using POSIX semantics.
+ */
+int
+unlink(__unused proc_t p, struct unlink_args *uap, __unused int32_t *retval)
+{
+ return unlinkat_internal(vfs_context_current(), AT_FDCWD, NULLVP,
+ uap->path, UIO_USERSPACE, 0);
+}
+
+int
+unlinkat(__unused proc_t p, struct unlinkat_args *uap, __unused int32_t *retval)
+{
+ if (uap->flag & ~(AT_REMOVEDIR | AT_REMOVEDIR_DATALESS)) {
+ return EINVAL;
+ }
+
+ if (uap->flag & (AT_REMOVEDIR | AT_REMOVEDIR_DATALESS)) {
+ int unlink_flags = 0;
+
+ if (uap->flag & AT_REMOVEDIR_DATALESS) {
+ unlink_flags |= VNODE_REMOVE_DATALESS_DIR;
+ }
+ return rmdirat_internal(vfs_context_current(), uap->fd,
+ uap->path, UIO_USERSPACE, unlink_flags);
+ } else {
+ return unlinkat_internal(vfs_context_current(), uap->fd,
+ NULLVP, uap->path, UIO_USERSPACE, 0);
+ }
+}
+
+/*
+ * Reposition read/write file offset.
+ */
+int
+lseek(proc_t p, struct lseek_args *uap, off_t *retval)
+{
+ struct fileproc *fp;
+ vnode_t vp;
+ struct vfs_context *ctx;
+ off_t offset = uap->offset, file_size;
+ int error;
+
+ if ((error = fp_getfvp(p, uap->fd, &fp, &vp))) {
+ if (error == ENOTSUP) {
+ return ESPIPE;
+ }
+ return error;
+ }
+ if (vnode_isfifo(vp)) {
+ file_drop(uap->fd);
+ return ESPIPE;
+ }
+
+
+ ctx = vfs_context_current();
+#if CONFIG_MACF
+ if (uap->whence == L_INCR && uap->offset == 0) {
+ error = mac_file_check_get_offset(vfs_context_ucred(ctx),
+ fp->fp_glob);
+ } else {
+ error = mac_file_check_change_offset(vfs_context_ucred(ctx),
+ fp->fp_glob);
+ }
+ if (error) {
+ file_drop(uap->fd);
+ return error;
+ }
+#endif
+ if ((error = vnode_getwithref(vp))) {
+ file_drop(uap->fd);
+ return error;
+ }
+
+ switch (uap->whence) {
+ case L_INCR:
+ offset += fp->fp_glob->fg_offset;
+ break;
+ case L_XTND:
+ if ((error = vnode_size(vp, &file_size, ctx)) != 0) {
+ break;
+ }
+ offset += file_size;
+ break;
+ case L_SET:
+ break;
+ case SEEK_HOLE:
+ error = VNOP_IOCTL(vp, FSIOC_FIOSEEKHOLE, (caddr_t)&offset, 0, ctx);
+ break;
+ case SEEK_DATA:
+ error = VNOP_IOCTL(vp, FSIOC_FIOSEEKDATA, (caddr_t)&offset, 0, ctx);
+ break;
+ default:
+ error = EINVAL;
+ }
+ if (error == 0) {
+ if (uap->offset > 0 && offset < 0) {
+ /* Incremented/relative move past max size */
+ error = EOVERFLOW;
+ } else {
+ /*
+ * Allow negative offsets on character devices, per
+ * POSIX 1003.1-2001. Most likely for writing disk
+ * labels.
+ */
+ if (offset < 0 && vp->v_type != VCHR) {
+ /* Decremented/relative move before start */
+ error = EINVAL;
+ } else {
+ /* Success */
+ fp->fp_glob->fg_offset = offset;
+ *retval = fp->fp_glob->fg_offset;
+ }
+ }
+ }
+
+ /*
+ * An lseek can affect whether data is "available to read." Use
+ * hint of NOTE_NONE so no EVFILT_VNODE events fire
+ */
+ post_event_if_success(vp, error, NOTE_NONE);
+ (void)vnode_put(vp);
+ file_drop(uap->fd);
+ return error;
+}
+
+
+/*
+ * Check access permissions.
+ *
+ * Returns: 0 Success
+ * vnode_authorize:???
+ */
+static int
+access1(vnode_t vp, vnode_t dvp, int uflags, vfs_context_t ctx)
+{
+ kauth_action_t action;
+ int error;
+
+ /*
+ * If just the regular access bits, convert them to something
+ * that vnode_authorize will understand.
+ */
+ if (!(uflags & _ACCESS_EXTENDED_MASK)) {
+ action = 0;
+ if (uflags & R_OK) {
+ action |= KAUTH_VNODE_READ_DATA; /* aka KAUTH_VNODE_LIST_DIRECTORY */
+ }
+ if (uflags & W_OK) {
+ if (vnode_isdir(vp)) {
+ action |= KAUTH_VNODE_ADD_FILE |
+ KAUTH_VNODE_ADD_SUBDIRECTORY;
+ /* might want delete rights here too */
+ } else {
+ action |= KAUTH_VNODE_WRITE_DATA;
+ }
+ }
+ if (uflags & X_OK) {
+ if (vnode_isdir(vp)) {
+ action |= KAUTH_VNODE_SEARCH;
+ } else {
+ action |= KAUTH_VNODE_EXECUTE;
+ }
+ }
+ } else {
+ /* take advantage of definition of uflags */
+ action = uflags >> 8;
+ }
+
+#if CONFIG_MACF
+ error = mac_vnode_check_access(ctx, vp, uflags);
+ if (error) {
+ return error;
+ }
+#endif /* MAC */
+
+ /* action == 0 means only check for existence */
+ if (action != 0) {
+ error = vnode_authorize(vp, dvp, action | KAUTH_VNODE_ACCESS, ctx);
+ } else {
+ error = 0;
+ }
+
+ return error;
+}
+
+
+
+/*
+ * access_extended: Check access permissions in bulk.
+ *
+ * Description: uap->entries Pointer to an array of accessx
+ * descriptor structs, plus one or
+ * more NULL terminated strings (see
+ * "Notes" section below).
+ * uap->size Size of the area pointed to by
+ * uap->entries.
+ * uap->results Pointer to the results array.
+ *
+ * Returns: 0 Success
+ * ENOMEM Insufficient memory
+ * EINVAL Invalid arguments
+ * namei:EFAULT Bad address
+ * namei:ENAMETOOLONG Filename too long
+ * namei:ENOENT No such file or directory
+ * namei:ELOOP Too many levels of symbolic links
+ * namei:EBADF Bad file descriptor
+ * namei:ENOTDIR Not a directory
+ * namei:???
+ * access1:
+ *
+ * Implicit returns:
+ * uap->results Array contents modified
+ *
+ * Notes: The uap->entries are structured as an arbitrary length array
+ * of accessx descriptors, followed by one or more NULL terminated
+ * strings
+ *
+ * struct accessx_descriptor[0]
+ * ...
+ * struct accessx_descriptor[n]
+ * char name_data[0];
+ *
+ * We determine the entry count by walking the buffer containing
+ * the uap->entries argument descriptor. For each descriptor we
+ * see, the valid values for the offset ad_name_offset will be
+ * in the byte range:
+ *
+ * [ uap->entries + sizeof(struct accessx_descriptor) ]
+ * to
+ * [ uap->entries + uap->size - 2 ]
+ *
+ * since we must have at least one string, and the string must
+ * be at least one character plus the NULL terminator in length.
+ *
+ * XXX: Need to support the check-as uid argument
+ */
+int
+access_extended(__unused proc_t p, struct access_extended_args *uap, __unused int32_t *retval)
+{
+ struct accessx_descriptor *input = NULL;
+ errno_t *result = NULL;
+ errno_t error = 0;
+ int wantdelete = 0;
+ size_t desc_max, desc_actual;
+ unsigned int i, j;
+ struct vfs_context context;
+ struct nameidata nd;
+ int niopts;
+ vnode_t vp = NULL;
+ vnode_t dvp = NULL;
+#define ACCESSX_MAX_DESCR_ON_STACK 10
+ struct accessx_descriptor stack_input[ACCESSX_MAX_DESCR_ON_STACK];
+
+ context.vc_ucred = NULL;
+
+ /*
+ * Validate parameters; if valid, copy the descriptor array and string
+ * arguments into local memory. Before proceeding, the following
+ * conditions must have been met:
+ *
+ * o The total size is not permitted to exceed ACCESSX_MAX_TABLESIZE
+ * o There must be sufficient room in the request for at least one
+ * descriptor and a one yte NUL terminated string.
+ * o The allocation of local storage must not fail.
+ */
+ if (uap->size > ACCESSX_MAX_TABLESIZE) {
+ return ENOMEM;
+ }
+ if (uap->size < (sizeof(struct accessx_descriptor) + 2)) {
+ return EINVAL;
+ }
+ if (uap->size <= sizeof(stack_input)) {
+ input = stack_input;
+ } else {
+ input = kheap_alloc(KHEAP_DATA_BUFFERS, uap->size, Z_WAITOK);
+ if (input == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ }
+ error = copyin(uap->entries, input, uap->size);
+ if (error) {
+ goto out;
+ }
+
+ AUDIT_ARG(opaque, input, uap->size);
+
+ /*
+ * Force NUL termination of the copyin buffer to avoid nami() running
+ * off the end. If the caller passes us bogus data, they may get a
+ * bogus result.
+ */
+ ((char *)input)[uap->size - 1] = 0;
+
+ /*
+ * Access is defined as checking against the process' real identity,
+ * even if operations are checking the effective identity. This
+ * requires that we use a local vfs context.
+ */
+ context.vc_ucred = kauth_cred_copy_real(kauth_cred_get());
+ context.vc_thread = current_thread();
+
+ /*
+ * Find out how many entries we have, so we can allocate the result
+ * array by walking the list and adjusting the count downward by the
+ * earliest string offset we see.
+ */
+ desc_max = (uap->size - 2) / sizeof(struct accessx_descriptor);
+ desc_actual = desc_max;
+ for (i = 0; i < desc_actual; i++) {
+ /*
+ * Take the offset to the name string for this entry and
+ * convert to an input array index, which would be one off
+ * the end of the array if this entry was the lowest-addressed
+ * name string.
+ */
+ j = input[i].ad_name_offset / sizeof(struct accessx_descriptor);
+
+ /*
+ * An offset greater than the max allowable offset is an error.
+ * It is also an error for any valid entry to point
+ * to a location prior to the end of the current entry, if
+ * it's not a reference to the string of the previous entry.
+ */
+ if (j > desc_max || (j != 0 && j <= i)) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /* Also do not let ad_name_offset point to something beyond the size of the input */
+ if (input[i].ad_name_offset >= uap->size) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * An offset of 0 means use the previous descriptor's offset;
+ * this is used to chain multiple requests for the same file
+ * to avoid multiple lookups.
+ */
+ if (j == 0) {
+ /* This is not valid for the first entry */
+ if (i == 0) {
+ error = EINVAL;
+ goto out;
+ }
+ continue;
+ }
+
+ /*
+ * If the offset of the string for this descriptor is before
+ * what we believe is the current actual last descriptor,
+ * then we need to adjust our estimate downward; this permits
+ * the string table following the last descriptor to be out
+ * of order relative to the descriptor list.
+ */
+ if (j < desc_actual) {
+ desc_actual = j;
+ }
+ }
+
+ /*
+ * We limit the actual number of descriptors we are willing to process
+ * to a hard maximum of ACCESSX_MAX_DESCRIPTORS. If the number being
+ * requested does not exceed this limit,
+ */
+ if (desc_actual > ACCESSX_MAX_DESCRIPTORS) {
+ error = ENOMEM;
+ goto out;
+ }
+ result = kheap_alloc(KHEAP_DATA_BUFFERS, desc_actual * sizeof(errno_t),
+ Z_WAITOK | Z_ZERO);
+ if (result == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Do the work by iterating over the descriptor entries we know to
+ * at least appear to contain valid data.
+ */
+ error = 0;
+ for (i = 0; i < desc_actual; i++) {
+ /*
+ * If the ad_name_offset is 0, then we use the previous
+ * results to make the check; otherwise, we are looking up
+ * a new file name.
+ */
+ if (input[i].ad_name_offset != 0) {
+ /* discard old vnodes */
+ if (vp) {
+ vnode_put(vp);
+ vp = NULL;
+ }
+ if (dvp) {
+ vnode_put(dvp);
+ dvp = NULL;
+ }
+
+ /*
+ * Scan forward in the descriptor list to see if we
+ * need the parent vnode. We will need it if we are
+ * deleting, since we must have rights to remove
+ * entries in the parent directory, as well as the
+ * rights to delete the object itself.
+ */
+ wantdelete = input[i].ad_flags & _DELETE_OK;
+ for (j = i + 1; (j < desc_actual) && (input[j].ad_name_offset == 0); j++) {
+ if (input[j].ad_flags & _DELETE_OK) {
+ wantdelete = 1;
+ }
+ }
+
+ niopts = FOLLOW | AUDITVNPATH1;
+
+ /* need parent for vnode_authorize for deletion test */
+ if (wantdelete) {
+ niopts |= WANTPARENT;
+ }
+
+ /* do the lookup */
+ NDINIT(&nd, LOOKUP, OP_ACCESS, niopts, UIO_SYSSPACE,
+ CAST_USER_ADDR_T(((const char *)input) + input[i].ad_name_offset),
+ &context);
+ error = namei(&nd);
+ if (!error) {
+ vp = nd.ni_vp;
+ if (wantdelete) {
+ dvp = nd.ni_dvp;
+ }
+ }
+ nameidone(&nd);
+ }
+
+ /*
+ * Handle lookup errors.
+ */
+ switch (error) {
+ case ENOENT:
+ case EACCES:
+ case EPERM:
+ case ENOTDIR:
+ result[i] = error;
+ break;
+ case 0:
+ /* run this access check */
+ result[i] = access1(vp, dvp, input[i].ad_flags, &context);
+ break;
+ default:
+ /* fatal lookup error */
+
+ goto out;
+ }
+ }
+
+ AUDIT_ARG(data, result, sizeof(errno_t), desc_actual);
+
+ /* copy out results */
+ error = copyout(result, uap->results, desc_actual * sizeof(errno_t));
+
+out:
+ if (input && input != stack_input) {
+ kheap_free(KHEAP_DATA_BUFFERS, input, uap->size);
+ }
+ if (result) {
+ kheap_free(KHEAP_DATA_BUFFERS, result, desc_actual * sizeof(errno_t));
+ }
+ if (vp) {
+ vnode_put(vp);
+ }
+ if (dvp) {
+ vnode_put(dvp);
+ }
+ if (IS_VALID_CRED(context.vc_ucred)) {
+ kauth_cred_unref(&context.vc_ucred);
+ }
+ return error;
+}
+
+
+/*
+ * Returns: 0 Success
+ * namei:EFAULT Bad address
+ * namei:ENAMETOOLONG Filename too long
+ * namei:ENOENT No such file or directory
+ * namei:ELOOP Too many levels of symbolic links
+ * namei:EBADF Bad file descriptor
+ * namei:ENOTDIR Not a directory
+ * namei:???
+ * access1:
+ */
+static int
+faccessat_internal(vfs_context_t ctx, int fd, user_addr_t path, int amode,
+ int flag, enum uio_seg segflg)
+{
+ int error;
+ struct nameidata nd;
+ int niopts;
+ struct vfs_context context;
+#if NAMEDRSRCFORK
+ int is_namedstream = 0;
+#endif
+
+ /*
+ * Unless the AT_EACCESS option is used, Access is defined as checking
+ * against the process' real identity, even if operations are checking
+ * the effective identity. So we need to tweak the credential
+ * in the context for that case.
+ */
+ if (!(flag & AT_EACCESS)) {
+ context.vc_ucred = kauth_cred_copy_real(kauth_cred_get());
+ } else {
+ context.vc_ucred = ctx->vc_ucred;
+ }
+ context.vc_thread = ctx->vc_thread;
+
+
+ niopts = (flag & AT_SYMLINK_NOFOLLOW ? NOFOLLOW : FOLLOW) | AUDITVNPATH1;
+ /* need parent for vnode_authorize for deletion test */
+ if (amode & _DELETE_OK) {
+ niopts |= WANTPARENT;
+ }
+ NDINIT(&nd, LOOKUP, OP_ACCESS, niopts, segflg,
+ path, &context);
+
+#if NAMEDRSRCFORK
+ /* access(F_OK) calls are allowed for resource forks. */
+ if (amode == F_OK) {
+ nd.ni_cnd.cn_flags |= CN_ALLOWRSRCFORK;
+ }
+#endif
+ error = nameiat(&nd, fd);
+ if (error) {
+ goto out;
+ }
+
+#if NAMEDRSRCFORK
+ /* Grab reference on the shadow stream file vnode to
+ * force an inactive on release which will mark it
+ * for recycle.
+ */
+ if (vnode_isnamedstream(nd.ni_vp) &&
+ (nd.ni_vp->v_parent != NULLVP) &&
+ vnode_isshadow(nd.ni_vp)) {
+ is_namedstream = 1;
+ vnode_ref(nd.ni_vp);
+ }
+#endif
+
+ error = access1(nd.ni_vp, nd.ni_dvp, amode, &context);
+
+#if NAMEDRSRCFORK
+ if (is_namedstream) {
+ vnode_rele(nd.ni_vp);
+ }
+#endif
+
+ vnode_put(nd.ni_vp);
+ if (amode & _DELETE_OK) {
+ vnode_put(nd.ni_dvp);
+ }
+ nameidone(&nd);
+
+out:
+ if (!(flag & AT_EACCESS)) {
+ kauth_cred_unref(&context.vc_ucred);
+ }
+ return error;
+}
+
+int
+access(__unused proc_t p, struct access_args *uap, __unused int32_t *retval)
+{
+ return faccessat_internal(vfs_context_current(), AT_FDCWD,
+ uap->path, uap->flags, 0, UIO_USERSPACE);
+}
+
+int
+faccessat(__unused proc_t p, struct faccessat_args *uap,
+ __unused int32_t *retval)
+{
+ if (uap->flag & ~(AT_EACCESS | AT_SYMLINK_NOFOLLOW)) {
+ return EINVAL;
+ }
+
+ return faccessat_internal(vfs_context_current(), uap->fd,
+ uap->path, uap->amode, uap->flag, UIO_USERSPACE);
+}
+
+/*
+ * Returns: 0 Success
+ * EFAULT
+ * copyout:EFAULT
+ * namei:???
+ * vn_stat:???
+ */
+static int
+fstatat_internal(vfs_context_t ctx, user_addr_t path, user_addr_t ub,
+ user_addr_t xsecurity, user_addr_t xsecurity_size, int isstat64,
+ enum uio_seg segflg, int fd, int flag)
+{
+ struct nameidata nd;
+ int follow;
+ union {
+ struct stat sb;
+ struct stat64 sb64;
+ } source = {};
+ union {
+ struct user64_stat user64_sb;
+ struct user32_stat user32_sb;
+ struct user64_stat64 user64_sb64;
+ struct user32_stat64 user32_sb64;
+ } dest = {};
+ caddr_t sbp;
+ int error, my_size;
+ kauth_filesec_t fsec;
+ size_t xsecurity_bufsize;
+ void * statptr;
+ struct fileproc *fp = NULL;
+ int needsrealdev = 0;
+
+ follow = (flag & AT_SYMLINK_NOFOLLOW) ? NOFOLLOW : FOLLOW;
+ NDINIT(&nd, LOOKUP, OP_GETATTR, follow | AUDITVNPATH1,
+ segflg, path, ctx);
+
+#if NAMEDRSRCFORK
+ int is_namedstream = 0;
+ /* stat calls are allowed for resource forks. */
+ nd.ni_cnd.cn_flags |= CN_ALLOWRSRCFORK;
+#endif
+
+ if (flag & AT_FDONLY) {
+ vnode_t fvp;
+
+ error = fp_getfvp(vfs_context_proc(ctx), fd, &fp, &fvp);
+ if (error) {
+ return error;
+ }
+ if ((error = vnode_getwithref(fvp))) {
+ file_drop(fd);
+ return error;
+ }
+ nd.ni_vp = fvp;
+ } else {
+ error = nameiat(&nd, fd);
+ if (error) {
+ return error;
+ }
+ }
+ fsec = KAUTH_FILESEC_NONE;
+
+ statptr = (void *)&source;
+
+#if NAMEDRSRCFORK
+ /* Grab reference on the shadow stream file vnode to
+ * force an inactive on release which will mark it
+ * for recycle.
+ */
+ if (vnode_isnamedstream(nd.ni_vp) &&
+ (nd.ni_vp->v_parent != NULLVP) &&
+ vnode_isshadow(nd.ni_vp)) {
+ is_namedstream = 1;
+ vnode_ref(nd.ni_vp);
+ }
+#endif
+
+ needsrealdev = flag & AT_REALDEV ? 1 : 0;
+ if (fp && (xsecurity == USER_ADDR_NULL)) {
+ /*
+ * If the caller has the file open, and is not
+ * requesting extended security information, we are
+ * going to let them get the basic stat information.
+ */
+ error = vn_stat_noauth(nd.ni_vp, statptr, NULL, isstat64, needsrealdev, ctx,
+ fp->fp_glob->fg_cred);
+ } else {
+ error = vn_stat(nd.ni_vp, statptr, (xsecurity != USER_ADDR_NULL ? &fsec : NULL),
+ isstat64, needsrealdev, ctx);
+ }
+
+#if NAMEDRSRCFORK
+ if (is_namedstream) {
+ vnode_rele(nd.ni_vp);
+ }
+#endif
+ vnode_put(nd.ni_vp);
+ nameidone(&nd);
+ if (fp) {
+ file_drop(fd);
+ fp = NULL;
+ }
+
+ if (error) {
+ return error;
+ }
+ /* Zap spare fields */
+ if (isstat64 != 0) {
+ source.sb64.st_lspare = 0;
+ source.sb64.st_qspare[0] = 0LL;
+ source.sb64.st_qspare[1] = 0LL;
+ if (IS_64BIT_PROCESS(vfs_context_proc(ctx))) {
+ munge_user64_stat64(&source.sb64, &dest.user64_sb64);
+ my_size = sizeof(dest.user64_sb64);
+ sbp = (caddr_t)&dest.user64_sb64;
+ } else {
+ munge_user32_stat64(&source.sb64, &dest.user32_sb64);
+ my_size = sizeof(dest.user32_sb64);
+ sbp = (caddr_t)&dest.user32_sb64;
+ }
+ /*
+ * Check if we raced (post lookup) against the last unlink of a file.
+ */
+ if ((source.sb64.st_nlink == 0) && S_ISREG(source.sb64.st_mode)) {
+ source.sb64.st_nlink = 1;
+ }
+ } else {
+ source.sb.st_lspare = 0;
+ source.sb.st_qspare[0] = 0LL;
+ source.sb.st_qspare[1] = 0LL;
+ if (IS_64BIT_PROCESS(vfs_context_proc(ctx))) {
+ munge_user64_stat(&source.sb, &dest.user64_sb);
+ my_size = sizeof(dest.user64_sb);
+ sbp = (caddr_t)&dest.user64_sb;
+ } else {
+ munge_user32_stat(&source.sb, &dest.user32_sb);
+ my_size = sizeof(dest.user32_sb);
+ sbp = (caddr_t)&dest.user32_sb;
+ }
+
+ /*
+ * Check if we raced (post lookup) against the last unlink of a file.
+ */
+ if ((source.sb.st_nlink == 0) && S_ISREG(source.sb.st_mode)) {
+ source.sb.st_nlink = 1;
+ }
+ }
+ if ((error = copyout(sbp, ub, my_size)) != 0) {
+ goto out;
+ }
+
+ /* caller wants extended security information? */
+ if (xsecurity != USER_ADDR_NULL) {
+ /* did we get any? */
+ if (fsec == KAUTH_FILESEC_NONE) {
+ if (susize(xsecurity_size, 0) != 0) {
+ error = EFAULT;
+ goto out;
+ }
+ } else {
+ /* find the user buffer size */
+ xsecurity_bufsize = fusize(xsecurity_size);
+
+ /* copy out the actual data size */
+ if (susize(xsecurity_size, KAUTH_FILESEC_COPYSIZE(fsec)) != 0) {
+ error = EFAULT;
+ goto out;
+ }
+
+ /* if the caller supplied enough room, copy out to it */
+ if (xsecurity_bufsize >= KAUTH_FILESEC_COPYSIZE(fsec)) {
+ error = copyout(fsec, xsecurity, KAUTH_FILESEC_COPYSIZE(fsec));
+ }
+ }
+ }
+out:
+ if (fsec != KAUTH_FILESEC_NONE) {
+ kauth_filesec_free(fsec);
+ }
+ return error;
+}
+
+/*
+ * stat_extended: Get file status; with extended security (ACL).
+ *
+ * Parameters: p (ignored)
+ * uap User argument descriptor (see below)
+ * retval (ignored)
+ *
+ * Indirect: uap->path Path of file to get status from
+ * uap->ub User buffer (holds file status info)
+ * uap->xsecurity ACL to get (extended security)
+ * uap->xsecurity_size Size of ACL
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ */
+int
+stat_extended(__unused proc_t p, struct stat_extended_args *uap,
+ __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ uap->xsecurity, uap->xsecurity_size, 0, UIO_USERSPACE, AT_FDCWD,
+ 0);
+}
+
+/*
+ * Returns: 0 Success
+ * fstatat_internal:??? [see fstatat_internal() in this file]
+ */
+int
+stat(__unused proc_t p, struct stat_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ 0, 0, 0, UIO_USERSPACE, AT_FDCWD, 0);
+}
+
+int
+stat64(__unused proc_t p, struct stat64_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ 0, 0, 1, UIO_USERSPACE, AT_FDCWD, 0);
+}
+
+/*
+ * stat64_extended: Get file status; can handle large inode numbers; with extended security (ACL).
+ *
+ * Parameters: p (ignored)
+ * uap User argument descriptor (see below)
+ * retval (ignored)
+ *
+ * Indirect: uap->path Path of file to get status from
+ * uap->ub User buffer (holds file status info)
+ * uap->xsecurity ACL to get (extended security)
+ * uap->xsecurity_size Size of ACL
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ */
+int
+stat64_extended(__unused proc_t p, struct stat64_extended_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ uap->xsecurity, uap->xsecurity_size, 1, UIO_USERSPACE, AT_FDCWD,
+ 0);
+}
+
+/*
+ * lstat_extended: Get file status; does not follow links; with extended security (ACL).
+ *
+ * Parameters: p (ignored)
+ * uap User argument descriptor (see below)
+ * retval (ignored)
+ *
+ * Indirect: uap->path Path of file to get status from
+ * uap->ub User buffer (holds file status info)
+ * uap->xsecurity ACL to get (extended security)
+ * uap->xsecurity_size Size of ACL
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ */
+int
+lstat_extended(__unused proc_t p, struct lstat_extended_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ uap->xsecurity, uap->xsecurity_size, 0, UIO_USERSPACE, AT_FDCWD,
+ AT_SYMLINK_NOFOLLOW);
+}
+
+/*
+ * Get file status; this version does not follow links.
+ */
+int
+lstat(__unused proc_t p, struct lstat_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ 0, 0, 0, UIO_USERSPACE, AT_FDCWD, AT_SYMLINK_NOFOLLOW);
+}
+
+int
+lstat64(__unused proc_t p, struct lstat64_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ 0, 0, 1, UIO_USERSPACE, AT_FDCWD, AT_SYMLINK_NOFOLLOW);
+}
+
+/*
+ * lstat64_extended: Get file status; can handle large inode numbers; does not
+ * follow links; with extended security (ACL).
+ *
+ * Parameters: p (ignored)
+ * uap User argument descriptor (see below)
+ * retval (ignored)
+ *
+ * Indirect: uap->path Path of file to get status from
+ * uap->ub User buffer (holds file status info)
+ * uap->xsecurity ACL to get (extended security)
+ * uap->xsecurity_size Size of ACL
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ */
+int
+lstat64_extended(__unused proc_t p, struct lstat64_extended_args *uap, __unused int32_t *retval)
+{
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ uap->xsecurity, uap->xsecurity_size, 1, UIO_USERSPACE, AT_FDCWD,
+ AT_SYMLINK_NOFOLLOW);
+}
+
+int
+fstatat(__unused proc_t p, struct fstatat_args *uap, __unused int32_t *retval)
+{
+ if (uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_REALDEV | AT_FDONLY)) {
+ return EINVAL;
+ }
+
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ 0, 0, 0, UIO_USERSPACE, uap->fd, uap->flag);
+}
+
+int
+fstatat64(__unused proc_t p, struct fstatat64_args *uap,
+ __unused int32_t *retval)
+{
+ if (uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_REALDEV | AT_FDONLY)) {
+ return EINVAL;
+ }
+
+ return fstatat_internal(vfs_context_current(), uap->path, uap->ub,
+ 0, 0, 1, UIO_USERSPACE, uap->fd, uap->flag);
+}
+
+/*
+ * Get configurable pathname variables.
+ *
+ * Returns: 0 Success
+ * namei:???
+ * vn_pathconf:???
+ *
+ * Notes: Global implementation constants are intended to be
+ * implemented in this function directly; all other constants
+ * are per-FS implementation, and therefore must be handled in
+ * each respective FS, instead.
+ *
+ * XXX We implement some things globally right now that should actually be
+ * XXX per-FS; we will need to deal with this at some point.
+ */
+/* ARGSUSED */
+int
+pathconf(__unused proc_t p, struct pathconf_args *uap, int32_t *retval)
+{
+ int error;
+ struct nameidata nd;
+ vfs_context_t ctx = vfs_context_current();
+
+ NDINIT(&nd, LOOKUP, OP_PATHCONF, FOLLOW | AUDITVNPATH1,
+ UIO_USERSPACE, uap->path, ctx);
+ error = namei(&nd);
+ if (error) {
+ return error;
+ }
+
+ error = vn_pathconf(nd.ni_vp, uap->name, retval, ctx);
+
+ vnode_put(nd.ni_vp);
+ nameidone(&nd);
+ return error;
+}
+
+/*
+ * Return target name of a symbolic link.
+ */
+/* ARGSUSED */
+static int
+readlinkat_internal(vfs_context_t ctx, int fd, user_addr_t path,
+ enum uio_seg seg, user_addr_t buf, size_t bufsize, enum uio_seg bufseg,
+ int *retval)
+{
+ vnode_t vp;
+ uio_t auio;
+ int error;
+ struct nameidata nd;
+ char uio_buf[UIO_SIZEOF(1)];
+
+ if (bufsize > INT32_MAX) {
+ return EINVAL;
+ }
+
+ NDINIT(&nd, LOOKUP, OP_READLINK, NOFOLLOW | AUDITVNPATH1,
+ seg, path, ctx);
+
+ error = nameiat(&nd, fd);
+ if (error) {
+ return error;
+ }
+ vp = nd.ni_vp;
+
+ nameidone(&nd);
+
+ auio = uio_createwithbuffer(1, 0, bufseg, UIO_READ,
+ &uio_buf[0], sizeof(uio_buf));
+ uio_addiov(auio, buf, bufsize);
+ if (vp->v_type != VLNK) {
+ error = EINVAL;
+ } else {
+#if CONFIG_MACF
+ error = mac_vnode_check_readlink(ctx, vp);
+#endif
+ if (error == 0) {
+ error = vnode_authorize(vp, NULL, KAUTH_VNODE_READ_DATA,
+ ctx);
+ }
+ if (error == 0) {
+ error = VNOP_READLINK(vp, auio, ctx);
+ }
+ }
+ vnode_put(vp);
+
+ *retval = (int)(bufsize - uio_resid(auio));
+ return error;
+}
+
+int
+readlink(proc_t p, struct readlink_args *uap, int32_t *retval)
+{
+ enum uio_seg procseg;
+
+ procseg = IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32;
+ return readlinkat_internal(vfs_context_current(), AT_FDCWD,
+ CAST_USER_ADDR_T(uap->path), procseg, CAST_USER_ADDR_T(uap->buf),
+ uap->count, procseg, retval);
+}
+
+int
+readlinkat(proc_t p, struct readlinkat_args *uap, int32_t *retval)
+{
+ enum uio_seg procseg;
+
+ procseg = IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32;
+ return readlinkat_internal(vfs_context_current(), uap->fd, uap->path,
+ procseg, uap->buf, uap->bufsize, procseg, retval);
+}
+
+/*
+ * Change file flags, the deep inner layer.
+ */
+static int
+chflags0(vnode_t vp, struct vnode_attr *va,
+ int (*setattr)(vnode_t, void *, vfs_context_t),
+ void *arg, vfs_context_t ctx)
+{
+ kauth_action_t action = 0;
+ int error;
+
+#if CONFIG_MACF
+ error = mac_vnode_check_setflags(ctx, vp, va->va_flags);
+ if (error) {
+ goto out;
+ }
+#endif
+
+ /* request authorisation, disregard immutability */
+ if ((error = vnode_authattr(vp, va, &action, ctx)) != 0) {
+ goto out;
+ }
+ /*
+ * Request that the auth layer disregard those file flags it's allowed to when
+ * authorizing this operation; we need to do this in order to be able to
+ * clear immutable flags.
+ */
+ if (action && ((error = vnode_authorize(vp, NULL, action | KAUTH_VNODE_NOIMMUTABLE, ctx)) != 0)) {
+ goto out;
+ }
+ error = (*setattr)(vp, arg, ctx);
+
+#if CONFIG_MACF
+ if (error == 0) {
+ mac_vnode_notify_setflags(ctx, vp, va->va_flags);
+ }
+#endif
+
+out:
+ return error;
+}
+
+/*
+ * Change file flags.
+ *
+ * NOTE: this will vnode_put() `vp'
+ */
+static int
+chflags1(vnode_t vp, int flags, vfs_context_t ctx)
+{
+ struct vnode_attr va;
+ int error;
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_flags, flags);
+
+ error = chflags0(vp, &va, (void *)vnode_setattr, &va, ctx);
+ vnode_put(vp);
+
+ if ((error == 0) && !VATTR_IS_SUPPORTED(&va, va_flags)) {
+ error = ENOTSUP;
+ }
+
+ return error;
+}
+
+/*
+ * Change flags of a file given a path name.
+ */
+/* ARGSUSED */
+int
+chflags(__unused proc_t p, struct chflags_args *uap, __unused int32_t *retval)
+{
+ vnode_t vp;
+ vfs_context_t ctx = vfs_context_current();
+ int error;
+ struct nameidata nd;
+
+ AUDIT_ARG(fflags, uap->flags);
+ NDINIT(&nd, LOOKUP, OP_SETATTR, FOLLOW | AUDITVNPATH1,
+ UIO_USERSPACE, uap->path, ctx);
+ error = namei(&nd);
+ if (error) {
+ return error;
+ }
+ vp = nd.ni_vp;
+ nameidone(&nd);
+
+ /* we don't vnode_put() here because chflags1 does internally */
+ error = chflags1(vp, uap->flags, ctx);
+
+ return error;
+}
+
+/*
+ * Change flags of a file given a file descriptor.
+ */
+/* ARGSUSED */
+int
+fchflags(__unused proc_t p, struct fchflags_args *uap, __unused int32_t *retval)
+{
+ vnode_t vp;
+ int error;
+
+ AUDIT_ARG(fd, uap->fd);
+ AUDIT_ARG(fflags, uap->flags);
+ if ((error = file_vnode(uap->fd, &vp))) {
+ return error;
+ }
+
+ if ((error = vnode_getwithref(vp))) {
+ file_drop(uap->fd);
+ return error;
+ }
+
+ AUDIT_ARG(vnpath, vp, ARG_VNODE1);
+
+ /* we don't vnode_put() here because chflags1 does internally */
+ error = chflags1(vp, uap->flags, vfs_context_current());
+
+ file_drop(uap->fd);
+ return error;
+}
+
+/*
+ * Change security information on a filesystem object.
+ *
+ * Returns: 0 Success
+ * EPERM Operation not permitted
+ * vnode_authattr:??? [anything vnode_authattr can return]
+ * vnode_authorize:??? [anything vnode_authorize can return]
+ * vnode_setattr:??? [anything vnode_setattr can return]
+ *
+ * Notes: If vnode_authattr or vnode_authorize return EACCES, it will be
+ * translated to EPERM before being returned.
+ */
+static int
+chmod_vnode(vfs_context_t ctx, vnode_t vp, struct vnode_attr *vap)
+{
+ kauth_action_t action;
+ int error;
+
+ AUDIT_ARG(mode, vap->va_mode);
+ /* XXX audit new args */
+
+#if NAMEDSTREAMS
+ /* chmod calls are not allowed for resource forks. */
+ if (vp->v_flag & VISNAMEDSTREAM) {
+ return EPERM;
+ }
+#endif
+
+#if CONFIG_MACF
+ if (VATTR_IS_ACTIVE(vap, va_mode) &&
+ (error = mac_vnode_check_setmode(ctx, vp, (mode_t)vap->va_mode)) != 0) {
+ return error;
+ }
+
+ if (VATTR_IS_ACTIVE(vap, va_uid) || VATTR_IS_ACTIVE(vap, va_gid)) {
+ if ((error = mac_vnode_check_setowner(ctx, vp,
+ VATTR_IS_ACTIVE(vap, va_uid) ? vap->va_uid : -1,
+ VATTR_IS_ACTIVE(vap, va_gid) ? vap->va_gid : -1))) {
+ return error;
+ }
+ }
+
+ if (VATTR_IS_ACTIVE(vap, va_acl) &&
+ (error = mac_vnode_check_setacl(ctx, vp, vap->va_acl))) {
+ return error;
+ }
+#endif
+
+ /* make sure that the caller is allowed to set this security information */
+ if (((error = vnode_authattr(vp, vap, &action, ctx)) != 0) ||
+ ((error = vnode_authorize(vp, NULL, action, ctx)) != 0)) {
+ if (error == EACCES) {
+ error = EPERM;
+ }
+ return error;
+ }
+
+ if ((error = vnode_setattr(vp, vap, ctx)) != 0) {
+ return error;
+ }
+
+#if CONFIG_MACF
+ if (VATTR_IS_ACTIVE(vap, va_mode)) {
+ mac_vnode_notify_setmode(ctx, vp, (mode_t)vap->va_mode);
+ }
+
+ if (VATTR_IS_ACTIVE(vap, va_uid) || VATTR_IS_ACTIVE(vap, va_gid)) {
+ mac_vnode_notify_setowner(ctx, vp,
+ VATTR_IS_ACTIVE(vap, va_uid) ? vap->va_uid : -1,
+ VATTR_IS_ACTIVE(vap, va_gid) ? vap->va_gid : -1);
+ }
+
+ if (VATTR_IS_ACTIVE(vap, va_acl)) {
+ mac_vnode_notify_setacl(ctx, vp, vap->va_acl);
+ }
+#endif
+
+ return error;
+}
+
+
+/*
+ * Change mode of a file given a path name.
+ *
+ * Returns: 0 Success
+ * namei:??? [anything namei can return]
+ * chmod_vnode:??? [anything chmod_vnode can return]
+ */
+static int
+chmodat(vfs_context_t ctx, user_addr_t path, struct vnode_attr *vap,
+ int fd, int flag, enum uio_seg segflg)
+{
+ struct nameidata nd;
+ int follow, error;
+
+ follow = (flag & AT_SYMLINK_NOFOLLOW) ? NOFOLLOW : FOLLOW;
+ NDINIT(&nd, LOOKUP, OP_SETATTR, follow | AUDITVNPATH1,
+ segflg, path, ctx);
+ if ((error = nameiat(&nd, fd))) {
+ return error;
+ }
+ error = chmod_vnode(ctx, nd.ni_vp, vap);
+ vnode_put(nd.ni_vp);
+ nameidone(&nd);
+ return error;
+}
+
+/*
+ * chmod_extended: Change the mode of a file given a path name; with extended
+ * argument list (including extended security (ACL)).
+ *
+ * Parameters: p Process requesting the open
+ * uap User argument descriptor (see below)
+ * retval (ignored)
+ *
+ * Indirect: uap->path Path to object (same as 'chmod')
+ * uap->uid UID to set
+ * uap->gid GID to set
+ * uap->mode File mode to set (same as 'chmod')
+ * uap->xsecurity ACL to set (or delete)
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ * Notes: The kauth_filesec_t in 'va', if any, is in host byte order.
+ *
+ * XXX: We should enummerate the possible errno values here, and where
+ * in the code they originated.
+ */
+int
+chmod_extended(__unused proc_t p, struct chmod_extended_args *uap, __unused int32_t *retval)
+{
+ int error;
+ struct vnode_attr va;
+ kauth_filesec_t xsecdst;
+
+ AUDIT_ARG(owner, uap->uid, uap->gid);
+
+ VATTR_INIT(&va);
+ if (uap->mode != -1) {
+ VATTR_SET(&va, va_mode, uap->mode & ALLPERMS);
+ }
+ if (uap->uid != KAUTH_UID_NONE) {
+ VATTR_SET(&va, va_uid, uap->uid);
+ }
+ if (uap->gid != KAUTH_GID_NONE) {
+ VATTR_SET(&va, va_gid, uap->gid);
+ }
+
+ xsecdst = NULL;
+ switch (uap->xsecurity) {
+ /* explicit remove request */
+ case CAST_USER_ADDR_T((void *)1): /* _FILESEC_REMOVE_ACL */
+ VATTR_SET(&va, va_acl, NULL);
+ break;
+ /* not being set */
+ case USER_ADDR_NULL:
+ break;
+ default:
+ if ((error = kauth_copyinfilesec(uap->xsecurity, &xsecdst)) != 0) {
+ return error;
+ }
+ VATTR_SET(&va, va_acl, &xsecdst->fsec_acl);
+ KAUTH_DEBUG("CHMOD - setting ACL with %d entries", va.va_acl->acl_entrycount);
+ }
+
+ error = chmodat(vfs_context_current(), uap->path, &va, AT_FDCWD, 0,
+ UIO_USERSPACE);
+
+ if (xsecdst != NULL) {
+ kauth_filesec_free(xsecdst);
+ }
+ return error;
+}
+
+/*
+ * Returns: 0 Success
+ * chmodat:??? [anything chmodat can return]
+ */
+static int
+fchmodat_internal(vfs_context_t ctx, user_addr_t path, int mode, int fd,
+ int flag, enum uio_seg segflg)
+{
+ struct vnode_attr va;
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_mode, mode & ALLPERMS);
+
+ return chmodat(ctx, path, &va, fd, flag, segflg);
+}
+
+int
+chmod(__unused proc_t p, struct chmod_args *uap, __unused int32_t *retval)
+{
+ return fchmodat_internal(vfs_context_current(), uap->path, uap->mode,
+ AT_FDCWD, 0, UIO_USERSPACE);
+}
+
+int
+fchmodat(__unused proc_t p, struct fchmodat_args *uap, __unused int32_t *retval)
+{
+ if (uap->flag & ~AT_SYMLINK_NOFOLLOW) {
+ return EINVAL;
+ }
+
+ return fchmodat_internal(vfs_context_current(), uap->path, uap->mode,
+ uap->fd, uap->flag, UIO_USERSPACE);
+}
+
+/*
+ * Change mode of a file given a file descriptor.
+ */
+static int
+fchmod1(__unused proc_t p, int fd, struct vnode_attr *vap)
+{
+ vnode_t vp;
+ int error;
+
+ AUDIT_ARG(fd, fd);
+
+ if ((error = file_vnode(fd, &vp)) != 0) {
+ return error;
+ }
+ if ((error = vnode_getwithref(vp)) != 0) {
+ file_drop(fd);
+ return error;
+ }
+ AUDIT_ARG(vnpath, vp, ARG_VNODE1);
+
+ error = chmod_vnode(vfs_context_current(), vp, vap);
+ (void)vnode_put(vp);
+ file_drop(fd);
+
+ return error;
+}
+
+/*
+ * fchmod_extended: Change mode of a file given a file descriptor; with
+ * extended argument list (including extended security (ACL)).
+ *
+ * Parameters: p Process requesting to change file mode
+ * uap User argument descriptor (see below)
+ * retval (ignored)
+ *
+ * Indirect: uap->mode File mode to set (same as 'chmod')
+ * uap->uid UID to set
+ * uap->gid GID to set
+ * uap->xsecurity ACL to set (or delete)
+ * uap->fd File descriptor of file to change mode
+ *
+ * Returns: 0 Success
+ * !0 errno value
+ *
+ */
+int
+fchmod_extended(proc_t p, struct fchmod_extended_args *uap, __unused int32_t *retval)
+{
+ int error;
+ struct vnode_attr va;
+ kauth_filesec_t xsecdst;
+
+ AUDIT_ARG(owner, uap->uid, uap->gid);
+
+ VATTR_INIT(&va);
+ if (uap->mode != -1) {
+ VATTR_SET(&va, va_mode, uap->mode & ALLPERMS);
+ }
+ if (uap->uid != KAUTH_UID_NONE) {
+ VATTR_SET(&va, va_uid, uap->uid);
+ }
+ if (uap->gid != KAUTH_GID_NONE) {
+ VATTR_SET(&va, va_gid, uap->gid);
+ }
+
+ xsecdst = NULL;
+ switch (uap->xsecurity) {
+ case USER_ADDR_NULL:
+ VATTR_SET(&va, va_acl, NULL);
+ break;
+ case CAST_USER_ADDR_T((void *)1): /* _FILESEC_REMOVE_ACL */
+ VATTR_SET(&va, va_acl, NULL);
+ break;
+ /* not being set */
+ case CAST_USER_ADDR_T(-1):
+ break;
+ default:
+ if ((error = kauth_copyinfilesec(uap->xsecurity, &xsecdst)) != 0) {
+ return error;
+ }
+ VATTR_SET(&va, va_acl, &xsecdst->fsec_acl);
+ }
+
+ error = fchmod1(p, uap->fd, &va);
+
+
+ switch (uap->xsecurity) {
+ case USER_ADDR_NULL:
+ case CAST_USER_ADDR_T(-1):
+ break;
+ default:
+ if (xsecdst != NULL) {
+ kauth_filesec_free(xsecdst);
+ }
+ }
+ return error;
+}
+
+int
+fchmod(proc_t p, struct fchmod_args *uap, __unused int32_t *retval)
+{
+ struct vnode_attr va;
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_mode, uap->mode & ALLPERMS);
+
+ return fchmod1(p, uap->fd, &va);
+}
+
+
+/*
+ * Set ownership given a path name.
+ */
+/* ARGSUSED */
+static int
+fchownat_internal(vfs_context_t ctx, int fd, user_addr_t path, uid_t uid,
+ gid_t gid, int flag, enum uio_seg segflg)
+{
+ vnode_t vp;
+ struct vnode_attr va;
+ int error;
+ struct nameidata nd;
+ int follow;
+ kauth_action_t action;
+
+ AUDIT_ARG(owner, uid, gid);
+
+ follow = (flag & AT_SYMLINK_NOFOLLOW) ? NOFOLLOW : FOLLOW;
+ NDINIT(&nd, LOOKUP, OP_SETATTR, follow | AUDITVNPATH1, segflg,
+ path, ctx);
+ error = nameiat(&nd, fd);
+ if (error) {
+ return error;
+ }
+ vp = nd.ni_vp;
+
+ nameidone(&nd);
+
+ VATTR_INIT(&va);
+ if (uid != (uid_t)VNOVAL) {
+ VATTR_SET(&va, va_uid, uid);
+ }
+ if (gid != (gid_t)VNOVAL) {
+ VATTR_SET(&va, va_gid, gid);
+ }
+
+#if CONFIG_MACF
+ error = mac_vnode_check_setowner(ctx, vp, uid, gid);
+ if (error) {
+ goto out;
+ }
+#endif
+
+ /* preflight and authorize attribute changes */
+ if ((error = vnode_authattr(vp, &va, &action, ctx)) != 0) {
+ goto out;
+ }
+ if (action && ((error = vnode_authorize(vp, NULL, action, ctx)) != 0)) {
+ goto out;
+ }
+ error = vnode_setattr(vp, &va, ctx);
+
+#if CONFIG_MACF
+ if (error == 0) {
+ mac_vnode_notify_setowner(ctx, vp, uid, gid);
+ }
+#endif
+
+out:
+ /*
+ * EACCES is only allowed from namei(); permissions failure should
+ * return EPERM, so we need to translate the error code.
+ */
+ if (error == EACCES) {
+ error = EPERM;
+ }
+
+ vnode_put(vp);
+ return error;
+}
+
+int
+chown(__unused proc_t p, struct chown_args *uap, __unused int32_t *retval)
+{
+ return fchownat_internal(vfs_context_current(), AT_FDCWD, uap->path,
+ uap->uid, uap->gid, 0, UIO_USERSPACE);
+}
+
+int
+lchown(__unused proc_t p, struct lchown_args *uap, __unused int32_t *retval)
+{
+ return fchownat_internal(vfs_context_current(), AT_FDCWD, uap->path,
+ uap->owner, uap->group, AT_SYMLINK_NOFOLLOW, UIO_USERSPACE);
+}
+
+int
+fchownat(__unused proc_t p, struct fchownat_args *uap, __unused int32_t *retval)
+{
+ if (uap->flag & ~AT_SYMLINK_NOFOLLOW) {
+ return EINVAL;
+ }
+
+ return fchownat_internal(vfs_context_current(), uap->fd, uap->path,
+ uap->uid, uap->gid, uap->flag, UIO_USERSPACE);
+}
+
+/*
+ * Set ownership given a file descriptor.
+ */
+/* ARGSUSED */
+int
+fchown(__unused proc_t p, struct fchown_args *uap, __unused int32_t *retval)
+{
+ struct vnode_attr va;
+ vfs_context_t ctx = vfs_context_current();
+ vnode_t vp;
+ int error;
+ kauth_action_t action;
+
+ AUDIT_ARG(owner, uap->uid, uap->gid);
+ AUDIT_ARG(fd, uap->fd);
+
+ if ((error = file_vnode(uap->fd, &vp))) {
+ return error;
+ }
+
+ if ((error = vnode_getwithref(vp))) {
+ file_drop(uap->fd);
+ return error;
+ }
+ AUDIT_ARG(vnpath, vp, ARG_VNODE1);
+
+ VATTR_INIT(&va);
+ if (uap->uid != VNOVAL) {
+ VATTR_SET(&va, va_uid, uap->uid);
+ }
+ if (uap->gid != VNOVAL) {
+ VATTR_SET(&va, va_gid, uap->gid);
+ }
+
+#if NAMEDSTREAMS
+ /* chown calls are not allowed for resource forks. */
+ if (vp->v_flag & VISNAMEDSTREAM) {
+ error = EPERM;
+ goto out;
+ }
+#endif
+
+#if CONFIG_MACF
+ error = mac_vnode_check_setowner(ctx, vp, uap->uid, uap->gid);
+ if (error) {
+ goto out;
+ }
+#endif
+
+ /* preflight and authorize attribute changes */
+ if ((error = vnode_authattr(vp, &va, &action, ctx)) != 0) {
+ goto out;
+ }
+ if (action && ((error = vnode_authorize(vp, NULL, action, ctx)) != 0)) {
+ if (error == EACCES) {
+ error = EPERM;
+ }
+ goto out;
+ }
+ error = vnode_setattr(vp, &va, ctx);
+
+#if CONFIG_MACF
+ if (error == 0) {
+ mac_vnode_notify_setowner(ctx, vp, uap->uid, uap->gid);
+ }
+#endif
+
+out:
+ (void)vnode_put(vp);
+ file_drop(uap->fd);
+ return error;
+}
+
+static int
+getutimes(user_addr_t usrtvp, struct timespec *tsp)
+{
+ int error;
+
+ if (usrtvp == USER_ADDR_NULL) {
+ struct timeval old_tv;
+ /* XXX Y2038 bug because of microtime argument */
+ microtime(&old_tv);
+ TIMEVAL_TO_TIMESPEC(&old_tv, &tsp[0]);
+ tsp[1] = tsp[0];
+ } else {
+ if (IS_64BIT_PROCESS(current_proc())) {
+ struct user64_timeval tv[2];
+ error = copyin(usrtvp, (void *)tv, sizeof(tv));
+ if (error) {
+ return error;
+ }
+ TIMEVAL64_TO_TIMESPEC(&tv[0], &tsp[0]);
+ TIMEVAL64_TO_TIMESPEC(&tv[1], &tsp[1]);
+ } else {
+ struct user32_timeval tv[2];
+ error = copyin(usrtvp, (void *)tv, sizeof(tv));
+ if (error) {
+ return error;
+ }
+ TIMEVAL_TO_TIMESPEC(&tv[0], &tsp[0]);
+ TIMEVAL_TO_TIMESPEC(&tv[1], &tsp[1]);
+ }
+ }
+ return 0;
+}
+
+static int
+setutimes(vfs_context_t ctx, vnode_t vp, const struct timespec *ts,
+ int nullflag)
+{
+ int error;
+ struct vnode_attr va;
+ kauth_action_t action;
+
+ AUDIT_ARG(vnpath, vp, ARG_VNODE1);
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_access_time, ts[0]);
+ VATTR_SET(&va, va_modify_time, ts[1]);
+ if (nullflag) {
+ va.va_vaflags |= VA_UTIMES_NULL;
+ }
+
+#if NAMEDSTREAMS
+ /* utimes calls are not allowed for resource forks. */
+ if (vp->v_flag & VISNAMEDSTREAM) {
+ error = EPERM;
+ goto out;
+ }
+#endif
+
+#if CONFIG_MACF
+ error = mac_vnode_check_setutimes(ctx, vp, ts[0], ts[1]);
+ if (error) {
+ goto out;
+ }
+#endif
+ if ((error = vnode_authattr(vp, &va, &action, ctx)) != 0) {
+ if (!nullflag && error == EACCES) {
+ error = EPERM;
+ }
+ goto out;
+ }
+
+ /* since we may not need to auth anything, check here */
+ if ((action != 0) && ((error = vnode_authorize(vp, NULL, action, ctx)) != 0)) {
+ if (!nullflag && error == EACCES) {
+ error = EPERM;
+ }
+ goto out;
+ }
+ error = vnode_setattr(vp, &va, ctx);
+
+#if CONFIG_MACF
+ if (error == 0) {
+ mac_vnode_notify_setutimes(ctx, vp, ts[0], ts[1]);
+ }
+#endif
+
+out:
+ return error;
+}
+
+/*
+ * Set the access and modification times of a file.
+ */
+/* ARGSUSED */
+int
+utimes(__unused proc_t p, struct utimes_args *uap, __unused int32_t *retval)
+{
+ struct timespec ts[2];
+ user_addr_t usrtvp;
+ int error;
+ struct nameidata nd;
+ vfs_context_t ctx = vfs_context_current();
+
+ /*
+ * AUDIT: Needed to change the order of operations to do the
+ * name lookup first because auditing wants the path.
+ */
+ NDINIT(&nd, LOOKUP, OP_SETATTR, FOLLOW | AUDITVNPATH1,
+ UIO_USERSPACE, uap->path, ctx);
+ error = namei(&nd);
+ if (error) {
+ return error;
+ }
+ nameidone(&nd);
+
+ /*
+ * Fetch the user-supplied time. If usrtvp is USER_ADDR_NULL, we fetch
+ * the current time instead.
+ */
+ usrtvp = uap->tptr;
+ if ((error = getutimes(usrtvp, ts)) != 0) {
+ goto out;
+ }
+
+ error = setutimes(ctx, nd.ni_vp, ts, usrtvp == USER_ADDR_NULL);
+
+out:
+ vnode_put(nd.ni_vp);
+ return error;
+}
+
+/*
+ * Set the access and modification times of a file.
+ */
+/* ARGSUSED */
+int
+futimes(__unused proc_t p, struct futimes_args *uap, __unused int32_t *retval)
+{
+ struct timespec ts[2];
+ vnode_t vp;
+ user_addr_t usrtvp;
+ int error;
+
+ AUDIT_ARG(fd, uap->fd);
+ usrtvp = uap->tptr;
+ if ((error = getutimes(usrtvp, ts)) != 0) {
+ return error;
+ }
+ if ((error = file_vnode(uap->fd, &vp)) != 0) {
+ return error;
+ }
+ if ((error = vnode_getwithref(vp))) {
+ file_drop(uap->fd);
+ return error;
+ }
+
+ error = setutimes(vfs_context_current(), vp, ts, usrtvp == 0);
+ vnode_put(vp);
+ file_drop(uap->fd);
+ return error;
+}
+
+/*
+ * Truncate a file given its path name.
+ */
+/* ARGSUSED */
+int
+truncate(proc_t p, struct truncate_args *uap, __unused int32_t *retval)
+{
+ vnode_t vp;
+ struct vnode_attr va;
+ vfs_context_t ctx = vfs_context_current();
+ int error;
+ struct nameidata nd;
+ kauth_action_t action;
+ rlim_t fsize_limit;
+
+ if (uap->length < 0) {
+ return EINVAL;
+ }
+
+ fsize_limit = proc_limitgetcur(p, RLIMIT_FSIZE, TRUE);
+ if ((rlim_t)uap->length > fsize_limit) {
+ psignal(p, SIGXFSZ);
+ return EFBIG;
+ }
+
+ NDINIT(&nd, LOOKUP, OP_TRUNCATE, FOLLOW | AUDITVNPATH1,
+ UIO_USERSPACE, uap->path, ctx);
+ if ((error = namei(&nd))) {
+ return error;
+ }
+ vp = nd.ni_vp;
+
+ nameidone(&nd);
+
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_data_size, uap->length);
+
+#if CONFIG_MACF
+ error = mac_vnode_check_truncate(ctx, NOCRED, vp);
+ if (error) {
+ goto out;
+ }
+#endif
+
+ if ((error = vnode_authattr(vp, &va, &action, ctx)) != 0) {
+ goto out;
+ }
+ if ((action != 0) && ((error = vnode_authorize(vp, NULL, action, ctx)) != 0)) {
+ goto out;
+ }
+ error = vnode_setattr(vp, &va, ctx);
+
+#if CONFIG_MACF
+ if (error == 0) {
+ mac_vnode_notify_truncate(ctx, NOCRED, vp);
+ }
+#endif
+
+out:
+ vnode_put(vp);
+ return error;
+}
+
+/*
+ * Truncate a file given a file descriptor.
+ */
+/* ARGSUSED */
+int
+ftruncate(proc_t p, struct ftruncate_args *uap, int32_t *retval)
+{
+ vfs_context_t ctx = vfs_context_current();
+ struct vnode_attr va;
+ vnode_t vp;
+ struct fileproc *fp;
+ int error;
+ int fd = uap->fd;
+ rlim_t fsize_limit;
+
+ AUDIT_ARG(fd, uap->fd);
+ if (uap->length < 0) {
+ return EINVAL;
+ }
+
+ fsize_limit = proc_limitgetcur(p, RLIMIT_FSIZE, TRUE);
+ if ((rlim_t)uap->length > fsize_limit) {
+ psignal(p, SIGXFSZ);
+ return EFBIG;
+ }
+
+ if ((error = fp_lookup(p, fd, &fp, 0))) {
+ return error;
+ }
+
+ switch (FILEGLOB_DTYPE(fp->fp_glob)) {
+ case DTYPE_PSXSHM:
+ error = pshm_truncate(p, fp, uap->fd, uap->length, retval);
+ goto out;
+ case DTYPE_VNODE:
+ break;
+ default:
+ error = EINVAL;
+ goto out;
+ }
+
+ vp = (vnode_t)fp->fp_glob->fg_data;
+
+ if ((fp->fp_glob->fg_flag & FWRITE) == 0) {
+ AUDIT_ARG(vnpath_withref, vp, ARG_VNODE1);
+ error = EINVAL;
+ goto out;
+ }
+
+ if ((error = vnode_getwithref(vp)) != 0) {
+ goto out;
+ }
+
+ AUDIT_ARG(vnpath, vp, ARG_VNODE1);
+
+#if CONFIG_MACF
+ error = mac_vnode_check_truncate(ctx,
+ fp->fp_glob->fg_cred, vp);
+ if (error) {
+ (void)vnode_put(vp);
+ goto out;
+ }
+#endif
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_data_size, uap->length);
+ error = vnode_setattr(vp, &va, ctx);
+
+#if CONFIG_MACF
+ if (error == 0) {
+ mac_vnode_notify_truncate(ctx, fp->fp_glob->fg_cred, vp);
+ }
+#endif
+
+ (void)vnode_put(vp);
+out:
+ file_drop(fd);
+ return error;
+}
+
+
+/*
+ * Sync an open file with synchronized I/O _file_ integrity completion
+ */
+/* ARGSUSED */
+int
+fsync(proc_t p, struct fsync_args *uap, __unused int32_t *retval)
+{
+ __pthread_testcancel(1);
+ return fsync_common(p, uap, MNT_WAIT);
+}
+
+
+/*
+ * Sync an open file with synchronized I/O _file_ integrity completion
+ *
+ * Notes: This is a legacy support function that does not test for
+ * thread cancellation points.
+ */
+/* ARGSUSED */
+int
+fsync_nocancel(proc_t p, struct fsync_nocancel_args *uap, __unused int32_t *retval)
+{
+ return fsync_common(p, (struct fsync_args *)uap, MNT_WAIT);
+}
+
+
+/*
+ * Sync an open file with synchronized I/O _data_ integrity completion
+ */
+/* ARGSUSED */
+int
+fdatasync(proc_t p, struct fdatasync_args *uap, __unused int32_t *retval)
+{
+ __pthread_testcancel(1);
+ return fsync_common(p, (struct fsync_args *)uap, MNT_DWAIT);
+}
+
+
+/*
+ * fsync_common
+ *
+ * Common fsync code to support both synchronized I/O file integrity completion
+ * (normal fsync) and synchronized I/O data integrity completion (fdatasync).
+ *
+ * If 'flags' is MNT_DWAIT, the caller is requesting data integrity, which
+ * will only guarantee that the file data contents are retrievable. If
+ * 'flags' is MNT_WAIT, the caller is rewuesting file integrity, which also
+ * includes additional metadata unnecessary for retrieving the file data
+ * contents, such as atime, mtime, ctime, etc., also be committed to stable
+ * storage.
+ *
+ * Parameters: p The process
+ * uap->fd The descriptor to synchronize
+ * flags The data integrity flags
+ *
+ * Returns: int Success
+ * fp_getfvp:EBADF Bad file descriptor
+ * fp_getfvp:ENOTSUP fd does not refer to a vnode
+ * VNOP_FSYNC:??? unspecified
+ *
+ * Notes: We use struct fsync_args because it is a short name, and all
+ * caller argument structures are otherwise identical.
+ */
+static int
+fsync_common(proc_t p, struct fsync_args *uap, int flags)
+{
+ vnode_t vp;
+ struct fileproc *fp;
+ vfs_context_t ctx = vfs_context_current();
+ int error;
+
+ AUDIT_ARG(fd, uap->fd);
+
+ if ((error = fp_getfvp(p, uap->fd, &fp, &vp))) {
+ return error;
+ }
+ if ((error = vnode_getwithref(vp))) {
+ file_drop(uap->fd);
+ return error;
+ }
+
+ AUDIT_ARG(vnpath, vp, ARG_VNODE1);
+
+ error = VNOP_FSYNC(vp, flags, ctx);
+
+#if NAMEDRSRCFORK
+ /* Sync resource fork shadow file if necessary. */
+ if ((error == 0) &&
+ (vp->v_flag & VISNAMEDSTREAM) &&
+ (vp->v_parent != NULLVP) &&
+ vnode_isshadow(vp) &&
+ (fp->fp_glob->fg_flag & FWASWRITTEN)) {
+ (void) vnode_flushnamedstream(vp->v_parent, vp, ctx);
+ }
+#endif
+
+ (void)vnode_put(vp);
+ file_drop(uap->fd);
+ return error;
+}
+
+/*
+ * Duplicate files. Source must be a file, target must be a file or
+ * must not exist.
+ *
+ * XXX Copyfile authorisation checking is woefully inadequate, and will not
+ * perform inheritance correctly.
+ */
+/* ARGSUSED */
+int
+copyfile(__unused proc_t p, struct copyfile_args *uap, __unused int32_t *retval)
+{
+ vnode_t tvp, fvp, tdvp, sdvp;
+ struct nameidata fromnd, tond;
+ int error;
+ vfs_context_t ctx = vfs_context_current();
+#if CONFIG_MACF
+ struct filedesc *fdp = (vfs_context_proc(ctx))->p_fd;
+ struct vnode_attr va;
+#endif
+
+ /* Check that the flags are valid. */
+
+ if (uap->flags & ~CPF_MASK) {
+ return EINVAL;
+ }
+
+ NDINIT(&fromnd, LOOKUP, OP_COPYFILE, AUDITVNPATH1,
+ UIO_USERSPACE, uap->from, ctx);
+ if ((error = namei(&fromnd))) {
+ return error;
+ }
+ fvp = fromnd.ni_vp;
+
+ NDINIT(&tond, CREATE, OP_LINK,
+ LOCKPARENT | LOCKLEAF | NOCACHE | SAVESTART | AUDITVNPATH2 | CN_NBMOUNTLOOK,
+ UIO_USERSPACE, uap->to, ctx);
+ if ((error = namei(&tond))) {
+ goto out1;
+ }
+ tdvp = tond.ni_dvp;
+ tvp = tond.ni_vp;
+
+ if (tvp != NULL) {
+ if (!(uap->flags & CPF_OVERWRITE)) {
+ error = EEXIST;
+ goto out;
+ }
+ }
+
+ if (fvp->v_type == VDIR || (tvp && tvp->v_type == VDIR)) {
+ error = EISDIR;
+ goto out;
+ }
+
+ /* This calls existing MAC hooks for open */
+ if ((error = vn_authorize_open_existing(fvp, &fromnd.ni_cnd, FREAD, ctx,
+ NULL))) {
+ goto out;
+ }
+
+ if (tvp) {
+ /*
+ * See unlinkat_internal for an explanation of the potential
+ * ENOENT from the MAC hook but the gist is that the MAC hook
+ * can fail because vn_getpath isn't able to return the full
+ * path. We choose to ignore this failure.
+ */
+ error = vn_authorize_unlink(tdvp, tvp, &tond.ni_cnd, ctx, NULL);
+ if (error && error != ENOENT) {
+ goto out;
+ }
+ error = 0;
+ }
+
+#if CONFIG_MACF
+ VATTR_INIT(&va);
+ VATTR_SET(&va, va_type, fvp->v_type);
+ /* Mask off all but regular access permissions */
+ VATTR_SET(&va, va_mode,
+ ((((uap->mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT) & ACCESSPERMS));
+ error = mac_vnode_check_create(ctx, tdvp, &tond.ni_cnd, &va);
+ if (error) {
+ goto out;
+ }
+#endif /* CONFIG_MACF */
+
+ if ((error = vnode_authorize(tdvp, NULL, KAUTH_VNODE_ADD_FILE, ctx)) != 0) {
+ goto out;
+ }
+
+ if (fvp == tdvp) {
+ error = EINVAL;
+ }
+ /*
+ * If source is the same as the destination (that is the
+ * same inode number) then there is nothing to do.
+ * (fixed to have POSIX semantics - CSM 3/2/98)
+ */
+ if (fvp == tvp) {
+ error = -1;
+ }
+ if (!error) {
+ error = VNOP_COPYFILE(fvp, tdvp, tvp, &tond.ni_cnd, uap->mode, uap->flags, ctx);
+ }
+out:
+ sdvp = tond.ni_startdir;
+ /*
+ * nameidone has to happen before we vnode_put(tdvp)
+ * since it may need to release the fs_nodelock on the tdvp
+ */
+ nameidone(&tond);
+
+ if (tvp) {
+ vnode_put(tvp);
+ }
+ vnode_put(tdvp);
+ vnode_put(sdvp);
+out1:
+ vnode_put(fvp);
+
+ nameidone(&fromnd);
+
+ if (error == -1) {
+ return 0;
+ }
+ return error;
+}
+
+#define CLONE_SNAPSHOT_FALLBACKS_ENABLED 1
+
+/*
+ * Helper function for doing clones. The caller is expected to provide an
+ * iocounted source vnode and release it.
+ */
+static int
+clonefile_internal(vnode_t fvp, boolean_t data_read_authorised, int dst_dirfd,
+ user_addr_t dst, uint32_t flags, vfs_context_t ctx)
+{
+ vnode_t tvp, tdvp;
+ struct nameidata tond;
+ int error;
+ int follow;
+ boolean_t free_src_acl;
+ boolean_t attr_cleanup;
+ enum vtype v_type;
+ kauth_action_t action;
+ struct componentname *cnp;
+ uint32_t defaulted;
+ struct vnode_attr va;
+ struct vnode_attr nva;
+ uint32_t vnop_flags;
+
+ v_type = vnode_vtype(fvp);
+ switch (v_type) {
+ case VLNK:
+ /* FALLTHRU */
+ case VREG:
+ action = KAUTH_VNODE_ADD_FILE;
+ break;
+ case VDIR:
+ if (vnode_isvroot(fvp) || vnode_ismount(fvp) ||
+ fvp->v_mountedhere) {
+ return EINVAL;
+ }
+ action = KAUTH_VNODE_ADD_SUBDIRECTORY;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ AUDIT_ARG(fd2, dst_dirfd);
+ AUDIT_ARG(value32, flags);
+
+ follow = (flags & CLONE_NOFOLLOW) ? NOFOLLOW : FOLLOW;
+ NDINIT(&tond, CREATE, OP_LINK, follow | WANTPARENT | AUDITVNPATH2,
+ UIO_USERSPACE, dst, ctx);
+ if ((error = nameiat(&tond, dst_dirfd))) {
+ return error;
+ }
+ cnp = &tond.ni_cnd;
+ tdvp = tond.ni_dvp;
+ tvp = tond.ni_vp;
+
+ free_src_acl = FALSE;
+ attr_cleanup = FALSE;
+
+ if (tvp != NULL) {
+ error = EEXIST;
+ goto out;
+ }
+
+ if (vnode_mount(tdvp) != vnode_mount(fvp)) {
+ error = EXDEV;
+ goto out;
+ }
+
+#if CONFIG_MACF
+ if ((error = mac_vnode_check_clone(ctx, tdvp, fvp, cnp))) {
+ goto out;
+ }
+#endif
+ if ((error = vnode_authorize(tdvp, NULL, action, ctx))) {
+ goto out;
+ }
+
+ action = KAUTH_VNODE_GENERIC_READ_BITS;
+ if (data_read_authorised) {
+ action &= ~KAUTH_VNODE_READ_DATA;
+ }
+ if ((error = vnode_authorize(fvp, NULL, action, ctx))) {
+ goto out;
+ }
+
+ /*
+ * certain attributes may need to be changed from the source, we ask for
+ * those here.
+ */
+ VATTR_INIT(&va);
+ VATTR_WANTED(&va, va_uid);
+ VATTR_WANTED(&va, va_gid);
+ VATTR_WANTED(&va, va_mode);
+ VATTR_WANTED(&va, va_flags);
+ VATTR_WANTED(&va, va_acl);
+
+ if ((error = vnode_getattr(fvp, &va, ctx)) != 0) {
+ goto out;
+ }
+
+ VATTR_INIT(&nva);
+ VATTR_SET(&nva, va_type, v_type);
+ if (VATTR_IS_SUPPORTED(&va, va_acl) && va.va_acl != NULL) {
+ VATTR_SET(&nva, va_acl, va.va_acl);
+ free_src_acl = TRUE;
+ }
+
+ /* Handle ACL inheritance, initialize vap. */
+ if (v_type == VLNK) {
+ error = vnode_authattr_new(tdvp, &nva, 0, ctx);
+ } else {
+ error = vn_attribute_prepare(tdvp, &nva, &defaulted, ctx);
+ if (error) {
+ goto out;
+ }
+ attr_cleanup = TRUE;
+ }
+
+ vnop_flags = VNODE_CLONEFILE_DEFAULT;
+ /*
+ * We've got initial values for all security parameters,
+ * If we are superuser, then we can change owners to be the
+ * same as the source. Both superuser and the owner have default
+ * WRITE_SECURITY privileges so all other fields can be taken
+ * from source as well.
+ */
+ if (!(flags & CLONE_NOOWNERCOPY) && vfs_context_issuser(ctx)) {
+ if (VATTR_IS_SUPPORTED(&va, va_uid)) {
+ VATTR_SET(&nva, va_uid, va.va_uid);
+ }
+ if (VATTR_IS_SUPPORTED(&va, va_gid)) {
+ VATTR_SET(&nva, va_gid, va.va_gid);
+ }
+ } else {
+ vnop_flags |= VNODE_CLONEFILE_NOOWNERCOPY;
+ }
+
+ if (VATTR_IS_SUPPORTED(&va, va_mode)) {
+ VATTR_SET(&nva, va_mode, va.va_mode);
+ }
+ if (VATTR_IS_SUPPORTED(&va, va_flags)) {
+ VATTR_SET(&nva, va_flags,
+ ((va.va_flags & ~(UF_DATAVAULT | SF_RESTRICTED)) | /* Turn off from source */
+ (nva.va_flags & (UF_DATAVAULT | SF_RESTRICTED))));
+ }
+
+ error = VNOP_CLONEFILE(fvp, tdvp, &tvp, cnp, &nva, vnop_flags, ctx);
+
+ if (!error && tvp) {
+ int update_flags = 0;
+#if CONFIG_FSE
+ int fsevent;
+#endif /* CONFIG_FSE */
+
+ /*
+ * If some of the requested attributes weren't handled by the
+ * VNOP, use our fallback code.
+ */
+ if (!VATTR_ALL_SUPPORTED(&va)) {
+ (void)vnode_setattr_fallback(tvp, &nva, ctx);
+ }
+
+#if CONFIG_MACF
+ (void)vnode_label(vnode_mount(tvp), tdvp, tvp, cnp,
+ VNODE_LABEL_CREATE, ctx);
+#endif
+
+ // Make sure the name & parent pointers are hooked up
+ if (tvp->v_name == NULL) {
+ update_flags |= VNODE_UPDATE_NAME;
+ }
+ if (tvp->v_parent == NULLVP) {
+ update_flags |= VNODE_UPDATE_PARENT;
+ }
+
+ if (update_flags) {
+ (void)vnode_update_identity(tvp, tdvp, cnp->cn_nameptr,
+ cnp->cn_namelen, cnp->cn_hash, update_flags);
+ }
+
+#if CONFIG_FSE
+ switch (vnode_vtype(tvp)) {
+ case VLNK:
+ /* FALLTHRU */
+ case VREG:
+ fsevent = FSE_CREATE_FILE;
+ break;
+ case VDIR:
+ fsevent = FSE_CREATE_DIR;
+ break;
+ default:
+ goto out;
+ }
+
+ if (need_fsevent(fsevent, tvp)) {
+ /*
+ * The following is a sequence of three explicit events.
+ * A pair of FSE_CLONE events representing the source and destination
+ * followed by an FSE_CREATE_[FILE | DIR] for the destination.
+ * fseventsd may coalesce the destination clone and create events
+ * into a single event resulting in the following sequence for a client
+ * FSE_CLONE (src)
+ * FSE_CLONE | FSE_CREATE (dst)
+ */
+ add_fsevent(FSE_CLONE, ctx, FSE_ARG_VNODE, fvp, FSE_ARG_VNODE, tvp,
+ FSE_ARG_DONE);
+ add_fsevent(fsevent, ctx, FSE_ARG_VNODE, tvp,
+ FSE_ARG_DONE);
+ }
+#endif /* CONFIG_FSE */
+ }
+
+out:
+ if (attr_cleanup) {
+ vn_attribute_cleanup(&nva, defaulted);
+ }
+ if (free_src_acl && va.va_acl) {
+ kauth_acl_free(va.va_acl);
+ }
+ nameidone(&tond);
+ if (tvp) {
+ vnode_put(tvp);
+ }
+ vnode_put(tdvp);
+ return error;
+}
+
+/*
+ * clone files or directories, target must not exist.
+ */
+/* ARGSUSED */
+int
+clonefileat(__unused proc_t p, struct clonefileat_args *uap,
+ __unused int32_t *retval)
+{
+ vnode_t fvp;
+ struct nameidata fromnd;
+ int follow;
+ int error;
+ vfs_context_t ctx = vfs_context_current();
+
+ /* Check that the flags are valid. */
+ if (uap->flags & ~(CLONE_NOFOLLOW | CLONE_NOOWNERCOPY)) {
+ return EINVAL;
+ }
+
+ AUDIT_ARG(fd, uap->src_dirfd);
+
+ follow = (uap->flags & CLONE_NOFOLLOW) ? NOFOLLOW : FOLLOW;
+ NDINIT(&fromnd, LOOKUP, OP_COPYFILE, follow | AUDITVNPATH1,
+ UIO_USERSPACE, uap->src, ctx);
+ if ((error = nameiat(&fromnd, uap->src_dirfd))) {
+ return error;
+ }
+
+ fvp = fromnd.ni_vp;
+ nameidone(&fromnd);
+
+ error = clonefile_internal(fvp, FALSE, uap->dst_dirfd, uap->dst,
+ uap->flags, ctx);
+
+ vnode_put(fvp);
+ return error;
+}
+
+int
+fclonefileat(__unused proc_t p, struct fclonefileat_args *uap,
+ __unused int32_t *retval)
+{
+ vnode_t fvp;
+ struct fileproc *fp;
+ int error;
+ vfs_context_t ctx = vfs_context_current();
+
+ /* Check that the flags are valid. */
+ if (uap->flags & ~(CLONE_NOFOLLOW | CLONE_NOOWNERCOPY)) {
+ return EINVAL;
+ }
+
+ AUDIT_ARG(fd, uap->src_fd);
+ error = fp_getfvp(p, uap->src_fd, &fp, &fvp);
+ if (error) {
+ return error;
+ }
+
+ if ((fp->fp_glob->fg_flag & FREAD) == 0) {
+ AUDIT_ARG(vnpath_withref, fvp, ARG_VNODE1);
+ error = EBADF;
+ goto out;
+ }
+
+ if ((error = vnode_getwithref(fvp))) {
+ goto out;
+ }
+
+ AUDIT_ARG(vnpath, fvp, ARG_VNODE1);
+
+ error = clonefile_internal(fvp, TRUE, uap->dst_dirfd, uap->dst,
+ uap->flags, ctx);
+
+ vnode_put(fvp);
+out:
+ file_drop(uap->src_fd);
+ return error;
+}
+
+static int
+rename_submounts_callback(mount_t mp, void *arg)
+{
+ int error = 0;
+ mount_t pmp = (mount_t)arg;
+ int prefix_len = (int)strlen(pmp->mnt_vfsstat.f_mntonname);
+
+ if (strncmp(mp->mnt_vfsstat.f_mntonname, pmp->mnt_vfsstat.f_mntonname, prefix_len) != 0) {
+ return 0;
+ }
+
+ if (mp->mnt_vfsstat.f_mntonname[prefix_len] != '/') {
+ return 0;
+ }
+
+ if ((error = vfs_busy(mp, LK_NOWAIT))) {
+ printf("vfs_busy failed with %d for %s\n", error, mp->mnt_vfsstat.f_mntonname);
+ return -1;
+ }
+
+ int pathlen = MAXPATHLEN;
+ if ((error = vn_getpath_ext(mp->mnt_vnodecovered, NULL, mp->mnt_vfsstat.f_mntonname, &pathlen, VN_GETPATH_FSENTER))) {
+ printf("vn_getpath_ext failed with %d for mnt_vnodecovered of %s\n", error, mp->mnt_vfsstat.f_mntonname);
+ }
+
+ vfs_unbusy(mp);
+
+ return error;
+}
+
+/*
+ * Rename files. Source and destination must either both be directories,
+ * or both not be directories. If target is a directory, it must be empty.
+ */
+/* ARGSUSED */
+static int
+renameat_internal(vfs_context_t ctx, int fromfd, user_addr_t from,
+ int tofd, user_addr_t to, int segflg, vfs_rename_flags_t flags)
+{
+ if (flags & ~VFS_RENAME_FLAGS_MASK) {
+ return EINVAL;
+ }
+
+ if (ISSET(flags, VFS_RENAME_SWAP) && ISSET(flags, VFS_RENAME_EXCL)) {
+ return EINVAL;
+ }
+
+ vnode_t tvp, tdvp;
+ vnode_t fvp, fdvp;
+ vnode_t mnt_fvp;
+ struct nameidata *fromnd, *tond;
+ int error;
+ int do_retry;
+ int retry_count;
+ int mntrename;
+ int need_event;
+ int need_kpath2;
+ int has_listeners;
+ const char *oname = NULL;
+ char *from_name = NULL, *to_name = NULL;
+ char *from_name_no_firmlink = NULL, *to_name_no_firmlink = NULL;
+ int from_len = 0, to_len = 0;
+ int from_len_no_firmlink = 0, to_len_no_firmlink = 0;
+ int holding_mntlock;
+ int vn_authorize_skipped;
+ mount_t locked_mp = NULL;
+ vnode_t oparent = NULLVP;
+#if CONFIG_FSE
+ fse_info from_finfo, to_finfo;
+#endif
+ int from_truncated = 0, to_truncated = 0;
+ int from_truncated_no_firmlink = 0, to_truncated_no_firmlink = 0;
+ int batched = 0;
+ struct vnode_attr *fvap, *tvap;
+ int continuing = 0;
+ /* carving out a chunk for structs that are too big to be on stack. */
+ struct {
+ struct nameidata from_node, to_node;
+ struct vnode_attr fv_attr, tv_attr;
+ } * __rename_data;
+ __rename_data = kheap_alloc(KHEAP_TEMP, sizeof(*__rename_data), Z_WAITOK);
+ fromnd = &__rename_data->from_node;
+ tond = &__rename_data->to_node;
+
+ holding_mntlock = 0;
+ do_retry = 0;
+ retry_count = 0;
+retry:
+ fvp = tvp = NULL;
+ fdvp = tdvp = NULL;
+ fvap = tvap = NULL;
+ mnt_fvp = NULLVP;
+ mntrename = FALSE;
+ vn_authorize_skipped = FALSE;
+
+ NDINIT(fromnd, DELETE, OP_UNLINK, WANTPARENT | AUDITVNPATH1,
+ segflg, from, ctx);
+ fromnd->ni_flag = NAMEI_COMPOUNDRENAME;
+
+ NDINIT(tond, RENAME, OP_RENAME, WANTPARENT | AUDITVNPATH2 | CN_NBMOUNTLOOK,
+ segflg, to, ctx);
+ tond->ni_flag = NAMEI_COMPOUNDRENAME;
+
+continue_lookup:
+ if ((fromnd->ni_flag & NAMEI_CONTLOOKUP) != 0 || !continuing) {
+ if ((error = nameiat(fromnd, fromfd))) {
+ goto out1;
+ }
+ fdvp = fromnd->ni_dvp;
+ fvp = fromnd->ni_vp;
+
+ if (fvp && fvp->v_type == VDIR) {
+ tond->ni_cnd.cn_flags |= WILLBEDIR;
+ }
+ }
+
+ if ((tond->ni_flag & NAMEI_CONTLOOKUP) != 0 || !continuing) {
+ if ((error = nameiat(tond, tofd))) {
+ /*
+ * Translate error code for rename("dir1", "dir2/.").
+ */
+ if (error == EISDIR && fvp->v_type == VDIR) {
+ error = EINVAL;
+ }
+ goto out1;
+ }
+ tdvp = tond->ni_dvp;
+ tvp = tond->ni_vp;
+ }
+
+#if DEVELOPMENT || DEBUG
+ /*
+ * XXX VSWAP: Check for entitlements or special flag here
+ * so we can restrict access appropriately.
+ */
+#else /* DEVELOPMENT || DEBUG */
+
+ if (fromnd->ni_vp && vnode_isswap(fromnd->ni_vp) && (ctx != vfs_context_kernel())) {
+ error = EPERM;
+ goto out1;
+ }
+
+ if (tond->ni_vp && vnode_isswap(tond->ni_vp) && (ctx != vfs_context_kernel())) {
+ error = EPERM;
+ goto out1;
+ }
+#endif /* DEVELOPMENT || DEBUG */
+
+ if (!tvp && ISSET(flags, VFS_RENAME_SWAP)) {
+ error = ENOENT;
+ goto out1;
+ }
+
+ if (tvp && ISSET(flags, VFS_RENAME_EXCL)) {
+ int32_t pval = 0;
+ int err = 0;
+
+ /*
+ * We allow rename with VFS_RENAME_EXCL flag for an existing file which
+ * has the same name as target iff the following conditions are met:
+ * 1. the target file system is case insensitive
+ * 2. source and target directories are the same
+ * 3. source and target files are the same
+ * 4. name only differs in case (determined by underlying filesystem)
+ */
+ if (fvp != tvp || fdvp != tdvp) {
+ error = EEXIST;
+ goto out1;
+ }
+
+ /*
+ * Assume that the target file system is case sensitive if
+ * _PC_CASE_SENSITIVE selector isn't supported.
+ */
+ err = VNOP_PATHCONF(tvp, _PC_CASE_SENSITIVE, &pval, ctx);
+ if (err != 0 || pval != 0) {
+ error = EEXIST;
+ goto out1;
+ }
+ }
+
+ batched = vnode_compound_rename_available(fdvp);
+
+#if CONFIG_FSE
+ need_event = need_fsevent(FSE_RENAME, fdvp);
+ if (need_event) {
+ if (fvp) {
+ get_fse_info(fvp, &from_finfo, ctx);
+ } else {
+ error = vfs_get_notify_attributes(&__rename_data->fv_attr);
+ if (error) {
+ goto out1;
+ }
+
+ fvap = &__rename_data->fv_attr;
+ }
+
+ if (tvp) {
+ get_fse_info(tvp, &to_finfo, ctx);
+ } else if (batched) {
+ error = vfs_get_notify_attributes(&__rename_data->tv_attr);
+ if (error) {
+ goto out1;
+ }
+
+ tvap = &__rename_data->tv_attr;
+ }
+ }
+#else
+ need_event = 0;
+#endif /* CONFIG_FSE */
+
+ has_listeners = kauth_authorize_fileop_has_listeners();
+
+ need_kpath2 = 0;
+#if CONFIG_AUDIT
+ if (AUDIT_RECORD_EXISTS()) {
+ need_kpath2 = 1;
+ }
+#endif
+
+ if (need_event || has_listeners) {
+ if (from_name == NULL) {
+ GET_PATH(from_name);
+ }
+
+ from_len = safe_getpath(fdvp, fromnd->ni_cnd.cn_nameptr, from_name, MAXPATHLEN, &from_truncated);
+
+ if (from_name_no_firmlink == NULL) {
+ GET_PATH(from_name_no_firmlink);
+ }
+
+ from_len_no_firmlink = safe_getpath_no_firmlink(fdvp, fromnd->ni_cnd.cn_nameptr, from_name_no_firmlink, MAXPATHLEN, &from_truncated_no_firmlink);
+ }
+
+ if (need_event || need_kpath2 || has_listeners) {
+ if (to_name == NULL) {
+ GET_PATH(to_name);
+ }
+
+ to_len = safe_getpath(tdvp, tond->ni_cnd.cn_nameptr, to_name, MAXPATHLEN, &to_truncated);
+
+ if (to_name_no_firmlink == NULL) {
+ GET_PATH(to_name_no_firmlink);
+ }
+
+ to_len_no_firmlink = safe_getpath_no_firmlink(tdvp, tond->ni_cnd.cn_nameptr, to_name_no_firmlink, MAXPATHLEN, &to_truncated_no_firmlink);
+ if (to_name && need_kpath2) {
+ AUDIT_ARG(kpath, to_name, ARG_KPATH2);
+ }
+ }
+ if (!fvp) {
+ /*
+ * Claim: this check will never reject a valid rename.
+ * For success, either fvp must be on the same mount as tdvp, or fvp must sit atop a vnode on the same mount as tdvp.
+ * Suppose fdvp and tdvp are not on the same mount.
+ * If fvp is on the same mount as tdvp, then fvp is not on the same mount as fdvp, so fvp is the root of its filesystem. If fvp is the root,
+ * then you can't move it to within another dir on the same mountpoint.
+ * If fvp sits atop a vnode on the same mount as fdvp, then that vnode must be part of the same mount as fdvp, which is a contradiction.
+ *
+ * If this check passes, then we are safe to pass these vnodes to the same FS.
+ */
+ if (fdvp->v_mount != tdvp->v_mount) {
+ error = EXDEV;
+ goto out1;
+ }
+ goto skipped_lookup;
+ }
+
+ /*
+ * If the source and destination are the same (i.e. they're
+ * links to the same vnode) and the target file system is
+ * case sensitive, then there is nothing to do.
+ *
+ * XXX Come back to this.
+ */
+ if (fvp == tvp) {
+ int pathconf_val;
+
+ /*
+ * Note: if _PC_CASE_SENSITIVE selector isn't supported,
+ * then assume that this file system is case sensitive.
+ */
+ if (VNOP_PATHCONF(fvp, _PC_CASE_SENSITIVE, &pathconf_val, ctx) != 0 ||
+ pathconf_val != 0) {
+ vn_authorize_skipped = TRUE;
+ goto out1;
+ }
+ }
+
+ /*
+ * Allow the renaming of mount points.
+ * - target must not exist
+ * - target must reside in the same directory as source
+ * - union mounts cannot be renamed
+ * - the root fs, and tightly-linked system volumes, cannot be renamed
+ *
+ * XXX Handle this in VFS after a continued lookup (if we missed
+ * in the cache to start off)
+ *
+ * N.B. If RENAME_SWAP is being used, then @tvp != NULL and so
+ * we'll skip past here. The file system is responsible for
+ * checking that @tvp is not a descendent of @fvp and vice versa
+ * so it should always return EINVAL if either @tvp or @fvp is the
+ * root of a volume.
+ */
+ if ((fvp->v_flag & VROOT) &&
+ (fvp->v_type == VDIR) &&
+ (tvp == NULL) &&
+ (fvp->v_mountedhere == NULL) &&
+ (fdvp == tdvp) &&
+ ((fvp->v_mount->mnt_flag & (MNT_UNION | MNT_ROOTFS)) == 0) &&
+ ((fvp->v_mount->mnt_kern_flag & MNTK_SYSTEM) == 0) &&
+ (fvp->v_mount->mnt_vnodecovered != NULLVP)) {
+ vnode_t coveredvp;
+
+ /* switch fvp to the covered vnode */