From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/modules/vfs_fruit.c | 5478 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5478 insertions(+) create mode 100644 source3/modules/vfs_fruit.c (limited to 'source3/modules/vfs_fruit.c') diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c new file mode 100644 index 0000000..4ab0f46 --- /dev/null +++ b/source3/modules/vfs_fruit.c @@ -0,0 +1,5478 @@ +/* + * OS X and Netatalk interoperability VFS module for Samba-3.x + * + * Copyright (C) Ralph Boehme, 2013, 2014 + * + * This program 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. + * + * This program 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 . + */ + +#include "includes.h" +#include "MacExtensions.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include "lib/util/time.h" +#include "system/shmem.h" +#include "locking/proto.h" +#include "smbd/globals.h" +#include "messages.h" +#include "libcli/security/security.h" +#include "../libcli/smb/smb2_create_ctx.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/tevent_unix.h" +#include "offload_token.h" +#include "string_replace.h" +#include "hash_inode.h" +#include "lib/adouble.h" +#include "lib/util_macstreams.h" +#include "source3/smbd/dir.h" + +/* + * Enhanced OS X and Netatalk compatibility + * ======================================== + * + * This modules takes advantage of vfs_streams_xattr and + * vfs_catia. VFS modules vfs_fruit and vfs_streams_xattr must be + * loaded in the correct order: + * + * vfs modules = catia fruit streams_xattr + * + * The module intercepts the OS X special streams "AFP_AfpInfo" and + * "AFP_Resource" and handles them in a special way. All other named + * streams are deferred to vfs_streams_xattr. + * + * The OS X client maps all NTFS illegal characters to the Unicode + * private range. This module optionally stores the characters using + * their native ASCII encoding using vfs_catia. If you're not enabling + * this feature, you can skip catia from vfs modules. + * + * Finally, open modes are optionally checked against Netatalk AFP + * share modes. + * + * The "AFP_AfpInfo" named stream is a binary blob containing OS X + * extended metadata for files and directories. This module optionally + * reads and stores this metadata in a way compatible with Netatalk 3 + * which stores the metadata in an EA "org.netatalk.metadata". Cf + * source3/include/MacExtensions.h for a description of the binary + * blobs content. + * + * The "AFP_Resource" named stream may be arbitrarily large, thus it + * can't be stored in an xattr on most filesystem. ZFS on Solaris is + * the only available filesystem where xattrs can be of any size and + * the OS supports using the file APIs for xattrs. + * + * The AFP_Resource stream is stored in an AppleDouble file prepending + * "._" to the filename. On Solaris with ZFS the stream is optionally + * stored in an EA "org.netatalk.resource". + * + * + * Extended Attributes + * =================== + * + * The OS X SMB client sends xattrs as ADS too. For xattr interop with + * other protocols you may want to adjust the xattr names the VFS + * module vfs_streams_xattr uses for storing ADS's. This defaults to + * user.DosStream.ADS_NAME:$DATA and can be changed by specifying + * these module parameters: + * + * streams_xattr:prefix = user. + * streams_xattr:store_stream_type = false + * + * + * TODO + * ==== + * + * - log diagnostic if any needed VFS module is not loaded + * (eg with lp_vfs_objects()) + * - add tests + */ + +static int vfs_fruit_debug_level = DBGC_VFS; + +static struct global_fruit_config { + bool nego_aapl; /* client negotiated AAPL */ + +} global_fruit_config; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_fruit_debug_level + +#define FRUIT_PARAM_TYPE_NAME "fruit" + +enum apple_fork {APPLE_FORK_DATA, APPLE_FORK_RSRC}; + +enum fruit_rsrc {FRUIT_RSRC_STREAM, FRUIT_RSRC_ADFILE, FRUIT_RSRC_XATTR}; +enum fruit_meta {FRUIT_META_STREAM, FRUIT_META_NETATALK}; +enum fruit_locking {FRUIT_LOCKING_NETATALK, FRUIT_LOCKING_NONE}; +enum fruit_encoding {FRUIT_ENC_NATIVE, FRUIT_ENC_PRIVATE}; + +struct fruit_config_data { + enum fruit_rsrc rsrc; + enum fruit_meta meta; + enum fruit_locking locking; + enum fruit_encoding encoding; + bool use_aapl; /* config from smb.conf */ + bool use_copyfile; + bool readdir_attr_enabled; + bool unix_info_enabled; + bool copyfile_enabled; + bool veto_appledouble; + bool posix_rename; + bool aapl_zero_file_id; + const char *model; + bool time_machine; + off_t time_machine_max_size; + bool convert_adouble; + bool wipe_intentionally_left_blank_rfork; + bool delete_empty_adfiles; + bool validate_afpinfo; + + /* + * Additional options, all enabled by default, + * possibly useful for analyzing performance. The associated + * operations with each of them may be expensive, so having + * the chance to disable them individually gives a chance + * tweaking the setup for the particular usecase. + */ + bool readdir_attr_rsize; + bool readdir_attr_finder_info; + bool readdir_attr_max_access; + /* Recursion guard. Will go away when we have STATX. */ + bool in_openat_pathref_fsp; +}; + +static const struct enum_list fruit_rsrc[] = { + {FRUIT_RSRC_STREAM, "stream"}, /* pass on to vfs_streams_xattr */ + {FRUIT_RSRC_ADFILE, "file"}, /* ._ AppleDouble file */ + {FRUIT_RSRC_XATTR, "xattr"}, /* Netatalk compatible xattr (ZFS only) */ + { -1, NULL} +}; + +static const struct enum_list fruit_meta[] = { + {FRUIT_META_STREAM, "stream"}, /* pass on to vfs_streams_xattr */ + {FRUIT_META_NETATALK, "netatalk"}, /* Netatalk compatible xattr */ + { -1, NULL} +}; + +static const struct enum_list fruit_locking[] = { + {FRUIT_LOCKING_NETATALK, "netatalk"}, /* synchronize locks with Netatalk */ + {FRUIT_LOCKING_NONE, "none"}, + { -1, NULL} +}; + +static const struct enum_list fruit_encoding[] = { + {FRUIT_ENC_NATIVE, "native"}, /* map unicode private chars to ASCII */ + {FRUIT_ENC_PRIVATE, "private"}, /* keep unicode private chars */ + { -1, NULL} +}; + +struct fio { + vfs_handle_struct *handle; + files_struct *fsp; /* backlink to itself */ + + /* tcon config handle */ + struct fruit_config_data *config; + + /* Backend fsp for AppleDouble file, can be NULL */ + files_struct *ad_fsp; + /* link from adouble_open_from_base_fsp() to fio */ + struct fio *real_fio; + + /* Denote stream type, meta or rsrc */ + adouble_type_t type; + + /* + * AFP_AfpInfo stream created, but not written yet, thus still a fake + * pipe fd. This is set to true in fruit_open_meta if there was no + * existing stream but the caller requested O_CREAT. It is later set to + * false when we get a write on the stream that then does open and + * create the stream. + */ + bool fake_fd; + int flags; + int mode; +}; + +/***************************************************************************** + * Helper functions + *****************************************************************************/ + +static struct adouble *ad_get_meta_fsp(TALLOC_CTX *ctx, + vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + NTSTATUS status; + struct adouble *ad = NULL; + struct smb_filename *smb_fname_cp = NULL; + struct fruit_config_data *config = NULL; + + if (smb_fname->fsp != NULL) { + return ad_get(ctx, handle, smb_fname, ADOUBLE_META); + } + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct fruit_config_data, + return NULL); + + if (config->in_openat_pathref_fsp) { + return NULL; + } + + smb_fname_cp = cp_smb_filename(ctx, + smb_fname); + if (smb_fname_cp == NULL) { + return NULL; + } + TALLOC_FREE(smb_fname_cp->stream_name); + config->in_openat_pathref_fsp = true; + status = openat_pathref_fsp(handle->conn->cwd_fsp, + smb_fname_cp); + config->in_openat_pathref_fsp = false; + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname_cp); + return NULL; + } + + ad = ad_get(ctx, handle, smb_fname_cp, ADOUBLE_META); + TALLOC_FREE(smb_fname_cp); + return ad; +} + +static struct fio *fruit_get_complete_fio(vfs_handle_struct *handle, + files_struct *fsp) +{ + struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + + if (fio == NULL) { + return NULL; + } + + if (fio->real_fio != NULL) { + /* + * This is an fsp from adouble_open_from_base_fsp() + * we should just pass this to the next + * module. + */ + return NULL; + } + + return fio; +} + +/** + * Initialize config struct from our smb.conf config parameters + **/ +static int init_fruit_config(vfs_handle_struct *handle) +{ + struct fruit_config_data *config; + int enumval = -1; + const char *tm_size_str = NULL; + + config = talloc_zero(handle->conn, struct fruit_config_data); + if (!config) { + DEBUG(1, ("talloc_zero() failed\n")); + errno = ENOMEM; + return -1; + } + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "resource", fruit_rsrc, FRUIT_RSRC_ADFILE); + if (enumval == -1) { + DEBUG(1, ("value for %s: resource type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->rsrc = (enum fruit_rsrc)enumval; + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "metadata", fruit_meta, FRUIT_META_NETATALK); + if (enumval == -1) { + DEBUG(1, ("value for %s: metadata type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->meta = (enum fruit_meta)enumval; + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "locking", fruit_locking, FRUIT_LOCKING_NONE); + if (enumval == -1) { + DEBUG(1, ("value for %s: locking type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->locking = (enum fruit_locking)enumval; + + enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "encoding", fruit_encoding, FRUIT_ENC_PRIVATE); + if (enumval == -1) { + DEBUG(1, ("value for %s: encoding type unknown\n", + FRUIT_PARAM_TYPE_NAME)); + return -1; + } + config->encoding = (enum fruit_encoding)enumval; + + if (config->rsrc == FRUIT_RSRC_ADFILE) { + config->veto_appledouble = lp_parm_bool(SNUM(handle->conn), + FRUIT_PARAM_TYPE_NAME, + "veto_appledouble", + true); + } + + config->use_aapl = lp_parm_bool( + -1, FRUIT_PARAM_TYPE_NAME, "aapl", true); + + config->time_machine = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, "time machine", false); + + config->unix_info_enabled = lp_parm_bool( + -1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true); + + config->use_copyfile = lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME, + "copyfile", false); + + config->posix_rename = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, "posix_rename", true); + + config->aapl_zero_file_id = + lp_parm_bool(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "zero_file_id", true); + + config->readdir_attr_rsize = lp_parm_bool( + SNUM(handle->conn), "readdir_attr", "aapl_rsize", true); + + config->readdir_attr_finder_info = lp_parm_bool( + SNUM(handle->conn), "readdir_attr", "aapl_finder_info", true); + + config->readdir_attr_max_access = lp_parm_bool( + SNUM(handle->conn), "readdir_attr", "aapl_max_access", true); + + config->model = lp_parm_const_string( + -1, FRUIT_PARAM_TYPE_NAME, "model", "MacSamba"); + + tm_size_str = lp_parm_const_string( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "time machine max size", NULL); + if (tm_size_str != NULL) { + config->time_machine_max_size = conv_str_size(tm_size_str); + } + + config->convert_adouble = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "convert_adouble", true); + + config->wipe_intentionally_left_blank_rfork = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "wipe_intentionally_left_blank_rfork", false); + + config->delete_empty_adfiles = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "delete_empty_adfiles", false); + + config->validate_afpinfo = lp_parm_bool( + SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, + "validate_afpinfo", true); + + SMB_VFS_HANDLE_SET_DATA(handle, config, + NULL, struct fruit_config_data, + return -1); + + return 0; +} + +static bool add_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, off_t size, + off_t alloc_size) +{ + struct stream_struct *tmp; + + tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_asprintf(tmp, "%s:$DATA", name); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +static bool filter_empty_rsrc_stream(unsigned int *num_streams, + struct stream_struct **streams) +{ + struct stream_struct *tmp = *streams; + unsigned int i; + + if (*num_streams == 0) { + return true; + } + + for (i = 0; i < *num_streams; i++) { + if (strequal_m(tmp[i].name, AFPRESOURCE_STREAM)) { + break; + } + } + + if (i == *num_streams) { + return true; + } + + if (tmp[i].size > 0) { + return true; + } + + TALLOC_FREE(tmp[i].name); + ARRAY_DEL_ELEMENT(tmp, i, *num_streams); + *num_streams -= 1; + return true; +} + +static bool del_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name) +{ + struct stream_struct *tmp = *streams; + unsigned int i; + + if (*num_streams == 0) { + return true; + } + + for (i = 0; i < *num_streams; i++) { + if (strequal_m(tmp[i].name, name)) { + break; + } + } + + if (i == *num_streams) { + return true; + } + + TALLOC_FREE(tmp[i].name); + ARRAY_DEL_ELEMENT(tmp, i, *num_streams); + *num_streams -= 1; + return true; +} + +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); + if (fi == NULL) { + DBG_ERR("Missing FinderInfo in struct adouble [%p]\n", ad); + return false; + } + + cmp = memcmp(emptybuf, fi, ADEDLEN_FINDERI); + return (cmp == 0); +} + +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; +} + +/** + * Map an access mask to a Netatalk single byte byte range lock + **/ +static off_t access_to_netatalk_brl(enum apple_fork fork_type, + uint32_t access_mask) +{ + off_t offset; + + switch (access_mask) { + case FILE_READ_DATA: + offset = AD_FILELOCK_OPEN_RD; + break; + + case FILE_WRITE_DATA: + case FILE_APPEND_DATA: + offset = AD_FILELOCK_OPEN_WR; + break; + + default: + offset = AD_FILELOCK_OPEN_NONE; + break; + } + + if (fork_type == APPLE_FORK_RSRC) { + if (offset == AD_FILELOCK_OPEN_NONE) { + offset = AD_FILELOCK_RSRC_OPEN_NONE; + } else { + offset += 2; + } + } + + return offset; +} + +/** + * Map a deny mode to a Netatalk brl + **/ +static off_t denymode_to_netatalk_brl(enum apple_fork fork_type, + uint32_t deny_mode) +{ + off_t offset = 0; + + switch (deny_mode) { + case DENY_READ: + offset = AD_FILELOCK_DENY_RD; + break; + + case DENY_WRITE: + offset = AD_FILELOCK_DENY_WR; + break; + + default: + smb_panic("denymode_to_netatalk_brl: bad deny mode\n"); + } + + if (fork_type == APPLE_FORK_RSRC) { + offset += 2; + } + + return offset; +} + +/** + * Call fcntl() with an exclusive F_GETLK request in order to + * determine if there's an existing shared lock + * + * @return true if the requested lock was found or any error occurred + * false if the lock was not found + **/ +static bool test_netatalk_lock(files_struct *fsp, off_t in_offset) +{ + bool result; + off_t offset = in_offset; + off_t len = 1; + int type = F_WRLCK; + pid_t pid = 0; + + result = SMB_VFS_GETLOCK(fsp, &offset, &len, &type, &pid); + if (result == false) { + return true; + } + + if (type != F_UNLCK) { + return true; + } + + return false; +} + +static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t access_mask, + uint32_t share_mode) +{ + NTSTATUS status = NT_STATUS_OK; + off_t off; + bool share_for_read = (share_mode & FILE_SHARE_READ); + bool share_for_write = (share_mode & FILE_SHARE_WRITE); + bool netatalk_already_open_for_reading = false; + bool netatalk_already_open_for_writing = false; + bool netatalk_already_open_with_deny_read = false; + bool netatalk_already_open_with_deny_write = false; + struct GUID req_guid = GUID_random(); + + /* FIXME: hardcoded data fork, add resource fork */ + enum apple_fork fork_type = APPLE_FORK_DATA; + + DBG_DEBUG("fruit_check_access: %s, am: %s/%s, sm: 0x%x\n", + fsp_str_dbg(fsp), + access_mask & FILE_READ_DATA ? "READ" :"-", + access_mask & FILE_WRITE_DATA ? "WRITE" : "-", + share_mode); + + if (fsp_get_io_fd(fsp) == -1) { + return NT_STATUS_OK; + } + + /* Read NetATalk opens and deny modes on the file. */ + netatalk_already_open_for_reading = test_netatalk_lock(fsp, + access_to_netatalk_brl(fork_type, + FILE_READ_DATA)); + + netatalk_already_open_with_deny_read = test_netatalk_lock(fsp, + denymode_to_netatalk_brl(fork_type, + DENY_READ)); + + netatalk_already_open_for_writing = test_netatalk_lock(fsp, + access_to_netatalk_brl(fork_type, + FILE_WRITE_DATA)); + + netatalk_already_open_with_deny_write = test_netatalk_lock(fsp, + denymode_to_netatalk_brl(fork_type, + DENY_WRITE)); + + /* If there are any conflicts - sharing violation. */ + if ((access_mask & FILE_READ_DATA) && + netatalk_already_open_with_deny_read) { + return NT_STATUS_SHARING_VIOLATION; + } + + if (!share_for_read && + netatalk_already_open_for_reading) { + return NT_STATUS_SHARING_VIOLATION; + } + + if ((access_mask & FILE_WRITE_DATA) && + netatalk_already_open_with_deny_write) { + return NT_STATUS_SHARING_VIOLATION; + } + + if (!share_for_write && + netatalk_already_open_for_writing) { + return NT_STATUS_SHARING_VIOLATION; + } + + if (!(access_mask & FILE_READ_DATA)) { + /* + * Nothing we can do here, we need read access + * to set locks. + */ + return NT_STATUS_OK; + } + + /* Set NetAtalk locks matching our access */ + if (access_mask & FILE_READ_DATA) { + off = access_to_netatalk_brl(fork_type, FILE_READ_DATA); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!share_for_read) { + off = denymode_to_netatalk_brl(fork_type, DENY_READ); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (access_mask & FILE_WRITE_DATA) { + off = access_to_netatalk_brl(fork_type, FILE_WRITE_DATA); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!share_for_write) { + off = denymode_to_netatalk_brl(fork_type, DENY_WRITE); + req_guid.time_hi_and_version = __LINE__; + status = do_lock( + fsp, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, + 1, + off, + READ_LOCK, + POSIX_LOCK, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS check_aapl(vfs_handle_struct *handle, + struct smb_request *req, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + struct fruit_config_data *config; + NTSTATUS status; + struct smb2_create_blob *aapl = NULL; + uint32_t cmd; + bool ok; + uint8_t p[16]; + DATA_BLOB blob = data_blob_talloc(req, NULL, 0); + uint64_t req_bitmap, client_caps; + uint64_t server_caps = SMB2_CRTCTX_AAPL_UNIX_BASED; + smb_ucs2_t *model; + size_t modellen; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (!config->use_aapl + || in_context_blobs == NULL + || out_context_blobs == NULL) { + return NT_STATUS_OK; + } + + aapl = smb2_create_blob_find(in_context_blobs, + SMB2_CREATE_TAG_AAPL); + if (aapl == NULL) { + return NT_STATUS_OK; + } + + if (aapl->data.length != 24) { + DEBUG(1, ("unexpected AAPL ctxt length: %ju\n", + (uintmax_t)aapl->data.length)); + return NT_STATUS_INVALID_PARAMETER; + } + + cmd = IVAL(aapl->data.data, 0); + if (cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) { + DEBUG(1, ("unsupported AAPL cmd: %d\n", cmd)); + return NT_STATUS_INVALID_PARAMETER; + } + + req_bitmap = BVAL(aapl->data.data, 8); + client_caps = BVAL(aapl->data.data, 16); + + SIVAL(p, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY); + SIVAL(p, 4, 0); + SBVAL(p, 8, req_bitmap); + ok = data_blob_append(req, &blob, p, 16); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (req_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) { + if ((client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) && + (handle->conn->fs_capabilities & FILE_NAMED_STREAMS)) { + server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR; + config->readdir_attr_enabled = true; + } + + if (config->use_copyfile) { + server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE; + config->copyfile_enabled = true; + } + + /* + * The client doesn't set the flag, so we can't check + * for it and just set it unconditionally + */ + if (config->unix_info_enabled) { + server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE; + } + + SBVAL(p, 0, server_caps); + ok = data_blob_append(req, &blob, p, 8); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (req_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) { + int val = lp_case_sensitive(SNUM(handle->conn)); + uint64_t caps = 0; + + switch (val) { + case Auto: + break; + + case True: + caps |= SMB2_CRTCTX_AAPL_CASE_SENSITIVE; + break; + + default: + break; + } + + if (config->time_machine) { + caps |= SMB2_CRTCTX_AAPL_FULL_SYNC; + } + + SBVAL(p, 0, caps); + + ok = data_blob_append(req, &blob, p, 8); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (req_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) { + ok = convert_string_talloc(req, + CH_UNIX, CH_UTF16LE, + config->model, strlen(config->model), + &model, &modellen); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + SIVAL(p, 0, 0); + SIVAL(p + 4, 0, modellen); + ok = data_blob_append(req, &blob, p, 8); + if (!ok) { + talloc_free(model); + return NT_STATUS_UNSUCCESSFUL; + } + + ok = data_blob_append(req, &blob, model, modellen); + talloc_free(model); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + status = smb2_create_blob_add(out_context_blobs, + out_context_blobs, + SMB2_CREATE_TAG_AAPL, + blob); + if (NT_STATUS_IS_OK(status)) { + global_fruit_config.nego_aapl = true; + } + + return status; +} + +static bool readdir_attr_meta_finderi_stream( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + AfpInfo *ai) +{ + struct smb_filename *stream_name = NULL; + files_struct *fsp = NULL; + ssize_t nread; + NTSTATUS status; + bool ok; + uint8_t buf[AFP_INFO_SIZE]; + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + AFPINFO_STREAM_NAME, + NULL, + smb_fname->twrp, + smb_fname->flags, + &stream_name); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + status = SMB_VFS_CREATE_FILE( + handle->conn, /* conn */ + NULL, /* req */ + NULL, /* dirfsp */ + stream_name, /* fname */ + FILE_READ_DATA, /* access_mask */ + (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */ + FILE_SHARE_DELETE), + FILE_OPEN, /* create_disposition*/ + 0, /* create_options */ + 0, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* pinfo */ + NULL, NULL); /* create context */ + + TALLOC_FREE(stream_name); + + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + nread = SMB_VFS_PREAD(fsp, &buf[0], AFP_INFO_SIZE, 0); + if (nread != AFP_INFO_SIZE) { + DBG_ERR("short read [%s] [%zd/%d]\n", + smb_fname_str_dbg(stream_name), nread, AFP_INFO_SIZE); + ok = false; + goto fail; + } + + memcpy(&ai->afpi_FinderInfo[0], &buf[AFP_OFF_FinderInfo], + AFP_FinderSize); + + ok = true; + +fail: + if (fsp != NULL) { + close_file_free(NULL, &fsp, NORMAL_CLOSE); + } + + return ok; +} + +static bool readdir_attr_meta_finderi_netatalk( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + AfpInfo *ai) +{ + struct adouble *ad = NULL; + char *p = NULL; + + ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname); + if (ad == NULL) { + return false; + } + + p = ad_get_entry(ad, ADEID_FINDERI); + if (p == NULL) { + DBG_ERR("No ADEID_FINDERI for [%s]\n", smb_fname->base_name); + TALLOC_FREE(ad); + return false; + } + + memcpy(&ai->afpi_FinderInfo[0], p, AFP_FinderSize); + TALLOC_FREE(ad); + return true; +} + +static bool readdir_attr_meta_finderi(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct readdir_attr_data *attr_data) +{ + struct fruit_config_data *config = NULL; + uint32_t date_added; + AfpInfo ai = {0}; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return false); + + switch (config->meta) { + case FRUIT_META_NETATALK: + ok = readdir_attr_meta_finderi_netatalk( + handle, smb_fname, &ai); + break; + + case FRUIT_META_STREAM: + ok = readdir_attr_meta_finderi_stream( + handle, smb_fname, &ai); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return false; + } + + if (!ok) { + /* Don't bother with errors, it's likely ENOENT */ + return true; + } + + if (S_ISREG(smb_fname->st.st_ex_mode)) { + /* finder_type */ + memcpy(&attr_data->attr_data.aapl.finder_info[0], + &ai.afpi_FinderInfo[0], 4); + + /* finder_creator */ + memcpy(&attr_data->attr_data.aapl.finder_info[0] + 4, + &ai.afpi_FinderInfo[4], 4); + } + + /* finder_flags */ + memcpy(&attr_data->attr_data.aapl.finder_info[0] + 8, + &ai.afpi_FinderInfo[8], 2); + + /* finder_ext_flags */ + memcpy(&attr_data->attr_data.aapl.finder_info[0] + 10, + &ai.afpi_FinderInfo[24], 2); + + /* creation date */ + date_added = convert_time_t_to_uint32_t( + smb_fname->st.st_ex_btime.tv_sec - AD_DATE_DELTA); + + RSIVAL(&attr_data->attr_data.aapl.finder_info[0], 12, date_added); + + return true; +} + +static uint64_t readdir_attr_rfork_size_adouble( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct adouble *ad = NULL; + uint64_t rfork_size; + + ad = ad_get(talloc_tos(), handle, smb_fname, + ADOUBLE_RSRC); + if (ad == NULL) { + return 0; + } + + rfork_size = ad_getentrylen(ad, ADEID_RFORK); + TALLOC_FREE(ad); + + return rfork_size; +} + +static uint64_t readdir_attr_rfork_size_stream( + struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct smb_filename *stream_name = NULL; + int ret; + uint64_t rfork_size; + + stream_name = synthetic_smb_fname(talloc_tos(), + smb_fname->base_name, + AFPRESOURCE_STREAM_NAME, + NULL, + smb_fname->twrp, + 0); + if (stream_name == NULL) { + return 0; + } + + ret = SMB_VFS_STAT(handle->conn, stream_name); + if (ret != 0) { + TALLOC_FREE(stream_name); + return 0; + } + + rfork_size = stream_name->st.st_ex_size; + TALLOC_FREE(stream_name); + + return rfork_size; +} + +static uint64_t readdir_attr_rfork_size(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct fruit_config_data *config = NULL; + uint64_t rfork_size; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return 0); + + switch (config->rsrc) { + case FRUIT_RSRC_ADFILE: + rfork_size = readdir_attr_rfork_size_adouble(handle, + smb_fname); + break; + + case FRUIT_RSRC_XATTR: + case FRUIT_RSRC_STREAM: + rfork_size = readdir_attr_rfork_size_stream(handle, + smb_fname); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + rfork_size = 0; + break; + } + + return rfork_size; +} + +static NTSTATUS readdir_attr_macmeta(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct readdir_attr_data *attr_data) +{ + NTSTATUS status = NT_STATUS_OK; + struct fruit_config_data *config = NULL; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + + /* Ensure we return a default value in the creation_date field */ + RSIVAL(&attr_data->attr_data.aapl.finder_info, 12, AD_DATE_START); + + /* + * Resource fork length + */ + + if (config->readdir_attr_rsize) { + uint64_t rfork_size; + + rfork_size = readdir_attr_rfork_size(handle, smb_fname); + attr_data->attr_data.aapl.rfork_size = rfork_size; + } + + /* + * FinderInfo + */ + + if (config->readdir_attr_finder_info) { + ok = readdir_attr_meta_finderi(handle, smb_fname, attr_data); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + } + } + + return status; +} + +static NTSTATUS remove_virtual_nfs_aces(struct security_descriptor *psd) +{ + NTSTATUS status; + uint32_t i; + + if (psd->dacl == NULL) { + return NT_STATUS_OK; + } + + for (i = 0; i < psd->dacl->num_aces; i++) { + /* MS NFS style mode/uid/gid */ + int cmp = dom_sid_compare_domain( + &global_sid_Unix_NFS, + &psd->dacl->aces[i].trustee); + if (cmp != 0) { + /* Normal ACE entry. */ + continue; + } + + /* + * security_descriptor_dacl_del() + * *must* return NT_STATUS_OK as we know + * we have something to remove. + */ + + status = security_descriptor_dacl_del(psd, + &psd->dacl->aces[i].trustee); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to remove MS NFS style ACE: %s\n", + nt_errstr(status)); + return status; + } + + /* + * security_descriptor_dacl_del() may delete more + * then one entry subsequent to this one if the + * SID matches, but we only need to ensure that + * we stay looking at the same element in the array. + */ + i--; + } + return NT_STATUS_OK; +} + +/* Search MS NFS style ACE with UNIX mode */ +static NTSTATUS check_ms_nfs(vfs_handle_struct *handle, + files_struct *fsp, + struct security_descriptor *psd, + mode_t *pmode, + bool *pdo_chmod) +{ + uint32_t i; + struct fruit_config_data *config = NULL; + + *pdo_chmod = false; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (!global_fruit_config.nego_aapl) { + return NT_STATUS_OK; + } + if (psd->dacl == NULL || !config->unix_info_enabled) { + return NT_STATUS_OK; + } + + for (i = 0; i < psd->dacl->num_aces; i++) { + if (dom_sid_compare_domain( + &global_sid_Unix_NFS_Mode, + &psd->dacl->aces[i].trustee) == 0) { + *pmode = (mode_t)psd->dacl->aces[i].trustee.sub_auths[2]; + *pmode &= (S_IRWXU | S_IRWXG | S_IRWXO); + *pdo_chmod = true; + + DEBUG(10, ("MS NFS chmod request %s, %04o\n", + fsp_str_dbg(fsp), (unsigned)(*pmode))); + break; + } + } + + /* + * Remove any incoming virtual ACE entries generated by + * fruit_fget_nt_acl(). + */ + + return remove_virtual_nfs_aces(psd); +} + +/**************************************************************************** + * VFS ops + ****************************************************************************/ + +static int fruit_connect(vfs_handle_struct *handle, + const char *service, + const char *user) +{ + int rc; + char *list = NULL, *newlist = NULL; + struct fruit_config_data *config; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + DEBUG(10, ("fruit_connect\n")); + + rc = SMB_VFS_NEXT_CONNECT(handle, service, user); + if (rc < 0) { + return rc; + } + + rc = init_fruit_config(handle); + if (rc != 0) { + return rc; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (config->veto_appledouble) { + list = lp_veto_files(talloc_tos(), lp_sub, SNUM(handle->conn)); + + if (list) { + if (strstr(list, "/" ADOUBLE_NAME_PREFIX "*/") == NULL) { + newlist = talloc_asprintf( + list, + "%s/" ADOUBLE_NAME_PREFIX "*/", + list); + lp_do_parameter(SNUM(handle->conn), + "veto files", + newlist); + } + } else { + lp_do_parameter(SNUM(handle->conn), + "veto files", + "/" ADOUBLE_NAME_PREFIX "*/"); + } + + TALLOC_FREE(list); + } + + if (config->encoding == FRUIT_ENC_NATIVE) { + lp_do_parameter(SNUM(handle->conn), + "catia:mappings", + macos_string_replace_map); + } + + if (config->time_machine) { + DBG_NOTICE("Enabling durable handles for Time Machine " + "support on [%s]\n", service); + lp_do_parameter(SNUM(handle->conn), "durable handles", "yes"); + lp_do_parameter(SNUM(handle->conn), "kernel oplocks", "no"); + lp_do_parameter(SNUM(handle->conn), "kernel share modes", "no"); + if (!lp_strict_sync(SNUM(handle->conn))) { + DBG_WARNING("Time Machine without strict sync is not " + "recommended!\n"); + } + lp_do_parameter(SNUM(handle->conn), "posix locking", "no"); + } + + return rc; +} + +static void fio_ref_destroy_fn(void *p_data) +{ + struct fio *ref_fio = (struct fio *)p_data; + if (ref_fio->real_fio != NULL) { + SMB_ASSERT(ref_fio->real_fio->ad_fsp == ref_fio->fsp); + ref_fio->real_fio->ad_fsp = NULL; + ref_fio->real_fio = NULL; + } +} + +static void fio_close_ad_fsp(struct fio *fio) +{ + if (fio->ad_fsp != NULL) { + fd_close(fio->ad_fsp); + file_free(NULL, fio->ad_fsp); + /* fio_ref_destroy_fn() should have cleared this */ + SMB_ASSERT(fio->ad_fsp == NULL); + } +} + +static void fio_destroy_fn(void *p_data) +{ + struct fio *fio = (struct fio *)p_data; + fio_close_ad_fsp(fio); +} + +static int fruit_open_meta_stream(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + struct fruit_config_data *config = NULL; + struct fio *fio = NULL; + struct vfs_open_how how = { + .flags = flags & ~O_CREAT, + .mode = mode, + }; + int fd; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn); + fio->handle = handle; + fio->fsp = fsp; + fio->type = ADOUBLE_META; + fio->config = config; + + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + if (fd != -1) { + return fd; + } + + if (!(flags & O_CREAT)) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + return -1; + } + + fd = vfs_fake_fd(); + if (fd == -1) { + VFS_REMOVE_FSP_EXTENSION(handle, fsp); + return -1; + } + + fio->fake_fd = true; + fio->flags = flags; + fio->mode = mode; + + return fd; +} + +static int fruit_open_meta_netatalk(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + struct fruit_config_data *config = NULL; + struct fio *fio = NULL; + struct adouble *ad = NULL; + bool meta_exists = false; + int fd; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + /* + * We know this is a stream open, so fsp->base_fsp must + * already be open. + */ + SMB_ASSERT(fsp_is_alternate_stream(fsp)); + SMB_ASSERT(fsp->base_fsp->fsp_name->fsp == fsp->base_fsp); + + ad = ad_get(talloc_tos(), handle, fsp->base_fsp->fsp_name, ADOUBLE_META); + if (ad != NULL) { + meta_exists = true; + } + + TALLOC_FREE(ad); + + if (!meta_exists && !(flags & O_CREAT)) { + errno = ENOENT; + return -1; + } + + fd = vfs_fake_fd(); + if (fd == -1) { + return -1; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn); + fio->handle = handle; + fio->fsp = fsp; + fio->type = ADOUBLE_META; + fio->config = config; + fio->fake_fd = true; + fio->flags = flags; + fio->mode = mode; + + return fd; +} + +static int fruit_open_meta(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, int flags, mode_t mode) +{ + int fd; + struct fruit_config_data *config = NULL; + + DBG_DEBUG("path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + fd = fruit_open_meta_stream(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + case FRUIT_META_NETATALK: + fd = fruit_open_meta_netatalk(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return -1; + } + + DBG_DEBUG("path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd); + + return fd; +} + +static int fruit_open_rsrc_adouble(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ + int rc = 0; + struct fruit_config_data *config = NULL; + struct files_struct *ad_fsp = NULL; + struct fio *fio = NULL; + struct fio *ref_fio = NULL; + NTSTATUS status; + int fd = -1; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if ((!(flags & O_CREAT)) && + S_ISDIR(fsp->base_fsp->fsp_name->st.st_ex_mode)) + { + /* sorry, but directories don't have a resource fork */ + errno = ENOENT; + rc = -1; + goto exit; + } + + /* + * We return a fake_fd to the vfs modules above, + * while we open an internal backend fsp for the + * '._' file for the next vfs modules. + * + * Note that adouble_open_from_base_fsp() recurses + * into fruit_openat(), but it'll just pass to + * the next module as just opens a flat file on + * disk. + */ + + fd = vfs_fake_fd(); + if (fd == -1) { + rc = fd; + goto exit; + } + + status = adouble_open_from_base_fsp(fsp->conn->cwd_fsp, + fsp->base_fsp, + ADOUBLE_RSRC, + flags, + mode, + &ad_fsp); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + rc = -1; + goto exit; + } + + /* + * Now we need to glue both handles together, + * so that they automatically detach each other + * on close. + */ + fio = fruit_get_complete_fio(handle, fsp); + if (fio == NULL) { + DBG_ERR("fio=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + rc = -1; + goto exit; + } + + ref_fio = VFS_ADD_FSP_EXTENSION(handle, ad_fsp, + struct fio, + fio_ref_destroy_fn); + if (ref_fio == NULL) { + int saved_errno = errno; + fd_close(ad_fsp); + file_free(NULL, ad_fsp); + ad_fsp = NULL; + errno = saved_errno; + rc = -1; + goto exit; + } + + SMB_ASSERT(ref_fio->fsp == NULL); + ref_fio->handle = handle; + ref_fio->fsp = ad_fsp; + ref_fio->type = ADOUBLE_RSRC; + ref_fio->config = config; + ref_fio->real_fio = fio; + SMB_ASSERT(fio->ad_fsp == NULL); + fio->ad_fsp = ad_fsp; + fio->fake_fd = true; + +exit: + + DEBUG(10, ("fruit_open resource fork: rc=%d\n", rc)); + if (rc != 0) { + int saved_errno = errno; + if (fd != -1) { + vfs_fake_fd_close(fd); + } + errno = saved_errno; + return rc; + } + return fd; +} + +static int fruit_open_rsrc_xattr(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + int flags, + mode_t mode) +{ +#ifdef HAVE_ATTROPEN + int fd = -1; + + /* + * As there's no attropenat() this is only going to work with AT_FDCWD. + */ + SMB_ASSERT(fsp_get_pathref_fd(dirfsp) == AT_FDCWD); + + fd = attropen(smb_fname->base_name, + AFPRESOURCE_EA_NETATALK, + flags, + mode); + if (fd == -1) { + return -1; + } + + return fd; + +#else + errno = ENOSYS; + return -1; +#endif +} + +static int fruit_open_rsrc(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, int flags, mode_t mode) +{ + int fd; + struct fruit_config_data *config = NULL; + struct fio *fio = NULL; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn); + fio->handle = handle; + fio->fsp = fsp; + fio->type = ADOUBLE_RSRC; + fio->config = config; + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: { + struct vfs_open_how how = { + .flags = flags, .mode = mode, + }; + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + break; + } + + case FRUIT_RSRC_ADFILE: + fd = fruit_open_rsrc_adouble(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + case FRUIT_RSRC_XATTR: + fd = fruit_open_rsrc_xattr(handle, dirfsp, smb_fname, + fsp, flags, mode); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + errno = EINVAL; + return -1; + } + + DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd); + + if (fd == -1) { + return -1; + } + + return fd; +} + +static int fruit_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *how) +{ + int fd; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + if (!is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + if (how->resolve != 0) { + errno = ENOSYS; + return -1; + } + + SMB_ASSERT(fsp_is_alternate_stream(fsp)); + + if (is_afpinfo_stream(smb_fname->stream_name)) { + fd = fruit_open_meta(handle, + dirfsp, + smb_fname, + fsp, + how->flags, + how->mode); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + fd = fruit_open_rsrc(handle, + dirfsp, + smb_fname, + fsp, + how->flags, + how->mode); + } else { + fd = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + } + + DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd); + + /* Prevent reopen optimisation */ + fsp->fsp_flags.have_proc_fds = false; + return fd; +} + +static int fruit_close_meta(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + { + struct fio *fio = fruit_get_complete_fio(handle, fsp); + if (fio == NULL) { + return -1; + } + if (fio->fake_fd) { + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + } else { + ret = SMB_VFS_NEXT_CLOSE(handle, fsp); + } + break; + } + case FRUIT_META_NETATALK: + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return -1; + } + + return ret; +} + + +static int fruit_close_rsrc(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + ret = SMB_VFS_NEXT_CLOSE(handle, fsp); + break; + + case FRUIT_RSRC_ADFILE: + { + struct fio *fio = fruit_get_complete_fio(handle, fsp); + if (fio == NULL) { + return -1; + } + fio_close_ad_fsp(fio); + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + break; + } + + case FRUIT_RSRC_XATTR: + ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp)); + fsp_set_fd(fsp, -1); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + return -1; + } + + return ret; +} + +static int fruit_close(vfs_handle_struct *handle, + files_struct *fsp) +{ + int ret; + int fd; + + fd = fsp_get_pathref_fd(fsp); + + DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(fsp->fsp_name), fd); + + if (!fsp_is_alternate_stream(fsp)) { + return SMB_VFS_NEXT_CLOSE(handle, fsp); + } + + if (is_afpinfo_stream(fsp->fsp_name->stream_name)) { + ret = fruit_close_meta(handle, fsp); + } else if (is_afpresource_stream(fsp->fsp_name->stream_name)) { + ret = fruit_close_rsrc(handle, fsp); + } else { + ret = SMB_VFS_NEXT_CLOSE(handle, fsp); + } + + return ret; +} + +static int fruit_renameat(struct vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *smb_fname_src, + files_struct *dstfsp, + const struct smb_filename *smb_fname_dst) +{ + int rc = -1; + struct fruit_config_data *config = NULL; + struct smb_filename *src_adp_smb_fname = NULL; + struct smb_filename *dst_adp_smb_fname = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (!VALID_STAT(smb_fname_src->st)) { + DBG_ERR("Need valid stat for [%s]\n", + smb_fname_str_dbg(smb_fname_src)); + return -1; + } + + rc = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); + if (rc != 0) { + return -1; + } + + if ((config->rsrc != FRUIT_RSRC_ADFILE) || + (!S_ISREG(smb_fname_src->st.st_ex_mode))) + { + return 0; + } + + rc = adouble_path(talloc_tos(), smb_fname_src, &src_adp_smb_fname); + if (rc != 0) { + goto done; + } + + rc = adouble_path(talloc_tos(), smb_fname_dst, &dst_adp_smb_fname); + if (rc != 0) { + goto done; + } + + DBG_DEBUG("%s -> %s\n", + smb_fname_str_dbg(src_adp_smb_fname), + smb_fname_str_dbg(dst_adp_smb_fname)); + + rc = SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + src_adp_smb_fname, + dstfsp, + dst_adp_smb_fname); + if (errno == ENOENT) { + rc = 0; + } + +done: + TALLOC_FREE(src_adp_smb_fname); + TALLOC_FREE(dst_adp_smb_fname); + return rc; +} + +static int fruit_unlink_meta_stream(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); +} + +static int fruit_unlink_meta_netatalk(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + SMB_ASSERT(smb_fname->fsp != NULL); + SMB_ASSERT(fsp_is_alternate_stream(smb_fname->fsp)); + return SMB_VFS_FREMOVEXATTR(smb_fname->fsp->base_fsp, + AFPINFO_EA_NETATALK); +} + +static int fruit_unlink_meta(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname) +{ + struct fruit_config_data *config = NULL; + int rc; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + rc = fruit_unlink_meta_stream(handle, + dirfsp, + smb_fname); + break; + + case FRUIT_META_NETATALK: + rc = fruit_unlink_meta_netatalk(handle, smb_fname); + break; + + default: + DBG_ERR("Unsupported meta config [%d]\n", config->meta); + return -1; + } + + return rc; +} + +static int fruit_unlink_rsrc_stream(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + int ret; + + if (!force_unlink) { + struct smb_filename *full_fname = NULL; + off_t size; + + /* + * TODO: use SMB_VFS_STATX() once we have it. + */ + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + /* + * 0 byte resource fork streams are not listed by + * vfs_streaminfo, as a result stream cleanup/deletion of file + * deletion doesn't remove the resourcefork stream. + */ + + ret = SMB_VFS_NEXT_STAT(handle, full_fname); + if (ret != 0) { + TALLOC_FREE(full_fname); + DBG_ERR("stat [%s] failed [%s]\n", + smb_fname_str_dbg(full_fname), strerror(errno)); + return -1; + } + + size = full_fname->st.st_ex_size; + TALLOC_FREE(full_fname); + + if (size > 0) { + /* OS X ignores resource fork stream delete requests */ + return 0; + } + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + if ((ret != 0) && (errno == ENOENT) && force_unlink) { + ret = 0; + } + + return ret; +} + +static int fruit_unlink_rsrc_adouble(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + int rc; + struct adouble *ad = NULL; + struct smb_filename *adp_smb_fname = NULL; + + if (!force_unlink) { + struct smb_filename *full_fname = NULL; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + return -1; + } + + ad = ad_get(talloc_tos(), handle, full_fname, + ADOUBLE_RSRC); + TALLOC_FREE(full_fname); + if (ad == NULL) { + errno = ENOENT; + return -1; + } + + + /* + * 0 byte resource fork streams are not listed by + * vfs_streaminfo, as a result stream cleanup/deletion of file + * deletion doesn't remove the resourcefork stream. + */ + + if (ad_getentrylen(ad, ADEID_RFORK) > 0) { + /* OS X ignores resource fork stream delete requests */ + TALLOC_FREE(ad); + return 0; + } + + TALLOC_FREE(ad); + } + + rc = adouble_path(talloc_tos(), smb_fname, &adp_smb_fname); + if (rc != 0) { + return -1; + } + + rc = SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + adp_smb_fname, + 0); + TALLOC_FREE(adp_smb_fname); + if ((rc != 0) && (errno == ENOENT || errno == ENAMETOOLONG) && force_unlink) { + rc = 0; + } + + return rc; +} + +static int fruit_unlink_rsrc_xattr(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + /* + * OS X ignores resource fork stream delete requests, so nothing to do + * here. Removing the file will remove the xattr anyway, so we don't + * have to take care of removing 0 byte resource forks that could be + * left behind. + */ + return 0; +} + +static int fruit_unlink_rsrc(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + bool force_unlink) +{ + struct fruit_config_data *config = NULL; + int rc; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + rc = fruit_unlink_rsrc_stream(handle, + dirfsp, + smb_fname, + force_unlink); + break; + + case FRUIT_RSRC_ADFILE: + rc = fruit_unlink_rsrc_adouble(handle, + dirfsp, + smb_fname, + force_unlink); + break; + + case FRUIT_RSRC_XATTR: + rc = fruit_unlink_rsrc_xattr(handle, smb_fname, force_unlink); + break; + + default: + DBG_ERR("Unsupported rsrc config [%d]\n", config->rsrc); + return -1; + } + + return rc; +} + +static int fruit_fchmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + int rc = -1; + struct fruit_config_data *config = NULL; + struct smb_filename *smb_fname_adp = NULL; + const struct smb_filename *smb_fname = NULL; + NTSTATUS status; + + rc = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + if (rc != 0) { + return rc; + } + + smb_fname = fsp->fsp_name; + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (config->rsrc != FRUIT_RSRC_ADFILE) { + return 0; + } + + if (!VALID_STAT(smb_fname->st)) { + return 0; + } + + if (!S_ISREG(smb_fname->st.st_ex_mode)) { + return 0; + } + + rc = adouble_path(talloc_tos(), smb_fname, &smb_fname_adp); + if (rc != 0) { + return -1; + } + + status = openat_pathref_fsp(handle->conn->cwd_fsp, + smb_fname_adp); + if (!NT_STATUS_IS_OK(status)) { + /* detect ENOENT (mapped to OBJECT_NAME_NOT_FOUND) */ + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)){ + rc = 0; + goto out; + } + rc = -1; + goto out; + } + + DBG_DEBUG("%s\n", smb_fname_adp->base_name); + + rc = SMB_VFS_NEXT_FCHMOD(handle, smb_fname_adp->fsp, mode); + if (errno == ENOENT) { + rc = 0; + } +out: + TALLOC_FREE(smb_fname_adp); + return rc; +} + +static int fruit_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + int flags) +{ + struct fruit_config_data *config = NULL; + struct smb_filename *rsrc_smb_fname = NULL; + int ret; + + if (flags & AT_REMOVEDIR) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + AT_REMOVEDIR); + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + if (is_afpinfo_stream(smb_fname->stream_name)) { + return fruit_unlink_meta(handle, + dirfsp, + smb_fname); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + return fruit_unlink_rsrc(handle, + dirfsp, + smb_fname, + false); + } else if (is_named_stream(smb_fname)) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + } else if (is_adouble_file(smb_fname->base_name)) { + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); + } + + /* + * A request to delete the base file. Because 0 byte resource + * fork streams are not listed by fruit_streaminfo, + * delete_all_streams() can't remove 0 byte resource fork + * streams, so we have to cleanup this here. + */ + rsrc_smb_fname = synthetic_smb_fname(talloc_tos(), + smb_fname->base_name, + AFPRESOURCE_STREAM_NAME, + NULL, + smb_fname->twrp, + smb_fname->flags); + if (rsrc_smb_fname == NULL) { + return -1; + } + + ret = fruit_unlink_rsrc(handle, dirfsp, rsrc_smb_fname, true); + if ((ret != 0) && (errno != ENOENT)) { + DBG_ERR("Forced unlink of [%s] failed [%s]\n", + smb_fname_str_dbg(rsrc_smb_fname), strerror(errno)); + TALLOC_FREE(rsrc_smb_fname); + return -1; + } + TALLOC_FREE(rsrc_smb_fname); + + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + smb_fname, + 0); +} + +static ssize_t fruit_pread_meta_stream(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + int ret; + + if ((fio == NULL) || fio->fake_fd) { + return -1; + } + + nread = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + if (nread == -1 || nread == n) { + return nread; + } + + DBG_ERR("Removing [%s] after short read [%zd]\n", + fsp_str_dbg(fsp), nread); + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + fsp->conn->cwd_fsp, + fsp->fsp_name, + 0); + if (ret != 0) { + DBG_ERR("Removing [%s] failed\n", fsp_str_dbg(fsp)); + return -1; + } + + errno = EINVAL; + return -1; +} + +static ssize_t fruit_pread_meta_adouble(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + AfpInfo *ai = NULL; + struct adouble *ad = NULL; + char afpinfo_buf[AFP_INFO_SIZE]; + char *p = NULL; + ssize_t nread; + + ai = afpinfo_new(talloc_tos()); + if (ai == NULL) { + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META); + if (ad == NULL) { + nread = -1; + goto fail; + } + + p = ad_get_entry(ad, ADEID_FINDERI); + if (p == NULL) { + DBG_ERR("No ADEID_FINDERI for [%s]\n", fsp_str_dbg(fsp)); + nread = -1; + goto fail; + } + + memcpy(&ai->afpi_FinderInfo[0], p, ADEDLEN_FINDERI); + + nread = afpinfo_pack(ai, afpinfo_buf); + if (nread != AFP_INFO_SIZE) { + nread = -1; + goto fail; + } + + memcpy(data, afpinfo_buf, n); + nread = n; + +fail: + TALLOC_FREE(ai); + return nread; +} + +static ssize_t fruit_pread_meta(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + ssize_t to_return; + + /* + * OS X has a off-by-1 error in the offset calculation, so we're + * bug compatible here. It won't hurt, as any relevant real + * world read requests from the AFP_AfpInfo stream will be + * offset=0 n=60. offset is ignored anyway, see below. + */ + if ((offset < 0) || (offset >= AFP_INFO_SIZE + 1)) { + return 0; + } + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + /* Yes, macOS always reads from offset 0 */ + offset = 0; + to_return = MIN(n, AFP_INFO_SIZE); + + switch (fio->config->meta) { + case FRUIT_META_STREAM: + nread = fruit_pread_meta_stream(handle, fsp, data, + to_return, offset); + break; + + case FRUIT_META_NETATALK: + nread = fruit_pread_meta_adouble(handle, fsp, data, + to_return, offset); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta); + return -1; + } + + if (nread == -1 && fio->fake_fd) { + AfpInfo *ai = NULL; + char afpinfo_buf[AFP_INFO_SIZE]; + + ai = afpinfo_new(talloc_tos()); + if (ai == NULL) { + return -1; + } + + nread = afpinfo_pack(ai, afpinfo_buf); + TALLOC_FREE(ai); + if (nread != AFP_INFO_SIZE) { + return -1; + } + + memcpy(data, afpinfo_buf, to_return); + return to_return; + } + + return nread; +} + +static ssize_t fruit_pread_rsrc_stream(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pread_rsrc_xattr(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pread_rsrc_adouble(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct adouble *ad = NULL; + ssize_t nread; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + nread = SMB_VFS_NEXT_PREAD(handle, fio->ad_fsp, data, n, + offset + ad_getentryoff(ad, ADEID_RFORK)); + + TALLOC_FREE(ad); + return nread; +} + +static ssize_t fruit_pread_rsrc(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + + if (fio == NULL) { + errno = EINVAL; + return -1; + } + + switch (fio->config->rsrc) { + case FRUIT_RSRC_STREAM: + nread = fruit_pread_rsrc_stream(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_ADFILE: + nread = fruit_pread_rsrc_adouble(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_XATTR: + nread = fruit_pread_rsrc_xattr(handle, fsp, data, n, offset); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + return nread; +} + +static ssize_t fruit_pread(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nread; + + DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n", + fsp_str_dbg(fsp), (intmax_t)offset, n); + + if (fio == NULL) { + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + } + + if (fio->type == ADOUBLE_META) { + nread = fruit_pread_meta(handle, fsp, data, n, offset); + } else { + nread = fruit_pread_rsrc(handle, fsp, data, n, offset); + } + + DBG_DEBUG("Path [%s] nread [%zd]\n", fsp_str_dbg(fsp), nread); + return nread; +} + +static bool fruit_must_handle_aio_stream(struct fio *fio) +{ + if (fio == NULL) { + return false; + }; + + if (fio->type == ADOUBLE_META) { + return true; + } + + if ((fio->type == ADOUBLE_RSRC) && + (fio->config->rsrc == FRUIT_RSRC_ADFILE)) + { + return true; + } + + return false; +} + +struct fruit_pread_state { + ssize_t nread; + struct vfs_aio_state vfs_aio_state; +}; + +static void fruit_pread_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_pread_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_pread_state *state = NULL; + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_pread_state); + if (req == NULL) { + return NULL; + } + + 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; + } + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, + data, n, offset); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_pread_done, req); + return req; +} + +static void fruit_pread_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_pread_state *state = tevent_req_data( + req, struct fruit_pread_state); + + state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (tevent_req_error(req, state->vfs_aio_state.error)) { + return; + } + tevent_req_done(req); +} + +static ssize_t fruit_pread_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct fruit_pread_state *state = tevent_req_data( + req, struct fruit_pread_state); + ssize_t retval = -1; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + retval = state->nread; + tevent_req_received(req); + return retval; +} + +static ssize_t fruit_pwrite_meta_stream(vfs_handle_struct *handle, + files_struct *fsp, const void *indata, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + const void *data = indata; + char afpinfo_buf[AFP_INFO_SIZE]; + AfpInfo *ai = NULL; + size_t nwritten; + int ret; + bool ok; + + DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n", + fsp_str_dbg(fsp), (intmax_t)offset, n); + + if (fio == NULL) { + return -1; + } + + if (fio->fake_fd) { + struct vfs_open_how how = { + .flags = fio->flags, .mode = fio->mode, + }; + int fd = fsp_get_pathref_fd(fsp); + + ret = vfs_fake_fd_close(fd); + fsp_set_fd(fsp, -1); + if (ret != 0) { + DBG_ERR("Close [%s] failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return -1; + } + + fd = SMB_VFS_NEXT_OPENAT(handle, + NULL, /* opening a stream */ + fsp->fsp_name, + fsp, + &how); + if (fd == -1) { + DBG_ERR("On-demand create [%s] in write failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return -1; + } + fsp_set_fd(fsp, fd); + fio->fake_fd = false; + } + + ai = afpinfo_unpack(talloc_tos(), data, fio->config->validate_afpinfo); + if (ai == NULL) { + return -1; + } + + if (ai_empty_finderinfo(ai)) { + /* + * Writing an all 0 blob to the metadata stream results in the + * stream being removed on a macOS server. This ensures we + * behave the same and it verified by the "delete AFP_AfpInfo by + * writing all 0" test. + */ + ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, 0); + if (ret != 0) { + DBG_ERR("SMB_VFS_NEXT_FTRUNCATE on [%s] failed\n", + fsp_str_dbg(fsp)); + return -1; + } + + ok = set_delete_on_close( + fsp, + true, + handle->conn->session_info->security_token, + handle->conn->session_info->unix_token); + if (!ok) { + DBG_ERR("set_delete_on_close on [%s] failed\n", + fsp_str_dbg(fsp)); + return -1; + } + return n; + } + + if (!fio->config->validate_afpinfo) { + /* + * Ensure the buffer contains a valid header, so marshall + * the data from the afpinfo struck back into a buffer + * and write that instead of the possibly malformed data + * we got from the client. + */ + nwritten = afpinfo_pack(ai, afpinfo_buf); + if (nwritten != AFP_INFO_SIZE) { + errno = EINVAL; + return -1; + } + data = afpinfo_buf; + } + + nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + if (nwritten != n) { + return -1; + } + + return n; +} + +static ssize_t fruit_pwrite_meta_netatalk(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fruit_config_data *config = NULL; + struct adouble *ad = NULL; + AfpInfo *ai = NULL; + char *p = NULL; + int ret; + bool ok; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + ai = afpinfo_unpack(talloc_tos(), data, config->validate_afpinfo); + if (ai == NULL) { + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META); + if (ad == NULL) { + ad = ad_init(talloc_tos(), ADOUBLE_META); + if (ad == NULL) { + return -1; + } + } + p = ad_get_entry(ad, ADEID_FINDERI); + if (p == NULL) { + DBG_ERR("No ADEID_FINDERI for [%s]\n", fsp_str_dbg(fsp)); + TALLOC_FREE(ad); + return -1; + } + + memcpy(p, &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI); + + ret = ad_fset(handle, ad, fsp); + if (ret != 0) { + DBG_ERR("ad_pwrite [%s] failed\n", fsp_str_dbg(fsp)); + TALLOC_FREE(ad); + return -1; + } + + TALLOC_FREE(ad); + + if (!ai_empty_finderinfo(ai)) { + return n; + } + + /* + * Writing an all 0 blob to the metadata stream results in the stream + * being removed on a macOS server. This ensures we behave the same and + * it verified by the "delete AFP_AfpInfo by writing all 0" test. + */ + + ok = set_delete_on_close( + fsp, + true, + handle->conn->session_info->security_token, + handle->conn->session_info->unix_token); + if (!ok) { + DBG_ERR("set_delete_on_close on [%s] failed\n", + fsp_str_dbg(fsp)); + return -1; + } + + return n; +} + +static ssize_t fruit_pwrite_meta(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nwritten; + uint8_t buf[AFP_INFO_SIZE]; + size_t to_write; + size_t to_copy; + int cmp; + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + if (n < 3) { + errno = EINVAL; + return -1; + } + + if (offset != 0 && n < 60) { + errno = EINVAL; + return -1; + } + + if (fio->config->validate_afpinfo) { + cmp = memcmp(data, "AFP", 3); + if (cmp != 0) { + errno = EINVAL; + return -1; + } + } + + if (n <= AFP_OFF_FinderInfo) { + /* + * Nothing to do here really, just return + */ + return n; + } + + offset = 0; + + to_copy = n; + if (to_copy > AFP_INFO_SIZE) { + to_copy = AFP_INFO_SIZE; + } + memcpy(buf, data, to_copy); + + to_write = n; + if (to_write != AFP_INFO_SIZE) { + to_write = AFP_INFO_SIZE; + } + + switch (fio->config->meta) { + case FRUIT_META_STREAM: + nwritten = fruit_pwrite_meta_stream(handle, + fsp, + buf, + to_write, + offset); + break; + + case FRUIT_META_NETATALK: + nwritten = fruit_pwrite_meta_netatalk(handle, + fsp, + buf, + to_write, + offset); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta); + return -1; + } + + if (nwritten != to_write) { + return -1; + } + + /* + * Return the requested amount, verified against macOS SMB server + */ + return n; +} + +static ssize_t fruit_pwrite_rsrc_stream(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pwrite_rsrc_xattr(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); +} + +static ssize_t fruit_pwrite_rsrc_adouble(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct adouble *ad = NULL; + ssize_t nwritten; + int ret; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + nwritten = SMB_VFS_NEXT_PWRITE(handle, fio->ad_fsp, data, n, + offset + ad_getentryoff(ad, ADEID_RFORK)); + if (nwritten != n) { + DBG_ERR("Short write on [%s] [%zd/%zd]\n", + fsp_str_dbg(fio->ad_fsp), nwritten, n); + TALLOC_FREE(ad); + return -1; + } + + if ((n + offset) > ad_getentrylen(ad, ADEID_RFORK)) { + ad_setentrylen(ad, ADEID_RFORK, n + offset); + ret = ad_fset(handle, ad, fio->ad_fsp); + if (ret != 0) { + DBG_ERR("ad_pwrite [%s] failed\n", fsp_str_dbg(fio->ad_fsp)); + TALLOC_FREE(ad); + return -1; + } + } + + TALLOC_FREE(ad); + return n; +} + +static ssize_t fruit_pwrite_rsrc(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nwritten; + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + switch (fio->config->rsrc) { + case FRUIT_RSRC_STREAM: + nwritten = fruit_pwrite_rsrc_stream(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_ADFILE: + nwritten = fruit_pwrite_rsrc_adouble(handle, fsp, data, n, offset); + break; + + case FRUIT_RSRC_XATTR: + nwritten = fruit_pwrite_rsrc_xattr(handle, fsp, data, n, offset); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + return nwritten; +} + +static ssize_t fruit_pwrite(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + ssize_t nwritten; + + DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n", + fsp_str_dbg(fsp), (intmax_t)offset, n); + + if (fio == NULL) { + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + } + + if (fio->type == ADOUBLE_META) { + nwritten = fruit_pwrite_meta(handle, fsp, data, n, offset); + } else { + nwritten = fruit_pwrite_rsrc(handle, fsp, data, n, offset); + } + + DBG_DEBUG("Path [%s] nwritten=%zd\n", fsp_str_dbg(fsp), nwritten); + return nwritten; +} + +struct fruit_pwrite_state { + ssize_t nwritten; + struct vfs_aio_state vfs_aio_state; +}; + +static void fruit_pwrite_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_pwrite_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp, + const void *data, + size_t n, off_t offset) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_pwrite_state *state = NULL; + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_pwrite_state); + if (req == NULL) { + return NULL; + } + + 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; + } + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, + data, n, offset); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_pwrite_done, req); + return req; +} + +static void fruit_pwrite_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_pwrite_state *state = tevent_req_data( + req, struct fruit_pwrite_state); + + state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + + if (tevent_req_error(req, state->vfs_aio_state.error)) { + return; + } + tevent_req_done(req); +} + +static ssize_t fruit_pwrite_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct fruit_pwrite_state *state = tevent_req_data( + req, struct fruit_pwrite_state); + ssize_t retval = -1; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + retval = state->nwritten; + tevent_req_received(req); + return retval; +} + +struct fruit_fsync_state { + int ret; + struct vfs_aio_state vfs_aio_state; +}; + +static void fruit_fsync_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_fsync_send( + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct files_struct *fsp) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_fsync_state *state = NULL; + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_fsync_state); + if (req == NULL) { + return NULL; + } + + if (fruit_must_handle_aio_stream(fio)) { + struct adouble *ad = NULL; + + if (fio->type == ADOUBLE_META) { + /* + * We must never pass a fake_fd + * to lower level fsync calls. + * Everything is already done + * synchronously, so just return + * true. + */ + SMB_ASSERT(fio->fake_fd); + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* + * We know the following must be true, + * as it's the condition for fruit_must_handle_aio_stream() + * to return true if fio->type == ADOUBLE_RSRC. + */ + SMB_ASSERT(fio->config->rsrc == FRUIT_RSRC_ADFILE); + if (fio->ad_fsp == NULL) { + tevent_req_error(req, EBADF); + return tevent_req_post(req, ev); + } + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + fsp = fio->ad_fsp; + } + + subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp); + if (tevent_req_nomem(req, subreq)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_fsync_done, req); + return req; +} + +static void fruit_fsync_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_fsync_state *state = tevent_req_data( + req, struct fruit_fsync_state); + + state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state); + TALLOC_FREE(subreq); + if (state->ret != 0) { + tevent_req_error(req, errno); + return; + } + tevent_req_done(req); +} + +static int fruit_fsync_recv(struct tevent_req *req, + struct vfs_aio_state *vfs_aio_state) +{ + struct fruit_fsync_state *state = tevent_req_data( + req, struct fruit_fsync_state); + int retval = -1; + + if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) { + tevent_req_received(req); + return -1; + } + + *vfs_aio_state = state->vfs_aio_state; + retval = state->ret; + tevent_req_received(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) +{ + char *tmp_stream_name; + int rc; + + 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; + + DBG_DEBUG("fruit_stat_base [%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; +} + +static int fruit_stat_meta_stream(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + int ret; + ino_t ino; + + ret = fruit_stat_base(handle, smb_fname, false); + if (ret != 0) { + return -1; + } + + ino = hash_inode(&smb_fname->st, smb_fname->stream_name); + + if (follow_links) { + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + } else { + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + smb_fname->st.st_ex_ino = ino; + + return ret; +} + +static int fruit_stat_meta_netatalk(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct adouble *ad = NULL; + + /* Populate the stat struct with info from the base file. */ + if (fruit_stat_base(handle, smb_fname, follow_links) == -1) { + return -1; + } + + 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)); + errno = ENOENT; + return -1; + } + 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; +} + +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; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->meta) { + case FRUIT_META_STREAM: + ret = fruit_stat_meta_stream(handle, smb_fname, follow_links); + break; + + case FRUIT_META_NETATALK: + ret = fruit_stat_meta_netatalk(handle, smb_fname, follow_links); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", config->meta); + return -1; + } + + return ret; +} + +static int fruit_stat_rsrc_netatalk(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct adouble *ad = NULL; + int ret; + + ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC); + if (ad == NULL) { + errno = ENOENT; + return -1; + } + + /* Populate the stat struct with info from the base file. */ + ret = fruit_stat_base(handle, smb_fname, follow_links); + if (ret != 0) { + TALLOC_FREE(ad); + return -1; + } + + smb_fname->st.st_ex_size = ad_getentrylen(ad, ADEID_RFORK); + smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, + smb_fname->stream_name); + TALLOC_FREE(ad); + return 0; +} + +static int fruit_stat_rsrc_stream(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + int ret; + + if (follow_links) { + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + } else { + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + return ret; +} + +static int fruit_stat_rsrc_xattr(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ +#ifdef HAVE_ATTROPEN + int ret; + int fd = -1; + + /* Populate the stat struct with info from the base file. */ + ret = fruit_stat_base(handle, smb_fname, follow_links); + if (ret != 0) { + return -1; + } + + fd = attropen(smb_fname->base_name, + AFPRESOURCE_EA_NETATALK, + O_RDONLY); + if (fd == -1) { + return 0; + } + + ret = sys_fstat(fd, &smb_fname->st, false); + if (ret != 0) { + close(fd); + DBG_ERR("fstat [%s:%s] failed\n", smb_fname->base_name, + AFPRESOURCE_EA_NETATALK); + return -1; + } + close(fd); + fd = -1; + + smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, + smb_fname->stream_name); + + return ret; + +#else + errno = ENOSYS; + return -1; +#endif +} + +static int fruit_stat_rsrc(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + bool follow_links) +{ + struct fruit_config_data *config = NULL; + int ret; + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, return -1); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + ret = fruit_stat_rsrc_stream(handle, smb_fname, follow_links); + break; + + case FRUIT_RSRC_XATTR: + ret = fruit_stat_rsrc_xattr(handle, smb_fname, follow_links); + break; + + case FRUIT_RSRC_ADFILE: + ret = fruit_stat_rsrc_netatalk(handle, smb_fname, follow_links); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc); + return -1; + } + + return ret; +} + +static int fruit_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int rc = -1; + + DEBUG(10, ("fruit_stat called for %s\n", + smb_fname_str_dbg(smb_fname))); + + if (!is_named_stream(smb_fname)) { + rc = SMB_VFS_NEXT_STAT(handle, smb_fname); + if (rc == 0) { + update_btime(handle, smb_fname); + } + return rc; + } + + /* + * Note if lp_posix_paths() is true, we can never + * get here as is_ntfs_stream_smb_fname() is + * always false. So we never need worry about + * not following links here. + */ + + if (is_afpinfo_stream(smb_fname->stream_name)) { + rc = fruit_stat_meta(handle, smb_fname, true); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + rc = fruit_stat_rsrc(handle, smb_fname, true); + } else { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + if (rc == 0) { + update_btime(handle, smb_fname); + smb_fname->st.st_ex_mode &= ~S_IFMT; + smb_fname->st.st_ex_mode |= S_IFREG; + smb_fname->st.st_ex_blocks = + smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1; + } + return rc; +} + +static int fruit_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + int rc = -1; + + DEBUG(10, ("fruit_lstat called for %s\n", + smb_fname_str_dbg(smb_fname))); + + if (!is_named_stream(smb_fname)) { + rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (rc == 0) { + update_btime(handle, smb_fname); + } + return rc; + } + + if (is_afpinfo_stream(smb_fname->stream_name)) { + rc = fruit_stat_meta(handle, smb_fname, false); + } else if (is_afpresource_stream(smb_fname->stream_name)) { + rc = fruit_stat_rsrc(handle, smb_fname, false); + } else { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + if (rc == 0) { + update_btime(handle, smb_fname); + smb_fname->st.st_ex_mode &= ~S_IFMT; + smb_fname->st.st_ex_mode |= S_IFREG; + smb_fname->st.st_ex_blocks = + smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1; + } + return rc; +} + +static int fruit_fstat_meta_stream(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct smb_filename smb_fname; + ino_t ino; + int ret; + + if (fio == NULL) { + return -1; + } + + if (fio->fake_fd) { + ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false); + if (ret != 0) { + return -1; + } + + *sbuf = fsp->base_fsp->fsp_name->st; + sbuf->st_ex_size = AFP_INFO_SIZE; + sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + return 0; + } + + smb_fname = (struct smb_filename) { + .base_name = fsp->fsp_name->base_name, + .twrp = fsp->fsp_name->twrp, + }; + + ret = fruit_stat_base(handle, &smb_fname, false); + if (ret != 0) { + return -1; + } + *sbuf = smb_fname.st; + + ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret != 0) { + return -1; + } + + sbuf->st_ex_ino = ino; + return 0; +} + +static int fruit_fstat_meta_netatalk(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + int ret; + + ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false); + if (ret != 0) { + return -1; + } + + *sbuf = fsp->base_fsp->fsp_name->st; + sbuf->st_ex_size = AFP_INFO_SIZE; + sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + + return 0; +} + +static int fruit_fstat_meta(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf, + struct fio *fio) +{ + int ret; + + DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp)); + + switch (fio->config->meta) { + case FRUIT_META_STREAM: + ret = fruit_fstat_meta_stream(handle, fsp, sbuf); + break; + + case FRUIT_META_NETATALK: + ret = fruit_fstat_meta_netatalk(handle, fsp, sbuf); + break; + + default: + DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta); + return -1; + } + + DBG_DEBUG("Path [%s] ret [%d]\n", fsp_str_dbg(fsp), ret); + return ret; +} + +static int fruit_fstat_rsrc_xattr(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); +} + +static int fruit_fstat_rsrc_stream(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); +} + +static int fruit_fstat_rsrc_adouble(vfs_handle_struct *handle, + files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + struct adouble *ad = NULL; + int ret; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + /* Populate the stat struct with info from the base file. */ + ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false); + if (ret == -1) { + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + *sbuf = fsp->base_fsp->fsp_name->st; + sbuf->st_ex_size = ad_getentrylen(ad, ADEID_RFORK); + sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name); + + TALLOC_FREE(ad); + return 0; +} + +static int fruit_fstat_rsrc(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf, struct fio *fio) +{ + int ret; + + switch (fio->config->rsrc) { + case FRUIT_RSRC_STREAM: + ret = fruit_fstat_rsrc_stream(handle, fsp, sbuf); + break; + + case FRUIT_RSRC_ADFILE: + ret = fruit_fstat_rsrc_adouble(handle, fsp, sbuf); + break; + + case FRUIT_RSRC_XATTR: + ret = fruit_fstat_rsrc_xattr(handle, fsp, sbuf); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + return ret; +} + +static int fruit_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int rc; + + if (fio == NULL) { + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + } + + DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp)); + + if (fio->type == ADOUBLE_META) { + rc = fruit_fstat_meta(handle, fsp, sbuf, fio); + } else { + rc = fruit_fstat_rsrc(handle, fsp, sbuf, fio); + } + + if (rc == 0) { + sbuf->st_ex_mode &= ~S_IFMT; + sbuf->st_ex_mode |= S_IFREG; + sbuf->st_ex_blocks = sbuf->st_ex_size / STAT_ST_BLOCKSIZE + 1; + } + + DBG_DEBUG("Path [%s] rc [%d] size [%"PRIdMAX"]\n", + fsp_str_dbg(fsp), rc, (intmax_t)sbuf->st_ex_size); + return rc; +} + +static NTSTATUS delete_invalid_meta_stream( + vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams, + off_t size) +{ + struct smb_filename *sname = NULL; + NTSTATUS status; + int ret; + bool ok; + + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, AFPINFO_STREAM); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (size == 0) { + return NT_STATUS_OK; + } + + status = synthetic_pathref(talloc_tos(), + handle->conn->cwd_fsp, + smb_fname->base_name, + AFPINFO_STREAM_NAME, + NULL, + smb_fname->twrp, + 0, + &sname); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + ret = SMB_VFS_NEXT_UNLINKAT(handle, + handle->conn->cwd_fsp, + sname, + 0); + if (ret != 0) { + DBG_ERR("Removing [%s] failed\n", smb_fname_str_dbg(sname)); + TALLOC_FREE(sname); + return map_nt_error_from_unix(errno); + } + + TALLOC_FREE(sname); + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_meta_stream( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *stream = *pstreams; + unsigned int num_streams = *pnum_streams; + int i; + + for (i = 0; i < num_streams; i++) { + if (strequal_m(stream[i].name, AFPINFO_STREAM)) { + break; + } + } + + if (i == num_streams) { + return NT_STATUS_OK; + } + + if (stream[i].size != AFP_INFO_SIZE) { + DBG_ERR("Removing invalid AFPINFO_STREAM size [%jd] from [%s]\n", + (intmax_t)stream[i].size, smb_fname_str_dbg(smb_fname)); + + return delete_invalid_meta_stream(handle, + smb_fname, + mem_ctx, + pnum_streams, + pstreams, + stream[i].size); + } + + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_meta_netatalk( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *stream = *pstreams; + unsigned int num_streams = *pnum_streams; + struct adouble *ad = NULL; + bool is_fi_empty; + int i; + bool ok; + + /* Remove the Netatalk xattr from the list */ + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, + ":" NETATALK_META_XATTR ":$DATA"); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Check if there's a AFPINFO_STREAM from the VFS streams + * backend and if yes, remove it from the list + */ + for (i = 0; i < num_streams; i++) { + if (strequal_m(stream[i].name, AFPINFO_STREAM)) { + break; + } + } + + if (i < num_streams) { + DBG_WARNING("Unexpected AFPINFO_STREAM on [%s]\n", + smb_fname_str_dbg(smb_fname)); + + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPINFO_STREAM); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + } + + ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname); + if (ad == NULL) { + return NT_STATUS_OK; + } + + is_fi_empty = ad_empty_finderinfo(ad); + TALLOC_FREE(ad); + + if (is_fi_empty) { + return NT_STATUS_OK; + } + + ok = add_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPINFO_STREAM_NAME, AFP_INFO_SIZE, + smb_roundup(handle->conn, AFP_INFO_SIZE)); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_meta(vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct fruit_config_data *config = NULL; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_INTERNAL_ERROR); + + switch (config->meta) { + case FRUIT_META_NETATALK: + status = fruit_streaminfo_meta_netatalk(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + case FRUIT_META_STREAM: + status = fruit_streaminfo_meta_stream(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + default: + return NT_STATUS_INTERNAL_ERROR; + } + + return status; +} + +static NTSTATUS fruit_streaminfo_rsrc_stream( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + bool ok; + + ok = filter_empty_rsrc_stream(pnum_streams, pstreams); + if (!ok) { + DBG_ERR("Filtering resource stream failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_rsrc_xattr( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + bool ok; + + ok = filter_empty_rsrc_stream(pnum_streams, pstreams); + if (!ok) { + DBG_ERR("Filtering resource stream failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_rsrc_adouble( + vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct stream_struct *stream = *pstreams; + unsigned int num_streams = *pnum_streams; + struct adouble *ad = NULL; + bool ok; + size_t rlen; + int i; + + /* + * Check if there's a AFPRESOURCE_STREAM from the VFS streams backend + * and if yes, remove it from the list + */ + for (i = 0; i < num_streams; i++) { + if (strequal_m(stream[i].name, AFPRESOURCE_STREAM)) { + break; + } + } + + if (i < num_streams) { + DBG_WARNING("Unexpected AFPRESOURCE_STREAM on [%s]\n", + smb_fname_str_dbg(smb_fname)); + + ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPRESOURCE_STREAM); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + } + + ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC); + if (ad == NULL) { + return NT_STATUS_OK; + } + + rlen = ad_getentrylen(ad, ADEID_RFORK); + TALLOC_FREE(ad); + + if (rlen == 0) { + return NT_STATUS_OK; + } + + ok = add_fruit_stream(mem_ctx, pnum_streams, pstreams, + AFPRESOURCE_STREAM_NAME, rlen, + smb_roundup(handle->conn, rlen)); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_streaminfo_rsrc(vfs_handle_struct *handle, + struct files_struct *fsp, + const struct smb_filename *smb_fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct fruit_config_data *config = NULL; + NTSTATUS status; + + if (S_ISDIR(smb_fname->st.st_ex_mode)) { + return NT_STATUS_OK; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_INTERNAL_ERROR); + + switch (config->rsrc) { + case FRUIT_RSRC_STREAM: + status = fruit_streaminfo_rsrc_stream(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + case FRUIT_RSRC_XATTR: + status = fruit_streaminfo_rsrc_xattr(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + case FRUIT_RSRC_ADFILE: + status = fruit_streaminfo_rsrc_adouble(handle, fsp, smb_fname, + mem_ctx, pnum_streams, + pstreams); + break; + + default: + return NT_STATUS_INTERNAL_ERROR; + } + + return status; +} + +static void fruit_filter_empty_streams(unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + unsigned num_streams = *pnum_streams; + struct stream_struct *streams = *pstreams; + unsigned i = 0; + + if (!global_fruit_config.nego_aapl) { + return; + } + + while (i < num_streams) { + struct smb_filename smb_fname = (struct smb_filename) { + .stream_name = streams[i].name, + }; + + if (is_ntfs_default_stream_smb_fname(&smb_fname) + || streams[i].size > 0) + { + i++; + continue; + } + + streams[i] = streams[num_streams - 1]; + num_streams--; + } + + *pnum_streams = num_streams; +} + +static NTSTATUS fruit_fstreaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + struct fruit_config_data *config = NULL; + const struct smb_filename *smb_fname = NULL; + NTSTATUS status; + + smb_fname = fsp->fsp_name; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname)); + + status = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx, + pnum_streams, pstreams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + fruit_filter_empty_streams(pnum_streams, pstreams); + + status = fruit_streaminfo_meta(handle, fsp, smb_fname, + mem_ctx, pnum_streams, pstreams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = fruit_streaminfo_rsrc(handle, fsp, smb_fname, + mem_ctx, pnum_streams, pstreams); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static int fruit_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + int rc = 0; + struct adouble *ad = NULL; + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return -1); + + if ((config->meta != FRUIT_META_NETATALK) || + is_omit_timespec(&ft->create_time)) + { + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); + } + + DBG_DEBUG("set btime for %s to %s", fsp_str_dbg(fsp), + time_to_asc(convert_timespec_to_time_t(ft->create_time))); + + ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META); + if (ad == NULL) { + goto exit; + } + + ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, + convert_time_t_to_uint32_t(ft->create_time.tv_sec)); + + rc = ad_fset(handle, ad, fsp); + +exit: + + TALLOC_FREE(ad); + if (rc != 0) { + DBG_WARNING("%s\n", fsp_str_dbg(fsp)); + return -1; + } + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int fruit_fallocate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t mode, + off_t offset, + off_t len) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + + if (fio == NULL) { + return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len); + } + + /* Let the pwrite code path handle it. */ + errno = ENOSYS; + return -1; +} + +static int fruit_ftruncate_rsrc_xattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ +#ifdef HAVE_ATTROPEN + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); +#endif + return 0; +} + +static int fruit_ftruncate_rsrc_adouble(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int rc; + struct adouble *ad = NULL; + off_t ad_off; + + if (fio == NULL || fio->ad_fsp == NULL) { + DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp)); + errno = EBADF; + return -1; + } + + ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC); + if (ad == NULL) { + DBG_ERR("ad_fget [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + return -1; + } + + ad_off = ad_getentryoff(ad, ADEID_RFORK); + + rc = SMB_VFS_NEXT_FTRUNCATE(handle, fio->ad_fsp, offset + ad_off); + if (rc != 0) { + TALLOC_FREE(ad); + return -1; + } + + ad_setentrylen(ad, ADEID_RFORK, offset); + + rc = ad_fset(handle, ad, fio->ad_fsp); + if (rc != 0) { + DBG_ERR("ad_fset [%s] failed [%s]\n", + fsp_str_dbg(fio->ad_fsp), strerror(errno)); + TALLOC_FREE(ad); + return -1; + } + + TALLOC_FREE(ad); + return 0; +} + +static int fruit_ftruncate_rsrc_stream(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); +} + +static int fruit_ftruncate_rsrc(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int ret; + + if (fio == NULL) { + DBG_ERR("Failed to fetch fsp extension\n"); + return -1; + } + + switch (fio->config->rsrc) { + case FRUIT_RSRC_XATTR: + ret = fruit_ftruncate_rsrc_xattr(handle, fsp, offset); + break; + + case FRUIT_RSRC_ADFILE: + ret = fruit_ftruncate_rsrc_adouble(handle, fsp, offset); + break; + + case FRUIT_RSRC_STREAM: + ret = fruit_ftruncate_rsrc_stream(handle, fsp, offset); + break; + + default: + DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc); + return -1; + } + + + return ret; +} + +static int fruit_ftruncate_meta(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + if (offset > 60) { + DBG_WARNING("ftruncate %s to %jd\n", + fsp_str_dbg(fsp), (intmax_t)offset); + /* OS X returns NT_STATUS_ALLOTTED_SPACE_EXCEEDED */ + errno = EOVERFLOW; + return -1; + } + + /* OS X returns success but does nothing */ + DBG_INFO("ignoring ftruncate %s to %jd\n", + fsp_str_dbg(fsp), (intmax_t)offset); + return 0; +} + +static int fruit_ftruncate(struct vfs_handle_struct *handle, + struct files_struct *fsp, + off_t offset) +{ + struct fio *fio = fruit_get_complete_fio(handle, fsp); + int ret; + + DBG_DEBUG("Path [%s] offset [%"PRIdMAX"]\n", fsp_str_dbg(fsp), + (intmax_t)offset); + + if (fio == NULL) { + return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset); + } + + if (fio->type == ADOUBLE_META) { + ret = fruit_ftruncate_meta(handle, fsp, offset); + } else { + ret = fruit_ftruncate_rsrc(handle, fsp, offset); + } + + DBG_DEBUG("Path [%s] result [%d]\n", fsp_str_dbg(fsp), ret); + return ret; +} + +static NTSTATUS fruit_create_file(vfs_handle_struct *handle, + struct smb_request *req, + struct files_struct *dirfsp, + struct smb_filename *smb_fname, + uint32_t access_mask, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + uint32_t file_attributes, + uint32_t oplock_request, + const struct smb2_lease *lease, + uint64_t allocation_size, + uint32_t private_flags, + struct security_descriptor *sd, + struct ea_list *ea_list, + files_struct **result, + int *pinfo, + const struct smb2_create_blobs *in_context_blobs, + struct smb2_create_blobs *out_context_blobs) +{ + NTSTATUS status; + struct fruit_config_data *config = NULL; + files_struct *fsp = NULL; + bool internal_open = (oplock_request & INTERNAL_OPEN_ONLY); + int ret; + + status = check_aapl(handle, req, in_context_blobs, out_context_blobs); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (is_apple_stream(smb_fname->stream_name) && + !internal_open && + config->convert_adouble) + { + uint32_t conv_flags = 0; + + if (config->wipe_intentionally_left_blank_rfork) { + conv_flags |= AD_CONV_WIPE_BLANK; + } + if (config->delete_empty_adfiles) { + conv_flags |= AD_CONV_DELETE; + } + + ret = ad_convert(handle, + smb_fname, + macos_string_replace_map, + conv_flags); + if (ret != 0) { + DBG_ERR("ad_convert(\"%s\") failed\n", + smb_fname_str_dbg(smb_fname)); + } + } + + status = SMB_VFS_NEXT_CREATE_FILE( + handle, req, dirfsp, smb_fname, + access_mask, share_access, + create_disposition, create_options, + file_attributes, oplock_request, + lease, + allocation_size, private_flags, + sd, ea_list, result, + pinfo, in_context_blobs, out_context_blobs); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + fsp = *result; + + if (global_fruit_config.nego_aapl) { + if (config->posix_rename && fsp->fsp_flags.is_directory) { + /* + * Enable POSIX directory rename behaviour + */ + fsp->posix_flags |= FSP_POSIX_FLAGS_RENAME; + } + } + + /* + * If this is a plain open for existing files, opening an 0 + * byte size resource fork MUST fail with + * NT_STATUS_OBJECT_NAME_NOT_FOUND. + * + * Cf the vfs_fruit torture tests in test_rfork_create(). + */ + if (global_fruit_config.nego_aapl && + create_disposition == FILE_OPEN && + smb_fname->st.st_ex_size == 0 && + is_named_stream(smb_fname)) + { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + + if (is_named_stream(smb_fname) || fsp->fsp_flags.is_directory) { + return status; + } + + if ((config->locking == FRUIT_LOCKING_NETATALK) && + (fsp->op != NULL) && + !fsp->fsp_flags.is_pathref) + { + status = fruit_check_access( + handle, *result, + access_mask, + share_access); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + return status; + +fail: + DEBUG(10, ("fruit_create_file: %s\n", nt_errstr(status))); + + if (fsp) { + close_file_free(req, &fsp, ERROR_CLOSE); + *result = NULL; + } + + return status; +} + +static NTSTATUS fruit_freaddir_attr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + TALLOC_CTX *mem_ctx, + struct readdir_attr_data **pattr_data) +{ + struct fruit_config_data *config = NULL; + struct readdir_attr_data *attr_data; + uint32_t conv_flags = 0; + NTSTATUS status; + int ret; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + if (!global_fruit_config.nego_aapl) { + return SMB_VFS_NEXT_FREADDIR_ATTR(handle, + fsp, + mem_ctx, + pattr_data); + } + + DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp)); + + if (config->convert_adouble) { + if (config->wipe_intentionally_left_blank_rfork) { + conv_flags |= AD_CONV_WIPE_BLANK; + } + if (config->delete_empty_adfiles) { + conv_flags |= AD_CONV_DELETE; + } + + ret = ad_convert(handle, + fsp->fsp_name, + macos_string_replace_map, + conv_flags); + if (ret != 0) { + DBG_ERR("ad_convert(\"%s\") failed\n", + fsp_str_dbg(fsp)); + } + } + + *pattr_data = talloc_zero(mem_ctx, struct readdir_attr_data); + if (*pattr_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + attr_data = *pattr_data; + attr_data->type = RDATTR_AAPL; + + /* + * Mac metadata: compressed FinderInfo, resource fork length + * and creation date + */ + status = readdir_attr_macmeta(handle, fsp->fsp_name, attr_data); + if (!NT_STATUS_IS_OK(status)) { + /* + * Error handling is tricky: if we return failure from + * this function, the corresponding directory entry + * will to be passed to the client, so we really just + * want to error out on fatal errors. + */ + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + goto fail; + } + } + + /* + * UNIX mode + */ + if (config->unix_info_enabled) { + attr_data->attr_data.aapl.unix_mode = + fsp->fsp_name->st.st_ex_mode; + } + + /* + * max_access + */ + if (!config->readdir_attr_max_access) { + attr_data->attr_data.aapl.max_access = FILE_GENERIC_ALL; + } else { + status = smbd_calculate_access_mask_fsp(fsp->conn->cwd_fsp, + fsp, + false, + SEC_FLAG_MAXIMUM_ALLOWED, + &attr_data->attr_data.aapl.max_access); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + return NT_STATUS_OK; + +fail: + DBG_WARNING("Path [%s], error: %s\n", fsp_str_dbg(fsp), + nt_errstr(status)); + TALLOC_FREE(*pattr_data); + return status; +} + +static NTSTATUS fruit_fget_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + NTSTATUS status; + struct security_ace ace; + struct dom_sid sid; + struct fruit_config_data *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NT_STATUS_UNSUCCESSFUL); + + status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, + mem_ctx, ppdesc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Add MS NFS style ACEs with uid, gid and mode + */ + if (!global_fruit_config.nego_aapl) { + return NT_STATUS_OK; + } + if (!config->unix_info_enabled) { + return NT_STATUS_OK; + } + + /* First remove any existing ACE's with NFS style mode/uid/gid SIDs. */ + status = remove_virtual_nfs_aces(*ppdesc); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to remove MS NFS style ACEs\n"); + return status; + } + + /* MS NFS style mode */ + sid_compose(&sid, &global_sid_Unix_NFS_Mode, fsp->fsp_name->st.st_ex_mode); + init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); + status = security_descriptor_dacl_add(*ppdesc, &ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("failed to add MS NFS style ACE\n")); + return status; + } + + /* MS NFS style uid */ + sid_compose(&sid, &global_sid_Unix_NFS_Users, fsp->fsp_name->st.st_ex_uid); + init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); + status = security_descriptor_dacl_add(*ppdesc, &ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("failed to add MS NFS style ACE\n")); + return status; + } + + /* MS NFS style gid */ + sid_compose(&sid, &global_sid_Unix_NFS_Groups, fsp->fsp_name->st.st_ex_gid); + init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0); + status = security_descriptor_dacl_add(*ppdesc, &ace); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("failed to add MS NFS style ACE\n")); + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fruit_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *orig_psd) +{ + NTSTATUS status; + bool do_chmod; + mode_t ms_nfs_mode = 0; + int result; + struct security_descriptor *psd = NULL; + uint32_t orig_num_aces = 0; + + if (orig_psd->dacl != NULL) { + orig_num_aces = orig_psd->dacl->num_aces; + } + + psd = security_descriptor_copy(talloc_tos(), orig_psd); + if (psd == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("fruit_fset_nt_acl: %s\n", fsp_str_dbg(fsp)); + + status = check_ms_nfs(handle, fsp, psd, &ms_nfs_mode, &do_chmod); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("fruit_fset_nt_acl: check_ms_nfs failed%s\n", fsp_str_dbg(fsp))); + TALLOC_FREE(psd); + return status; + } + + /* + * If only ms_nfs ACE entries were sent, ensure we set the DACL + * sent/present flags correctly now we've removed them. + */ + + if (orig_num_aces != 0) { + /* + * Are there any ACE's left ? + */ + if (psd->dacl->num_aces == 0) { + /* No - clear the DACL sent/present flags. */ + security_info_sent &= ~SECINFO_DACL; + psd->type &= ~SEC_DESC_DACL_PRESENT; + } + } + + status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("fruit_fset_nt_acl: SMB_VFS_NEXT_FSET_NT_ACL failed%s\n", fsp_str_dbg(fsp))); + TALLOC_FREE(psd); + return status; + } + + if (do_chmod) { + result = SMB_VFS_FCHMOD(fsp, ms_nfs_mode); + if (result != 0) { + DBG_WARNING("%s, result: %d, %04o error %s\n", + fsp_str_dbg(fsp), + result, + (unsigned)ms_nfs_mode, + strerror(errno)); + status = map_nt_error_from_unix(errno); + TALLOC_FREE(psd); + return status; + } + } + + TALLOC_FREE(psd); + return NT_STATUS_OK; +} + +static struct vfs_offload_ctx *fruit_offload_ctx; + +struct fruit_offload_read_state { + struct vfs_handle_struct *handle; + struct tevent_context *ev; + files_struct *fsp; + uint32_t fsctl; + uint32_t flags; + uint64_t xferlen; + DATA_BLOB token; +}; + +static void fruit_offload_read_done(struct tevent_req *subreq); + +static struct tevent_req *fruit_offload_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct vfs_handle_struct *handle, + files_struct *fsp, + uint32_t fsctl, + uint32_t ttl, + off_t offset, + size_t to_copy) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct fruit_offload_read_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct fruit_offload_read_state); + if (req == NULL) { + return NULL; + } + *state = (struct fruit_offload_read_state) { + .handle = handle, + .ev = ev, + .fsp = fsp, + .fsctl = fsctl, + }; + + subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp, + fsctl, ttl, offset, to_copy); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fruit_offload_read_done, req); + return req; +} + +static void fruit_offload_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_offload_read_state *state = tevent_req_data( + req, struct fruit_offload_read_state); + NTSTATUS status; + + status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq, + state->handle, + state, + &state->flags, + &state->xferlen, + &state->token); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->fsctl != FSCTL_SRV_REQUEST_RESUME_KEY) { + tevent_req_done(req); + return; + } + + status = vfs_offload_token_ctx_init(state->fsp->conn->sconn->client, + &fruit_offload_ctx); + if (tevent_req_nterror(req, status)) { + return; + } + + status = vfs_offload_token_db_store_fsp(fruit_offload_ctx, + state->fsp, + &state->token); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); + return; +} + +static NTSTATUS fruit_offload_read_recv(struct tevent_req *req, + struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + uint32_t *flags, + uint64_t *xferlen, + DATA_BLOB *token) +{ + struct fruit_offload_read_state *state = tevent_req_data( + req, struct fruit_offload_read_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *flags = state->flags; + *xferlen = state->xferlen; + token->length = state->token.length; + token->data = talloc_move(mem_ctx, &state->token.data); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct fruit_offload_write_state { + struct vfs_handle_struct *handle; + off_t copied; + struct files_struct *src_fsp; + struct files_struct *dst_fsp; + bool is_copyfile; +}; + +static void fruit_offload_write_done(struct tevent_req *subreq); +static struct tevent_req *fruit_offload_write_send(struct vfs_handle_struct *handle, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t fsctl, + DATA_BLOB *token, + off_t transfer_offset, + struct files_struct *dest_fsp, + off_t dest_off, + off_t num) +{ + struct tevent_req *req, *subreq; + struct fruit_offload_write_state *state; + NTSTATUS status; + struct fruit_config_data *config; + off_t src_off = transfer_offset; + files_struct *src_fsp = NULL; + off_t to_copy = num; + bool copyfile_enabled = false; + + DEBUG(10,("soff: %ju, doff: %ju, len: %ju\n", + (uintmax_t)src_off, (uintmax_t)dest_off, (uintmax_t)num)); + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return NULL); + + req = tevent_req_create(mem_ctx, &state, + struct fruit_offload_write_state); + if (req == NULL) { + return NULL; + } + state->handle = handle; + state->dst_fsp = dest_fsp; + + switch (fsctl) { + case FSCTL_SRV_COPYCHUNK: + case FSCTL_SRV_COPYCHUNK_WRITE: + copyfile_enabled = config->copyfile_enabled; + break; + default: + break; + } + + /* + * Check if this a OS X copyfile style copychunk request with + * a requested chunk count of 0 that was translated to a + * offload_write_send VFS call overloading the parameters src_off + * = dest_off = num = 0. + */ + if (copyfile_enabled && num == 0 && src_off == 0 && dest_off == 0) { + status = vfs_offload_token_db_fetch_fsp( + fruit_offload_ctx, token, &src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + state->src_fsp = src_fsp; + + status = vfs_stat_fsp(src_fsp); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + to_copy = src_fsp->fsp_name->st.st_ex_size; + state->is_copyfile = true; + } + + subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, + mem_ctx, + ev, + fsctl, + token, + transfer_offset, + dest_fsp, + dest_off, + to_copy); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, fruit_offload_write_done, req); + return req; +} + +static void fruit_offload_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fruit_offload_write_state *state = tevent_req_data( + req, struct fruit_offload_write_state); + NTSTATUS status; + unsigned int num_streams = 0; + struct stream_struct *streams = NULL; + unsigned int i; + struct smb_filename *src_fname_tmp = NULL; + struct smb_filename *dst_fname_tmp = NULL; + + status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle, + subreq, + &state->copied); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!state->is_copyfile) { + tevent_req_done(req); + return; + } + + /* + * Now copy all remaining streams. We know the share supports + * streams, because we're in vfs_fruit. We don't do this async + * because streams are few and small. + */ + status = vfs_fstreaminfo(state->src_fsp, + req, &num_streams, &streams); + if (tevent_req_nterror(req, status)) { + return; + } + + if (num_streams == 1) { + /* There is always one stream, ::$DATA. */ + tevent_req_done(req); + return; + } + + for (i = 0; i < num_streams; i++) { + DEBUG(10, ("%s: stream: '%s'/%zu\n", + __func__, streams[i].name, (size_t)streams[i].size)); + + src_fname_tmp = synthetic_smb_fname( + req, + state->src_fsp->fsp_name->base_name, + streams[i].name, + NULL, + state->src_fsp->fsp_name->twrp, + state->src_fsp->fsp_name->flags); + if (tevent_req_nomem(src_fname_tmp, req)) { + return; + } + + if (is_ntfs_default_stream_smb_fname(src_fname_tmp)) { + TALLOC_FREE(src_fname_tmp); + continue; + } + + dst_fname_tmp = synthetic_smb_fname( + req, + state->dst_fsp->fsp_name->base_name, + streams[i].name, + NULL, + state->dst_fsp->fsp_name->twrp, + state->dst_fsp->fsp_name->flags); + if (tevent_req_nomem(dst_fname_tmp, req)) { + TALLOC_FREE(src_fname_tmp); + return; + } + + status = copy_file(req, + state->handle->conn, + src_fname_tmp, + dst_fname_tmp, + FILE_CREATE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("%s: copy %s to %s failed: %s\n", __func__, + smb_fname_str_dbg(src_fname_tmp), + smb_fname_str_dbg(dst_fname_tmp), + nt_errstr(status))); + TALLOC_FREE(src_fname_tmp); + TALLOC_FREE(dst_fname_tmp); + tevent_req_nterror(req, status); + return; + } + + TALLOC_FREE(src_fname_tmp); + TALLOC_FREE(dst_fname_tmp); + } + + TALLOC_FREE(streams); + TALLOC_FREE(src_fname_tmp); + TALLOC_FREE(dst_fname_tmp); + tevent_req_done(req); +} + +static NTSTATUS fruit_offload_write_recv(struct vfs_handle_struct *handle, + struct tevent_req *req, + off_t *copied) +{ + struct fruit_offload_write_state *state = tevent_req_data( + req, struct fruit_offload_write_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(1, ("server side copy chunk failed: %s\n", + nt_errstr(status))); + *copied = 0; + tevent_req_received(req); + return status; + } + + *copied = state->copied; + tevent_req_received(req); + + return NT_STATUS_OK; +} + +static char *fruit_get_bandsize_line(char **lines, int numlines) +{ + static regex_t re; + static bool re_initialized = false; + int i; + int ret; + + if (!re_initialized) { + ret = regcomp(&re, "^[[:blank:]]*band-size$", 0); + if (ret != 0) { + return NULL; + } + re_initialized = true; + } + + for (i = 0; i < numlines; i++) { + regmatch_t matches[1]; + + ret = regexec(&re, lines[i], 1, matches, 0); + if (ret == 0) { + /* + * Check if the match was on the last line, sa we want + * the subsequent line. + */ + if (i + 1 == numlines) { + return NULL; + } + return lines[i + 1]; + } + if (ret != REG_NOMATCH) { + return NULL; + } + } + + return NULL; +} + +static bool fruit_get_bandsize_from_line(char *line, size_t *_band_size) +{ + static regex_t re; + static bool re_initialized = false; + regmatch_t matches[2]; + uint64_t band_size; + int ret; + bool ok; + + if (!re_initialized) { + ret = regcomp(&re, + "^[[:blank:]]*" + "\\([[:digit:]]*\\)$", + 0); + if (ret != 0) { + return false; + } + re_initialized = true; + } + + ret = regexec(&re, line, 2, matches, 0); + if (ret != 0) { + DBG_ERR("regex failed [%s]\n", line); + return false; + } + + line[matches[1].rm_eo] = '\0'; + + ok = conv_str_u64(&line[matches[1].rm_so], &band_size); + if (!ok) { + return false; + } + *_band_size = (size_t)band_size; + return true; +} + +/* + * This reads and parses an Info.plist from a TM sparsebundle looking for the + * "band-size" key and value. + */ +static bool fruit_get_bandsize(vfs_handle_struct *handle, + const char *dir, + size_t *band_size) +{ +#define INFO_PLIST_MAX_SIZE 64*1024 + char *plist = NULL; + struct smb_filename *smb_fname = NULL; + files_struct *fsp = NULL; + uint8_t *file_data = NULL; + char **lines = NULL; + char *band_size_line = NULL; + size_t plist_file_size; + ssize_t nread; + int numlines; + int ret; + bool ok = false; + NTSTATUS status; + + plist = talloc_asprintf(talloc_tos(), + "%s/%s/Info.plist", + handle->conn->connectpath, + dir); + if (plist == NULL) { + ok = false; + goto out; + } + + smb_fname = synthetic_smb_fname(talloc_tos(), + plist, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + ok = false; + goto out; + } + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + if (ret != 0) { + DBG_INFO("Ignoring Sparsebundle without Info.plist [%s]\n", dir); + ok = true; + goto out; + } + + plist_file_size = smb_fname->st.st_ex_size; + + if (plist_file_size > INFO_PLIST_MAX_SIZE) { + DBG_INFO("%s is too large, ignoring\n", plist); + ok = true; + goto out; + } + + status = SMB_VFS_NEXT_CREATE_FILE( + handle, /* conn */ + NULL, /* req */ + NULL, /* dirfsp */ + smb_fname, /* fname */ + FILE_GENERIC_READ, /* access_mask */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */ + FILE_OPEN, /* create_disposition */ + 0, /* create_options */ + 0, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* psbuf */ + NULL, NULL); /* create context */ + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("Opening [%s] failed [%s]\n", + smb_fname_str_dbg(smb_fname), nt_errstr(status)); + ok = false; + goto out; + } + + file_data = talloc_zero_array(talloc_tos(), + uint8_t, + plist_file_size + 1); + if (file_data == NULL) { + ok = false; + goto out; + } + + nread = SMB_VFS_NEXT_PREAD(handle, fsp, file_data, plist_file_size, 0); + if (nread != plist_file_size) { + DBG_ERR("Short read on [%s]: %zu/%zd\n", + fsp_str_dbg(fsp), nread, plist_file_size); + ok = false; + goto out; + + } + + status = close_file_free(NULL, &fsp, NORMAL_CLOSE); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("close_file failed: %s\n", nt_errstr(status)); + ok = false; + goto out; + } + + lines = file_lines_parse((char *)file_data, + plist_file_size, + &numlines, + talloc_tos()); + if (lines == NULL) { + ok = false; + goto out; + } + + band_size_line = fruit_get_bandsize_line(lines, numlines); + if (band_size_line == NULL) { + DBG_ERR("Didn't find band-size key in [%s]\n", + smb_fname_str_dbg(smb_fname)); + ok = false; + goto out; + } + + ok = fruit_get_bandsize_from_line(band_size_line, band_size); + if (!ok) { + DBG_ERR("fruit_get_bandsize_from_line failed\n"); + goto out; + } + + DBG_DEBUG("Parsed band-size [%zu] for [%s]\n", *band_size, plist); + +out: + if (fsp != NULL) { + status = close_file_free(NULL, &fsp, NORMAL_CLOSE); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("close_file failed: %s\n", nt_errstr(status)); + } + } + TALLOC_FREE(plist); + TALLOC_FREE(smb_fname); + TALLOC_FREE(file_data); + TALLOC_FREE(lines); + return ok; +} + +struct fruit_disk_free_state { + off_t total_size; +}; + +static bool fruit_get_num_bands(vfs_handle_struct *handle, + const char *bundle, + size_t *_nbands) +{ + char *path = NULL; + struct smb_filename *bands_dir = NULL; + struct smb_Dir *dir_hnd = NULL; + const char *dname = NULL; + char *talloced = NULL; + size_t nbands; + NTSTATUS status; + + path = talloc_asprintf(talloc_tos(), + "%s/%s/bands", + handle->conn->connectpath, + bundle); + if (path == NULL) { + return false; + } + + bands_dir = synthetic_smb_fname(talloc_tos(), + path, + NULL, + NULL, + 0, + 0); + TALLOC_FREE(path); + if (bands_dir == NULL) { + return false; + } + + status = OpenDir(talloc_tos(), + handle->conn, + bands_dir, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(bands_dir); + errno = map_errno_from_nt_status(status); + return false; + } + + nbands = 0; + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + if (ISDOT(dname) || ISDOTDOT(dname)) { + continue; + } + nbands++; + } + TALLOC_FREE(dir_hnd); + + DBG_DEBUG("%zu bands in [%s]\n", nbands, smb_fname_str_dbg(bands_dir)); + + TALLOC_FREE(bands_dir); + + *_nbands = nbands; + return true; +} + +static bool fruit_tmsize_do_dirent(vfs_handle_struct *handle, + struct fruit_disk_free_state *state, + const char *name) +{ + bool ok; + char *p = NULL; + size_t sparsebundle_strlen = strlen("sparsebundle"); + size_t bandsize = 0; + size_t nbands; + off_t tm_size; + + p = strstr(name, "sparsebundle"); + if (p == NULL) { + return true; + } + + if (p[sparsebundle_strlen] != '\0') { + return true; + } + + DBG_DEBUG("Processing sparsebundle [%s]\n", name); + + ok = fruit_get_bandsize(handle, name, &bandsize); + if (!ok) { + /* + * Beware of race conditions: this may be an uninitialized + * Info.plist that a client is just creating. We don't want let + * this to trigger complete failure. + */ + DBG_ERR("Processing sparsebundle [%s] failed\n", name); + return true; + } + + ok = fruit_get_num_bands(handle, name, &nbands); + if (!ok) { + /* + * Beware of race conditions: this may be a backup sparsebundle + * in an early stage lacking a bands subdirectory. We don't want + * let this to trigger complete failure. + */ + DBG_ERR("Processing sparsebundle [%s] failed\n", name); + return true; + } + + /* + * Arithmetic on 32-bit systems may cause overflow, depending on + * size_t precision. First we check its unlikely, then we + * force the precision into target off_t, then we check that + * the total did not overflow either. + */ + if (bandsize > SIZE_MAX/nbands) { + DBG_ERR("tmsize potential overflow: bandsize [%zu] nbands [%zu]\n", + bandsize, nbands); + return false; + } + tm_size = (off_t)bandsize * (off_t)nbands; + + if (state->total_size + tm_size < state->total_size) { + DBG_ERR("tm total size overflow: bandsize [%zu] nbands [%zu]\n", + bandsize, nbands); + return false; + } + + state->total_size += tm_size; + + DBG_DEBUG("[%s] tm_size [%jd] total_size [%jd]\n", + name, (intmax_t)tm_size, (intmax_t)state->total_size); + + return true; +} + +/** + * Calculate used size of a TimeMachine volume + * + * This assumes that the volume is used only for TimeMachine. + * + * - readdir(basedir of share), then + * - for every element that matches regex "^\(.*\)\.sparsebundle$" : + * - parse "\1.sparsebundle/Info.plist" and read the band-size XML key + * - count band files in "\1.sparsebundle/bands/" + * - calculate used size of all bands: band_count * band_size + **/ +static uint64_t fruit_disk_free(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + uint64_t *_bsize, + uint64_t *_dfree, + uint64_t *_dsize) +{ + struct fruit_config_data *config = NULL; + struct fruit_disk_free_state state = {0}; + struct smb_Dir *dir_hnd = NULL; + const char *dname = NULL; + char *talloced = NULL; + uint64_t dfree; + uint64_t dsize; + bool ok; + NTSTATUS status; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return UINT64_MAX); + + if (!config->time_machine || + config->time_machine_max_size == 0) + { + return SMB_VFS_NEXT_DISK_FREE(handle, + smb_fname, + _bsize, + _dfree, + _dsize); + } + + status = OpenDir(talloc_tos(), + handle->conn, + smb_fname, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + errno = map_errno_from_nt_status(status); + return UINT64_MAX; + } + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + ok = fruit_tmsize_do_dirent(handle, &state, dname); + if (!ok) { + TALLOC_FREE(talloced); + TALLOC_FREE(dir_hnd); + return UINT64_MAX; + } + TALLOC_FREE(talloced); + } + + TALLOC_FREE(dir_hnd); + + dsize = config->time_machine_max_size / 512; + dfree = dsize - (state.total_size / 512); + if (dfree > dsize) { + dfree = 0; + } + + *_bsize = 512; + *_dsize = dsize; + *_dfree = dfree; + return dfree / 2; +} + +static uint64_t fruit_fs_file_id(struct vfs_handle_struct *handle, + const SMB_STRUCT_STAT *psbuf) +{ + struct fruit_config_data *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, config, + struct fruit_config_data, + return 0); + + if (global_fruit_config.nego_aapl && + config->aapl_zero_file_id) + { + return 0; + } + + return SMB_VFS_NEXT_FS_FILE_ID(handle, psbuf); +} + +static struct vfs_fn_pointers vfs_fruit_fns = { + .connect_fn = fruit_connect, + .disk_free_fn = fruit_disk_free, + + /* File operations */ + .fchmod_fn = fruit_fchmod, + .unlinkat_fn = fruit_unlinkat, + .renameat_fn = fruit_renameat, + .openat_fn = fruit_openat, + .close_fn = fruit_close, + .pread_fn = fruit_pread, + .pwrite_fn = fruit_pwrite, + .pread_send_fn = fruit_pread_send, + .pread_recv_fn = fruit_pread_recv, + .pwrite_send_fn = fruit_pwrite_send, + .pwrite_recv_fn = fruit_pwrite_recv, + .fsync_send_fn = fruit_fsync_send, + .fsync_recv_fn = fruit_fsync_recv, + .stat_fn = fruit_stat, + .lstat_fn = fruit_lstat, + .fstat_fn = fruit_fstat, + .fstreaminfo_fn = fruit_fstreaminfo, + .fntimes_fn = fruit_fntimes, + .ftruncate_fn = fruit_ftruncate, + .fallocate_fn = fruit_fallocate, + .create_file_fn = fruit_create_file, + .freaddir_attr_fn = fruit_freaddir_attr, + .offload_read_send_fn = fruit_offload_read_send, + .offload_read_recv_fn = fruit_offload_read_recv, + .offload_write_send_fn = fruit_offload_write_send, + .offload_write_recv_fn = fruit_offload_write_recv, + .fs_file_id_fn = fruit_fs_file_id, + + /* NT ACL operations */ + .fget_nt_acl_fn = fruit_fget_nt_acl, + .fset_nt_acl_fn = fruit_fset_nt_acl, +}; + +static_decl_vfs; +NTSTATUS vfs_fruit_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fruit", + &vfs_fruit_fns); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + vfs_fruit_debug_level = debug_add_class("fruit"); + if (vfs_fruit_debug_level == -1) { + vfs_fruit_debug_level = DBGC_VFS; + DEBUG(0, ("%s: Couldn't register custom debugging class!\n", + "vfs_fruit_init")); + } else { + DEBUG(10, ("%s: Debug class number of '%s': %d\n", + "vfs_fruit_init","fruit",vfs_fruit_debug_level)); + } + + return ret; +} -- cgit v1.2.3