fuse: auxiliary gfid mount support

* files can be accessed directly through their gfid and not just
  through their paths. For eg., if the gfid of a file is
  f3142503-c75e-45b1-b92a-463cf4c01f99, that file can be accessed
  using <gluster-mount>/.gfid/f3142503-c75e-45b1-b92a-463cf4c01f99

  .gfid is a virtual directory used to seperate out the namespace
  for accessing files through gfid. This way, we do not conflict with
  filenames which can be qualified as uuids.

* A new file/directory/symlink can be created with a pre-specified
  gfid. A setxattr done on parent directory with fuse_auxgfid_newfile_args_t
  initialized with appropriate fields as value to key "glusterfs.gfid.newfile"
  results in the entry <parent>/bname whose gfid is set to args.gfid. The
  contents of the structure should be in network byte order.

  struct auxfuse_symlink_in {
        char     linkpath[]; /* linkpath is a null terminated string */
  } __attribute__ ((__packed__));

  struct auxfuse_mknod_in {
        unsigned int   mode;
        unsigned int   rdev;
        unsigned int   umask;
  } __attribute__ ((__packed__));

  struct auxfuse_mkdir_in {
        unsigned int   mode;
        unsigned int   umask;
  } __attribute__ ((__packed__));

  typedef struct {
        unsigned int  uid;
        unsigned int  gid;
        char          gfid[UUID_CANONICAL_FORM_LEN + 1]; /* a null terminated gfid string
                                                      * in canonical form.
                                                      */
        unsigned int  st_mode;
        char          bname[];     /* bname is a null terminated string */

        union {
                struct auxfuse_mkdir_in   mkdir;
                struct auxfuse_mknod_in   mknod;
                struct auxfuse_symlink_in symlink;
        } __attribute__ ((__packed__)) args;
  } __attribute__ ((__packed__)) fuse_auxgfid_newfile_args_t;

  An initial consumer of this feature would be geo-replication to
  create files on slave mount with same gfids as that on master.
  It will also help gsyncd to access files directly through their
  gfids. gsyncd in its newer version will be consuming a changelog
  (of master) containing operations on gfids and sync corresponding
  files to slave.

* Also, bring in support to heal gfids with a specific value.
  fuse-bridge sends across a gfid during a lookup, which storage
  translators assign to an inode (file/directory etc) if there is
  no gfid associated it. This patch brings in support
  to specify that gfid value from an application, instead of relying
  on random gfid generated by fuse-bridge.

  gfids can be healed through setxattr interface. setxattr should be
  done on parent directory. The key used is "glusterfs.gfid.heal"
  and the value should be the following structure whose contents
  should be in network byte order.

  typedef struct {
        char      gfid[UUID_CANONICAL_FORM_LEN + 1]; /* a null terminated gfid
                                                      * string in canonical form
                                                      */
        char      bname[]; /* a null terminated basename */
  } __attribute__((__packed__)) fuse_auxgfid_heal_args_t;

  This feature can be used for upgrading older geo-rep setups where gfids
  of files are different on master and slave to newer setups where they
  should be same. One can delete gfids on slave using setxattr -x and
  .glusterfs and issue stat on all the files with gfids from master.

Thanks to "Amar Tumballi" <amarts@redhat.com> and "Csaba Henk"
<csaba@redhat.com> for their inputs.

Signed-off-by: Raghavendra G <rgowdapp@redhat.com>
Change-Id: Ie8ddc0fb3732732315c7ec49eab850c16d905e4e
BUG: 952029
Reviewed-on: http://review.gluster.com/#/c/4702
Reviewed-by: Amar Tumballi <amarts@redhat.com>
Tested-by: Amar Tumballi <amarts@redhat.com>
Reviewed-on: http://review.gluster.org/4702
Reviewed-by: Xavier Hernandez <xhernandez@datalab.es>
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Vijay Bellur <vbellur@redhat.com>
This commit is contained in:
Raghavendra G 2013-07-16 11:49:20 +05:30 committed by Vijay Bellur
parent a1ebee3b0a
commit 4c0f4c8a89
13 changed files with 1317 additions and 136 deletions

View File

