/* * UNFS3 attribute handling * (C) 2004, Pascal Schmidt * see file LICENSE for license details */ #include "config.h" #include #include #include #include #include #include #include #include #include "nfs.h" #include "attr.h" #include "error.h" #include "fh.h" #include "fh_cache.h" #include "daemon.h" #include "backend.h" /* * check whether stat_cache is for a regular file * * fh_decomp must be called before to fill the stat cache */ nfsstat3 is_reg(void) { if (!st_cache_valid) return NFS3ERR_STALE; else if (S_ISREG(st_cache.st_mode)) return NFS3_OK; else return NFS3ERR_INVAL; } /* * find stat bit corresponding to given NFS file type */ mode_t type_to_mode(ftype3 ftype) { switch (ftype) { case NF3REG: return S_IFREG; case NF3DIR: return S_IFDIR; case NF3LNK: return S_IFLNK; case NF3CHR: return S_IFCHR; case NF3BLK: return S_IFBLK; case NF3FIFO: return S_IFIFO; case NF3SOCK: return S_IFSOCK; } /* fix gcc warning */ return 0; } /* * post_op_attr for error returns */ #ifdef __GNUC__ static post_op_attr error_attr = {.attributes_follow = FALSE }; #else static post_op_attr error_attr = { FALSE }; #endif /* * return pre-operation attributes * * fh_decomp must be called before to fill the stat cache */ pre_op_attr get_pre_cached(void) { pre_op_attr result; if (!st_cache_valid) { result.attributes_follow = FALSE; return result; } result.attributes_follow = TRUE; result.pre_op_attr_u.attributes.size = st_cache.st_size; result.pre_op_attr_u.attributes.mtime.seconds = st_cache.st_mtime; result.pre_op_attr_u.attributes.mtime.nseconds = 0; result.pre_op_attr_u.attributes.ctime.seconds = st_cache.st_ctime; result.pre_op_attr_u.attributes.ctime.nseconds = 0; return result; } /* * compute post-operation attributes given a stat buffer */ post_op_attr get_post_buf(struct stat buf, struct svc_req * req) { post_op_attr result; result.attributes_follow = TRUE; if (S_ISDIR(buf.st_mode)) result.post_op_attr_u.attributes.type = NF3DIR; else if (S_ISBLK(buf.st_mode)) result.post_op_attr_u.attributes.type = NF3BLK; else if (S_ISCHR(buf.st_mode)) result.post_op_attr_u.attributes.type = NF3CHR; else if (S_ISLNK(buf.st_mode)) result.post_op_attr_u.attributes.type = NF3LNK; else if (S_ISSOCK(buf.st_mode)) result.post_op_attr_u.attributes.type = NF3SOCK; else if (S_ISFIFO(buf.st_mode)) result.post_op_attr_u.attributes.type = NF3FIFO; else result.post_op_attr_u.attributes.type = NF3REG; /* adapt permissions for executable files */ if (S_ISREG(buf.st_mode)) { if (buf.st_mode & S_IXUSR) buf.st_mode |= S_IRUSR; if (buf.st_mode & S_IXGRP) buf.st_mode |= S_IRGRP; if (buf.st_mode & S_IXOTH) buf.st_mode |= S_IROTH; } result.post_op_attr_u.attributes.mode = buf.st_mode & 0xFFFF; result.post_op_attr_u.attributes.nlink = buf.st_nlink; /* If -s, translate uids */ if (opt_singleuser) { unsigned int req_uid = 0; unsigned int req_gid = 0; struct authunix_parms *auth = (void *) req->rq_clntcred; uid_t ruid = backend_getuid(); if (req->rq_cred.oa_flavor == AUTH_UNIX) { req_uid = auth->aup_uid; req_gid = auth->aup_gid; } if ((buf.st_uid == ruid) || (ruid == 0)) result.post_op_attr_u.attributes.uid = req_uid; else result.post_op_attr_u.attributes.uid = 0; if ((buf.st_gid == backend_getgid()) || (ruid == 0)) result.post_op_attr_u.attributes.gid = req_gid; else result.post_op_attr_u.attributes.gid = 0; } else { /* Normal case */ result.post_op_attr_u.attributes.uid = buf.st_uid; result.post_op_attr_u.attributes.gid = buf.st_gid; } result.post_op_attr_u.attributes.size = buf.st_size; result.post_op_attr_u.attributes.used = buf.st_blocks * 512; result.post_op_attr_u.attributes.rdev.specdata1 = (buf.st_rdev >> 8) & 0xFF; result.post_op_attr_u.attributes.rdev.specdata2 = buf.st_rdev & 0xFF; result.post_op_attr_u.attributes.fsid = buf.st_dev; result.post_op_attr_u.attributes.fileid = ((uint64) buf.st_dev << 32) + buf.st_ino; result.post_op_attr_u.attributes.atime.seconds = buf.st_atime; result.post_op_attr_u.attributes.atime.nseconds = 0; result.post_op_attr_u.attributes.mtime.seconds = buf.st_mtime; result.post_op_attr_u.attributes.mtime.nseconds = 0; result.post_op_attr_u.attributes.ctime.seconds = buf.st_ctime; result.post_op_attr_u.attributes.ctime.nseconds = 0; return result; } /* * lowlevel routine for getting post-operation attributes */ static post_op_attr get_post_ll(const char *path, uint32 dev, uint32 ino, struct svc_req *req) { struct stat buf; int res; if (!path) return error_attr; res = backend_lstat(path, &buf); if (res == -1) return error_attr; /* protect against local fs race */ if (dev != buf.st_dev || ino != buf.st_ino) return error_attr; return get_post_buf(buf, req); } /* * return post-operation attributes, using fh for old dev/ino */ post_op_attr get_post_attr(const char *path, nfs_fh3 nfh, struct svc_req * req) { unfs3_fh_t *fh = (void *) nfh.data.data_val; return get_post_ll(path, fh->dev, fh->ino, req); } /* * return post-operation attributes, using stat cache for old dev/ino */ post_op_attr get_post_stat(const char *path, struct svc_req * req) { return get_post_ll(path, st_cache.st_dev, st_cache.st_ino, req); } /* * return post-operation attributes using stat cache * * fd_decomp must be called before to fill the stat cache */ post_op_attr get_post_cached(struct svc_req * req) { if (!st_cache_valid) return error_attr; return get_post_buf(st_cache, req); } /* * setting of time, races with local filesystem * * there is no futimes() function in POSIX or Linux */ static nfsstat3 set_time(const char *path, struct stat buf, sattr3 new) { time_t new_atime, new_mtime; struct utimbuf utim; int res; /* set atime and mtime */ if (new.atime.set_it != DONT_CHANGE || new.mtime.set_it != DONT_CHANGE) { /* compute atime to set */ if (new.atime.set_it == SET_TO_SERVER_TIME) new_atime = time(NULL); else if (new.atime.set_it == SET_TO_CLIENT_TIME) new_atime = new.atime.set_atime_u.atime.seconds; else /* DONT_CHANGE */ new_atime = buf.st_atime; /* compute mtime to set */ if (new.mtime.set_it == SET_TO_SERVER_TIME) new_mtime = time(NULL); else if (new.mtime.set_it == SET_TO_CLIENT_TIME) new_mtime = new.mtime.set_mtime_u.mtime.seconds; else /* DONT_CHANGE */ new_mtime = buf.st_mtime; utim.actime = new_atime; utim.modtime = new_mtime; res = backend_utime(path, &utim); if (res == -1) return setattr_err(); } return NFS3_OK; } /* * race unsafe setting of attributes */ static nfsstat3 set_attr_unsafe(const char *path, nfs_fh3 nfh, sattr3 new) { unfs3_fh_t *fh = (void *) nfh.data.data_val; uid_t new_uid; gid_t new_gid; struct stat buf; int res; res = backend_stat(path, &buf); if (res != 0) return NFS3ERR_STALE; /* check local fs race */ if (buf.st_dev != fh->dev || buf.st_ino != fh->ino) return NFS3ERR_STALE; /* set file size */ if (new.size.set_it == TRUE) { res = backend_truncate(path, new.size.set_size3_u.size); if (res == -1) return setattr_err(); } /* set uid and gid */ if (new.uid.set_it == TRUE || new.gid.set_it == TRUE) { if (new.uid.set_it == TRUE) new_uid = new.uid.set_uid3_u.uid; else new_uid = -1; if (new_uid == buf.st_uid) new_uid = -1; if (new.gid.set_it == TRUE) new_gid = new.gid.set_gid3_u.gid; else new_gid = -1; res = backend_chown(path, new_uid, new_gid); if (res == -1) return setattr_err(); } /* set mode */ if (new.mode.set_it == TRUE) { res = backend_chmod(path, new.mode.set_mode3_u.mode); if (res == -1) return setattr_err(); } return set_time(path, buf, new); } /* * set attributes of an object */ nfsstat3 set_attr(const char *path, nfs_fh3 nfh, sattr3 new) { unfs3_fh_t *fh = (void *) nfh.data.data_val; int res, fd; uid_t new_uid; gid_t new_gid; struct stat buf; res = backend_lstat(path, &buf); if (res != 0) return NFS3ERR_STALE; /* * don't open(2) device nodes, it could trigger * module loading on the server */ if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode)) return set_attr_unsafe(path, nfh, new); /* * open object for atomic setting of attributes */ fd = backend_open(path, O_WRONLY | O_NONBLOCK); if (fd == -1) fd = backend_open(path, O_RDONLY | O_NONBLOCK); if (fd == -1) return set_attr_unsafe(path, nfh, new); res = backend_fstat(fd, &buf); if (res == -1) { backend_close(fd); return NFS3ERR_STALE; } /* check local fs race */ if (fh->dev != buf.st_dev || fh->ino != buf.st_ino || fh->gen != backend_get_gen(buf, fd, path)) { backend_close(fd); return NFS3ERR_STALE; } /* set file size */ if (new.size.set_it == TRUE) { res = backend_ftruncate(fd, new.size.set_size3_u.size); if (res == -1) { backend_close(fd); return setattr_err(); } } /* set uid and gid */ if (new.uid.set_it == TRUE || new.gid.set_it == TRUE) { if (new.uid.set_it == TRUE) new_uid = new.uid.set_uid3_u.uid; else new_uid = -1; if (new_uid == buf.st_uid) new_uid = -1; if (new.gid.set_it == TRUE) new_gid = new.gid.set_gid3_u.gid; else new_gid = -1; res = backend_fchown(fd, new_uid, new_gid); if (res == -1) { backend_close(fd); return setattr_err(); } } /* set mode */ if (new.mode.set_it == TRUE) { res = backend_fchmod(fd, new.mode.set_mode3_u.mode); if (res == -1) { backend_close(fd); return setattr_err(); } } res = backend_close(fd); if (res == -1) { /* error on close probably means attributes didn't make it */ return NFS3ERR_IO; } /* finally, set times */ return set_time(path, buf, new); } /* * deduce mode from given settable attributes * default to rwxrwxr-x if no mode given */ mode_t create_mode(sattr3 new) { if (new.mode.set_it == TRUE) return new.mode.set_mode3_u.mode; else return S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH; } /* * check whether an sattr3 is settable atomically on a create op */ nfsstat3 atomic_attr(sattr3 attr) { if ((attr.uid.set_it == TRUE && attr.uid.set_uid3_u.uid != backend_geteuid()) || (attr.gid.set_it == TRUE && attr.gid.set_gid3_u.gid != backend_getegid()) || (attr.size.set_it == TRUE && attr.size.set_size3_u.size != 0) || attr.atime.set_it == SET_TO_CLIENT_TIME || attr.mtime.set_it == SET_TO_CLIENT_TIME) return NFS3ERR_INVAL; else return NFS3_OK; }