123401Smckusick /* 2*63180Sbostic * Copyright (c) 1982, 1986, 1989, 1993 3*63180Sbostic * The Regents of the University of California. All rights reserved. 423401Smckusick * 544457Sbostic * %sccs.include.redist.c% 637715Smckusick * 7*63180Sbostic * @(#)vfs_lookup.c 8.1 (Berkeley) 06/10/93 823401Smckusick */ 930Sbill 1056517Sbostic #include <sys/param.h> 1156517Sbostic #include <sys/syslimits.h> 1256517Sbostic #include <sys/time.h> 1356517Sbostic #include <sys/namei.h> 1456517Sbostic #include <sys/vnode.h> 1556517Sbostic #include <sys/mount.h> 1656517Sbostic #include <sys/errno.h> 1756517Sbostic #include <sys/malloc.h> 1856517Sbostic #include <sys/filedesc.h> 1956517Sbostic #include <sys/proc.h> 2037715Smckusick 2137582Smarc #ifdef KTRACE 2256517Sbostic #include <sys/ktrace.h> 2337582Smarc #endif 2430Sbill 2530Sbill /* 2627268Smckusick * Convert a pathname into a pointer to a locked inode. 2730Sbill * 2816688Smckusick * The FOLLOW flag is set when symbolic links are to be followed 299166Ssam * when they occur at the end of the name translation process. 3027268Smckusick * Symbolic links are always followed for all other pathname 3127268Smckusick * components other than the last. 329166Ssam * 3327268Smckusick * The segflg defines whether the name is to be copied from user 3427268Smckusick * space or kernel space. 3527268Smckusick * 3615798Smckusick * Overall outline of namei: 3715798Smckusick * 387534Sroot * copy in name 397534Sroot * get starting directory 4049736Smckusick * while (!done && !error) { 4149736Smckusick * call lookup to search path. 4249736Smckusick * if symbolic link, massage name in buffer and continue 4349736Smckusick * } 4449736Smckusick */ 4552633Sheideman int 4652633Sheideman namei(ndp) 4749736Smckusick register struct nameidata *ndp; 4849736Smckusick { 4949736Smckusick register struct filedesc *fdp; /* pointer to file descriptor state */ 5049736Smckusick register char *cp; /* pointer into pathname argument */ 5149736Smckusick register struct vnode *dp; /* the directory we are searching */ 5249736Smckusick struct iovec aiov; /* uio for reading symbolic links */ 5349736Smckusick struct uio auio; 5449736Smckusick int error, linklen; 5552633Sheideman struct componentname *cnp = &ndp->ni_cnd; 5649736Smckusick 5752633Sheideman ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_proc->p_ucred; 5852633Sheideman #ifdef DIAGNOSTIC 5952633Sheideman if (!cnp->cn_cred || !cnp->cn_proc) 6052633Sheideman panic ("namei: bad cred/proc"); 6152633Sheideman if (cnp->cn_nameiop & (~OPMASK)) 6252633Sheideman panic ("namei: nameiop contaminated with flags"); 6352633Sheideman if (cnp->cn_flags & OPMASK) 6452633Sheideman panic ("namei: flags contaminated with nameiops"); 6552633Sheideman #endif 6652633Sheideman fdp = cnp->cn_proc->p_fd; 6749736Smckusick 6849736Smckusick /* 6949736Smckusick * Get a buffer for the name to be translated, and copy the 7049736Smckusick * name into the buffer. 7149736Smckusick */ 7252633Sheideman if ((cnp->cn_flags & HASBUF) == 0) 7352633Sheideman MALLOC(cnp->cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); 7449736Smckusick if (ndp->ni_segflg == UIO_SYSSPACE) 7552633Sheideman error = copystr(ndp->ni_dirp, cnp->cn_pnbuf, 7649736Smckusick MAXPATHLEN, &ndp->ni_pathlen); 7749736Smckusick else 7852633Sheideman error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, 7949736Smckusick MAXPATHLEN, &ndp->ni_pathlen); 8049736Smckusick if (error) { 8152633Sheideman free(cnp->cn_pnbuf, M_NAMEI); 8249736Smckusick ndp->ni_vp = NULL; 8349736Smckusick return (error); 8449736Smckusick } 8549736Smckusick ndp->ni_loopcnt = 0; 8649736Smckusick #ifdef KTRACE 8752633Sheideman if (KTRPOINT(cnp->cn_proc, KTR_NAMEI)) 8852633Sheideman ktrnamei(cnp->cn_proc->p_tracep, cnp->cn_pnbuf); 8949736Smckusick #endif 9049736Smckusick 9149736Smckusick /* 9249736Smckusick * Get starting point for the translation. 9349736Smckusick */ 9449736Smckusick if ((ndp->ni_rootdir = fdp->fd_rdir) == NULL) 9549736Smckusick ndp->ni_rootdir = rootdir; 9649736Smckusick dp = fdp->fd_cdir; 9749736Smckusick VREF(dp); 9849736Smckusick for (;;) { 9949736Smckusick /* 10049736Smckusick * Check if root directory should replace current directory. 10149736Smckusick * Done at start of translation and after symbolic link. 10249736Smckusick */ 10352633Sheideman cnp->cn_nameptr = cnp->cn_pnbuf; 10452633Sheideman if (*(cnp->cn_nameptr) == '/') { 10549736Smckusick vrele(dp); 10652633Sheideman while (*(cnp->cn_nameptr) == '/') { 10752633Sheideman cnp->cn_nameptr++; 10849736Smckusick ndp->ni_pathlen--; 10949736Smckusick } 11049736Smckusick dp = ndp->ni_rootdir; 11149736Smckusick VREF(dp); 11249736Smckusick } 11349736Smckusick ndp->ni_startdir = dp; 11452633Sheideman if (error = lookup(ndp)) { 11552633Sheideman FREE(cnp->cn_pnbuf, M_NAMEI); 11649736Smckusick return (error); 11749736Smckusick } 11849736Smckusick /* 11949736Smckusick * Check for symbolic link 12049736Smckusick */ 12152633Sheideman if ((cnp->cn_flags & ISSYMLINK) == 0) { 12252633Sheideman if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0) 12352633Sheideman FREE(cnp->cn_pnbuf, M_NAMEI); 12449736Smckusick else 12552633Sheideman cnp->cn_flags |= HASBUF; 12649736Smckusick return (0); 12749736Smckusick } 12852633Sheideman if ((cnp->cn_flags & LOCKPARENT) && ndp->ni_pathlen == 1) 12949736Smckusick VOP_UNLOCK(ndp->ni_dvp); 13049736Smckusick if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { 13149736Smckusick error = ELOOP; 13249736Smckusick break; 13349736Smckusick } 13449736Smckusick if (ndp->ni_pathlen > 1) 13549736Smckusick MALLOC(cp, char *, MAXPATHLEN, M_NAMEI, M_WAITOK); 13649736Smckusick else 13752633Sheideman cp = cnp->cn_pnbuf; 13849736Smckusick aiov.iov_base = cp; 13949736Smckusick aiov.iov_len = MAXPATHLEN; 14049736Smckusick auio.uio_iov = &aiov; 14149736Smckusick auio.uio_iovcnt = 1; 14249736Smckusick auio.uio_offset = 0; 14349736Smckusick auio.uio_rw = UIO_READ; 14449736Smckusick auio.uio_segflg = UIO_SYSSPACE; 14549736Smckusick auio.uio_procp = (struct proc *)0; 14649736Smckusick auio.uio_resid = MAXPATHLEN; 14752633Sheideman if (error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred)) { 14849736Smckusick if (ndp->ni_pathlen > 1) 14949736Smckusick free(cp, M_NAMEI); 15049736Smckusick break; 15149736Smckusick } 15249736Smckusick linklen = MAXPATHLEN - auio.uio_resid; 15349736Smckusick if (linklen + ndp->ni_pathlen >= MAXPATHLEN) { 15449736Smckusick if (ndp->ni_pathlen > 1) 15549736Smckusick free(cp, M_NAMEI); 15649736Smckusick error = ENAMETOOLONG; 15749736Smckusick break; 15849736Smckusick } 15949736Smckusick if (ndp->ni_pathlen > 1) { 16049736Smckusick bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen); 16152633Sheideman FREE(cnp->cn_pnbuf, M_NAMEI); 16252633Sheideman cnp->cn_pnbuf = cp; 16349736Smckusick } else 16452633Sheideman cnp->cn_pnbuf[linklen] = '\0'; 16549736Smckusick ndp->ni_pathlen += linklen; 16649736Smckusick vput(ndp->ni_vp); 16749736Smckusick dp = ndp->ni_dvp; 16849736Smckusick } 16952633Sheideman FREE(cnp->cn_pnbuf, M_NAMEI); 17049736Smckusick vrele(ndp->ni_dvp); 17149736Smckusick vput(ndp->ni_vp); 17249736Smckusick ndp->ni_vp = NULL; 17349736Smckusick return (error); 17449736Smckusick } 17549736Smckusick 17649736Smckusick /* 17749736Smckusick * Search a pathname. 17849736Smckusick * This is a very central and rather complicated routine. 17949736Smckusick * 18049736Smckusick * The pathname is pointed to by ni_ptr and is of length ni_pathlen. 18149736Smckusick * The starting directory is taken from ni_startdir. The pathname is 18249736Smckusick * descended until done, or a symbolic link is encountered. The variable 18349736Smckusick * ni_more is clear if the path is completed; it is set to one if a 18449736Smckusick * symbolic link needing interpretation is encountered. 18549736Smckusick * 18649736Smckusick * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on 18749736Smckusick * whether the name is to be looked up, created, renamed, or deleted. 18849736Smckusick * When CREATE, RENAME, or DELETE is specified, information usable in 18949736Smckusick * creating, renaming, or deleting a directory entry may be calculated. 19049736Smckusick * If flag has LOCKPARENT or'ed into it, the parent directory is returned 19149736Smckusick * locked. If flag has WANTPARENT or'ed into it, the parent directory is 19249736Smckusick * returned unlocked. Otherwise the parent directory is not returned. If 19349736Smckusick * the target of the pathname exists and LOCKLEAF is or'ed into the flag 19449736Smckusick * the target is returned locked, otherwise it is returned unlocked. 19549736Smckusick * When creating or renaming and LOCKPARENT is specified, the target may not 19649736Smckusick * be ".". When deleting and LOCKPARENT is specified, the target may be ".". 19749736Smckusick * 19849736Smckusick * Overall outline of lookup: 19949736Smckusick * 2007534Sroot * dirloop: 20149736Smckusick * identify next component of name at ndp->ni_ptr 2027534Sroot * handle degenerate case where name is null string 20349736Smckusick * if .. and crossing mount points and on mounted filesys, find parent 20449736Smckusick * call VOP_LOOKUP routine for next component name 20549736Smckusick * directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set 20649736Smckusick * component vnode returned in ni_vp (if it exists), locked. 20749736Smckusick * if result vnode is mounted on and crossing mount points, 20849736Smckusick * find mounted on vnode 2097534Sroot * if more components of name, do next level at dirloop 21049736Smckusick * return the answer in ni_vp, locked if LOCKLEAF set 21149736Smckusick * if LOCKPARENT set, return locked parent in ni_dvp 21249736Smckusick * if WANTPARENT set, return unlocked parent in ni_dvp 21330Sbill */ 21452633Sheideman int 21552633Sheideman lookup(ndp) 21616688Smckusick register struct nameidata *ndp; 21730Sbill { 2187534Sroot register char *cp; /* pointer into pathname argument */ 21937715Smckusick register struct vnode *dp = 0; /* the directory we are searching */ 22037715Smckusick struct vnode *tdp; /* saved dp */ 22137715Smckusick struct mount *mp; /* mount table entry */ 22218109Smckusick int docache; /* == 0 do not cache last component */ 22337715Smckusick int wantparent; /* 1 => wantparent or lockparent flag */ 22452181Smckusick int rdonly; /* lookup read-only flag bit */ 22537715Smckusick int error = 0; 22652230Sheideman struct componentname *cnp = &ndp->ni_cnd; 22730Sbill 22841337Smckusick /* 22941337Smckusick * Setup: break out flag bits into variables. 23041337Smckusick */ 23152633Sheideman wantparent = cnp->cn_flags & (LOCKPARENT | WANTPARENT); 23252230Sheideman docache = (cnp->cn_flags & NOCACHE) ^ NOCACHE; 23352633Sheideman if (cnp->cn_nameiop == DELETE || 23452633Sheideman (wantparent && cnp->cn_nameiop != CREATE)) 23515798Smckusick docache = 0; 23652230Sheideman rdonly = cnp->cn_flags & RDONLY; 23749736Smckusick ndp->ni_dvp = NULL; 23852230Sheideman cnp->cn_flags &= ~ISSYMLINK; 23949736Smckusick dp = ndp->ni_startdir; 24049736Smckusick ndp->ni_startdir = NULLVP; 24137715Smckusick VOP_LOCK(dp); 2427534Sroot 2436571Smckusic dirloop: 24430Sbill /* 24549736Smckusick * Search a new directory. 24649736Smckusick * 24752230Sheideman * The cn_hash value is for use by vfs_cache. 24849736Smckusick * The last component of the filename is left accessible via 24952230Sheideman * cnp->cn_nameptr for callers that need the name. Callers needing 25049736Smckusick * the name set the SAVENAME flag. When done, they assume 25149736Smckusick * responsibility for freeing the pathname buffer. 2527534Sroot */ 25355091Spendry cnp->cn_consume = 0; 25452230Sheideman cnp->cn_hash = 0; 25552230Sheideman for (cp = cnp->cn_nameptr; *cp != 0 && *cp != '/'; cp++) 25652230Sheideman cnp->cn_hash += (unsigned char)*cp; 25752230Sheideman cnp->cn_namelen = cp - cnp->cn_nameptr; 25857653Smckusick if (cnp->cn_namelen > NAME_MAX) { 25949736Smckusick error = ENAMETOOLONG; 26049736Smckusick goto bad; 26149736Smckusick } 26237743Smckusick #ifdef NAMEI_DIAGNOSTIC 26349736Smckusick { char c = *cp; 26449736Smckusick *cp = '\0'; 26552230Sheideman printf("{%s}: ", cnp->cn_nameptr); 26649736Smckusick *cp = c; } 26737743Smckusick #endif 26852230Sheideman ndp->ni_pathlen -= cnp->cn_namelen; 26949736Smckusick ndp->ni_next = cp; 27052230Sheideman cnp->cn_flags |= MAKEENTRY; 27118109Smckusick if (*cp == '\0' && docache == 0) 27252230Sheideman cnp->cn_flags &= ~MAKEENTRY; 27352230Sheideman if (cnp->cn_namelen == 2 && 27455181Smckusick cnp->cn_nameptr[1] == '.' && cnp->cn_nameptr[0] == '.') 27552230Sheideman cnp->cn_flags |= ISDOTDOT; 27655181Smckusick else 27755181Smckusick cnp->cn_flags &= ~ISDOTDOT; 27852230Sheideman if (*ndp->ni_next == 0) 27952230Sheideman cnp->cn_flags |= ISLASTCN; 28055181Smckusick else 28155181Smckusick cnp->cn_flags &= ~ISLASTCN; 2827534Sroot 28352230Sheideman 2847534Sroot /* 2857534Sroot * Check for degenerate name (e.g. / or "") 2867534Sroot * which is a way of talking about a directory, 2877534Sroot * e.g. like "/." or ".". 2887534Sroot */ 28952230Sheideman if (cnp->cn_nameptr[0] == '\0') { 29059371Smckusick if (cnp->cn_nameiop != LOOKUP) { 29137715Smckusick error = EISDIR; 2927534Sroot goto bad; 2935972Swnj } 29449736Smckusick if (dp->v_type != VDIR) { 29549736Smckusick error = ENOTDIR; 29649736Smckusick goto bad; 29749736Smckusick } 29859371Smckusick if (wantparent) { 29959371Smckusick ndp->ni_dvp = dp; 30059371Smckusick vref(dp); 30159371Smckusick } 30259371Smckusick ndp->ni_vp = dp; 30359371Smckusick if (!(cnp->cn_flags & (LOCKPARENT | LOCKLEAF))) 30437715Smckusick VOP_UNLOCK(dp); 30552230Sheideman if (cnp->cn_flags & SAVESTART) 30649736Smckusick panic("lookup: SAVESTART"); 30752633Sheideman return (0); 3085972Swnj } 3097534Sroot 3106571Smckusic /* 31137715Smckusick * Handle "..": two special cases. 31237715Smckusick * 1. If at root directory (e.g. after chroot) 31356313Shibler * or at absolute root directory 31437715Smckusick * then ignore it so can't get out. 31537715Smckusick * 2. If this vnode is the root of a mounted 31649736Smckusick * filesystem, then replace it with the 31737715Smckusick * vnode which was mounted on so we take the 31837715Smckusick * .. in the other file system. 31936547Smckusick */ 32052230Sheideman if (cnp->cn_flags & ISDOTDOT) { 32136547Smckusick for (;;) { 32256313Shibler if (dp == ndp->ni_rootdir || dp == rootdir) { 32337715Smckusick ndp->ni_dvp = dp; 32438390Smckusick ndp->ni_vp = dp; 32538347Smckusick VREF(dp); 32637715Smckusick goto nextname; 32736547Smckusick } 32841337Smckusick if ((dp->v_flag & VROOT) == 0 || 32952230Sheideman (cnp->cn_flags & NOCROSSMOUNT)) 33036547Smckusick break; 33137715Smckusick tdp = dp; 33241400Smckusick dp = dp->v_mount->mnt_vnodecovered; 33337715Smckusick vput(tdp); 33438390Smckusick VREF(dp); 33537715Smckusick VOP_LOCK(dp); 33636547Smckusick } 33736547Smckusick } 33836547Smckusick 33936547Smckusick /* 34015798Smckusick * We now have a segment name to search for, and a directory to search. 34115798Smckusick */ 34255451Spendry unionlookup: 34352230Sheideman ndp->ni_dvp = dp; 34452230Sheideman if (error = VOP_LOOKUP(dp, &ndp->ni_vp, cnp)) { 34549736Smckusick #ifdef DIAGNOSTIC 34637715Smckusick if (ndp->ni_vp != NULL) 34737715Smckusick panic("leaf should be empty"); 34849736Smckusick #endif 34937582Smarc #ifdef NAMEI_DIAGNOSTIC 35037715Smckusick printf("not found\n"); 35137582Smarc #endif 35255451Spendry if ((error == ENOENT) && 35355451Spendry (dp->v_flag & VROOT) && 35455451Spendry (dp->v_mount->mnt_flag & MNT_UNION)) { 35555451Spendry tdp = dp; 35655451Spendry dp = dp->v_mount->mnt_vnodecovered; 35755451Spendry vput(tdp); 35855451Spendry VREF(dp); 35955451Spendry VOP_LOCK(dp); 36055451Spendry goto unionlookup; 36155451Spendry } 36255451Spendry 36352798Smckusick if (error != EJUSTRETURN) 36438581Smckusick goto bad; 3655972Swnj /* 36637715Smckusick * If creating and at end of pathname, then can consider 36737715Smckusick * allowing file to be created. 3685972Swnj */ 36952181Smckusick if (rdonly || (ndp->ni_dvp->v_mount->mnt_flag & MNT_RDONLY)) { 37037715Smckusick error = EROFS; 3717534Sroot goto bad; 37238581Smckusick } 3735972Swnj /* 37437715Smckusick * We return with ni_vp NULL to indicate that the entry 37537715Smckusick * doesn't currently exist, leaving a pointer to the 37637715Smckusick * (possibly locked) directory inode in ndp->ni_dvp. 3775972Swnj */ 37852230Sheideman if (cnp->cn_flags & SAVESTART) { 37949736Smckusick ndp->ni_startdir = ndp->ni_dvp; 38049736Smckusick VREF(ndp->ni_startdir); 38149736Smckusick } 38252633Sheideman return (0); 3837534Sroot } 38437582Smarc #ifdef NAMEI_DIAGNOSTIC 38537715Smckusick printf("found\n"); 38637582Smarc #endif 3877534Sroot 38855091Spendry /* 38955091Spendry * Take into account any additional components consumed by 39055091Spendry * the underlying filesystem. 39155091Spendry */ 39255091Spendry if (cnp->cn_consume > 0) { 39355091Spendry cnp->cn_nameptr += cnp->cn_consume; 39455091Spendry ndp->ni_next += cnp->cn_consume; 39555091Spendry ndp->ni_pathlen -= cnp->cn_consume; 39655091Spendry cnp->cn_consume = 0; 39755091Spendry } 39855091Spendry 39949736Smckusick dp = ndp->ni_vp; 4007534Sroot /* 40137715Smckusick * Check for symbolic link 4027534Sroot */ 40337715Smckusick if ((dp->v_type == VLNK) && 40452230Sheideman ((cnp->cn_flags & FOLLOW) || *ndp->ni_next == '/')) { 40552230Sheideman cnp->cn_flags |= ISSYMLINK; 40652633Sheideman return (0); 40715798Smckusick } 40815798Smckusick 4097534Sroot /* 41037715Smckusick * Check to see if the vnode has been mounted on; 41137715Smckusick * if so find the root of the mounted file system. 4127534Sroot */ 41341337Smckusick while (dp->v_type == VDIR && (mp = dp->v_mountedhere) && 41452230Sheideman (cnp->cn_flags & NOCROSSMOUNT) == 0) { 41552116Smarc if (mp->mnt_flag & MNT_MLOCK) { 41641400Smckusick mp->mnt_flag |= MNT_MWAIT; 41737715Smckusick sleep((caddr_t)mp, PVFS); 41852634Smckusick continue; 41921014Smckusick } 42049736Smckusick if (error = VFS_ROOT(dp->v_mountedhere, &tdp)) 4217534Sroot goto bad2; 42237715Smckusick vput(dp); 42337715Smckusick ndp->ni_vp = dp = tdp; 42430Sbill } 4257534Sroot 42637715Smckusick nextname: 42730Sbill /* 4287534Sroot * Not a symbolic link. If more pathname, 4297534Sroot * continue at next component, else return. 43030Sbill */ 43149736Smckusick if (*ndp->ni_next == '/') { 43252230Sheideman cnp->cn_nameptr = ndp->ni_next; 43352230Sheideman while (*cnp->cn_nameptr == '/') { 43452230Sheideman cnp->cn_nameptr++; 43537715Smckusick ndp->ni_pathlen--; 43637715Smckusick } 43737715Smckusick vrele(ndp->ni_dvp); 4387534Sroot goto dirloop; 43930Sbill } 4407534Sroot /* 44138400Smckusick * Check for read-only file systems. 4427534Sroot */ 44352230Sheideman if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME) { 44438267Smckusick /* 44538400Smckusick * Disallow directory write attempts on read-only 44638400Smckusick * file systems. 44738267Smckusick */ 44852181Smckusick if (rdonly || (dp->v_mount->mnt_flag & MNT_RDONLY) || 44952181Smckusick (wantparent && 45052181Smckusick (ndp->ni_dvp->v_mount->mnt_flag & MNT_RDONLY))) { 45138267Smckusick error = EROFS; 45238267Smckusick goto bad2; 45338267Smckusick } 45438267Smckusick } 45552230Sheideman if (cnp->cn_flags & SAVESTART) { 45649736Smckusick ndp->ni_startdir = ndp->ni_dvp; 45749736Smckusick VREF(ndp->ni_startdir); 45849736Smckusick } 45937715Smckusick if (!wantparent) 46037715Smckusick vrele(ndp->ni_dvp); 46152230Sheideman if ((cnp->cn_flags & LOCKLEAF) == 0) 46237715Smckusick VOP_UNLOCK(dp); 46352633Sheideman return (0); 4647534Sroot 46537715Smckusick bad2: 46652230Sheideman if ((cnp->cn_flags & LOCKPARENT) && *ndp->ni_next == '\0') 46738001Smckusick VOP_UNLOCK(ndp->ni_dvp); 46837715Smckusick vrele(ndp->ni_dvp); 46937715Smckusick bad: 47037715Smckusick vput(dp); 47137715Smckusick ndp->ni_vp = NULL; 47252633Sheideman return (error); 4735972Swnj } 47452230Sheideman 47552230Sheideman 476