@ -145,6 +145,10 @@ static struct argp_option gf_options[] = {
"Mount the filesystem with POSIX ACL support"},
{"selinux", ARGP_SELINUX_KEY, 0, 0,
"Enable SELinux label (extened attributes) support on inodes"},
#ifdef GF_LINUX_HOST_OS
{"aux-gfid-mount", ARGP_AUX_GFID_MOUNT_KEY, 0, 0,
"Enable access to filesystem through gfid directly"},
#endif
{"enable-ino32", ARGP_INODE32_KEY, "BOOL", OPTION_ARG_OPTIONAL,
"Use 32-bit inodes when mounting to workaround broken applications"
"that don't support 64-bit inodes"},
@ -349,6 +353,17 @@ set_fuse_mount_options (glusterfs_ctx_t *ctx, dict_t *options)
}
}
if (cmd_args->aux_gfid_mount) {
ret = dict_set_static_ptr (options, "auxiliary-gfid-mount",
"on");
if (ret < 0) {
gf_log ("glusterfsd", GF_LOG_ERROR,
"failed to set dict value for key "
"aux-gfid-mount");
goto err;
}
}
if (cmd_args->enable_ino32) {
ret = dict_set_static_ptr (options, "enable-ino32", "on");
if (ret < 0) {
@ -688,6 +703,10 @@ parse_opts (int key, char *arg, struct argp_state *state)
"*-md-cache.cache-selinux=true");
break;
case ARGP_AUX_GFID_MOUNT_KEY:
cmd_args->aux_gfid_mount = 1;
break;
case ARGP_INODE32_KEY:
cmd_args->enable_ino32 = 1;
break;

View File

@ -85,6 +85,7 @@ enum argp_option_keys {
ARGP_INODE32_KEY = 163,
ARGP_FUSE_MOUNTOPTS_KEY = 164,
ARGP_FUSE_USE_READDIRP_KEY = 165,
ARGP_AUX_GFID_MOUNT_KEY = 166,
};
struct _gfd_vol_top_priv_t {

View File

@ -102,6 +102,7 @@
#define UUID_CANONICAL_FORM_LEN 36
#define GLUSTERFS_INTERNAL_FOP_KEY "glusterfs-internal-fop"
#define GLUSTERFS_CREATE_MODE_KEY "glusterfs-create-mode"
#define ZR_FILE_CONTENT_STR "glusterfs.file."
#define ZR_FILE_CONTENT_STRLEN 15
@ -320,6 +321,7 @@ struct _cmd_args {
int mac_compat;
int fopen_keep_cache;
int gid_timeout;
int aux_gfid_mount;
struct list_head xlator_options; /* list of xlator_option_t */
/* fuse options */
@ -348,6 +350,7 @@ struct _cmd_args {
int brick_port;
char *brick_name;
int brick_port2;
};
typedef struct _cmd_args cmd_args_t;

View File

@ -310,7 +310,7 @@ __inode_destroy (inode_t *inode)
goto noctx;
}
for (index = 0; index < inode->table->xl->graph->xl_count; index++) {
for (index = 0; index < inode->table->ctxcount; index++) {
if (inode->_ctx[index].xl_key) {
xl = (xlator_t *)(long)inode->_ctx[index].xl_key;
old_THIS = THIS;
@ -528,10 +528,9 @@ __inode_create (inode_table_t *table)
INIT_LIST_HEAD (&newi->hash);
INIT_LIST_HEAD (&newi->dentry_list);
newi->_ctx = GF_CALLOC (1, (sizeof (struct _inode_ctx) *
table->xl->graph->xl_count),
newi->_ctx = GF_CALLOC (1,
(sizeof (struct _inode_ctx) * table->ctxcount),
gf_common_mt_inode_ctx);
if (newi->_ctx == NULL) {
LOCK_DESTROY (&newi->lock);
mem_put (newi);
@ -1316,6 +1315,7 @@ inode_table_new (size_t lru_limit, xlator_t *xl)
return NULL;
new->xl = xl;
new->ctxcount = xl->graph->xl_count + 1;
new->lru_limit = lru_limit;
@ -1466,7 +1466,7 @@ __inode_ctx_set2 (inode_t *inode, xlator_t *xlator, uint64_t *value1_p,
if (!inode || !xlator)
return -1;
for (index = 0; index < xlator->graph->xl_count; index++) {
for (index = 0; index < inode->table->ctxcount; index++) {
if (!inode->_ctx[index].xl_key) {
if (set_idx == -1)
set_idx = index;
@ -1523,12 +1523,12 @@ __inode_ctx_get2 (inode_t *inode, xlator_t *xlator, uint64_t *value1,
if (!inode || !xlator)
return -1;
for (index = 0; index < xlator->graph->xl_count; index++) {
for (index = 0; index < inode->table->ctxcount; index++) {
if (inode->_ctx[index].xl_key == xlator)
break;
}
if (index == xlator->graph->xl_count) {
if (index == inode->table->ctxcount) {
ret = -1;
goto out;
}
@ -1575,12 +1575,13 @@ inode_ctx_del2 (inode_t *inode, xlator_t *xlator, uint64_t *value1,
LOCK (&inode->lock);
{
for (index = 0; index < xlator->graph->xl_count; index++) {
for (index = 0; index < inode->table->ctxcount;
index++) {
if (inode->_ctx[index].xl_key == xlator)
break;
}
if (index == xlator->graph->xl_count) {
if (index == inode->table->ctxcount) {
ret = -1;
goto unlock;
}
@ -1628,14 +1629,15 @@ inode_dump (inode_t *inode, char *prefix)
gf_proc_dump_write("ref", "%u", inode->ref);
gf_proc_dump_write("ia_type", "%d", inode->ia_type);
if (inode->_ctx) {
inode_ctx = GF_CALLOC (inode->table->xl->graph->xl_count,
inode_ctx = GF_CALLOC (inode->table->ctxcount,
sizeof (*inode_ctx),
gf_common_mt_inode_ctx);
if (inode_ctx == NULL) {
goto unlock;
}
for (i = 0; i < inode->table->xl->graph->xl_count; i++) {
for (i = 0; i < inode->table->ctxcount;
i++) {
inode_ctx[i] = inode->_ctx[i];
}
}
@ -1652,7 +1654,7 @@ unlock:
UNLOCK(&inode->lock);
if (inode_ctx && (dump_options.xl_options.dump_inodectx == _gf_true)) {
for (i = 0; i < inode->table->xl->graph->xl_count; i++) {
for (i = 0; i < inode->table->ctxcount; i++) {
if (inode_ctx[i].xl_key) {
xl = (xlator_t *)(long)inode_ctx[i].xl_key;
if (xl->dumpops && xl->dumpops->inodectx)

View File

@ -56,6 +56,7 @@ struct _inode_table {
struct mem_pool *inode_pool; /* memory pool for inodes */
struct mem_pool *dentry_pool; /* memory pool for dentrys */
struct mem_pool *fd_mem_pool; /* memory pool for fd_t */
int ctxcount; /* number of slots in inode->ctx */
};

View File

@ -1436,7 +1436,6 @@ dht_lookup (call_frame_t *frame, xlator_t *this,
VALIDATE_OR_GOTO (this, err);
VALIDATE_OR_GOTO (loc, err);
VALIDATE_OR_GOTO (loc->inode, err);
VALIDATE_OR_GOTO (loc->path, err);
conf = this->private;
if (!conf)
@ -2144,7 +2143,6 @@ dht_getxattr (call_frame_t *frame, xlator_t *this,
VALIDATE_OR_GOTO (this, err);
VALIDATE_OR_GOTO (loc, err);
VALIDATE_OR_GOTO (loc->inode, err);
VALIDATE_OR_GOTO (loc->path, err);
VALIDATE_OR_GOTO (this->private, err);
conf = this->private;
@ -2503,7 +2501,6 @@ dht_setxattr (call_frame_t *frame, xlator_t *this,
VALIDATE_OR_GOTO (this, err);
VALIDATE_OR_GOTO (loc, err);
VALIDATE_OR_GOTO (loc->inode, err);
VALIDATE_OR_GOTO (loc->path, err);
conf = this->private;
@ -2717,7 +2714,6 @@ dht_removexattr (call_frame_t *frame, xlator_t *this,
VALIDATE_OR_GOTO (frame, err);
VALIDATE_OR_GOTO (loc, err);
VALIDATE_OR_GOTO (loc->inode, err);
VALIDATE_OR_GOTO (loc->path, err);
local = dht_local_init (frame, loc, NULL, GF_FOP_REMOVEXATTR);
if (!local) {
@ -2949,7 +2945,6 @@ dht_statfs (call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xdata)
VALIDATE_OR_GOTO (this, err);
VALIDATE_OR_GOTO (loc, err);
VALIDATE_OR_GOTO (loc->inode, err);
VALIDATE_OR_GOTO (loc->path, err);
VALIDATE_OR_GOTO (this->private, err);
conf = this->private;
@ -4885,7 +4880,6 @@ dht_entrylk (call_frame_t *frame, xlator_t *this,
VALIDATE_OR_GOTO (this, err);
VALIDATE_OR_GOTO (loc, err);
VALIDATE_OR_GOTO (loc->inode, err);
VALIDATE_OR_GOTO (loc->path, err);
local = dht_local_init (frame, loc, NULL, GF_FOP_ENTRYLK);
if (!local) {

View File

@ -6,6 +6,9 @@ noinst_HEADERS_common = $(CONTRIBDIR)/fuse-include/fuse-mount.h\
$(CONTRIBDIR)/fuse-include/fuse-misc.h fuse-mem-types.h \
fuse-bridge.h
fuse_HEADERS = glfs-fuse-bridge.h
fusedir = $(includedir)/glusterfs/
if GF_DARWIN_HOST_OS
noinst_HEADERS = $(noinst_HEADERS_common) $(noinst_HEADERS_darwin)
else

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@
#include "defaults.h"
#include "common-utils.h"
#include "statedump.h"
#include "glfs-fuse-bridge.h"
#ifdef GF_DARWIN_HOST_OS
/* This is MacFUSE's marker for MacFUSE-specific code */
@ -50,12 +51,26 @@
#if defined(GF_LINUX_HOST_OS) || defined(__NetBSD__)
#define FUSE_OP_HIGH (FUSE_READDIRPLUS + 1)
#endif
#ifdef GF_DARWIN_HOST_OS
#define FUSE_OP_HIGH (FUSE_DESTROY + 1)
#endif
#define GLUSTERFS_XATTR_LEN_MAX 65536
#define MAX_FUSE_PROC_DELAY 1
#define GF_GFID_KEY "GLUSTERFS_GFID"
#define GF_GFID_DIR ".gfid"
#define GF_AUX_GFID 0xd
typedef enum {
GF_FUSE_PATH_NAMESPACE,
GF_FUSE_GFID_NAMESPACE
} gf_lookup_namespace_t;
typedef struct {
inode_t *inode_path_ns;
inode_t *inode_gfid_ns;
} gf_fuse_nodeid_t;
typedef struct fuse_in_header fuse_in_header_t;
typedef void (fuse_handler_t) (xlator_t *this, fuse_in_header_t *finh,
@ -122,6 +137,14 @@ struct fuse_private {
/* for using fuse-kernel readdirp*/
gf_boolean_t use_readdirp;
gf_boolean_t aux_gfid_mount;
/* root inode's stbuf */
struct iatt root_stbuf;
struct iatt gfiddir_stbuf;
struct mem_pool *fuse_nodeid_pool;
struct mem_pool *auxgfid_newfile_args_pool;
struct mem_pool *auxgfid_heal_args_pool;
};
typedef struct fuse_private fuse_private_t;
@ -509,7 +532,8 @@ void gf_fuse_stat2attr (struct iatt *st, struct fuse_attr *fa,
gf_boolean_t enable_ino32);
void gf_fuse_fill_dirent (gf_dirent_t *entry, struct fuse_dirent *fde,
gf_boolean_t enable_ino32);
uint64_t inode_to_fuse_nodeid (inode_t *inode);
uint64_t
inode_to_fuse_nodeid (xlator_t *this, inode_t *inode, gf_lookup_namespace_t ns);
xlator_t *fuse_active_subvol (xlator_t *fuse);
inode_t *fuse_ino_to_inode (uint64_t ino, xlator_t *fuse);
int send_fuse_err (xlator_t *this, fuse_in_header_t *finh, int error);

View File

@ -317,12 +317,31 @@ get_call_frame_for_req (fuse_state_t *state)
return frame;
}
inode_t *
fuse_ino_to_inode_gfid_mount (uint64_t ino, xlator_t *fuse)
{
inode_t **ptr = NULL, *inode = NULL;
xlator_t *active_subvol = NULL;
if (ino == 1) {
active_subvol = fuse_active_subvol (fuse);
if (active_subvol)
inode = active_subvol->itable->root;
} else {
ptr = (void *)ino;
if (ptr != NULL)
inode = inode_ref (*ptr);
}
return inode;
}
inode_t *
fuse_ino_to_inode (uint64_t ino, xlator_t *fuse)
fuse_ino_to_inode_normal_mount (uint64_t ino, xlator_t *fuse)
{
inode_t *inode = NULL;
xlator_t *active_subvol = NULL;
inode_t *inode = NULL;
if (ino == 1) {
active_subvol = fuse_active_subvol (fuse);
@ -336,17 +355,98 @@ fuse_ino_to_inode (uint64_t ino, xlator_t *fuse)
return inode;
}
uint64_t
inode_to_fuse_nodeid (inode_t *inode)
inode_t *
fuse_ino_to_inode (uint64_t ino, xlator_t *fuse)
{
inode_t *inode = NULL;
fuse_private_t *priv = NULL;
priv = fuse->private;
if (priv->aux_gfid_mount) {
inode = fuse_ino_to_inode_gfid_mount (ino, fuse);
} else {
inode = fuse_ino_to_inode_normal_mount (ino, fuse);
}
return inode;
}
inline uint64_t
inode_to_fuse_nodeid_gfid_mount (xlator_t *this, inode_t *inode,
gf_lookup_namespace_t ns)
{
inode_t **ptr = NULL;
gf_fuse_nodeid_t *nodeid = NULL;
fuse_private_t *priv = NULL;
uint64_t value = 0;
int32_t ret = 0;
priv = this->private;
LOCK (&inode->lock);
{
__inode_ctx_get (inode, this, &value);
nodeid = (void *)value;
if (nodeid == NULL) {
nodeid = mem_get0 (priv->fuse_nodeid_pool);
if (nodeid == NULL)
goto unlock;
ret = __inode_ctx_set (inode, this, (uint64_t *)nodeid);
if (ret < 0)
goto unlock;
nodeid->inode_path_ns = nodeid->inode_gfid_ns = inode;
}
}
unlock:
UNLOCK (&inode->lock);
if (ret < 0) {
mem_put (nodeid);
nodeid = NULL;
}
if (nodeid != NULL) {
if (ns == GF_FUSE_GFID_NAMESPACE)
ptr = &nodeid->inode_gfid_ns;
else
ptr = &nodeid->inode_path_ns;
}
return (uint64_t) ptr;
}
inline uint64_t
inode_to_fuse_nodeid_normal_mount (inode_t *inode)
{
if (!inode)
return 0;
if (__is_root_gfid (inode->gfid))
return 1;
return (unsigned long) inode;
}
uint64_t
inode_to_fuse_nodeid (xlator_t *this, inode_t *inode, gf_lookup_namespace_t ns)
{
fuse_private_t *priv = NULL;
uint64_t ino = 0;
priv = this->private;
if (!inode)
return 0;
if (priv->aux_gfid_mount)
ino = inode_to_fuse_nodeid_gfid_mount (this, inode, ns);
else
ino = inode_to_fuse_nodeid_normal_mount (inode);
return ino;
}
GF_MUST_CHECK int32_t
fuse_loc_fill (loc_t *loc, fuse_state_t *state, ino_t ino,
@ -580,7 +680,8 @@ fuse_ignore_xattr_set (fuse_private_t *priv, char *key)
|| (fnmatch ("*.glusterfs.volume-mark",
key, FNM_PERIOD) == 0)
|| (fnmatch ("*.glusterfs.volume-mark.*",
key, FNM_PERIOD) == 0)))
key, FNM_PERIOD) == 0)
|| (fnmatch ("glusterfs.gfid.newfile", key, FNM_PERIOD) == 0)))
ret = -1;
out:

View File

@ -0,0 +1,53 @@
/*
Copyright (c) 2006-2012 Red Hat, Inc. <http://www.redhat.com>
This file is part of GlusterFS.
This file is licensed to you under your choice of the GNU Lesser
General Public License, version 3 or any later version (LGPLv3 or
later), or the GNU General Public License, version 2 (GPLv2), in all
cases as published by the Free Software Foundation.
*/
#ifndef _GLFS_FUSE_BRIDGE_H
#define _GLFS_FUSE_BRIDGE_H
#define UUID_CANONICAL_FORM_LEN 36
#define GF_FUSE_AUX_GFID_NEWFILE "glusterfs.gfid.newfile"
#define GF_FUSE_AUX_GFID_HEAL "glusterfs.gfid.heal"
struct auxfuse_symlink_in {
char *linkpath;
} __attribute__ ((__packed__));
struct auxfuse_mknod_in {
unsigned int mode;
unsigned int rdev;
unsigned int umask;
} __attribute__ ((__packed__));
struct auxfuse_mkdir_in {
unsigned int mode;
unsigned int umask;
} __attribute__ ((__packed__));
typedef struct {
unsigned int uid;
unsigned int gid;
char gfid[UUID_CANONICAL_FORM_LEN + 1];
unsigned int st_mode;
char *bname;
union {
struct auxfuse_mkdir_in mkdir;
struct auxfuse_mknod_in mknod;
struct auxfuse_symlink_in symlink;
} __attribute__ ((__packed__)) args;
} __attribute__((__packed__)) fuse_auxgfid_newfile_args_t;
typedef struct {
char gfid[UUID_CANONICAL_FORM_LEN + 1];
char *bname; /* a null terminated basename */
} __attribute__((__packed__)) fuse_auxgfid_heal_args_t;
#endif

View File

@ -122,6 +122,10 @@ start_glusterfs ()
cmd_line=$(echo "$cmd_line --mem-accounting");
fi
if [ -n "$aux_gfid_mount" ]; then
cmd_line=$(echo "$cmd_line --aux-gfid-mount");
fi
#options with values start here
if [ -n "$log_level" ]; then
cmd_line=$(echo "$cmd_line --log-level=$log_level");
@ -268,13 +272,13 @@ mount.glusterfs --version"
# check for recursive mounts. i.e, mounting over an existing brick
check_recursive_mount ()
{
if [ $2 = "/" ]; then
if [ $1 = "/" ]; then
echo Cannot mount over root;
exit 2;
fi
# GFID check first
# remove trailing / from mount point
mnt_dir=${2%/};
mnt_dir=${1%/};
export PATH;
# check whether getfattr exists
@ -333,7 +337,6 @@ check_recursive_mount ()
main ()
{
helper=$(echo "$@" | sed -n 's/.*\--[ ]*\([^ ]*\).*/\1/p');
in_opt="no"
pos_args=0
for opt in "$@"; do
@ -348,6 +351,11 @@ main ()
"fopen-keep-cache") fopen_keep_cache=1 ;;
"enable-ino32") enable_ino32=1 ;;
"mem-accounting") mem_accounting=1;;
"aux-gfid-mount")
if [ `uname -s` = "Linux" ]; then
aux_gfid_mount=1
fi
;;
# "mount -t glusterfs" sends this, but it's useless.
"rw") ;;
# these ones are interpreted during system initialization
@ -437,7 +445,7 @@ main ()
exit 0;
fi
check_recursive_mount "$@";
check_recursive_mount "$mount_point";
# Append fuse.glusterfs to PRUNEFS variable in updatedb.conf(5). updatedb(8)
# should not index files under GlusterFS, indexing will slow down GlusteFS

View File

@ -842,6 +842,7 @@ posix_mknod (call_frame_t *frame, xlator_t *this,
struct iatt preparent = {0,};
struct iatt postparent = {0,};
void * uuid_req = NULL;
mode_t st_mode = 0;
DECLARE_OLD_FS_ID_VAR;
@ -917,6 +918,19 @@ real_op:
strerror (op_errno));
goto out;
}
} else {
op_ret = dict_get_uint32 (xdata, GLUSTERFS_CREATE_MODE_KEY,
&st_mode);
if (op_ret >= 0) {
op_ret = chmod (real_path, st_mode);
if (op_ret < 0) {
gf_log (this->name, GF_LOG_WARNING,
"chmod failed (%s)", strerror (errno));
}
dict_del (xdata, GLUSTERFS_CREATE_MODE_KEY);
}
}
op_ret = posix_gfid_set (this, real_path, loc, xdata);