int imageboot_setup() { dev_t dev; int error = 0; char *root_path = NULL; DBG_TRACE("%s: entry\n", __FUNCTION__); MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (root_path == NULL) return (ENOMEM); if(PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == FALSE) { error = ENOENT; goto done; } printf("%s: root image url is %s\n", __FUNCTION__, root_path); error = di_root_image(root_path, rootdevice, &dev); if(error) { printf("%s: di_root_image failed: %d\n", __FUNCTION__, error); goto done; } rootdev = dev; mountroot = NULL; printf("%s: root device 0x%x\n", __FUNCTION__, rootdev); error = vfs_mountroot(); if (error == 0 && rootvnode != NULL) { struct vnode *tvp; struct vnode *newdp; /* * Get the vnode for '/'. * Set fdp->fd_fd.fd_cdir to reference it. */ if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel())) panic("%s: cannot find root vnode", __FUNCTION__); vnode_ref(newdp); vnode_put(newdp); tvp = rootvnode; vnode_rele(tvp); filedesc0.fd_cdir = newdp; rootvnode = newdp; mount_list_lock(); TAILQ_REMOVE(&mountlist, TAILQ_FIRST(&mountlist), mnt_list); mount_list_unlock(); mountlist.tqh_first->mnt_flag |= MNT_ROOTFS; DBG_TRACE("%s: root switched\n", __FUNCTION__); } done: FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); DBG_TRACE("%s: exit\n", __FUNCTION__); return (error); }
int imageboot_needed(void) { int result = 0; char *root_path = NULL; DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__); MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (root_path == NULL) panic("%s: M_NAMEI zone exhausted", __FUNCTION__); if(PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == TRUE) { /* Got it, now verify scheme */ if (strncmp(root_path, kIBFilePrefix, strlen(kIBFilePrefix)) == 0) { DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); result = 1; } else { DBG_TRACE("%s: Invalid URL scheme for %s\n", __FUNCTION__, root_path); } } FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); return (result); }
/* * forkproc * * Description: Create a new process structure, given a parent process * structure. * * Parameters: parent_proc The parent process * * Returns: !NULL The new process structure * NULL Error (insufficient free memory) * * Note: When successful, the newly created process structure is * partially initialized; if a caller needs to deconstruct the * returned structure, they must call forkproc_free() to do so. */ proc_t forkproc(proc_t parent_proc) { proc_t child_proc; /* Our new process */ static int nextpid = 0, pidwrap = 0, nextpidversion = 0; int error = 0; struct session *sessp; uthread_t parent_uthread = (uthread_t)get_bsdthread_info(current_thread()); MALLOC_ZONE(child_proc, proc_t , sizeof *child_proc, M_PROC, M_WAITOK); if (child_proc == NULL) { printf("forkproc: M_PROC zone exhausted\n"); goto bad; } /* zero it out as we need to insert in hash */ bzero(child_proc, sizeof *child_proc); MALLOC_ZONE(child_proc->p_stats, struct pstats *, sizeof *child_proc->p_stats, M_PSTATS, M_WAITOK); if (child_proc->p_stats == NULL) { printf("forkproc: M_SUBPROC zone exhausted (p_stats)\n"); FREE_ZONE(child_proc, sizeof *child_proc, M_PROC); child_proc = NULL; goto bad; } MALLOC_ZONE(child_proc->p_sigacts, struct sigacts *, sizeof *child_proc->p_sigacts, M_SIGACTS, M_WAITOK); if (child_proc->p_sigacts == NULL) { printf("forkproc: M_SUBPROC zone exhausted (p_sigacts)\n"); FREE_ZONE(child_proc->p_stats, sizeof *child_proc->p_stats, M_PSTATS); FREE_ZONE(child_proc, sizeof *child_proc, M_PROC); child_proc = NULL; goto bad; } /* allocate a callout for use by interval timers */ child_proc->p_rcall = thread_call_allocate((thread_call_func_t)realitexpire, child_proc); if (child_proc->p_rcall == NULL) { FREE_ZONE(child_proc->p_sigacts, sizeof *child_proc->p_sigacts, M_SIGACTS); FREE_ZONE(child_proc->p_stats, sizeof *child_proc->p_stats, M_PSTATS); FREE_ZONE(child_proc, sizeof *child_proc, M_PROC); child_proc = NULL; goto bad; } /* * Find an unused PID. */ proc_list_lock(); nextpid++; retry: /* * If the process ID prototype has wrapped around, * restart somewhat above 0, as the low-numbered procs * tend to include daemons that don't exit. */ if (nextpid >= PID_MAX) { nextpid = 100; pidwrap = 1; } if (pidwrap != 0) { /* if the pid stays in hash both for zombie and runniing state */ if (pfind_locked(nextpid) != PROC_NULL) { nextpid++; goto retry; } if (pgfind_internal(nextpid) != PGRP_NULL) { nextpid++; goto retry; } if (session_find_internal(nextpid) != SESSION_NULL) { nextpid++; goto retry; } } nprocs++; child_proc->p_pid = nextpid; child_proc->p_idversion = nextpidversion++; #if 1 if (child_proc->p_pid != 0) { if (pfind_locked(child_proc->p_pid) != PROC_NULL) panic("proc in the list already\n"); } #endif /* Insert in the hash */ child_proc->p_listflag |= (P_LIST_INHASH | P_LIST_INCREATE); LIST_INSERT_HEAD(PIDHASH(child_proc->p_pid), child_proc, p_hash); proc_list_unlock(); /* * We've identified the PID we are going to use; initialize the new * process structure. */ child_proc->p_stat = SIDL; child_proc->p_pgrpid = PGRPID_DEAD; /* * The zero'ing of the proc was at the allocation time due to need * for insertion to hash. Copy the section that is to be copied * directly from the parent. */ bcopy(&parent_proc->p_startcopy, &child_proc->p_startcopy, (unsigned) ((caddr_t)&child_proc->p_endcopy - (caddr_t)&child_proc->p_startcopy)); /* * Some flags are inherited from the parent. * Duplicate sub-structures as needed. * Increase reference counts on shared objects. * The p_stats and p_sigacts substructs are set in vm_fork. */ child_proc->p_flag = (parent_proc->p_flag & (P_LP64 | P_TRANSLATED | P_AFFINITY)); if (parent_proc->p_flag & P_PROFIL) startprofclock(child_proc); /* * Note that if the current thread has an assumed identity, this * credential will be granted to the new process. */ child_proc->p_ucred = kauth_cred_get_with_ref(); #ifdef CONFIG_EMBEDDED lck_mtx_init(&child_proc->p_mlock, proc_lck_grp, proc_lck_attr); lck_mtx_init(&child_proc->p_fdmlock, proc_lck_grp, proc_lck_attr); #if CONFIG_DTRACE lck_mtx_init(&child_proc->p_dtrace_sprlock, proc_lck_grp, proc_lck_attr); #endif lck_spin_init(&child_proc->p_slock, proc_lck_grp, proc_lck_attr); #else /* !CONFIG_EMBEDDED */ lck_mtx_init(&child_proc->p_mlock, proc_mlock_grp, proc_lck_attr); lck_mtx_init(&child_proc->p_fdmlock, proc_fdmlock_grp, proc_lck_attr); #if CONFIG_DTRACE lck_mtx_init(&child_proc->p_dtrace_sprlock, proc_lck_grp, proc_lck_attr); #endif lck_spin_init(&child_proc->p_slock, proc_slock_grp, proc_lck_attr); #endif /* !CONFIG_EMBEDDED */ klist_init(&child_proc->p_klist); if (child_proc->p_textvp != NULLVP) { /* bump references to the text vnode */ /* Need to hold iocount across the ref call */ if (vnode_getwithref(child_proc->p_textvp) == 0) { error = vnode_ref(child_proc->p_textvp); vnode_put(child_proc->p_textvp); if (error != 0) child_proc->p_textvp = NULLVP; } } /* * Copy the parents per process open file table to the child; if * there is a per-thread current working directory, set the childs * per-process current working directory to that instead of the * parents. * * XXX may fail to copy descriptors to child */ child_proc->p_fd = fdcopy(parent_proc, parent_uthread->uu_cdir); #if SYSV_SHM if (parent_proc->vm_shm) { /* XXX may fail to attach shm to child */ (void)shmfork(parent_proc, child_proc); } #endif /* * inherit the limit structure to child */ proc_limitfork(parent_proc, child_proc); if (child_proc->p_limit->pl_rlimit[RLIMIT_CPU].rlim_cur != RLIM_INFINITY) { uint64_t rlim_cur = child_proc->p_limit->pl_rlimit[RLIMIT_CPU].rlim_cur; child_proc->p_rlim_cpu.tv_sec = (rlim_cur > __INT_MAX__) ? __INT_MAX__ : rlim_cur; } /* Intialize new process stats, including start time */ /* <rdar://6640543> non-zeroed portion contains garbage AFAICT */ bzero(&child_proc->p_stats->pstat_startzero, (unsigned) ((caddr_t)&child_proc->p_stats->pstat_endzero - (caddr_t)&child_proc->p_stats->pstat_startzero)); bzero(&child_proc->p_stats->user_p_prof, sizeof(struct user_uprof)); microtime(&child_proc->p_start); child_proc->p_stats->p_start = child_proc->p_start; /* for compat */ if (parent_proc->p_sigacts != NULL) (void)memcpy(child_proc->p_sigacts, parent_proc->p_sigacts, sizeof *child_proc->p_sigacts); else (void)memset(child_proc->p_sigacts, 0, sizeof *child_proc->p_sigacts); sessp = proc_session(parent_proc); if (sessp->s_ttyvp != NULL && parent_proc->p_flag & P_CONTROLT) OSBitOrAtomic(P_CONTROLT, &child_proc->p_flag); session_rele(sessp); /* * block all signals to reach the process. * no transition race should be occuring with the child yet, * but indicate that the process is in (the creation) transition. */ proc_signalstart(child_proc, 0); proc_transstart(child_proc, 0); child_proc->p_pcaction = (parent_proc->p_pcaction) & P_PCMAX; TAILQ_INIT(&child_proc->p_uthlist); TAILQ_INIT(&child_proc->p_aio_activeq); TAILQ_INIT(&child_proc->p_aio_doneq); /* Inherit the parent flags for code sign */ child_proc->p_csflags = parent_proc->p_csflags; /* * All processes have work queue locks; cleaned up by * reap_child_locked() */ workqueue_init_lock(child_proc); /* * Copy work queue information * * Note: This should probably only happen in the case where we are * creating a child that is a copy of the parent; since this * routine is called in the non-duplication case of vfork() * or posix_spawn(), then this information should likely not * be duplicated. * * <rdar://6640553> Work queue pointers that no longer point to code */ child_proc->p_wqthread = parent_proc->p_wqthread; child_proc->p_threadstart = parent_proc->p_threadstart; child_proc->p_pthsize = parent_proc->p_pthsize; child_proc->p_targconc = parent_proc->p_targconc; if ((parent_proc->p_lflag & P_LREGISTER) != 0) { child_proc->p_lflag |= P_LREGISTER; } child_proc->p_dispatchqueue_offset = parent_proc->p_dispatchqueue_offset; #if PSYNCH pth_proc_hashinit(child_proc); #endif /* PSYNCH */ #if CONFIG_LCTX child_proc->p_lctx = NULL; /* Add new process to login context (if any). */ if (parent_proc->p_lctx != NULL) { /* * <rdar://6640564> This should probably be delayed in the * vfork() or posix_spawn() cases. */ LCTX_LOCK(parent_proc->p_lctx); enterlctx(child_proc, parent_proc->p_lctx, 0); } #endif bad: return(child_proc); }
static struct netboot_info * netboot_info_init(struct in_addr iaddr) { boolean_t have_root_path = FALSE; struct netboot_info * info = NULL; char * root_path = NULL; info = (struct netboot_info *)kalloc(sizeof(*info)); bzero(info, sizeof(*info)); info->client_ip = iaddr; info->image_type = kNetBootImageTypeUnknown; /* check for a booter-specified path then a NetBoot path */ MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (root_path == NULL) panic("netboot_info_init: M_NAMEI zone exhausted"); if (PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) == TRUE || PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == TRUE || PE_parse_boot_argn("rootpath", root_path, MAXPATHLEN) == TRUE) { if (imageboot_format_is_valid(root_path)) { printf("netboot_info_init: rp0='%s' isn't a network path," " ignoring\n", root_path); } else { have_root_path = TRUE; } } if (have_root_path == FALSE) { have_root_path = get_root_path(root_path); } if (have_root_path) { const char * server_name = NULL; char * mount_point = NULL; char * image_path = NULL; struct in_addr server_ip; if (parse_image_path(root_path, &server_ip, &server_name, &mount_point, &image_path)) { info->image_type = kNetBootImageTypeNFS; info->server_ip = server_ip; info->server_name_length = strlen(server_name) + 1; info->server_name = (char *)kalloc(info->server_name_length); info->mount_point_length = strlen(mount_point) + 1; info->mount_point = (char *)kalloc(info->mount_point_length); strlcpy(info->server_name, server_name, info->server_name_length); strlcpy(info->mount_point, mount_point, info->mount_point_length); printf("netboot: NFS Server %s Mount %s", server_name, info->mount_point); if (image_path != NULL) { boolean_t needs_slash = FALSE; info->image_path_length = strlen(image_path) + 1; if (image_path[0] != '/') { needs_slash = TRUE; info->image_path_length++; } info->image_path = (char *)kalloc(info->image_path_length); if (needs_slash) { info->image_path[0] = '/'; strlcpy(info->image_path + 1, image_path, info->image_path_length - 1); } else { strlcpy(info->image_path, image_path, info->image_path_length); } printf(" Image %s", info->image_path); } printf("\n"); } else if (strncmp(root_path, kNetBootRootPathPrefixHTTP, strlen(kNetBootRootPathPrefixHTTP)) == 0) { info->image_type = kNetBootImageTypeHTTP; save_path(&info->image_path, &info->image_path_length, root_path); printf("netboot: HTTP URL %s\n", info->image_path); } else { printf("netboot: root path uses unrecognized format\n"); } /* check for image-within-image */ if (info->image_path != NULL) { if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) || PE_parse_boot_argn("rp1", root_path, MAXPATHLEN)) { /* rp1/root-dmg is the second-level image */ save_path(&info->second_image_path, &info->second_image_path_length, root_path); } } if (info->second_image_path != NULL) { printf("netboot: nested image %s\n", info->second_image_path); } } FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); return (info); }
/* * Convert a pathname into a pointer to a locked inode. * * The FOLLOW flag is set when symbolic links are to be followed * when they occur at the end of the name translation process. * Symbolic links are always followed for all other pathname * components other than the last. * * The segflg defines whether the name is to be copied from user * space or kernel space. * * Overall outline of namei: * * copy in name * get starting directory * while (!done && !error) { * call lookup to search path. * if symbolic link, massage name in buffer and continue * } * * Returns: 0 Success * ENOENT No such file or directory * ELOOP Too many levels of symbolic links * ENAMETOOLONG Filename too long * copyinstr:EFAULT Bad address * copyinstr:ENAMETOOLONG Filename too long * lookup:EBADF Bad file descriptor * lookup:EROFS * lookup:EACCES * lookup:EPERM * lookup:ERECYCLE vnode was recycled from underneath us in lookup. * This means we should re-drive lookup from this point. * lookup: ??? * VNOP_READLINK:??? */ int namei(struct nameidata *ndp) { struct filedesc *fdp; /* pointer to file descriptor state */ struct vnode *dp; /* the directory we are searching */ struct vnode *usedvp = ndp->ni_dvp; /* store pointer to vp in case we must loop due to heavy vnode pressure */ u_long cnpflags = ndp->ni_cnd.cn_flags; /* store in case we have to restore after loop */ int error; struct componentname *cnp = &ndp->ni_cnd; vfs_context_t ctx = cnp->cn_context; proc_t p = vfs_context_proc(ctx); #if CONFIG_AUDIT /* XXX ut should be from context */ uthread_t ut = (struct uthread *)get_bsdthread_info(current_thread()); #endif fdp = p->p_fd; #if DIAGNOSTIC if (!vfs_context_ucred(ctx) || !p) panic ("namei: bad cred/proc"); if (cnp->cn_nameiop & (~OPMASK)) panic ("namei: nameiop contaminated with flags"); if (cnp->cn_flags & OPMASK) panic ("namei: flags contaminated with nameiops"); #endif /* * A compound VNOP found something that needs further processing: * either a trigger vnode, a covered directory, or a symlink. */ if (ndp->ni_flag & NAMEI_CONTLOOKUP) { int rdonly, vbusyflags, keep_going, wantparent; rdonly = cnp->cn_flags & RDONLY; vbusyflags = ((cnp->cn_flags & CN_NBMOUNTLOOK) != 0) ? LK_NOWAIT : 0; keep_going = 0; wantparent = cnp->cn_flags & (LOCKPARENT | WANTPARENT); ndp->ni_flag &= ~(NAMEI_CONTLOOKUP); error = lookup_handle_found_vnode(ndp, &ndp->ni_cnd, rdonly, vbusyflags, &keep_going, ndp->ni_ncgeneration, wantparent, 0, ctx); if (error) goto out_drop; if (keep_going) { if ((cnp->cn_flags & ISSYMLINK) == 0) { panic("We need to keep going on a continued lookup, but for vp type %d (tag %d)\n", ndp->ni_vp->v_type, ndp->ni_vp->v_tag); } goto continue_symlink; } return 0; } vnode_recycled: /* * Get a buffer for the name to be translated, and copy the * name into the buffer. */ if ((cnp->cn_flags & HASBUF) == 0) { cnp->cn_pnbuf = ndp->ni_pathbuf; cnp->cn_pnlen = PATHBUFLEN; } #if LP64_DEBUG if ((UIO_SEG_IS_USER_SPACE(ndp->ni_segflg) == 0) && (ndp->ni_segflg != UIO_SYSSPACE) && (ndp->ni_segflg != UIO_SYSSPACE32)) { panic("%s :%d - invalid ni_segflg\n", __FILE__, __LINE__); } #endif /* LP64_DEBUG */ retry_copy: if (UIO_SEG_IS_USER_SPACE(ndp->ni_segflg)) { error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, cnp->cn_pnlen, (size_t *)&ndp->ni_pathlen); } else { error = copystr(CAST_DOWN(void *, ndp->ni_dirp), cnp->cn_pnbuf, cnp->cn_pnlen, (size_t *)&ndp->ni_pathlen); } if (error == ENAMETOOLONG && !(cnp->cn_flags & HASBUF)) { MALLOC_ZONE(cnp->cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (cnp->cn_pnbuf == NULL) { error = ENOMEM; goto error_out; } cnp->cn_flags |= HASBUF; cnp->cn_pnlen = MAXPATHLEN; goto retry_copy; } if (error) goto error_out; #if CONFIG_VOLFS /* * Check for legacy volfs style pathnames. * * For compatibility reasons we currently allow these paths, * but future versions of the OS may not support them. */ if (ndp->ni_pathlen >= VOLFS_MIN_PATH_LEN && cnp->cn_pnbuf[0] == '/' && cnp->cn_pnbuf[1] == '.' && cnp->cn_pnbuf[2] == 'v' && cnp->cn_pnbuf[3] == 'o' && cnp->cn_pnbuf[4] == 'l' && cnp->cn_pnbuf[5] == '/' ) { char * realpath; int realpath_err; /* Attempt to resolve a legacy volfs style pathname. */ MALLOC_ZONE(realpath, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (realpath) { /* * We only error out on the ENAMETOOLONG cases where we know that * vfs_getrealpath translation succeeded but the path could not fit into * MAXPATHLEN characters. In other failure cases, we may be dealing with a path * that legitimately looks like /.vol/1234/567 and is not meant to be translated */ if ((realpath_err= vfs_getrealpath(&cnp->cn_pnbuf[6], realpath, MAXPATHLEN, ctx))) { FREE_ZONE(realpath, MAXPATHLEN, M_NAMEI); if (realpath_err == ENOSPC || realpath_err == ENAMETOOLONG){ error = ENAMETOOLONG; goto error_out; } } else { if (cnp->cn_flags & HASBUF) { FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI); } cnp->cn_pnbuf = realpath; cnp->cn_pnlen = MAXPATHLEN; ndp->ni_pathlen = strlen(realpath) + 1; cnp->cn_flags |= HASBUF | CN_VOLFSPATH; } } } #endif /* CONFIG_VOLFS */ #if CONFIG_AUDIT /* If we are auditing the kernel pathname, save the user pathname */ if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(upath, ut->uu_cdir, cnp->cn_pnbuf, ARG_UPATH1); if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(upath, ut->uu_cdir, cnp->cn_pnbuf, ARG_UPATH2); #endif /* CONFIG_AUDIT */ /* * Do not allow empty pathnames */ if (*cnp->cn_pnbuf == '\0') { error = ENOENT; goto error_out; } ndp->ni_loopcnt = 0; /* * determine the starting point for the translation. */ if ((ndp->ni_rootdir = fdp->fd_rdir) == NULLVP) { if ( !(fdp->fd_flags & FD_CHROOT)) ndp->ni_rootdir = rootvnode; } cnp->cn_nameptr = cnp->cn_pnbuf; ndp->ni_usedvp = NULLVP; if (*(cnp->cn_nameptr) == '/') { while (*(cnp->cn_nameptr) == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; } dp = ndp->ni_rootdir; } else if (cnp->cn_flags & USEDVP) { dp = ndp->ni_dvp; ndp->ni_usedvp = dp; } else dp = vfs_context_cwd(ctx); if (dp == NULLVP || (dp->v_lflag & VL_DEAD)) { error = ENOENT; goto error_out; } ndp->ni_dvp = NULLVP; ndp->ni_vp = NULLVP; for (;;) { ndp->ni_startdir = dp; if ( (error = lookup(ndp)) ) { goto error_out; } /* * Check for symbolic link */ if ((cnp->cn_flags & ISSYMLINK) == 0) { return (0); } continue_symlink: /* Gives us a new path to process, and a starting dir */ error = lookup_handle_symlink(ndp, &dp, ctx); if (error != 0) { break; } } /* * only come here if we fail to handle a SYMLINK... * if either ni_dvp or ni_vp is non-NULL, then * we need to drop the iocount that was picked * up in the lookup routine */ out_drop: if (ndp->ni_dvp) vnode_put(ndp->ni_dvp); if (ndp->ni_vp) vnode_put(ndp->ni_vp); error_out: if ( (cnp->cn_flags & HASBUF) ) { cnp->cn_flags &= ~HASBUF; FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI); } cnp->cn_pnbuf = NULL; ndp->ni_vp = NULLVP; ndp->ni_dvp = NULLVP; if (error == ERECYCLE){ /* vnode was recycled underneath us. re-drive lookup to start at the beginning again, since recycling invalidated last lookup*/ ndp->ni_cnd.cn_flags = cnpflags; ndp->ni_dvp = usedvp; goto vnode_recycled; } return (error); }
/* * Look up a vnode/nfsnode by file handle. * Callers must check for mount points!! * In all cases, a pointer to a * nfsnode structure is returned. */ int nfs_nget( mount_t mp, nfsnode_t dnp, struct componentname *cnp, u_char *fhp, int fhsize, struct nfs_vattr *nvap, u_int64_t *xidp, uint32_t auth, int flags, nfsnode_t *npp) { nfsnode_t np; struct nfsnodehashhead *nhpp; vnode_t vp; int error, nfsvers; mount_t mp2; struct vnode_fsparam vfsp; uint32_t vid; FSDBG_TOP(263, mp, dnp, flags, npp); /* Check for unmount in progress */ if (!mp || vfs_isforce(mp)) { *npp = NULL; error = ENXIO; FSDBG_BOT(263, mp, dnp, 0xd1e, error); return (error); } nfsvers = VFSTONFS(mp)->nm_vers; nhpp = NFSNOHASH(nfs_hash(fhp, fhsize)); loop: lck_mtx_lock(nfs_node_hash_mutex); for (np = nhpp->lh_first; np != 0; np = np->n_hash.le_next) { mp2 = (np->n_hflag & NHINIT) ? np->n_mount : NFSTOMP(np); if (mp != mp2 || np->n_fhsize != fhsize || bcmp(fhp, np->n_fhp, fhsize)) continue; if (nvap && (nvap->nva_flags & NFS_FFLAG_TRIGGER_REFERRAL) && cnp && (cnp->cn_namelen > (fhsize - (int)sizeof(dnp)))) { /* The name was too long to fit in the file handle. Check it against the node's name. */ int namecmp = 0; const char *vname = vnode_getname(NFSTOV(np)); if (vname) { if (cnp->cn_namelen != (int)strlen(vname)) namecmp = 1; else namecmp = strncmp(vname, cnp->cn_nameptr, cnp->cn_namelen); vnode_putname(vname); } if (namecmp) /* full name didn't match */ continue; } FSDBG(263, dnp, np, np->n_flag, 0xcace0000); /* if the node is locked, sleep on it */ if ((np->n_hflag & NHLOCKED) && !(flags & NG_NOCREATE)) { np->n_hflag |= NHLOCKWANT; FSDBG(263, dnp, np, np->n_flag, 0xcace2222); msleep(np, nfs_node_hash_mutex, PDROP | PINOD, "nfs_nget", NULL); FSDBG(263, dnp, np, np->n_flag, 0xcace3333); goto loop; } vp = NFSTOV(np); vid = vnode_vid(vp); lck_mtx_unlock(nfs_node_hash_mutex); if ((error = vnode_getwithvid(vp, vid))) { /* * If vnode is being reclaimed or has already * changed identity, no need to wait. */ FSDBG_BOT(263, dnp, *npp, 0xcace0d1e, error); return (error); } if ((error = nfs_node_lock(np))) { /* this only fails if the node is now unhashed */ /* so let's see if we can find/create it again */ FSDBG(263, dnp, *npp, 0xcaced1e2, error); vnode_put(vp); if (flags & NG_NOCREATE) { *npp = 0; FSDBG_BOT(263, dnp, *npp, 0xcaced1e0, ENOENT); return (ENOENT); } goto loop; } /* update attributes */ if (nvap) error = nfs_loadattrcache(np, nvap, xidp, 0); if (error) { nfs_node_unlock(np); vnode_put(vp); } else { if (dnp && cnp && (flags & NG_MAKEENTRY)) cache_enter(NFSTOV(dnp), vp, cnp); /* * Update the vnode if the name/and or the parent has * changed. We need to do this so that if getattrlist is * called asking for ATTR_CMN_NAME, that the "most" * correct name is being returned. In addition for * monitored vnodes we need to kick the vnode out of the * name cache. We do this so that if there are hard * links in the same directory the link will not be * found and a lookup will get us here to return the * name of the current link. In addition by removing the * name from the name cache the old name will not be * found after a rename done on another client or the * server. The principle reason to do this is because * Finder is asking for notifications on a directory. * The directory changes, Finder gets notified, reads * the directory (which we have purged) and for each * entry returned calls getattrlist with the name * returned from readdir. gettattrlist has to call * namei/lookup to resolve the name, because its not in * the cache we end up here. We need to update the name * so Finder will get the name it called us with. * * We had an imperfect solution with respect to case * sensitivity. There is a test that is run in * FileBuster that does renames from some name to * another name differing only in case. It then reads * the directory looking for the new name, after it * finds that new name, it ask gettattrlist to verify * that the name is the new name. Usually that works, * but renames generate fsevents and fseventsd will do a * lookup on the name via lstat. Since that test renames * old name to new name back and forth there is a race * that an fsevent will be behind and will access the * file by the old name, on a case insensitive file * system that will work. Problem is if we do a case * sensitive compare, we're going to change the name, * which the test's getattrlist verification step is * going to fail. So we will check the case sensitivity * of the file system and do the appropriate compare. In * a rare instance for non homogeneous file systems * w.r.t. pathconf we will use case sensitive compares. * That could break if the file system is actually case * insensitive. * * Note that V2 does not know the case, so we just * assume case sensitivity. * * This is clearly not perfect due to races, but this is * as good as its going to get. You can defeat the * handling of hard links simply by doing: * * while :; do ls -l > /dev/null; done * * in a terminal window. Even a single ls -l can cause a * race. * * <rant>What we really need is for the caller, that * knows the name being used is valid since it got it * from a readdir to use that name and not ask for the * ATTR_CMN_NAME</rant> */ if (dnp && cnp && (vp != NFSTOV(dnp))) { int update_flags = (vnode_ismonitored((NFSTOV(dnp)))) ? VNODE_UPDATE_CACHE : 0; int (*cmp)(const char *s1, const char *s2, size_t n); cmp = nfs_case_insensitive(mp) ? strncasecmp : strncmp; if (vp->v_name && cnp->cn_namelen && (*cmp)(cnp->cn_nameptr, vp->v_name, cnp->cn_namelen)) update_flags |= VNODE_UPDATE_NAME; if ((vp->v_name == NULL && cnp->cn_namelen != 0) || (vp->v_name != NULL && cnp->cn_namelen == 0)) update_flags |= VNODE_UPDATE_NAME; if (vnode_parent(vp) != NFSTOV(dnp)) update_flags |= VNODE_UPDATE_PARENT; if (update_flags) { NFS_NODE_DBG("vnode_update_identity old name %s new name %.*s update flags = %x\n", vp->v_name, cnp->cn_namelen, cnp->cn_nameptr ? cnp->cn_nameptr : "", update_flags); vnode_update_identity(vp, NFSTOV(dnp), cnp->cn_nameptr, cnp->cn_namelen, 0, update_flags); } } *npp = np; } FSDBG_BOT(263, dnp, *npp, 0xcace0000, error); return(error); } FSDBG(263, mp, dnp, npp, 0xaaaaaaaa); if (flags & NG_NOCREATE) { lck_mtx_unlock(nfs_node_hash_mutex); *npp = 0; FSDBG_BOT(263, dnp, *npp, 0x80000001, ENOENT); return (ENOENT); } /* * allocate and initialize nfsnode and stick it in the hash * before calling getnewvnode(). Anyone finding it in the * hash before initialization is complete will wait for it. */ MALLOC_ZONE(np, nfsnode_t, sizeof *np, M_NFSNODE, M_WAITOK); if (!np) { lck_mtx_unlock(nfs_node_hash_mutex); *npp = 0; FSDBG_BOT(263, dnp, *npp, 0x80000001, ENOMEM); return (ENOMEM); } bzero(np, sizeof *np); np->n_hflag |= (NHINIT | NHLOCKED); np->n_mount = mp; np->n_auth = auth; TAILQ_INIT(&np->n_opens); TAILQ_INIT(&np->n_lock_owners); TAILQ_INIT(&np->n_locks); np->n_dlink.tqe_next = NFSNOLIST; np->n_dreturn.tqe_next = NFSNOLIST; np->n_monlink.le_next = NFSNOLIST; /* ugh... need to keep track of ".zfs" directories to workaround server bugs */ if ((nvap->nva_type == VDIR) && cnp && (cnp->cn_namelen == 4) && (cnp->cn_nameptr[0] == '.') && (cnp->cn_nameptr[1] == 'z') && (cnp->cn_nameptr[2] == 'f') && (cnp->cn_nameptr[3] == 's')) np->n_flag |= NISDOTZFS; if (dnp && (dnp->n_flag & NISDOTZFS)) np->n_flag |= NISDOTZFSCHILD; if (dnp && cnp && ((cnp->cn_namelen != 2) || (cnp->cn_nameptr[0] != '.') || (cnp->cn_nameptr[1] != '.'))) { vnode_t dvp = NFSTOV(dnp); if (!vnode_get(dvp)) { if (!vnode_ref(dvp)) np->n_parent = dvp; vnode_put(dvp); } } /* setup node's file handle */ if (fhsize > NFS_SMALLFH) { MALLOC_ZONE(np->n_fhp, u_char *, fhsize, M_NFSBIGFH, M_WAITOK); if (!np->n_fhp) { lck_mtx_unlock(nfs_node_hash_mutex); FREE_ZONE(np, sizeof *np, M_NFSNODE); *npp = 0; FSDBG_BOT(263, dnp, *npp, 0x80000002, ENOMEM); return (ENOMEM); } } else {
/* * Convert a pathname into a pointer to a locked inode. * * The FOLLOW flag is set when symbolic links are to be followed * when they occur at the end of the name translation process. * Symbolic links are always followed for all other pathname * components other than the last. * * The segflg defines whether the name is to be copied from user * space or kernel space. * * Overall outline of namei: * * copy in name * get starting directory * while (!done && !error) { * call lookup to search path. * if symbolic link, massage name in buffer and continue * } * * Returns: 0 Success * ENOENT No such file or directory * ELOOP Too many levels of symbolic links * ENAMETOOLONG Filename too long * copyinstr:EFAULT Bad address * copyinstr:ENAMETOOLONG Filename too long * lookup:EBADF Bad file descriptor * lookup:EROFS * lookup:EACCES * lookup:EPERM * lookup:ERECYCLE vnode was recycled from underneath us in lookup. * This means we should re-drive lookup from this point. * lookup: ??? * VNOP_READLINK:??? */ int namei(struct nameidata *ndp) { struct filedesc *fdp; /* pointer to file descriptor state */ char *cp; /* pointer into pathname argument */ struct vnode *dp; /* the directory we are searching */ struct vnode *usedvp = ndp->ni_dvp; /* store pointer to vp in case we must loop due to heavy vnode pressure */ u_long cnpflags = ndp->ni_cnd.cn_flags; /* store in case we have to restore after loop */ uio_t auio; int error; struct componentname *cnp = &ndp->ni_cnd; vfs_context_t ctx = cnp->cn_context; proc_t p = vfs_context_proc(ctx); /* XXX ut should be from context */ uthread_t ut = (struct uthread *)get_bsdthread_info(current_thread()); char *tmppn; char uio_buf[ UIO_SIZEOF(1) ]; #if DIAGNOSTIC if (!vfs_context_ucred(ctx) || !p) panic ("namei: bad cred/proc"); if (cnp->cn_nameiop & (~OPMASK)) panic ("namei: nameiop contaminated with flags"); if (cnp->cn_flags & OPMASK) panic ("namei: flags contaminated with nameiops"); #endif fdp = p->p_fd; vnode_recycled: /* * Get a buffer for the name to be translated, and copy the * name into the buffer. */ if ((cnp->cn_flags & HASBUF) == 0) { cnp->cn_pnbuf = ndp->ni_pathbuf; cnp->cn_pnlen = PATHBUFLEN; } #if LP64_DEBUG if (IS_VALID_UIO_SEGFLG(ndp->ni_segflg) == 0) { panic("%s :%d - invalid ni_segflg\n", __FILE__, __LINE__); } #endif /* LP64_DEBUG */ retry_copy: if (UIO_SEG_IS_USER_SPACE(ndp->ni_segflg)) { error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, cnp->cn_pnlen, (size_t *)&ndp->ni_pathlen); } else { error = copystr(CAST_DOWN(void *, ndp->ni_dirp), cnp->cn_pnbuf, cnp->cn_pnlen, (size_t *)&ndp->ni_pathlen); } if (error == ENAMETOOLONG && !(cnp->cn_flags & HASBUF)) { MALLOC_ZONE(cnp->cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (cnp->cn_pnbuf == NULL) { error = ENOMEM; goto error_out; } cnp->cn_flags |= HASBUF; cnp->cn_pnlen = MAXPATHLEN; goto retry_copy; } if (error) goto error_out; #if CONFIG_VOLFS /* * Check for legacy volfs style pathnames. * * For compatibility reasons we currently allow these paths, * but future versions of the OS may not support them. */ if (ndp->ni_pathlen >= VOLFS_MIN_PATH_LEN && cnp->cn_pnbuf[0] == '/' && cnp->cn_pnbuf[1] == '.' && cnp->cn_pnbuf[2] == 'v' && cnp->cn_pnbuf[3] == 'o' && cnp->cn_pnbuf[4] == 'l' && cnp->cn_pnbuf[5] == '/' ) { char * realpath; int realpath_err; /* Attempt to resolve a legacy volfs style pathname. */ MALLOC_ZONE(realpath, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); if (realpath) { if ((realpath_err= vfs_getrealpath(&cnp->cn_pnbuf[6], realpath, MAXPATHLEN, ctx))) { FREE_ZONE(realpath, MAXPATHLEN, M_NAMEI); if (realpath_err == ENOSPC){ error = ENAMETOOLONG; goto error_out; } } else { if (cnp->cn_flags & HASBUF) { FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI); } cnp->cn_pnbuf = realpath; cnp->cn_pnlen = MAXPATHLEN; ndp->ni_pathlen = strlen(realpath) + 1; cnp->cn_flags |= HASBUF | CN_VOLFSPATH; } } } #endif /* CONFIG_VOLFS */ /* If we are auditing the kernel pathname, save the user pathname */ if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(upath, ut->uu_cdir, cnp->cn_pnbuf, ARG_UPATH1); if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(upath, ut->uu_cdir, cnp->cn_pnbuf, ARG_UPATH2); /* * Do not allow empty pathnames */ if (*cnp->cn_pnbuf == '\0') { error = ENOENT; goto error_out; } ndp->ni_loopcnt = 0; /* * determine the starting point for the translation. */ if ((ndp->ni_rootdir = fdp->fd_rdir) == NULLVP) { if ( !(fdp->fd_flags & FD_CHROOT)) ndp->ni_rootdir = rootvnode; } cnp->cn_nameptr = cnp->cn_pnbuf; ndp->ni_usedvp = NULLVP; if (*(cnp->cn_nameptr) == '/') { while (*(cnp->cn_nameptr) == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; } dp = ndp->ni_rootdir; } else if (cnp->cn_flags & USEDVP) { dp = ndp->ni_dvp; ndp->ni_usedvp = dp; } else dp = vfs_context_cwd(ctx); if (dp == NULLVP || (dp->v_lflag & VL_DEAD)) { error = ENOENT; goto error_out; } ndp->ni_dvp = NULLVP; ndp->ni_vp = NULLVP; for (;;) { int need_newpathbuf; int linklen; ndp->ni_startdir = dp; if ( (error = lookup(ndp)) ) { goto error_out; } /* * Check for symbolic link */ if ((cnp->cn_flags & ISSYMLINK) == 0) { return (0); } if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { error = ELOOP; break; } #if CONFIG_MACF if ((error = mac_vnode_check_readlink(ctx, ndp->ni_vp)) != 0) break; #endif /* MAC */ if (ndp->ni_pathlen > 1 || !(cnp->cn_flags & HASBUF)) need_newpathbuf = 1; else need_newpathbuf = 0; if (need_newpathbuf) { MALLOC_ZONE(cp, char *, MAXPATHLEN, M_NAMEI, M_WAITOK); if (cp == NULL) { error = ENOMEM; break; } } else { cp = cnp->cn_pnbuf; } auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ, &uio_buf[0], sizeof(uio_buf)); uio_addiov(auio, CAST_USER_ADDR_T(cp), MAXPATHLEN); error = VNOP_READLINK(ndp->ni_vp, auio, ctx); if (error) { if (need_newpathbuf) FREE_ZONE(cp, MAXPATHLEN, M_NAMEI); break; } // LP64todo - fix this linklen = MAXPATHLEN - uio_resid(auio); if (linklen + ndp->ni_pathlen > MAXPATHLEN) { if (need_newpathbuf) FREE_ZONE(cp, MAXPATHLEN, M_NAMEI); error = ENAMETOOLONG; break; } if (need_newpathbuf) { long len = cnp->cn_pnlen; tmppn = cnp->cn_pnbuf; bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen); cnp->cn_pnbuf = cp; cnp->cn_pnlen = MAXPATHLEN; if ( (cnp->cn_flags & HASBUF) ) FREE_ZONE(tmppn, len, M_NAMEI); else cnp->cn_flags |= HASBUF; } else cnp->cn_pnbuf[linklen] = '\0'; ndp->ni_pathlen += linklen; cnp->cn_nameptr = cnp->cn_pnbuf; /* * starting point for 'relative' * symbolic link path */ dp = ndp->ni_dvp; /* * get rid of references returned via 'lookup' */ vnode_put(ndp->ni_vp); vnode_put(ndp->ni_dvp); ndp->ni_vp = NULLVP; ndp->ni_dvp = NULLVP; /* * Check if symbolic link restarts us at the root */ if (*(cnp->cn_nameptr) == '/') { while (*(cnp->cn_nameptr) == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; } if ((dp = ndp->ni_rootdir) == NULLVP) { error = ENOENT; goto error_out; } } } /* * only come here if we fail to handle a SYMLINK... * if either ni_dvp or ni_vp is non-NULL, then * we need to drop the iocount that was picked * up in the lookup routine */ if (ndp->ni_dvp) vnode_put(ndp->ni_dvp); if (ndp->ni_vp) vnode_put(ndp->ni_vp); error_out: if ( (cnp->cn_flags & HASBUF) ) { cnp->cn_flags &= ~HASBUF; FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI); } cnp->cn_pnbuf = NULL; ndp->ni_vp = NULLVP; if (error == ERECYCLE){ /* vnode was recycled underneath us. re-drive lookup to start at the beginning again, since recycling invalidated last lookup*/ ndp->ni_cnd.cn_flags = cnpflags; ndp->ni_dvp = usedvp; goto vnode_recycled; } return (error); }