From 77adf4cd648dce41f89469dd185deec6b6b53a0b Mon Sep 17 00:00:00 2001 From: Vikas Gorur Date: Wed, 18 Feb 2009 17:36:07 +0530 Subject: Added all files --- xlators/features/filter/src/Makefile.am | 13 + xlators/features/filter/src/filter.c | 1768 +++++++++++++++++++++++++++++++ 2 files changed, 1781 insertions(+) create mode 100644 xlators/features/filter/src/Makefile.am create mode 100644 xlators/features/filter/src/filter.c (limited to 'xlators/features/filter/src') diff --git a/xlators/features/filter/src/Makefile.am b/xlators/features/filter/src/Makefile.am new file mode 100644 index 00000000..fa0b9221 --- /dev/null +++ b/xlators/features/filter/src/Makefile.am @@ -0,0 +1,13 @@ +xlator_LTLIBRARIES = filter.la +xlatordir = $(libdir)/glusterfs/$(PACKAGE_VERSION)/xlator/features + +filter_la_LDFLAGS = -module -avoidversion + +filter_la_SOURCES = filter.c +filter_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la + +AM_CFLAGS = -fPIC -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -Wall -D$(GF_HOST_OS) \ + -I$(top_srcdir)/libglusterfs/src -shared -nostartfiles $(GF_CFLAGS) + +CLEANFILES = + diff --git a/xlators/features/filter/src/filter.c b/xlators/features/filter/src/filter.c new file mode 100644 index 00000000..67ea45d3 --- /dev/null +++ b/xlators/features/filter/src/filter.c @@ -0,0 +1,1768 @@ +/* + Copyright (c) 2008 Z RESEARCH, Inc. + This file is part of GlusterFS. + + GlusterFS is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3 of the License, + or (at your option) any later version. + + GlusterFS is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see + . +*/ + +#ifndef _CONFIG_H +#define _CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "glusterfs.h" +#include "logging.h" +#include "dict.h" +#include "xlator.h" + +#define GF_FILTER_NOBODY_UID 65534 +#define GF_FILTER_NOBODY_GID 65534 +#define GF_FILTER_ROOT_UID 0 +#define GF_FILTER_ROOT_GID 0 + +#define GF_MAXIMUM_FILTERING_ALLOWED 32 + +/* + option root-filtering on (off by default) + option translate-uid + option translate-gid + option read-only + option fixed-uid + option fixed-gid + option filter-uid + option filter-gid // not supported yet + +*/ + +struct gf_filter { + /* Flags */ + gf_boolean_t complete_read_only; + char fixed_uid_set; + char fixed_gid_set; + char partial_filter; + + /* Options */ + /* Mapping/Filtering/Translate whatever you want to call */ + int translate_num_uid_entries; + int translate_num_gid_entries; + int translate_input_uid[GF_MAXIMUM_FILTERING_ALLOWED][2]; + int translate_output_uid[GF_MAXIMUM_FILTERING_ALLOWED]; + int translate_input_gid[GF_MAXIMUM_FILTERING_ALLOWED][2]; + int translate_output_gid[GF_MAXIMUM_FILTERING_ALLOWED]; + + /* Fixed uid/gid */ + int fixed_uid; + int fixed_gid; + + /* Filter */ + int filter_num_uid_entries; + int filter_num_gid_entries; + int filter_input_uid[GF_MAXIMUM_FILTERING_ALLOWED][2]; + int filter_input_gid[GF_MAXIMUM_FILTERING_ALLOWED][2]; + +}; + +/* update_frame: The main logic of the whole translator. + Return values: + 0: no change + // TRANSLATE + 1: only uid changed + 2: only gid changed + 3: both uid/gid changed + // FILTER + 4: uid in filter range + 5: gid in filter range // not supported yet + 6: complete fs is readonly +*/ + +#define GF_FILTER_NO_CHANGE 0 +#define GF_FILTER_MAP_UID 1 +#define GF_FILTER_MAP_GID 2 +#define GF_FILTER_MAP_BOTH 3 +#define GF_FILTER_FILTER_UID 4 +#define GF_FILTER_FILTER_GID 5 +#define GF_FILTER_RO_FS 6 + +static int32_t +update_frame (call_frame_t *frame, + inode_t *inode, + struct gf_filter *filter) +{ + uid_t uid = 0; + int32_t idx = 0; + int32_t ret = 0; + int32_t dictret = 0; + uint64_t tmp_uid = 0; + + for (idx = 0; idx < filter->translate_num_uid_entries; idx++) { + if ((frame->root->uid >=filter->translate_input_uid[idx][0]) && + (frame->root->uid <=filter->translate_input_uid[idx][1])) { + dictret = inode_ctx_get (inode, frame->this, &tmp_uid); + uid = (uid_t)tmp_uid; + if (dictret == 0) { + if (frame->root->uid != uid) + ret = GF_FILTER_MAP_UID; + } else { + ret = GF_FILTER_MAP_UID; + } + break; + } + } + + for (idx = 0; idx < filter->translate_num_gid_entries; idx++) { + if ((frame->root->gid >=filter->translate_input_gid[idx][0]) && + (frame->root->gid <=filter->translate_input_gid[idx][1])) { + if (ret == GF_FILTER_NO_CHANGE) + ret = GF_FILTER_MAP_GID; + else + ret = GF_FILTER_MAP_BOTH; + break; + } + } + + + if (filter->complete_read_only) + return GF_FILTER_RO_FS; + + if (filter->partial_filter) { + dictret = inode_ctx_get (inode, frame->this, &tmp_uid); + uid = (uid_t)tmp_uid; + if (dictret != -1) { + for (idx = 0; idx < filter->filter_num_uid_entries; + idx++) { + if ((uid >=filter->filter_input_uid[idx][0]) && + (uid <=filter->filter_input_uid[idx][1])) { + return GF_FILTER_FILTER_UID; + } + } + } + } + + return ret; +} + +/* if 'root' don't change the uid/gid */ +static int32_t +update_stat (struct stat *stbuf, + struct gf_filter *filter) +{ + int32_t idx = 0; + for (idx = 0; idx < filter->translate_num_uid_entries; idx++) { + if (stbuf->st_uid == GF_FILTER_ROOT_UID) + continue; + if ((stbuf->st_uid >= filter->translate_input_uid[idx][0]) && + (stbuf->st_uid <= filter->translate_input_uid[idx][1])) { + stbuf->st_uid = filter->translate_output_uid[idx]; + break; + } + } + + for (idx = 0; idx < filter->translate_num_gid_entries; idx++) { + if (stbuf->st_gid == GF_FILTER_ROOT_GID) + continue; + if ((stbuf->st_gid >= filter->translate_input_gid[idx][0]) && + (stbuf->st_gid <= filter->translate_input_gid[idx][1])) { + stbuf->st_gid = filter->translate_output_gid[idx]; + break; + } + } + + if (filter->fixed_uid_set) { + stbuf->st_uid = filter->fixed_uid; + } + + if (filter->fixed_gid_set) { + stbuf->st_gid = filter->fixed_gid; + } + + return 0; +} + +static int32_t +filter_lookup_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + inode_t *inode, + struct stat *buf, + dict_t *dict) +{ + int ret = 0; + if (op_ret >= 0) { + update_stat (buf, this->private); + ret = inode_ctx_put (inode, this, (uint64_t)(long)buf->st_uid); + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, + "couldn't set context"); + } + } + STACK_UNWIND (frame, op_ret, op_errno, inode, buf, dict); + return 0; +} + +int32_t +filter_lookup (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + dict_t *xattr_req) +{ + STACK_WIND (frame, + filter_lookup_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->lookup, + loc, + xattr_req); + return 0; +} + + +static int32_t +filter_stat_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_stat (call_frame_t *frame, + xlator_t *this, + loc_t *loc) +{ + STACK_WIND (frame, + filter_stat_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->stat, + loc); + return 0; +} + +static int32_t +filter_chmod_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_chmod (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + mode_t mode) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, NULL); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + default: + break; + } + + STACK_WIND (frame, + filter_chmod_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->chmod, + loc, + mode); + return 0; +} + + +static int32_t +filter_fchmod_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, + op_ret, + op_errno, + buf); + return 0; +} + +int32_t +filter_fchmod (call_frame_t *frame, + xlator_t *this, + fd_t *fd, + mode_t mode) +{ + STACK_WIND (frame, + filter_fchmod_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fchmod, + fd, + mode); + return 0; +} + +static int32_t +filter_chown_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_chown (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + uid_t uid, + gid_t gid) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, NULL); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + default: + break; + } + + STACK_WIND (frame, + filter_chown_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->chown, + loc, + uid, + gid); + return 0; +} + +static int32_t +filter_fchown_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_fchown (call_frame_t *frame, + xlator_t *this, + fd_t *fd, + uid_t uid, + gid_t gid) +{ + STACK_WIND (frame, + filter_fchown_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fchown, + fd, + uid, + gid); + return 0; +} + +static int32_t +filter_truncate_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_truncate (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + off_t offset) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, NULL); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + } + + STACK_WIND (frame, + filter_truncate_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->truncate, + loc, + offset); + return 0; +} + +static int32_t +filter_ftruncate_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_ftruncate (call_frame_t *frame, + xlator_t *this, + fd_t *fd, + off_t offset) +{ + STACK_WIND (frame, + filter_ftruncate_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->ftruncate, + fd, + offset); + return 0; +} + +int32_t +filter_utimens_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + + +int32_t +filter_utimens (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + struct timespec tv[2]) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, NULL); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + } + + STACK_WIND (frame, + filter_utimens_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->utimens, + loc, + tv); + return 0; +} + +static int32_t +filter_readlink_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + const char *path) +{ + STACK_UNWIND (frame, op_ret, op_errno, path); + return 0; +} + +int32_t +filter_readlink (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + size_t size) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IRGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IROTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, NULL); + return 0; + } + STACK_WIND (frame, + filter_readlink_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->readlink, + loc, + size); + return 0; +} + + +static int32_t +filter_mknod_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + inode_t *inode, + struct stat *buf) +{ + int ret = 0; + + if (op_ret >= 0) { + update_stat (buf, this->private); + ret = inode_ctx_put (inode, this, (uint64_t)(long)buf->st_uid); + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, + "couldn't set context"); + } + } + STACK_UNWIND (frame, op_ret, op_errno, inode, buf); + return 0; +} + +int32_t +filter_mknod (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + mode_t mode, + dev_t rdev) +{ + int ret = 0; + inode_t *parent = loc->parent; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL, NULL); + return 0; + } + STACK_WIND (frame, + filter_mknod_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->mknod, + loc, mode, rdev); + return 0; +} + +static int32_t +filter_mkdir_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + inode_t *inode, + struct stat *buf) +{ + int ret = 0; + if (op_ret >= 0) { + update_stat (buf, this->private); + ret = inode_ctx_put (inode, this, (uint64_t)(long)buf->st_uid); + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, + "couldn't set context"); + } + } + STACK_UNWIND (frame, op_ret, op_errno, inode, buf); + return 0; +} + +int32_t +filter_mkdir (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + mode_t mode) +{ + int ret = 0; + inode_t *parent = loc->parent; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL, NULL); + return 0; + } + STACK_WIND (frame, + filter_mkdir_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->mkdir, + loc, mode); + return 0; +} + +static int32_t +filter_unlink_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno) +{ + STACK_UNWIND (frame, op_ret, op_errno); + return 0; +} + +int32_t +filter_unlink (call_frame_t *frame, + xlator_t *this, + loc_t *loc) +{ + int32_t ret = 0; + inode_t *parent = loc->parent; + if (!parent) + parent = inode_parent (loc->inode, 0, NULL); + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS); + return 0; + } + STACK_WIND (frame, + filter_unlink_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->unlink, + loc); + return 0; +} + +static int32_t +filter_rmdir_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno) +{ + STACK_UNWIND (frame, op_ret, op_errno); + return 0; +} + +int32_t +filter_rmdir (call_frame_t *frame, + xlator_t *this, + loc_t *loc) +{ + int32_t ret = 0; + inode_t *parent = loc->parent; + if (!parent) + parent = inode_parent (loc->inode, 0, NULL); + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS); + return 0; + } + STACK_WIND (frame, + filter_rmdir_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->rmdir, + loc); + return 0; +} + +static int32_t +filter_symlink_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + inode_t *inode, + struct stat *buf) +{ + int ret = 0; + if (op_ret >= 0) { + update_stat (buf, this->private); + ret = inode_ctx_put (inode, this, (uint64_t)(long)buf->st_uid); + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, + "couldn't set context"); + } + } + STACK_UNWIND (frame, op_ret, op_errno, inode, buf); + return 0; +} + +int32_t +filter_symlink (call_frame_t *frame, + xlator_t *this, + const char *linkpath, + loc_t *loc) +{ + int ret = 0; + inode_t *parent = loc->parent; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL, NULL); + return 0; + } + STACK_WIND (frame, + filter_symlink_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->symlink, + linkpath, loc); + return 0; +} + + +static int32_t +filter_rename_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, op_ret, op_errno, buf); + return 0; +} + +int32_t +filter_rename (call_frame_t *frame, + xlator_t *this, + loc_t *oldloc, + loc_t *newloc) +{ + int32_t ret = 0; + inode_t *parent = oldloc->parent; + if (!parent) + parent = inode_parent (oldloc->inode, 0, NULL); + ret = update_frame (frame, oldloc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + if (oldloc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + if (oldloc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, + "%s -> %s: returning permission denied", oldloc->path, newloc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + } + STACK_WIND (frame, + filter_rename_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->rename, + oldloc, newloc); + return 0; +} + + +static int32_t +filter_link_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + inode_t *inode, + struct stat *buf) +{ + int ret = 0; + if (op_ret >= 0) { + update_stat (buf, this->private); + ret = inode_ctx_put (inode, this, (uint64_t)(long)buf->st_uid); + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, + "couldn't set context"); + } + } + STACK_UNWIND (frame, op_ret, op_errno, inode, buf); + return 0; +} + +int32_t +filter_link (call_frame_t *frame, + xlator_t *this, + loc_t *oldloc, + loc_t *newloc) +{ + int ret = 0; + ret = update_frame (frame, oldloc->inode, this->private); + switch (ret) { + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL, NULL); + return 0; + } + STACK_WIND (frame, + filter_link_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->link, + oldloc, newloc); + return 0; +} + + +static int32_t +filter_create_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + fd_t *fd, + inode_t *inode, + struct stat *buf) +{ + int ret = 0; + if (op_ret >= 0) { + update_stat (buf, this->private); + ret = inode_ctx_put (inode, this, (uint64_t)(long)buf->st_uid); + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, + "couldn't set context"); + } + } + STACK_UNWIND (frame, op_ret, op_errno, fd, inode, buf); + return 0; +} + +int32_t +filter_create (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + int32_t flags, + mode_t mode, fd_t *fd) +{ + int ret = 0; + inode_t *parent = loc->parent; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (parent->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (parent->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL, NULL, NULL); + return 0; + } + STACK_WIND (frame, filter_create_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->create, + loc, flags, mode, fd); + return 0; +} + +static int32_t +filter_open_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + fd_t *fd) +{ + STACK_UNWIND (frame, op_ret, op_errno, fd); + return 0; +} + +int32_t +filter_open (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + int32_t flags, + fd_t *fd) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + if (!((flags & O_WRONLY) || (flags & O_RDWR)) + && (loc->inode->st_mode & S_IRGRP)) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + if (!((flags & O_WRONLY) || (flags & O_RDWR)) + && (loc->inode->st_mode & S_IROTH)) + break; + gf_log (this->name, GF_LOG_DEBUG, + "%s: returning permission denied (mode: 0%o, flag=0%o)", + loc->path, loc->inode->st_mode, flags); + STACK_UNWIND (frame, -1, EPERM, fd); + return 0; + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + if (!((flags & O_WRONLY) || (flags & O_RDWR))) + break; + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + + } + STACK_WIND (frame, + filter_open_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->open, + loc, flags, fd); + return 0; +} + +static int32_t +filter_readv_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct iovec *vector, + int32_t count, + struct stat *stbuf) +{ + if (op_ret >= 0) { + update_stat (stbuf, this->private); + } + STACK_UNWIND (frame, + op_ret, + op_errno, + vector, + count, + stbuf); + return 0; +} + +int32_t +filter_readv (call_frame_t *frame, + xlator_t *this, + fd_t *fd, + size_t size, + off_t offset) +{ + STACK_WIND (frame, + filter_readv_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->readv, + fd, + size, + offset); + return 0; +} + + +static int32_t +filter_writev_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *stbuf) +{ + if (op_ret >= 0) { + update_stat (stbuf, this->private); + } + STACK_UNWIND (frame, + op_ret, + op_errno, + stbuf); + return 0; +} + +int32_t +filter_writev (call_frame_t *frame, + xlator_t *this, + fd_t *fd, + struct iovec *vector, + int32_t count, + off_t off) +{ + int32_t ret = 0; + ret = update_frame (frame, fd->inode, this->private); + switch (ret) { + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS, NULL); + return 0; + } + + STACK_WIND (frame, + filter_writev_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->writev, + fd, + vector, + count, + off); + return 0; +} + +static int32_t +filter_fstat_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + struct stat *buf) +{ + if (op_ret >= 0) { + update_stat (buf, this->private); + } + STACK_UNWIND (frame, + op_ret, + op_errno, + buf); + return 0; +} + +int32_t +filter_fstat (call_frame_t *frame, + xlator_t *this, + fd_t *fd) +{ + STACK_WIND (frame, + filter_fstat_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fstat, + fd); + return 0; +} + +static int32_t +filter_opendir_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + fd_t *fd) +{ + STACK_UNWIND (frame, + op_ret, + op_errno, + fd); + return 0; +} + +int32_t +filter_opendir (call_frame_t *frame, + xlator_t *this, + loc_t *loc, fd_t *fd) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + if (loc->inode->st_mode & S_IRGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + if (loc->inode->st_mode & S_IROTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, fd); + return 0; + } + STACK_WIND (frame, + filter_opendir_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->opendir, + loc, fd); + return 0; +} + + +static int32_t +filter_setxattr_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno) +{ + STACK_UNWIND (frame, + op_ret, + op_errno); + return 0; +} + +int32_t +filter_setxattr (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + dict_t *dict, + int32_t flags) +{ + + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS); + return 0; + } + + STACK_WIND (frame, + filter_setxattr_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->setxattr, + loc, + dict, + flags); + return 0; +} + +static int32_t +filter_getxattr_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno, + dict_t *dict) +{ + STACK_UNWIND (frame, + op_ret, + op_errno, + dict); + return 0; +} + +int32_t +filter_getxattr (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + const char *name) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IRGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IROTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM, NULL); + return 0; + } + + STACK_WIND (frame, + filter_getxattr_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->getxattr, + loc, + name); + return 0; +} + +static int32_t +filter_removexattr_cbk (call_frame_t *frame, + void *cookie, + xlator_t *this, + int32_t op_ret, + int32_t op_errno) +{ + STACK_UNWIND (frame, op_ret, op_errno); + return 0; +} + +int32_t +filter_removexattr (call_frame_t *frame, + xlator_t *this, + loc_t *loc, + const char *name) +{ + int32_t ret = 0; + ret = update_frame (frame, loc->inode, this->private); + switch (ret) { + case GF_FILTER_MAP_UID: + if (loc->inode->st_mode & S_IWGRP) + break; + case GF_FILTER_MAP_BOTH: + if (loc->inode->st_mode & S_IWOTH) + break; + gf_log (this->name, GF_LOG_DEBUG, "%s: returning permission denied", loc->path); + STACK_UNWIND (frame, -1, EPERM); + return 0; + case GF_FILTER_FILTER_UID: + case GF_FILTER_FILTER_GID: + case GF_FILTER_RO_FS: + STACK_UNWIND (frame, -1, EROFS); + return 0; + } + + STACK_WIND (frame, + filter_removexattr_cbk, + FIRST_CHILD(this), + FIRST_CHILD(this)->fops->removexattr, + loc, + name); + return 0; +} + +int32_t +init (xlator_t *this) +{ + char *value = NULL; + char *tmp_str = NULL; + char *tmp_str1 = NULL; + char *tmp_str2 = NULL; + char *dup_str = NULL; + char *input_value_str1 = NULL; + char *input_value_str2 = NULL; + char *output_value_str = NULL; + int32_t input_value = 0; + int32_t output_value = 0; + data_t *option_data = NULL; + struct gf_filter *filter = NULL; + gf_boolean_t tmp_bool = 0; + + if (!this->children || this->children->next) { + gf_log (this->name, + GF_LOG_ERROR, + "translator not configured with exactly one child"); + return -1; + } + + if (!this->parents) { + gf_log (this->name, GF_LOG_WARNING, + "dangling volume. check volfile "); + } + + filter = CALLOC (sizeof (*filter), 1); + ERR_ABORT (filter); + + if (dict_get (this->options, "read-only")) { + value = data_to_str (dict_get (this->options, "read-only")); + if (gf_string2boolean (value, &filter->complete_read_only) == -1) { + gf_log (this->name, GF_LOG_ERROR, + "wrong value provided for 'read-only'"); + return -1; + } + } + + if (dict_get (this->options, "root-squashing")) { + value = data_to_str (dict_get (this->options, "root-squashing")); + if (gf_string2boolean (value, &tmp_bool) == -1) { + gf_log (this->name, GF_LOG_ERROR, + "wrong value provided for 'root-squashing'"); + return -1; + } + if (tmp_bool) { + filter->translate_num_uid_entries = 1; + filter->translate_num_gid_entries = 1; + filter->translate_input_uid[0][0] = GF_FILTER_ROOT_UID; /* root */ + filter->translate_input_uid[0][1] = GF_FILTER_ROOT_UID; /* root */ + filter->translate_input_gid[0][0] = GF_FILTER_ROOT_GID; /* root */ + filter->translate_input_gid[0][1] = GF_FILTER_ROOT_GID; /* root */ + filter->translate_output_uid[0] = GF_FILTER_NOBODY_UID; + filter->translate_output_gid[0] = GF_FILTER_NOBODY_GID; + } + } + + if (dict_get (this->options, "translate-uid")) { + option_data = dict_get (this->options, "translate-uid"); + value = strtok_r (option_data->data, ",", &tmp_str); + while (value) { + dup_str = strdup (value); + input_value_str1 = strtok_r (dup_str, "=", &tmp_str1); + if (input_value_str1) { + /* Check for n-m */ + char *temp_string = strdup (input_value_str1); + input_value_str2 = strtok_r (temp_string, "-", &tmp_str2); + if (gf_string2int (input_value_str2, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str2); + return -1; + } + filter->translate_input_uid[filter->translate_num_uid_entries][0] = input_value; + input_value_str2 = strtok_r (NULL, "-", &tmp_str2); + if (input_value_str2) { + if (gf_string2int (input_value_str2, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str2); + return -1; + } + } + filter->translate_input_uid[filter->translate_num_uid_entries][1] = input_value; + FREE (temp_string); + output_value_str = strtok_r (NULL, "=", &tmp_str1); + if (output_value_str) { + if (gf_string2int (output_value_str, &output_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + output_value_str); + return -1; + } + } else { + gf_log (this->name, GF_LOG_ERROR, + "mapping string not valid"); + return -1; + } + } else { + gf_log (this->name, GF_LOG_ERROR, + "mapping string not valid"); + return -1; + } + filter->translate_output_uid[filter->translate_num_uid_entries] = output_value; + gf_log (this->name, + GF_LOG_DEBUG, + "pair %d: input uid '%d' will be changed to uid '%d'", + filter->translate_num_uid_entries, input_value, output_value); + + filter->translate_num_uid_entries++; + if (filter->translate_num_uid_entries == GF_MAXIMUM_FILTERING_ALLOWED) + break; + value = strtok_r (NULL, ",", &tmp_str); + FREE (dup_str); + } + } + + tmp_str1 = NULL; + tmp_str2 = NULL; + tmp_str = NULL; + + if (dict_get (this->options, "translate-gid")) { + option_data = dict_get (this->options, "translate-gid"); + value = strtok_r (option_data->data, ",", &tmp_str); + while (value) { + dup_str = strdup (value); + input_value_str1 = strtok_r (dup_str, "=", &tmp_str1); + if (input_value_str1) { + /* Check for n-m */ + char *temp_string = strdup (input_value_str1); + input_value_str2 = strtok_r (temp_string, "-", &tmp_str2); + if (gf_string2int (input_value_str2, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str2); + return -1; + } + filter->translate_input_gid[filter->translate_num_gid_entries][0] = input_value; + input_value_str2 = strtok_r (NULL, "-", &tmp_str2); + if (input_value_str2) { + if (gf_string2int (input_value_str2, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str2); + return -1; + } + } + filter->translate_input_gid[filter->translate_num_gid_entries][1] = input_value; + FREE (temp_string); + output_value_str = strtok_r (NULL, "=", &tmp_str1); + if (output_value_str) { + if (gf_string2int (output_value_str, &output_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + output_value_str); + return -1; + } + } else { + gf_log (this->name, GF_LOG_ERROR, + "translate-gid value not valid"); + return -1; + } + } else { + gf_log (this->name, GF_LOG_ERROR, + "translate-gid value not valid"); + return -1; + } + + filter->translate_output_gid[filter->translate_num_gid_entries] = output_value; + + gf_log (this->name, GF_LOG_DEBUG, + "pair %d: input gid '%d' will be changed to gid '%d'", + filter->translate_num_gid_entries, input_value, output_value); + + filter->translate_num_gid_entries++; + if (filter->translate_num_gid_entries == GF_MAXIMUM_FILTERING_ALLOWED) + break; + value = strtok_r (NULL, ",", &tmp_str); + FREE (dup_str); + } + } + + tmp_str = NULL; + tmp_str1 = NULL; + + if (dict_get (this->options, "filter-uid")) { + option_data = dict_get (this->options, "filter-uid"); + value = strtok_r (option_data->data, ",", &tmp_str); + while (value) { + dup_str = strdup (value); + /* Check for n-m */ + input_value_str1 = strtok_r (dup_str, "-", &tmp_str1); + if (gf_string2int (input_value_str1, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str1); + return -1; + } + filter->filter_input_uid[filter->filter_num_uid_entries][0] = input_value; + input_value_str1 = strtok_r (NULL, "-", &tmp_str1); + if (input_value_str1) { + if (gf_string2int (input_value_str1, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str1); + return -1; + } + } + filter->filter_input_uid[filter->filter_num_uid_entries][1] = input_value; + + gf_log (this->name, + GF_LOG_DEBUG, + "filter [%d]: input uid(s) '%s' will be filtered", + filter->filter_num_uid_entries, dup_str); + + filter->filter_num_uid_entries++; + if (filter->filter_num_uid_entries == GF_MAXIMUM_FILTERING_ALLOWED) + break; + value = strtok_r (NULL, ",", &tmp_str); + FREE (dup_str); + } + filter->partial_filter = 1; + } + + tmp_str = NULL; + tmp_str1 = NULL; + + if (dict_get (this->options, "filter-gid")) { + option_data = dict_get (this->options, "filter-gid"); + value = strtok_r (option_data->data, ",", &tmp_str); + while (value) { + dup_str = strdup (value); + /* Check for n-m */ + input_value_str1 = strtok_r (dup_str, "-", &tmp_str1); + if (gf_string2int (input_value_str1, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str1); + return -1; + } + filter->filter_input_gid[filter->filter_num_gid_entries][0] = input_value; + input_value_str1 = strtok_r (NULL, "-", &tmp_str1); + if (input_value_str1) { + if (gf_string2int (input_value_str1, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + input_value_str1); + return -1; + } + } + filter->filter_input_gid[filter->filter_num_gid_entries][1] = input_value; + + gf_log (this->name, + GF_LOG_DEBUG, + "filter [%d]: input gid(s) '%s' will be filtered", + filter->filter_num_gid_entries, dup_str); + + filter->filter_num_gid_entries++; + if (filter->filter_num_gid_entries == GF_MAXIMUM_FILTERING_ALLOWED) + break; + value = strtok_r (NULL, ",", &tmp_str); + FREE (dup_str); + } + gf_log (this->name, GF_LOG_ERROR, "this option is not supported currently.. exiting"); + return -1; + filter->partial_filter = 1; + } + + if (dict_get (this->options, "fixed-uid")) { + option_data = dict_get (this->options, "fixed-uid"); + if (gf_string2int (option_data->data, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + option_data->data); + return -1; + } + filter->fixed_uid = input_value; + filter->fixed_uid_set = 1; + } + + if (dict_get (this->options, "fixed-gid")) { + option_data = dict_get (this->options, "fixed-gid"); + if (gf_string2int (option_data->data, &input_value) != 0) { + gf_log (this->name, GF_LOG_ERROR, + "invalid number format \"%s\"", + option_data->data); + return -1; + } + filter->fixed_gid = input_value; + filter->fixed_gid_set = 1; + } + + this->private = filter; + return 0; +} + + +void +fini (xlator_t *this) +{ + struct gf_filter *filter = this->private; + + FREE (filter); + + return; +} + + +struct xlator_fops fops = { + .lookup = filter_lookup, + .stat = filter_stat, + .fstat = filter_fstat, + .chmod = filter_chmod, + .fchmod = filter_fchmod, + .readlink = filter_readlink, + .mknod = filter_mknod, + .mkdir = filter_mkdir, + .unlink = filter_unlink, + .rmdir = filter_rmdir, + .symlink = filter_symlink, + .rename = filter_rename, + .link = filter_link, + .chown = filter_chown, + .fchown = filter_fchown, + .truncate = filter_truncate, + .ftruncate = filter_ftruncate, + .create = filter_create, + .open = filter_open, + .readv = filter_readv, + .writev = filter_writev, + .setxattr = filter_setxattr, + .getxattr = filter_getxattr, + .removexattr = filter_removexattr, + .opendir = filter_opendir, + .utimens = filter_utimens, +}; + +struct xlator_mops mops = { +}; + +struct xlator_cbks cbks = { +}; + +struct volume_options options[] = { + { .key = { "root-squashing" }, + .type = GF_OPTION_TYPE_BOOL + }, + { .key = { "read-only" }, + .type = GF_OPTION_TYPE_BOOL + }, + { .key = { "fixed-uid" }, + .type = GF_OPTION_TYPE_INT + }, + { .key = { "fixed-gid" }, + .type = GF_OPTION_TYPE_INT + }, + { .key = { "translate-uid" }, + .type = GF_OPTION_TYPE_ANY + }, + { .key = { "translate-gid" }, + .type = GF_OPTION_TYPE_ANY + }, + { .key = { "filter-uid" }, + .type = GF_OPTION_TYPE_ANY + }, + { .key = { "filter-gid" }, + .type = GF_OPTION_TYPE_ANY + }, + { .key = {NULL} }, +}; -- cgit