The branch, master has been updated
via 8aaa73e5e90 vfs_fruit: Call fruit_fstatat() from fruit_[l]stat()
via 720193220bc vfs_fruit: Implement fstatat
via 01a3c14a083 lib: Add adouble_buf_parse()
via 91a75272058 vfs_fruit: Use all_zero() to check for an all-0 buffer
via b8ce454a69e vfs_fruit: Fix signed/unsigned comparison warnings
via edf7009f0d5 vfs_fruit: Modernize a DEBUG
via 572e5372da8 lib: Simplify data definitions
via 9f1dd0bbfa6 lib: Avoid a talloc_zero in afpinfo_new()
via c4d60b712bf vfs_fruit: Make struct allocation in
fruit_freaddir_attr() more common
via c45b78d5849 lib: Fix typos
via e1059e7baa5 lib: Slightly simplify ad_read_rsrc_adouble()
via c5a070a9b06 vfs_fruit: Slightly simplify
readdir_attr_meta_finderi_stream()
from 708ae38a76a mdssvc: call mangle_reset_cache()
https://git.samba.org/?p=samba.git;a=shortlog;h=master
- Log -----------------------------------------------------------------
commit 8aaa73e5e90ce8ae82d6c88a1ac4d8b77df44d31
Author: Volker Lendecke <[email protected]>
Date: Thu Oct 2 21:56:59 2025 +0200
vfs_fruit: Call fruit_fstatat() from fruit_[l]stat()
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
Autobuild-User(master): Volker Lendecke <[email protected]>
Autobuild-Date(master): Wed Oct 8 09:02:25 UTC 2025 on atb-devel-224
commit 720193220bc2bb48ae40ea0a19a4dab63d87ac19
Author: Volker Lendecke <[email protected]>
Date: Thu Oct 2 12:45:18 2025 +0200
vfs_fruit: Implement fstatat
This violates the abstraction in adouble.[ch], but passing "dirfsp"
and "relname" through ad_get() & friends would have been a more churn,
and with this violation of abstraction we only do fgetxattr once where
with a separate update_btime we do it twice. So in theory it should be
more efficient.
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit 01a3c14a0837d3f45ad5603740c26ddf8265a292
Author: Volker Lendecke <[email protected]>
Date: Thu Oct 2 10:38:24 2025 +0200
lib: Add adouble_buf_parse()
Simplified version of ad_get that takes a buffer and does basic parsing of
an
AppleDouble file format. The entries are represented as DATA_BLOBs directly
pointing at "buf" to avoid offset calculations in users of this.
Yes, this is a duplication of logic, but it makes the next patch
possible. Future patches could use this in ad_unpack()
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit 91a75272058186f81896f2ecf508500094115db6
Author: Volker Lendecke <[email protected]>
Date: Wed Oct 1 17:23:16 2025 +0200
vfs_fruit: Use all_zero() to check for an all-0 buffer
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit b8ce454a69e0ace72d77dc1a1ecc690c08c7debb
Author: Volker Lendecke <[email protected]>
Date: Tue Sep 30 12:24:51 2025 +0200
vfs_fruit: Fix signed/unsigned comparison warnings
It also factors out the tevent_req_post() in pread/pwrite_send()
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit edf7009f0d591b132e7ac5eca878c8cc6077cb56
Author: Volker Lendecke <[email protected]>
Date: Tue Sep 30 11:35:56 2025 +0200
vfs_fruit: Modernize a DEBUG
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit 572e5372da8181cdb35bfbd8c56bad2b67d0da2f
Author: Volker Lendecke <[email protected]>
Date: Mon Sep 29 21:40:58 2025 +0200
lib: Simplify data definitions
C calculates the array size itself
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit 9f1dd0bbfa685e7c8376fba9ecab6cf0880efd4b
Author: Volker Lendecke <[email protected]>
Date: Mon Sep 29 19:25:41 2025 +0200
lib: Avoid a talloc_zero in afpinfo_new()
Use a struct assignment.
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit c4d60b712bfa32f9cdad3cf570a4862461342e12
Author: Volker Lendecke <[email protected]>
Date: Mon Sep 29 14:33:18 2025 +0200
vfs_fruit: Make struct allocation in fruit_freaddir_attr() more common
Just assign the output buffer on success
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit c45b78d58497dd5dec38eb5aee3e3d2727c899ce
Author: Volker Lendecke <[email protected]>
Date: Sat Sep 27 09:59:33 2025 +0200
lib: Fix typos
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit e1059e7baa569952a4de68b0d6b5ae48b8d82f52
Author: Volker Lendecke <[email protected]>
Date: Thu Oct 2 11:46:40 2025 +0200
lib: Slightly simplify ad_read_rsrc_adouble()
We have the MIN() macro for this
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
commit c5a070a9b064370f78cb91a2f422da1feefef215
Author: Volker Lendecke <[email protected]>
Date: Mon Sep 29 13:38:55 2025 +0200
vfs_fruit: Slightly simplify readdir_attr_meta_finderi_stream()
"&buf[0]" is equivalent to just "buf" in this case
Signed-off-by: Volker Lendecke <[email protected]>
Reviewed-by: Ralph Boehme <[email protected]>
-----------------------------------------------------------------------
Summary of changes:
source3/lib/adouble.c | 83 ++++++-
source3/lib/adouble.h | 10 +
source3/modules/vfs_fruit.c | 572 ++++++++++++++++++++++----------------------
3 files changed, 366 insertions(+), 299 deletions(-)
Changeset truncated at 500 lines:
diff --git a/source3/lib/adouble.c b/source3/lib/adouble.c
index bc48c9d8e2e..0cea2ba23fe 100644
--- a/source3/lib/adouble.c
+++ b/source3/lib/adouble.c
@@ -183,7 +183,7 @@ struct ad_entry_order {
/* Netatalk AppleDouble metadata xattr */
static const
-struct ad_entry_order entry_order_meta_xattr[ADEID_NUM_XATTR + 1] = {
+struct ad_entry_order entry_order_meta_xattr[] = {
{ADEID_FINDERI, ADEDOFF_FINDERI_XATTR, ADEDLEN_FINDERI},
{ADEID_COMMENT, ADEDOFF_COMMENT_XATTR, 0},
{ADEID_FILEDATESI, ADEDOFF_FILEDATESI_XATTR, ADEDLEN_FILEDATESI},
@@ -197,7 +197,7 @@ struct ad_entry_order
entry_order_meta_xattr[ADEID_NUM_XATTR + 1] = {
/* AppleDouble resource fork file (the ones prefixed by "._") */
static const
-struct ad_entry_order entry_order_dot_und[ADEID_NUM_DOT_UND + 1] = {
+struct ad_entry_order entry_order_dot_und[] = {
{ADEID_FINDERI, ADEDOFF_FINDERI_DOT_UND, ADEDLEN_FINDERI},
{ADEID_RFORK, ADEDOFF_RFORK_DOT_UND, 0},
{0, 0, 0}
@@ -271,8 +271,8 @@ size_t ad_setentryoff(struct adouble *ad, int eid, size_t
off)
/*
* All entries besides FinderInfo and resource fork must fit into the
- * buffer. FinderInfo is special as it may be larger then the default 32 bytes
- * if it contains marshalled xattrs, which we will fixup that in
+ * buffer. FinderInfo is special as it may be larger than the default 32 bytes
+ * if it contains marshalled xattrs, which we will fix up in
* ad_convert(). The first 32 bytes however must also be part of the buffer.
*
* The resource fork is never accessed directly by the ad_data buf.
@@ -2369,9 +2369,7 @@ static ssize_t ad_read_rsrc_adouble(vfs_handle_struct
*handle,
}
to_read = ad->ad_fsp->fsp_name->st.st_ex_size;
- if (to_read > AD_XATTR_MAX_HDR_SIZE) {
- to_read = AD_XATTR_MAX_HDR_SIZE;
- }
+ to_read = MIN(to_read, AD_XATTR_MAX_HDR_SIZE);
len = SMB_VFS_NEXT_PREAD(handle,
ad->ad_fsp,
@@ -2808,13 +2806,15 @@ struct smb_filename *adouble_name(TALLOC_CTX *mem_ctx,
**/
AfpInfo *afpinfo_new(TALLOC_CTX *ctx)
{
- AfpInfo *ai = talloc_zero(ctx, AfpInfo);
+ AfpInfo *ai = talloc(ctx, AfpInfo);
if (ai == NULL) {
return NULL;
}
- ai->afpi_Signature = AFP_Signature;
- ai->afpi_Version = AFP_Version;
- ai->afpi_BackupTime = AD_DATE_START;
+ *ai = (AfpInfo){
+ .afpi_Signature = AFP_Signature,
+ .afpi_Version = AFP_Version,
+ .afpi_BackupTime = AD_DATE_START,
+ };
return ai;
}
@@ -2869,3 +2869,64 @@ AfpInfo *afpinfo_unpack(TALLOC_CTX *ctx, const void
*data, bool validate)
return ai;
}
+
+bool adouble_buf_parse(const uint8_t *buf,
+ size_t buflen,
+ struct adouble_buf *ad)
+{
+ size_t i, nentries;
+
+ if (buflen < AD_HEADER_LEN) {
+ return false;
+ }
+
+ *ad = (struct adouble_buf){
+ .magic = PULL_BE_U32(buf, ADEDOFF_MAGIC),
+ .version = PULL_BE_U32(buf, ADEDOFF_VERSION),
+ };
+
+ if ((ad->magic != AD_MAGIC) || (ad->version != AD_VERSION)) {
+ return false;
+ }
+
+ nentries = PULL_BE_U16(buf, ADEDOFF_NENTRIES);
+
+ /*
+ * no overflow, nentries is just 16 bits
+ */
+
+ if ((AD_HEADER_LEN + (AD_ENTRY_LEN * (size_t)nentries)) > buflen) {
+ return false;
+ }
+
+ for (i = 0; i < nentries; i++) {
+ size_t eoff = AD_HEADER_LEN + i * AD_ENTRY_LEN;
+ uint32_t id = get_eid(PULL_BE_U32(buf, eoff));
+ uint32_t off = PULL_BE_U32(buf, eoff + 4);
+ uint32_t len = PULL_BE_U32(buf, eoff + 8);
+ bool ok;
+
+ if ((id == 0) || (id >= ADEID_MAX)) {
+ return false;
+ }
+
+ ok = ad_entry_check_size(id, buflen, off, len);
+ if (!ok) {
+ return false;
+ }
+
+ if (ad->entries[id].data != NULL) {
+ /*
+ * Duplicate id
+ */
+ return false;
+ }
+
+ ad->entries[i] = (DATA_BLOB){
+ .data = discard_const_p(uint8_t, buf) + off,
+ .length = len,
+ };
+ }
+
+ return true;
+}
diff --git a/source3/lib/adouble.h b/source3/lib/adouble.h
index 3879b79fed1..8fd4b7e69b6 100644
--- a/source3/lib/adouble.h
+++ b/source3/lib/adouble.h
@@ -191,4 +191,14 @@ AfpInfo *afpinfo_new(TALLOC_CTX *ctx);
ssize_t afpinfo_pack(const AfpInfo *ai, char *buf);
AfpInfo *afpinfo_unpack(TALLOC_CTX *ctx, const void *data, bool validate);
+struct adouble_buf {
+ uint32_t magic;
+ uint32_t version;
+ DATA_BLOB entries[ADEID_MAX];
+};
+
+bool adouble_buf_parse(const uint8_t *buf,
+ size_t buflen,
+ struct adouble_buf *dst);
+
#endif
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
index b274274d46c..4ef7a68a30f 100644
--- a/source3/modules/vfs_fruit.c
+++ b/source3/modules/vfs_fruit.c
@@ -470,8 +470,6 @@ static bool del_fruit_stream(TALLOC_CTX *mem_ctx, unsigned
int *num_streams,
static bool ad_empty_finderinfo(const struct adouble *ad)
{
- int cmp;
- char emptybuf[ADEDLEN_FINDERI] = {0};
char *fi = NULL;
fi = ad_get_entry(ad, ADEID_FINDERI);
@@ -479,59 +477,12 @@ static bool ad_empty_finderinfo(const struct adouble *ad)
DBG_ERR("Missing FinderInfo in struct adouble [%p]\n", ad);
return false;
}
-
- cmp = memcmp(emptybuf, fi, ADEDLEN_FINDERI);
- return (cmp == 0);
+ return all_zero((const uint8_t *)fi, ADEDLEN_FINDERI);
}
static bool ai_empty_finderinfo(const AfpInfo *ai)
{
- int cmp;
- char emptybuf[ADEDLEN_FINDERI] = {0};
-
- cmp = memcmp(emptybuf, &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI);
- return (cmp == 0);
-}
-
-/**
- * Update btime with btime from Netatalk
- **/
-static void update_btime(vfs_handle_struct *handle,
- struct smb_filename *smb_fname)
-{
- uint32_t t;
- struct timespec creation_time = {0};
- struct adouble *ad;
- struct fruit_config_data *config = NULL;
-
- SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
- return);
-
- switch (config->meta) {
- case FRUIT_META_STREAM:
- return;
- case FRUIT_META_NETATALK:
- /* Handled below */
- break;
- default:
- DBG_ERR("Unexpected meta config [%d]\n", config->meta);
- return;
- }
-
- ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname);
- if (ad == NULL) {
- return;
- }
- if (ad_getdate(ad, AD_DATE_UNIX | AD_DATE_CREATE, &t) != 0) {
- TALLOC_FREE(ad);
- return;
- }
- TALLOC_FREE(ad);
-
- creation_time.tv_sec = convert_uint32_t_to_time_t(t);
- update_stat_ex_create_time(&smb_fname->st, creation_time);
-
- return;
+ return all_zero(&ai->afpi_FinderInfo[0], ADEDLEN_FINDERI);
}
/**
@@ -1001,7 +952,7 @@ static bool readdir_attr_meta_finderi_stream(
return false;
}
- nread = SMB_VFS_PREAD(fsp, &buf[0], AFP_INFO_SIZE, 0);
+ nread = SMB_VFS_PREAD(fsp, buf, sizeof(buf), 0);
if (nread != AFP_INFO_SIZE) {
DBG_ERR("short read [%s] [%zd/%d]\n",
smb_fname_str_dbg(stream_name), nread, AFP_INFO_SIZE);
@@ -2371,7 +2322,7 @@ static ssize_t fruit_pread_meta_stream(vfs_handle_struct
*handle,
}
nread = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
- if (nread == -1 || nread == n) {
+ if (nread == -1 || ((size_t)nread == n)) {
return nread;
}
@@ -2646,14 +2597,13 @@ static struct tevent_req *fruit_pread_send(
if (fruit_must_handle_aio_stream(fio)) {
state->nread = SMB_VFS_PREAD(fsp, data, n, offset);
- if (state->nread != n) {
- if (state->nread != -1) {
- errno = EIO;
- }
+ if (state->nread == -1) {
tevent_req_error(req, errno);
- return tevent_req_post(req, ev);
+ } else if ((size_t)state->nread != n) {
+ tevent_req_error(req, EIO);
+ } else {
+ tevent_req_done(req);
}
- tevent_req_done(req);
return tevent_req_post(req, ev);
}
@@ -2946,7 +2896,7 @@ static ssize_t fruit_pwrite_meta(vfs_handle_struct
*handle,
return -1;
}
- if (nwritten != to_write) {
+ if ((size_t)nwritten != to_write) {
return -1;
}
@@ -2994,7 +2944,7 @@ static ssize_t
fruit_pwrite_rsrc_adouble(vfs_handle_struct *handle,
nwritten = SMB_VFS_NEXT_PWRITE(handle, fio->ad_fsp, data, n,
offset + ad_getentryoff(ad,
ADEID_RFORK));
- if (nwritten != n) {
+ if ((nwritten == -1) || ((size_t)nwritten != n)) {
DBG_ERR("Short write on [%s] [%zd/%zd]\n",
fsp_str_dbg(fio->ad_fsp), nwritten, n);
TALLOC_FREE(ad);
@@ -3100,14 +3050,13 @@ static struct tevent_req *fruit_pwrite_send(
if (fruit_must_handle_aio_stream(fio)) {
state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset);
- if (state->nwritten != n) {
- if (state->nwritten != -1) {
- errno = EIO;
- }
+ if (state->nwritten == -1) {
tevent_req_error(req, errno);
- return tevent_req_post(req, ev);
+ } else if ((size_t)state->nwritten != n) {
+ tevent_req_error(req, EIO);
+ } else {
+ tevent_req_done(req);
}
- tevent_req_done(req);
return tevent_req_post(req, ev);
}
@@ -3254,300 +3203,346 @@ static int fruit_fsync_recv(struct tevent_req *req,
return retval;
}
-/**
- * Helper to stat/lstat the base file of an smb_fname.
- */
-static int fruit_stat_base(vfs_handle_struct *handle,
- struct smb_filename *smb_fname,
- bool follow_links)
+static int fruit_fstatat_meta(struct vfs_handle_struct *handle,
+ const struct fruit_config_data *config,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *_smb_relname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
{
- char *tmp_stream_name;
- int rc;
+ struct adouble_buf ad = {};
+ struct smb_filename *smb_relname = NULL;
+ uint8_t ad_data[402];
+ NTSTATUS status;
+ int ret = -1;
+ ssize_t ealen;
+ ino_t ino;
+ bool ok;
- tmp_stream_name = smb_fname->stream_name;
- smb_fname->stream_name = NULL;
- if (follow_links) {
- rc = SMB_VFS_NEXT_STAT(handle, smb_fname);
- } else {
- rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
- }
- smb_fname->stream_name = tmp_stream_name;
+ {
+ /* Populate the stat struct with info from the base file. */
- DBG_DEBUG("[%s] dev [%ju] ino [%ju]\n",
- smb_fname->base_name,
- (uintmax_t)smb_fname->st.st_ex_dev,
- (uintmax_t)smb_fname->st.st_ex_ino);
- return rc;
-}
+ struct smb_filename base_name = *smb_relname;
+ base_name.stream_name = NULL;
-static int fruit_stat_meta_stream(vfs_handle_struct *handle,
- struct smb_filename *smb_fname,
- bool follow_links)
-{
- int ret;
- ino_t ino;
+ ret = SMB_VFS_NEXT_FSTATAT(
+ handle, dirfsp, &base_name, sbuf, flags);
+ }
- ret = fruit_stat_base(handle, smb_fname, false);
- if (ret != 0) {
- return -1;
+ if (ret == -1) {
+ goto fail;
}
- ino = hash_inode(&smb_fname->st, smb_fname->stream_name);
+ ino = hash_inode(sbuf, smb_relname->stream_name);
- if (follow_links) {
- ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
- } else {
- ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (config->meta == FRUIT_META_STREAM) {
+ ret = SMB_VFS_NEXT_FSTATAT(
+ handle, dirfsp, smb_relname, sbuf, flags);
+ if (ret == -1) {
+ goto fail;
+ }
+ sbuf->st_ex_ino = ino;
+ goto done;
}
- smb_fname->st.st_ex_ino = ino;
+ if (config->meta != FRUIT_META_NETATALK) {
+ DBG_ERR("Unexpected meta config [%d]\n", config->meta);
+ errno = EINVAL;
+ goto fail;
+ }
- return ret;
-}
+ sbuf->st_ex_ino = ino;
+ sbuf->st_ex_size = AFP_INFO_SIZE;
-static int fruit_stat_meta_netatalk(vfs_handle_struct *handle,
- struct smb_filename *smb_fname,
- bool follow_links)
-{
- struct adouble *ad = NULL;
+ /*
+ * FRUIT_META_NETATALK
+ */
- /* Populate the stat struct with info from the base file. */
- if (fruit_stat_base(handle, smb_fname, follow_links) == -1) {
- return -1;
+ smb_relname = cp_smb_filename_nostream(talloc_tos(), _smb_relname);
+ if (smb_relname == NULL) {
+ errno = ENOMEM;
+ goto fail;
}
- ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname);
- if (ad == NULL) {
- DBG_INFO("fruit_stat_meta %s: %s\n",
- smb_fname_str_dbg(smb_fname), strerror(errno));
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+ status = openat_pathref_fsp_lcomp(
+ discard_const_p(struct files_struct, dirfsp),
+ smb_relname,
+ 0);
+ } else {
+ status = openat_pathref_fsp(dirfsp, smb_relname);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
errno = ENOENT;
- return -1;
+ goto fail;
}
- TALLOC_FREE(ad);
- smb_fname->st.st_ex_size = AFP_INFO_SIZE;
- smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st,
- smb_fname->stream_name);
- return 0;
-}
+ ealen = SMB_VFS_FGETXATTR(
+ smb_relname->fsp, AFPINFO_EA_NETATALK, ad_data,
+ sizeof(ad_data));
+ if (ealen == -1) {
+ if (errno == ENOATTR) {
+ errno = ENOENT;
+ }
+ goto fail;
+ }
-static int fruit_stat_meta(vfs_handle_struct *handle,
- struct smb_filename *smb_fname,
- bool follow_links)
-{
- struct fruit_config_data *config = NULL;
- int ret;
+ ok = adouble_buf_parse(ad_data, ealen, &ad);
+ if (!ok) {
+ errno = EIO;
+ goto fail;
+ }
- SMB_VFS_HANDLE_GET_DATA(handle, config,
- struct fruit_config_data, return -1);
+ if (ad.entries[ADEID_FILEDATESI].data != NULL) {
+ /*
+ * Update btime
+ */
+ struct timespec btime = {};
+ uint32_t ad_btime;
- switch (config->meta) {
- case FRUIT_META_STREAM:
- ret = fruit_stat_meta_stream(handle, smb_fname, follow_links);
- break;
+ memcpy(&ad_btime,
+ ad.entries[ADEID_FILEDATESI].data,
+ sizeof(ad_btime));
+ ad_btime = AD_DATE_TO_UNIX(ad_btime);
+ btime.tv_sec = convert_uint32_t_to_time_t(ad_btime);
+ update_stat_ex_create_time(sbuf, btime);
+ }
- case FRUIT_META_NETATALK:
- ret = fruit_stat_meta_netatalk(handle, smb_fname, follow_links);
- break;
+done:
+ TALLOC_FREE(smb_relname);
+ return 0;
- default:
- DBG_ERR("Unexpected meta config [%d]\n", config->meta);
- return -1;
+fail:
+ {
+ int err = errno;
+ TALLOC_FREE(smb_relname);
+ errno = err;
}
-
- return ret;
+ return -1;
}
-static int fruit_stat_rsrc_netatalk(vfs_handle_struct *handle,
--
Samba Shared Repository