return NT_STATUS_OK;
}
+bool smbd_is_tmpname(const char *n, int *_unlink_flags)
+{
+ const char *p = n;
+ int unlink_flags = INT_MAX;
+ struct server_id id;
+ bool exists;
+
+ if (_unlink_flags != NULL) {
+ *_unlink_flags = INT_MAX;
+ }
+
+ if (!IS_SMBD_TMPNAME_PREFIX(n)) {
+ return false;
+ }
+ p += sizeof(SMBD_TMPNAME_PREFIX) - 1;
+ switch (p[0]) {
+ case 'D':
+ unlink_flags = AT_REMOVEDIR;
+ break;
+ default:
+ return false;
+ }
+ p += 1;
+ if (p[0] != ':') {
+ return false;
+ }
+ p += 1;
+
+ id = server_id_from_string_ex(get_my_vnn(), '%', p);
+ if (id.pid == UINT64_MAX) {
+ return false;
+ }
+ if (id.unique_id == 0) {
+ return false;
+ }
+ if (id.unique_id == SERVERID_UNIQUE_ID_NOT_TO_VERIFY) {
+ return false;
+ }
+
+ if (_unlink_flags == NULL) {
+ return true;
+ }
+
+ exists = serverid_exists(&id);
+ if (!exists) {
+ /*
+ * let the caller know it's stale
+ * and should be removed
+ */
+ *_unlink_flags = unlink_flags;
+ }
+
+ return true;
+}
+
static NTSTATUS mkdir_internal(connection_struct *conn,
struct smb_filename *parent_dir_fname, /* parent. */
struct smb_filename *smb_fname_atname, /* atname relative to parent. */
bool posix_open = false;
bool need_re_stat = false;
uint32_t access_mask = SEC_DIR_ADD_SUBDIR;
+ struct smb_filename *first_atname = NULL;
+ struct smb_filename *tmp_atname = NULL;
+ char *orig_dname = NULL;
+ char *tmp_dname = NULL;
+ int vfs_use_tmp = lp_vfs_mkdir_use_tmp_name(SNUM(conn));
+ bool need_tmpname = false;
+ struct server_id id = messaging_server_id(conn->sconn->msg_ctx);
+ struct server_id_buf idbuf;
+ char *idstr = server_id_str_buf_unique_ex(id, '%', &idbuf);
struct vfs_open_how how = { .flags = O_RDONLY|O_DIRECTORY, };
int ret;
if (directory_has_default_acl_fsp(parent_dir_fname->fsp)) {
mode = (0777 & lp_directory_mask(SNUM(conn)));
}
+ need_tmpname = true;
+ } else if (lp_store_dos_attributes(SNUM(conn))) {
+ need_tmpname = true;
+ } else if (lp_inherit_permissions(SNUM(conn))) {
+ need_tmpname = true;
+ } else if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
+ need_tmpname = true;
+ } else if (lp_nt_acl_support(SNUM(conn)) && sd != NULL) {
+ need_tmpname = true;
+ }
+
+ if (vfs_use_tmp != Auto) {
+ need_tmpname = vfs_use_tmp;
+ }
+
+ if (!need_tmpname) {
+ first_atname = smb_fname_atname;
+ goto mkdir_first;
+ }
+
+ /*
+ * In order to avoid races where other clients could
+ * see the directory before it is setup completely
+ * we use a temporary name and rename it
+ * when everything is ready.
+ */
+
+ orig_dname = smb_dname->base_name;
+
+ tmp_atname = cp_smb_filename(frame,
+ smb_fname_atname);
+ if (tmp_atname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(tmp_atname->base_name);
+ tmp_atname->base_name = talloc_asprintf(tmp_atname,
+ "%s%s:%s",
+ SMBD_TMPDIR_PREFIX,
+ idstr,
+ smb_fname_atname->base_name);
+ if (tmp_atname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ SMB_ASSERT(smbd_is_tmpname(tmp_atname->base_name, NULL));
+ if (!ISDOT(parent_dir_fname->base_name)) {
+ tmp_dname = talloc_asprintf(frame,
+ "%s/%s",
+ parent_dir_fname->base_name,
+ tmp_atname->base_name);
+ if (tmp_dname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ tmp_dname = talloc_strdup(frame, tmp_atname->base_name);
+ if (tmp_dname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
}
+ smb_dname->base_name = tmp_dname;
+
+ DBG_DEBUG("temporary replace '%s' by '%s'\n",
+ orig_dname, tmp_dname);
+
+ first_atname = tmp_atname;
+
+mkdir_first:
ret = SMB_VFS_MKDIRAT(conn,
parent_dir_fname->fsp,
- smb_fname_atname,
+ first_atname,
mode);
if (ret != 0) {
status = map_nt_error_from_unix(errno);
DBG_NOTICE("MKDIRAT failed for '%s': %s\n",
smb_fname_str_dbg(smb_dname), nt_errstr(status));
- TALLOC_FREE(frame);
- return status;
+ goto restore_orig;
}
/*
*/
fsp->fsp_flags.is_pathref = true;
- status = fd_openat(parent_dir_fname->fsp, smb_fname_atname, fsp, &how);
+ status = fd_openat(parent_dir_fname->fsp, first_atname, fsp, &how);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("fd_openat() failed for '%s': %s\n",
smb_fname_str_dbg(smb_dname), nt_errstr(status));
- TALLOC_FREE(frame);
- return status;
+ goto restore_orig;
}
/* Ensure we're checking for a symlink here.... */
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Could not stat directory '%s' just created: %s\n",
smb_fname_str_dbg(smb_dname), nt_errstr(status));
- TALLOC_FREE(frame);
- return status;
+ goto restore_orig;
}
if (!S_ISDIR(smb_dname->st.st_ex_mode)) {
- status = NT_STATUS_NOT_A_DIRECTORY;
DBG_ERR("Directory '%s' just created is not a directory !\n",
smb_fname_str_dbg(smb_dname));
- TALLOC_FREE(frame);
- return status;
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto restore_orig;
}
if (lp_store_dos_attributes(SNUM(conn))) {
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Could not stat directory '%s' just created: %s\n",
smb_fname_str_dbg(smb_dname), nt_errstr(status));
- TALLOC_FREE(frame);
- return status;
+ goto restore_orig;
}
}
DBG_WARNING("apply_new_nt_acl() failed for %s with %s\n",
fsp_str_dbg(fsp),
nt_errstr(status));
- TALLOC_FREE(frame);
- return status;
+ goto do_unlink;
+ }
+ }
+
+ if (!need_tmpname) {
+ goto done;
+ }
+
+ /*
+ * A rename needs a valid stat for the source,
+ * see vfs_fruit.c ...
+ */
+ tmp_atname->st = smb_dname->st;
+
+ {
+ /*
+ * This is the strategie we use without having
+ * renameat2(RENAME_NOREPLACE):
+ *
+ * renameat() is able to replace a directory if the source is
+ * also a directory.
+ *
+ * So in order to avoid races as much as possible we do a
+ * mkdirat() with mode 0 in order to catch EEXIST almost
+ * atomically, when this code runs by two processes at the same
+ * time.
+ *
+ * Then a renameat() makes the temporary directory available for
+ * clients.
+ *
+ * This a much smaller window where the other clients may see
+ * the incomplete directory, which has a mode of 0.
+ */
+
+ DBG_DEBUG("MKDIRAT/RENAMEAT '%s' -> '%s'\n",
+ tmp_dname, orig_dname);
+
+ ret = SMB_VFS_MKDIRAT(conn,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_NOTICE("MKDIRAT failed for '%s': %s\n",
+ orig_dname, nt_errstr(status));
+ goto do_unlink;
}
+
+ ret = SMB_VFS_RENAMEAT(conn,
+ parent_dir_fname->fsp,
+ tmp_atname,
+ parent_dir_fname->fsp,
+ smb_fname_atname);
}
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_NOTICE("RENAMEAT failed for '%s' -> '%s': %s\n",
+ tmp_dname, orig_dname, nt_errstr(status));
+ goto do_unlink;
+ }
+ smb_fname_atname->st = tmp_atname->st;
+ smb_dname->base_name = orig_dname;
+
+done:
DBG_DEBUG("Created directory '%s'\n",
smb_fname_str_dbg(smb_dname));
TALLOC_FREE(frame);
return NT_STATUS_OK;
+
+do_unlink:
+ DBG_NOTICE("%s: rollback and unlink '%s'\n",
+ nt_errstr(status),
+ tmp_dname);
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_dir_fname->fsp,
+ tmp_atname,
+ AT_REMOVEDIR);
+ if (ret == 0) {
+ DBG_NOTICE("SMB_VFS_UNLINKAT(%s): OK\n",
+ tmp_dname);
+ } else {
+ NTSTATUS status2 = map_nt_error_from_unix(errno);
+ DBG_WARNING("SMB_VFS_UNLINKAT(%s) ignoring %s\n",
+ tmp_dname, nt_errstr(status2));
+ }
+
+restore_orig:
+ if (!need_tmpname) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ DBG_NOTICE("%s: restoring '%s' -> '%s'\n",
+ nt_errstr(status),
+ tmp_dname,
+ orig_dname);
+ SET_STAT_INVALID(smb_fname_atname->st);
+ smb_dname->base_name = orig_dname;
+ SET_STAT_INVALID(smb_dname->st);
+ TALLOC_FREE(frame);
+ return status;
}
/****************************************************************************