/* 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; }
/* 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_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; }
/* 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);