+ 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);
+ 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) {
+ VATTR_SET(&va, va_dataprotect_class, class);
+ }
+
+ if (dpflags & O_DP_GETRAWENCRYPTED) {
+ if ( flags & (O_RDWR | O_WRONLY)) {
+ /* Not allowed to write raw encrypted bytes */
+ return EINVAL;
+ }
+ VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWENCRYPTED);
+ }
+
+ 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 vnode_attr va;
+ struct nameidata nd;
+ int cmode;
+
+ VATTR_INIT(&va);
+ /* Mask off all but regular access permissions */
+ cmode = ((mode &~ fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT;
+ VATTR_SET(&va, va_mode, cmode & ACCESSPERMS);
+
+ NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1,
+ segflg, path, ctx);
+
+ return (open1at(ctx, &nd, flags, &va, fileproc_alloc_init, NULL,
+ retval, fd));
+}
+
+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 = 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 {
+ MALLOC(buf, char *, buflen + 1, M_TEMP, M_WAITOK);
+ if (buf == NULL) {
+ return (ENOMEM);
+ }
+
+ error = fsgetpath_internal(
+ ctx, fsid.val[0], objid,
+ buflen, buf, &pathlen);
+
+ if (error) {
+ FREE(buf, M_TEMP);
+ 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);
+
+ FREE(buf, M_TEMP);
+
+ 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, 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_IFMT: /* used by badsect to flag bad sectors */
+ VATTR_SET(&va, va_type, VBAD);
+ break;
+ 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));
+}
+
+
+static char *
+my_strrchr(char *p, int ch)
+{
+ char *save;
+
+ for (save = NULL;; ++p) {
+ if (*p == ch)
+ save = p;
+ if (!*p)
+ return(save);
+ }
+ /* NOTREACHED */
+}
+
+extern int safe_getpath(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path);
+
+int
+safe_getpath(struct vnode *dvp, char *leafname, char *path, int _len, int *truncated_path)
+{
+ int ret, len = _len;
+
+ *truncated_path = 0;
+ ret = vn_getpath(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 = my_strrchr(path, '/');
+ if (ptr) {
+ *ptr = '\0'; // chop off the string at the last directory component
+ }
+ len = 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;
+ ret = vn_getpath(mydvp, path, &len);
+ } while (ret == ENOSPC);
+ }
+
+ return len;
+}
+
+
+/*
+ * 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, dvp, lvp;
+ struct nameidata nd;
+ int follow;
+ int error;
+#if CONFIG_FSE
+ fse_info finfo;
+#endif
+ int need_event, has_listeners;
+ 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 (!(vp->v_mount->mnt_vtable->vfc_vfsflags & VFC_VFSDIRLINKS)) {
+ 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();
+
+ if (need_event || has_listeners) {
+ char *link_to_path = NULL;
+ int len, link_name_len;
+
+ /* build the path to the new link file */
+ GET_PATH(target_path);
+ if (target_path == NULL) {
+ error = ENOMEM;
+ goto out2;
+ }
+
+ len = safe_getpath(dvp, nd.ni_cnd.cn_nameptr, target_path, MAXPATHLEN, &truncated);
+
+ if (has_listeners) {
+ /* build the path to file we are linking to */
+ GET_PATH(link_to_path);
+ if (link_to_path == NULL) {
+ error = ENOMEM;
+ goto out2;
+ }
+
+ 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);
+ }
+ if (vp->v_parent) {
+ add_fsevent(FSE_STAT_CHANGED, ctx,
+ FSE_ARG_VNODE, vp->v_parent,
+ FSE_ARG_DONE);
+ }
+ }
+#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;
+ uint32_t dfflags; // Directory file flags
+ size_t dummy=0;
+ proc_t p;
+
+ error = 0;
+ if (UIO_SEG_IS_USER_SPACE(segflg)) {
+ MALLOC_ZONE(path, char *, MAXPATHLEN, M_NAMEI, M_WAITOK);
+ 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);
+
+ /*
+ * Handle inheritance of restricted flag
+ */
+ error = vnode_flags(dvp, &dfflags, ctx);
+ if (error)
+ goto skipit;
+ if (dfflags & SF_RESTRICTED)
+ VATTR_SET(&va, va_flags, SF_RESTRICTED);
+
+#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);
+
+#if CONFIG_MACF
+ if (error == 0)
+ error = vnode_label(vnode_mount(vp), dvp, vp, &nd.ni_cnd, VNODE_LABEL_CREATE, ctx);
+#endif
+
+ /* do fallback attribute handling */
+ if (error == 0)
+ error = vnode_setattr_fallback(vp, &va, ctx);
+
+ if (error == 0) {
+ int update_flags = 0;
+
+ 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))
+ FREE_ZONE(path, MAXPATHLEN, M_NAMEI);
+
+ 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
+unlink1at(vfs_context_t ctx, struct nameidata *ndp, int unlink_flags, int fd)
+{
+ vnode_t vp, dvp;
+ int error;
+ struct componentname *cnp;
+ char *path = NULL;
+ int len=0;
+#if CONFIG_FSE
+ fse_info finfo;
+ struct vnode_attr va;
+#endif
+ int flags = 0;
+ int need_event = 0;
+ int has_listeners = 0;
+ int truncated_path=0;
+ int batched;
+ struct vnode_attr *vap = NULL;
+
+#if NAMEDRSRCFORK
+ /* unlink or delete is allowed on rsrc forks and named streams */
+ ndp->ni_cnd.cn_flags |= CN_ALLOWRSRCFORK;
+#endif
+
+ ndp->ni_cnd.cn_flags |= LOCKPARENT;
+ ndp->ni_flag |= NAMEI_COMPOUNDREMOVE;
+ cnp = &ndp->ni_cnd;
+
+lookup_continue:
+ error = nameiat(ndp, fd);
+ if (error)
+ return (error);
+
+ dvp = ndp->ni_dvp;
+ vp = ndp->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) {
+ error = EBUSY;
+ }
+
+ if (!batched) {
+ error = vn_authorize_unlink(dvp, vp, cnp, ctx, NULL);
+ if (error) {
+ 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);
+ if (path == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ }
+ len = safe_getpath(dvp, ndp->ni_cnd.cn_nameptr, path, MAXPATHLEN, &truncated_path);
+ }
+
+#if NAMEDRSRCFORK
+ if (ndp->ni_cnd.cn_flags & CN_WANTSRSRCFORK)
+ error = vnode_removenamedstream(dvp, vp, XATTR_RESOURCEFORK_NAME, 0, ctx);
+ else
+#endif
+ {
+ error = vn_remove(dvp, &ndp->ni_vp, ndp, flags, vap, ctx);
+ vp = ndp->ni_vp;
+ if (error == EKEEPLOOKING) {
+ if (!batched) {
+ panic("EKEEPLOOKING, but not a filesystem that supports compound VNOPs?");
+ }
+
+ if ((ndp->ni_flag & NAMEI_CONTLOOKUP) == 0) {
+ panic("EKEEPLOOKING, but continue flag not set?");
+ }
+
+ if (vnode_isdir(vp)) {
+ error = EISDIR;
+ goto out;
+ }
+ goto lookup_continue;
+ }
+ }
+
+ /*
+ * 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, path,
+ FSE_ARG_FINFO, &finfo,
+ FSE_ARG_DONE);
+ }
+#endif
+ }
+
+out:
+ if (path != NULL)
+ RELEASE_PATH(path);
+
+#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(ndp);
+ vnode_put(dvp);
+ if (vp) {
+ vnode_put(vp);
+ }
+ return (error);
+}
+
+int
+unlink1(vfs_context_t ctx, struct nameidata *ndp, int unlink_flags)
+{
+ return (unlink1at(ctx, ndp, unlink_flags, AT_FDCWD));
+}
+
+/*
+ * Delete a name from the filesystem using POSIX semantics.
+ */
+static int
+unlinkat_internal(vfs_context_t ctx, int fd, user_addr_t path,
+ enum uio_seg segflg)
+{
+ struct nameidata nd;
+
+ NDINIT(&nd, DELETE, OP_UNLINK, AUDITVNPATH1, segflg,
+ path, ctx);
+ return (unlink1at(ctx, &nd, 0, fd));
+}
+
+int
+unlink(__unused proc_t p, struct unlink_args *uap, __unused int32_t *retval)
+{
+ return (unlinkat_internal(vfs_context_current(), AT_FDCWD, uap->path,
+ UIO_USERSPACE));
+}
+
+int
+unlinkat(__unused proc_t p, struct unlinkat_args *uap, __unused int32_t *retval)
+{
+ if (uap->flag & ~AT_REMOVEDIR)
+ return (EINVAL);
+
+ if (uap->flag & AT_REMOVEDIR)
+ return (rmdirat_internal(vfs_context_current(), uap->fd,
+ uap->path, UIO_USERSPACE));
+ else
+ return (unlinkat_internal(vfs_context_current(), uap->fd,
+ uap->path, UIO_USERSPACE));
+}
+
+/*
+ * Delete a name from the filesystem using Carbon semantics.
+ */
+int
+delete(__unused proc_t p, struct delete_args *uap, __unused int32_t *retval)
+{
+ struct nameidata nd;
+ vfs_context_t ctx = vfs_context_current();
+
+ NDINIT(&nd, DELETE, OP_UNLINK, AUDITVNPATH1, UIO_USERSPACE,
+ uap->path, ctx);
+ return unlink1(ctx, &nd, VNODE_REMOVE_NODELETEBUSY);
+}
+
+/*
+ * 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->f_fglob);
+ else
+ error = mac_file_check_change_offset(vfs_context_ucred(ctx),
+ fp->f_fglob);
+ 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->f_fglob->fg_offset;
+ break;
+ case L_XTND:
+ if ((error = vnode_size(vp, &file_size, ctx)) != 0)
+ break;
+ offset += file_size;
+ break;
+ case L_SET:
+ 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->f_fglob->fg_offset = offset;
+ *retval = fp->f_fglob->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;
+ unsigned int desc_max, desc_actual, 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;