void ports_dead_name (void *notify, mach_port_t dead_name) { struct protid *pi = ports_lookup_port (diskfs_port_bucket, dead_name, diskfs_protid_class); struct node *np; if (pi) { np = pi->po->np; pthread_mutex_lock (&np->lock); if (dead_name == np->sockaddr) { mach_port_deallocate (mach_task_self (), np->sockaddr); np->sockaddr = MACH_PORT_NULL; diskfs_nput (np); } else pthread_mutex_unlock (&np->lock); } fshelp_remove_active_translator (dead_name); ports_interrupt_notified_rpcs (notify, dead_name, MACH_NOTIFY_DEAD_NAME); }
/* Implement the diskfs_lookup callback from the diskfs library. See <hurd/diskfs.h> for the interface specification. */ error_t diskfs_lookup_hard (struct node *dp, const char *name, enum lookup_type type, struct node **npp, struct dirstat *ds, struct protid *cred) { error_t err = 0; struct lookup_context ctx; int namelen; int spec_dotdot; void *buf; void *blockaddr; ino_t id; if ((type == REMOVE) || (type == RENAME)) assert (npp); if (npp) *npp = 0; spec_dotdot = type & SPEC_DOTDOT; type &= ~SPEC_DOTDOT; namelen = strlen (name); /* This error is constant, but the errors for CREATE and REMOVE depend on whether NAME exists. */ if (type == RENAME) return EROFS; buf = disk_image + (dp->dn->file_start << store->log2_block_size); for (blockaddr = buf; blockaddr < buf + dp->dn_stat.st_size; blockaddr += logical_sector_size) { err = dirscanblock (blockaddr, name, namelen, &ctx.dr, &ctx.rr); if (!err) break; if (err != ENOENT) return err; } if ((!err && type == REMOVE) || (err == ENOENT && type == CREATE)) err = EROFS; if (err) return err; err = cache_id (ctx.dr, &ctx.rr, &id); if (err) return err; /* Load the inode */ if (namelen == 2 && name[0] == '.' && name[1] == '.') { if (dp == diskfs_root_node) err = EAGAIN; else if (spec_dotdot) { /* renames and removes can't get this far. */ assert (type == LOOKUP); diskfs_nput (dp); err = diskfs_cached_lookup_context (id, npp, &ctx); } else { /* We don't have to do the normal rigamarole, because we are permanently read-only, so things are necessarily quiescent. Just be careful to honor the locking order. */ pthread_mutex_unlock (&dp->lock); err = diskfs_cached_lookup_context (id, npp, &ctx); pthread_mutex_lock (&dp->lock); } } else if (namelen == 1 && name[0] == '.') { *npp = dp; diskfs_nref (dp); } else err = diskfs_cached_lookup_context (id, npp, &ctx); release_rrip (&ctx.rr); return err; }
/* Return in FILE & FILE_TYPE the file in FSYS corresponding to the NFS file handle HANDLE & HANDLE_LEN. */ error_t diskfs_S_fsys_getfile (mach_port_t fsys, mach_port_t reply, mach_msg_type_name_t reply_type, uid_t *uids, mach_msg_type_number_t nuids, gid_t *gids, mach_msg_type_number_t ngids, char *handle, mach_msg_type_number_t handle_len, mach_port_t *file, mach_msg_type_name_t *file_type) { int flags; error_t err; struct node *node; const union diskfs_fhandle *f; struct protid *new_cred; struct peropen *new_po; struct iouser *user; struct port_info *pt = ports_lookup_port (diskfs_port_bucket, fsys, diskfs_control_class); if (!pt) return EOPNOTSUPP; if (handle_len != sizeof *f) { ports_port_deref (pt); return EINVAL; } f = (const union diskfs_fhandle *) handle; err = diskfs_cached_lookup (f->data.cache_id, &node); if (err) { ports_port_deref (pt); return err; } if (node->dn_stat.st_gen != f->data.gen) { diskfs_nput (node); ports_port_deref (pt); return ESTALE; } err = iohelp_create_complex_iouser (&user, uids, nuids, gids, ngids); if (err) { diskfs_nput (node); ports_port_deref (pt); return err; } flags = 0; if (! fshelp_access (&node->dn_stat, S_IREAD, user)) flags |= O_READ; if (! fshelp_access (&node->dn_stat, S_IEXEC, user)) flags |= O_EXEC; if (! fshelp_access (&node->dn_stat, S_IWRITE, user) && ! S_ISDIR (node->dn_stat.st_mode) && ! diskfs_check_readonly ()) flags |= O_WRITE; err = diskfs_make_peropen (node, flags, 0, &new_po); if (! err) { err = diskfs_create_protid (new_po, user, &new_cred); if (err) diskfs_release_peropen (new_po); } iohelp_free_iouser (user); diskfs_nput (node); ports_port_deref (pt); if (! err) { *file = ports_get_right (new_cred); *file_type = MACH_MSG_TYPE_MAKE_SEND; } return err; }
/* Implement the diskfs_lookup callback from the diskfs library. See <hurd/diskfs.h> for the interface specification. */ error_t diskfs_lookup_hard (struct node *dp, const char *name, enum lookup_type type, struct node **npp, struct dirstat *ds, struct protid *cred) { error_t err; ino_t inum; int namelen; int spec_dotdot; struct node *np = 0; int retry_dotdot = 0; vm_prot_t prot = (type == LOOKUP) ? VM_PROT_READ : (VM_PROT_READ | VM_PROT_WRITE); memory_object_t memobj; vm_address_t buf = 0; vm_size_t buflen = 0; int blockaddr; int idx, lastidx; int looped; if ((type == REMOVE) || (type == RENAME)) assert (npp); if (npp) *npp = 0; spec_dotdot = type & SPEC_DOTDOT; type &= ~SPEC_DOTDOT; namelen = strlen (name); if (namelen > FAT_NAME_MAX) return ENAMETOOLONG; try_again: if (ds) { ds->type = LOOKUP; ds->mapbuf = 0; ds->mapextent = 0; } if (buf) { munmap ((caddr_t) buf, buflen); buf = 0; } if (ds && (type == CREATE || type == RENAME)) ds->stat = LOOKING; /* Map in the directory contents. */ memobj = diskfs_get_filemap (dp, prot); if (memobj == MACH_PORT_NULL) return errno; buf = 0; /* We allow extra space in case we have to do an EXTEND. */ buflen = round_page (dp->dn_stat.st_size + DIRBLKSIZ); err = vm_map (mach_task_self (), &buf, buflen, 0, 1, memobj, 0, 0, prot, prot, 0); mach_port_deallocate (mach_task_self (), memobj); inum = 0; diskfs_set_node_atime (dp); /* Start the lookup at DP->dn->dir_idx. */ idx = dp->dn->dir_idx; if (idx << LOG2_DIRBLKSIZ > dp->dn_stat.st_size) idx = 0; /* just in case */ blockaddr = buf + (idx << LOG2_DIRBLKSIZ); looped = (idx == 0); lastidx = idx; if (lastidx == 0) lastidx = dp->dn_stat.st_size >> LOG2_DIRBLKSIZ; while (!looped || idx < lastidx) { err = dirscanblock (blockaddr, dp, idx, name, namelen, type, ds, &inum); if (!err) { dp->dn->dir_idx = idx; break; } if (err != ENOENT) { munmap ((caddr_t) buf, buflen); return err; } blockaddr += DIRBLKSIZ; idx++; if (blockaddr - buf >= dp->dn_stat.st_size && !looped) { /* We've gotten to the end; start back at the beginning. */ looped = 1; blockaddr = buf; idx = 0; } } diskfs_set_node_atime (dp); if (diskfs_synchronous) diskfs_node_update (dp, 1); /* If err is set here, it's ENOENT, and we don't want to think about that as an error yet. */ err = 0; if (inum && npp) { if (namelen != 2 || name[0] != '.' || name[1] != '.') { if (inum == dp->cache_id) { np = dp; diskfs_nref (np); } else { err = diskfs_cached_lookup_in_dirbuf (inum, &np, buf); if (err) goto out; } } /* We are looking up "..". */ /* Check to see if this is the root of the filesystem. */ else if (dp == diskfs_root_node) { err = EAGAIN; goto out; } /* We can't just do diskfs_cached_lookup, because we would then deadlock. So we do this. Ick. */ else if (retry_dotdot) { /* Check to see that we got the same answer as last time. */ if (inum != retry_dotdot) { /* Drop what we *thought* was .. (but isn't any more) and try *again*. */ diskfs_nput (np); mutex_unlock (&dp->lock); err = diskfs_cached_lookup_in_dirbuf (inum, &np, buf); mutex_lock (&dp->lock); if (err) goto out; retry_dotdot = inum; goto try_again; } /* Otherwise, we got it fine and np is already set properly. */ } else if (!spec_dotdot) { /* Lock them in the proper order, and then repeat the directory scan to see if this is still right. */ mutex_unlock (&dp->lock); err = diskfs_cached_lookup_in_dirbuf (inum, &np, buf); mutex_lock (&dp->lock); if (err) goto out; retry_dotdot = inum; goto try_again; } /* Here below are the spec dotdot cases. */ else if (type == RENAME || type == REMOVE) np = ifind (inum); else if (type == LOOKUP) { diskfs_nput (dp); err = diskfs_cached_lookup_in_dirbuf (inum, &np, buf); if (err) goto out; } else assert (0); } if ((type == CREATE || type == RENAME) && !inum && ds && ds->stat == LOOKING) { /* We didn't find any room, so mark ds to extend the dir. */ ds->type = CREATE; ds->stat = EXTEND; ds->idx = dp->dn_stat.st_size >> LOG2_DIRBLKSIZ; }
/* Implement dir_rename as described in <hurd/fs.defs>. */ kern_return_t diskfs_S_dir_rename (struct protid *fromcred, char *fromname, struct protid *tocred, char *toname, int excl) { struct node *fdp, *tdp, *fnp, *tnp, *tmpnp; error_t err; struct dirstat *ds = alloca (diskfs_dirstat_size); if (!fromcred) return EOPNOTSUPP; /* Verify that tocred really is a port to us. */ if (! tocred) return EXDEV; if (!strcmp (fromname, ".") || !strcmp (fromname, "..") || !strcmp (toname, ".") || !strcmp (toname, "..")) return EINVAL; if (tocred->po->shadow_root != fromcred->po->shadow_root) /* Same translator, but in different shadow trees. */ return EXDEV; if (diskfs_check_readonly ()) return EROFS; fdp = fromcred->po->np; tdp = tocred->po->np; try_again: /* Acquire the source; hold a reference to it. This will prevent anyone from deleting it before we create the new link. */ mutex_lock (&fdp->lock); err = diskfs_lookup (fdp, fromname, LOOKUP, &fnp, 0, fromcred); mutex_unlock (&fdp->lock); if (err == EAGAIN) err = EINVAL; if (err) return err; if (S_ISDIR (fnp->dn_stat.st_mode)) { mutex_unlock (&fnp->lock); if (!mutex_try_lock (&renamedirlock)) { diskfs_nrele (fnp); mutex_lock (&renamedirlock); goto try_again; } err = diskfs_rename_dir (fdp, fnp, fromname, tdp, toname, fromcred, tocred); if (diskfs_synchronous) { mutex_lock (&fdp->lock); diskfs_file_update (fdp, 1); mutex_unlock (&fdp->lock); mutex_lock (&fnp->lock); diskfs_file_update (fnp, 1); mutex_unlock (&fnp->lock); mutex_lock (&tdp->lock); diskfs_file_update (tdp, 1); mutex_unlock (&tdp->lock); } diskfs_nrele (fnp); mutex_unlock (&renamedirlock); if (!err) /* MiG won't do this for us, which it ought to. */ mach_port_deallocate (mach_task_self (), tocred->pi.port_right); return err; } mutex_unlock (&fnp->lock); /* We now hold no locks */ /* Link the node into the new directory. */ mutex_lock (&tdp->lock); err = diskfs_lookup (tdp, toname, RENAME, &tnp, ds, tocred); if (err == EAGAIN) err = EINVAL; else if (!err && excl) { err = EEXIST; diskfs_nput (tnp); } if (err && err != ENOENT) { diskfs_drop_dirstat (tdp, ds); diskfs_nrele (fnp); mutex_unlock (&tdp->lock); return err; } /* rename("foo", "link-to-foo") is guaranteed to return 0 and do nothing by Posix. */ if (tnp == fnp) { diskfs_drop_dirstat (tdp, ds); diskfs_nrele (fnp); diskfs_nput (tnp); mutex_unlock (&tdp->lock); mach_port_deallocate (mach_task_self (), tocred->pi.port_right); return 0; } /* rename("foo", dir) should fail. */ if (tnp && S_ISDIR (tnp->dn_stat.st_mode)) { diskfs_drop_dirstat (tdp, ds); diskfs_nrele (fnp); diskfs_nput (tnp); mutex_unlock (&tdp->lock); return EISDIR; } mutex_lock (&fnp->lock); /* Increment the link count for the upcoming link */ if (fnp->dn_stat.st_nlink == diskfs_link_max - 1) { diskfs_drop_dirstat (tdp, ds); diskfs_nput (fnp); if (tnp) diskfs_nput (tnp); mutex_unlock (&tdp->lock); return EMLINK; } fnp->dn_stat.st_nlink++; fnp->dn_set_ctime = 1; diskfs_node_update (fnp, 1); if (tnp) { err = diskfs_dirrewrite (tdp, tnp, fnp, toname, ds); if (!err) { tnp->dn_stat.st_nlink--; tnp->dn_set_ctime = 1; if (diskfs_synchronous) diskfs_node_update (tnp, 1); } diskfs_nput (tnp); } else err = diskfs_direnter (tdp, toname, fnp, ds, tocred); if (diskfs_synchronous) diskfs_node_update (tdp, 1); mutex_unlock (&tdp->lock); mutex_unlock (&fnp->lock); if (err) { diskfs_nrele (fnp); return err; } /* We now hold no locks */ /* Now we remove the source. Unfortunately, we haven't held fdp locked (nor could we), so someone else might have already removed it. */ mutex_lock (&fdp->lock); err = diskfs_lookup (fdp, fromname, REMOVE, &tmpnp, ds, fromcred); if (err) { diskfs_drop_dirstat (tdp, ds); mutex_unlock (&fdp->lock); diskfs_nrele (fnp); return err; } if (tmpnp != fnp) { /* This is no longer the node being renamed, so just return. */ diskfs_drop_dirstat (tdp, ds); diskfs_nput (tmpnp); diskfs_nrele (fnp); mutex_unlock (&fdp->lock); mach_port_deallocate (mach_task_self (), tocred->pi.port_right); return 0; } diskfs_nrele (tmpnp); err = diskfs_dirremove (fdp, fnp, fromname, ds); if (diskfs_synchronous) diskfs_node_update (fdp, 1); fnp->dn_stat.st_nlink--; fnp->dn_set_ctime = 1; if (diskfs_synchronous) diskfs_node_update (fnp, 1); diskfs_nput (fnp); mutex_unlock (&fdp->lock); if (!err) mach_port_deallocate (mach_task_self (), tocred->pi.port_right); return err; }
/* Implement dir_unlink as described in <hurd/fs.defs>. */ kern_return_t diskfs_S_dir_unlink (struct protid *dircred, char *name) { struct node *dnp; struct node *np; struct dirstat *ds = alloca (diskfs_dirstat_size); error_t err; mach_port_t control = MACH_PORT_NULL; if (!dircred) return EOPNOTSUPP; dnp = dircred->po->np; if (diskfs_check_readonly ()) return EROFS; pthread_mutex_lock (&dnp->lock); err = diskfs_lookup (dnp, name, REMOVE, &np, ds, dircred); if (err == EAGAIN) err = EPERM; /* 1003.1-1996 5.5.1.4 */ if (err) { diskfs_drop_dirstat (dnp, ds); pthread_mutex_unlock (&dnp->lock); return err; } /* This isn't the BSD behavior, but it is Posix compliant and saves us on several race conditions.*/ if (S_ISDIR(np->dn_stat.st_mode)) { if (np == dnp) /* gotta catch '.' */ diskfs_nrele (np); else diskfs_nput (np); diskfs_drop_dirstat (dnp, ds); pthread_mutex_unlock (&dnp->lock); return EPERM; /* 1003.1-1996 5.5.1.4 */ } err = diskfs_dirremove (dnp, np, name, ds); if (diskfs_synchronous) diskfs_node_update (dnp, 1); if (err) { diskfs_nput (np); pthread_mutex_unlock (&dnp->lock); return err; } np->dn_stat.st_nlink--; np->dn_set_ctime = 1; if (diskfs_synchronous) diskfs_node_update (np, 1); if (np->dn_stat.st_nlink == 0) fshelp_fetch_control (&np->transbox, &control); /* This check is necessary because we might get here on an error while checking the mode on something which happens to be `.'. */ if (np == dnp) diskfs_nrele (np); else diskfs_nput (np); pthread_mutex_unlock (&dnp->lock); if (control) { fsys_goaway (control, FSYS_GOAWAY_UNLINK); mach_port_deallocate (mach_task_self (), control); } return err; }
/* Lookup in directory DP (which is locked) the name NAME. TYPE will either be LOOKUP, CREATE, RENAME, or REMOVE. CRED identifies the user making the call. NAME will have leading and trailing slashes stripped. It is an error if there are internal slashes. NAME will be modified in place if there are slashes in it; it is therefore an error to specify a constant NAME which contains slashes. If the name is found, return zero, and (if NP is nonzero) set *NP to point to the node for it, locked. If the name is not found, return ENOENT, and (if NP is nonzero) set *NP to zero. If NP is zero, then the node found must not be locked, even transitorily. Lookups for REMOVE and RENAME (which must often check permissions on the node being found) will always set NP. If DS is nonzero then: For LOOKUP: set *DS to be ignored by diskfs_drop_dirstat. For CREATE: on success, set *DS to be ignored by diskfs_drop_dirstat. on failure, set *DS for a future call to diskfs_direnter. For RENAME: on success, set *DS for a future call to diskfs_dirrewrite. on failure, set *DS for a future call to diskfs_direnter. For REMOVE: on success, set *DS for a future call to diskfs_dirremove. on failure, set *DS to be ignored by diskfs_drop_dirstat. The caller of this function guarantees that if DS is nonzero, then either the appropriate call listed above or diskfs_drop_dirstat will be called with DS before the directory DP is unlocked, and guarantees that no lookup calls will be made on this directory between this lookup and the use (or descruction) of *DS. If you use the library's versions of diskfs_rename_dir, diskfs_clear_directory, and diskfs_init_dir, then lookups for `..' might have the flag SPEC_DOTDOT or'd in. This has the following special meaning: For LOOKUP: DP should be unlocked and its reference dropped before returning. For RENAME and REMOVE: The node being found (*NP) is already held locked, so don't lock it or add a reference to it. (SPEC_DOTDOT will not be given with CREATE.) Return ENOTDIR if DP is not a directory. Return EACCES if CRED isn't allowed to search DP. Return EACCES if completing the operation will require writing the directory and diskfs_checkdirmod won't allow the modification. Return ENOENT if NAME isn't in the directory. Return EAGAIN if NAME refers to the `..' of this filesystem's root. Return EIO if appropriate. This function is a wrapper for diskfs_lookup_hard. */ error_t diskfs_lookup (struct node *dp, const char *name, enum lookup_type type, struct node **np, struct dirstat *ds, struct protid *cred) { error_t err; struct node *cached; if (type == REMOVE || type == RENAME) assert (np); if (!S_ISDIR (dp->dn_stat.st_mode)) { if (ds) diskfs_null_dirstat (ds); return ENOTDIR; } /* Strip leading and trailing slashes. */ while (*name == '/') name++; if (name[0] == '\0') { if (ds) diskfs_null_dirstat (ds); return EINVAL; } else { char *p = strchr (name, '/'); if (p != 0) { *p = '\0'; do ++p; while (*p == '/'); if (*p != '\0') { if (ds) diskfs_null_dirstat (ds); return EINVAL; } } } err = fshelp_access (&dp->dn_stat, S_IEXEC, cred->user); if (err) { if (ds) diskfs_null_dirstat (ds); return err; } if (dp == cred->po->shadow_root && name[0] == '.' && name[1] == '.' && name[2] == '\0') /* Ran into the root. */ { if (ds) diskfs_null_dirstat (ds); return EAGAIN; } if (type == LOOKUP) /* Check the cache first */ cached = diskfs_check_lookup_cache (dp, name); else cached = 0; if (cached == (struct node *)-1) /* Negative lookup cached. */ { if (np) *np = 0; return ENOENT; } else if (cached) { if (np) *np = cached; /* Return what we found. */ else /* Ick, the user doesn't want the result, we have to drop our reference. */ if (cached == dp) diskfs_nrele (cached); else diskfs_nput (cached); if (ds) diskfs_null_dirstat (ds); } else { err = diskfs_lookup_hard (dp, name, type, np, ds, cred); if (err && err != ENOENT) return err; if (type == RENAME || (type == CREATE && err == ENOENT) || (type == REMOVE && err != ENOENT)) { error_t err2; if (diskfs_name_max > 0 && strlen (name) > diskfs_name_max) err2 = ENAMETOOLONG; else err2 = fshelp_checkdirmod (&dp->dn_stat, (err || !np) ? 0 : &(*np)->dn_stat, cred->user); if (err2) { if (np && !err) { if (*np == dp) diskfs_nrele (*np); else diskfs_nput (*np); *np = 0; } return err2; } } if ((type == LOOKUP || type == CREATE) && !err && np) diskfs_enter_lookup_cache (dp, *np, name); else if (type == LOOKUP && err == ENOENT) diskfs_enter_lookup_cache (dp, 0, name); } return err; }
/* Implement dir_lookup as described in <hurd/fs.defs>. */ kern_return_t diskfs_S_dir_lookup (struct protid *dircred, char *path, int flags, mode_t mode, enum retry_type *retry, char *retryname, file_t *returned_port, mach_msg_type_name_t *returned_port_poly) { struct node *dnp; struct node *np; int nsymlink = 0; char *nextname; char *relpath; int nextnamelen; error_t error = 0; char *pathbuf = 0; int pathbuflen = 0; int newnamelen; int create, excl; int lastcomp = 0; int newnode = 0; struct dirstat *ds = 0; int mustbedir = 0; size_t amt; int type; struct protid *newpi = 0; struct peropen *newpo = 0; if (!dircred) return EOPNOTSUPP; flags &= O_HURD; create = (flags & O_CREAT); excl = (flags & O_EXCL); /* Skip leading slashes */ while (path[0] == '/') path++; /* Preserve the path relative to diruser->po->path. */ relpath = strdup (path); if (! relpath) return ENOMEM; /* Keep a pointer to the start of the path for length calculations. */ char *path_start = path; *returned_port_poly = MACH_MSG_TYPE_MAKE_SEND; *retry = FS_RETRY_NORMAL; retryname[0] = '\0'; if (path[0] == '\0') { /* Set things up in the state expected by the code from gotit: on. */ dnp = 0; np = dircred->po->np; pthread_mutex_lock (&np->lock); diskfs_nref (np); goto gotit; } dnp = dircred->po->np; pthread_mutex_lock (&dnp->lock); np = 0; diskfs_nref (dnp); /* acquire a reference for later diskfs_nput */ do { assert (!lastcomp); /* Find the name of the next pathname component */ nextname = index (path, '/'); if (nextname) { *nextname++ = '\0'; while (*nextname == '/') nextname++; if (*nextname == '\0') { /* These are the rules for filenames ending in /. */ nextname = 0; lastcomp = 1; mustbedir = 1; create = 0; } else lastcomp = 0; } else lastcomp = 1; np = 0; /* diskfs_lookup the next pathname component */ if (lastcomp && create) { if (!ds) ds = alloca (diskfs_dirstat_size); error = diskfs_lookup (dnp, path, CREATE, &np, ds, dircred); } else error = diskfs_lookup (dnp, path, LOOKUP, &np, 0, dircred); if (lastcomp && create && excl && (!error || error == EAGAIN)) error = EEXIST; /* If we get an error we're done */ if (error == EAGAIN) { if (dnp == dircred->po->shadow_root) /* We're at the root of a shadow tree. */ { if (dircred->po->shadow_root_parent == MACH_PORT_NULL) { /* This is a shadow root with no parent, meaning we should treat it as a virtual root disconnected from its real .. directory. */ error = 0; np = dnp; diskfs_nref (np); } else { /* Punt the client up to the shadow root parent. */ *retry = FS_RETRY_REAUTH; *returned_port = dircred->po->shadow_root_parent; *returned_port_poly = MACH_MSG_TYPE_COPY_SEND; if (! lastcomp) strcpy (retryname, nextname); error = 0; goto out; } } else if (dircred->po->root_parent != MACH_PORT_NULL) /* We're at a real translator root; even if DIRCRED->po has a shadow root, we can get here if its in a directory that was renamed out from under it... */ { *retry = FS_RETRY_REAUTH; *returned_port = dircred->po->root_parent; *returned_port_poly = MACH_MSG_TYPE_COPY_SEND; if (!lastcomp) strcpy (retryname, nextname); error = 0; goto out; } else /* We're at a REAL root, as in there's no way up from here. */ { error = 0; np = dnp; diskfs_nref (np); } } /* Create the new node if necessary */ if (lastcomp && create) { if (error == ENOENT) { mode &= ~(S_IFMT | S_ISPARE | S_ISVTX | S_ITRANS); mode |= S_IFREG; error = diskfs_create_node (dnp, path, mode, &np, dircred, ds); if (diskfs_synchronous) { diskfs_file_update (dnp, 1); diskfs_file_update (np, 1); } newnode = 1; } else diskfs_drop_dirstat (dnp, ds); } if (error) goto out; /* If this is translated, start the translator (if necessary) and return. */ if ((((flags & O_NOTRANS) == 0) || !lastcomp) && ((np->dn_stat.st_mode & S_IPTRANS) || S_ISFIFO (np->dn_stat.st_mode) || S_ISCHR (np->dn_stat.st_mode) || S_ISBLK (np->dn_stat.st_mode) || fshelp_translated (&np->transbox))) { mach_port_t dirport; struct iouser *user; /* A callback function for short-circuited translators. Symlink & ifsock are handled elsewhere. */ error_t short_circuited_callback1 (void *cookie1, void *cookie2, uid_t *uid, gid_t *gid, char **argz, size_t *argz_len) { struct node *node = cookie1; switch (node->dn_stat.st_mode & S_IFMT) { case S_IFCHR: case S_IFBLK: asprintf (argz, "%s%c%d%c%d", (S_ISCHR (node->dn_stat.st_mode) ? _HURD_CHRDEV : _HURD_BLKDEV), 0, major (node->dn_stat.st_rdev), 0, minor (node->dn_stat.st_rdev)); *argz_len = strlen (*argz) + 1; *argz_len += strlen (*argz + *argz_len) + 1; *argz_len += strlen (*argz + *argz_len) + 1; break; case S_IFIFO: asprintf (argz, "%s", _HURD_FIFO); *argz_len = strlen (*argz) + 1; break; default: return ENOENT; } *uid = node->dn_stat.st_uid; *gid = node->dn_stat.st_gid; return 0; } /* Create an unauthenticated port for DNP, and then unlock it. */ error = iohelp_create_empty_iouser (&user); if (! error) { error = diskfs_make_peropen (dnp, 0, dircred->po, &newpo); if (! error) { error = diskfs_create_protid (newpo, user, &newpi); if (! error) newpo = 0; } iohelp_free_iouser (user); } if (error) goto out; dirport = ports_get_send_right (newpi); if (np != dnp) pthread_mutex_unlock (&dnp->lock); /* Check if an active translator is currently running. If not, fshelp_fetch_root will start one. In that case, we need to register it in the list of active translators. */ boolean_t register_translator = np->transbox.active == MACH_PORT_NULL; error = fshelp_fetch_root (&np->transbox, dircred->po, dirport, dircred->user, lastcomp ? flags : 0, ((np->dn_stat.st_mode & S_IPTRANS) ? _diskfs_translator_callback1 : short_circuited_callback1), _diskfs_translator_callback2, retry, retryname, returned_port); /* fetch_root copies DIRPORT for success, so we always should deallocate our send right. */ mach_port_deallocate (mach_task_self (), dirport); if (error != ENOENT) { *returned_port_poly = MACH_MSG_TYPE_MOVE_SEND; if (!lastcomp && !error) { char *end = strchr (retryname, '\0'); *end++ = '/'; strcpy (end, nextname); } if (register_translator) { char *translator_path = strdupa (relpath); char *complete_path; if (nextname != NULL) { /* This was not the last path component. NEXTNAME points to the next component, locate the end of the current component and use it to trim TRANSLATOR_PATH. */ char *end = nextname; while (*end != 0) end--; translator_path[end - path_start] = '\0'; } if (dircred->po->path == NULL || !strcmp (dircred->po->path,".")) /* dircred is the root directory. */ complete_path = translator_path; else asprintf (&complete_path, "%s/%s", dircred->po->path, translator_path); error = fshelp_set_active_translator (&newpi->pi, complete_path, np->transbox.active); if (complete_path != translator_path) free(complete_path); if (error) goto out; } goto out; } ports_port_deref (newpi); newpi = NULL; /* ENOENT means there was a hiccup, and the translator vanished while NP was unlocked inside fshelp_fetch_root. Reacquire the locks, and continue as normal. */ error = 0; if (np != dnp) { if (!strcmp (path, "..")) pthread_mutex_lock (&dnp->lock); else { if (pthread_mutex_trylock (&dnp->lock)) { pthread_mutex_unlock (&np->lock); pthread_mutex_lock (&dnp->lock); pthread_mutex_lock (&np->lock); } } } } if (S_ISLNK (np->dn_stat.st_mode) && (!lastcomp || mustbedir /* "foo/" must see that foo points to a dir */ || !(flags & (O_NOLINK|O_NOTRANS)))) { /* Handle symlink interpretation */ if (nsymlink++ > diskfs_maxsymlinks) { error = ELOOP; goto out; } nextnamelen = nextname ? strlen (nextname) + 1 : 0; newnamelen = nextnamelen + np->dn_stat.st_size + 1 + 1; if (pathbuflen < newnamelen) { pathbuf = alloca (newnamelen); pathbuflen = newnamelen; } if (diskfs_read_symlink_hook) error = (*diskfs_read_symlink_hook)(np, pathbuf); if (!diskfs_read_symlink_hook || error == EINVAL) { error = diskfs_node_rdwr (np, pathbuf, 0, np->dn_stat.st_size, 0, dircred, &amt); if (!error) assert (amt == np->dn_stat.st_size); } if (error) goto out; if (np->dn_stat.st_size == 0) /* symlink to "" */ path = nextname; else { if (nextname) { pathbuf[np->dn_stat.st_size] = '/'; memcpy (pathbuf + np->dn_stat.st_size + 1, nextname, nextnamelen - 1); } pathbuf[nextnamelen + np->dn_stat.st_size] = '\0'; if (pathbuf[0] == '/') { /* Punt to the caller. */ *retry = FS_RETRY_MAGICAL; *returned_port = MACH_PORT_NULL; memcpy (retryname, pathbuf, nextnamelen + np->dn_stat.st_size + 1); if (mustbedir) { retryname[nextnamelen + np->dn_stat.st_size] = '/'; retryname[nextnamelen + np->dn_stat.st_size + 1] = '\0'; } goto out; } path = pathbuf; } if (lastcomp) lastcomp = 0; diskfs_nput (np); np = 0; if (path == 0) /* symlink to "" was the last component */ { np = dnp; dnp = 0; break; } } else { /* Handle normal nodes */ path = nextname; if (np == dnp) diskfs_nrele (dnp); else diskfs_nput (dnp); if (!lastcomp) { dnp = np; np = 0; } else dnp = 0; } } while (path && *path);