files_struct *file_find_dif(struct smbd_server_connection *sconn, struct file_id id, unsigned long gen_id) { int count=0; files_struct *fsp; if (gen_id == 0) { return NULL; } for (fsp=sconn->files; fsp; fsp=fsp->next,count++) { /* We can have a fsp->fh->fd == -1 here as it could be a stat open. */ if (file_id_equal(&fsp->file_id, &id) && fsp->fh->gen_id == gen_id ) { if (count > 10) { DLIST_PROMOTE(sconn->files, fsp); } /* Paranoia check. */ if ((fsp->fh->fd == -1) && (fsp->oplock_type != NO_OPLOCK)) { DEBUG(0,("file_find_dif: file %s file_id = " "%s, gen = %u oplock_type = %u is a " "stat open with oplock type !\n", fsp_str_dbg(fsp), file_id_string_tos(&fsp->file_id), (unsigned int)fsp->fh->gen_id, (unsigned int)fsp->oplock_type )); smb_panic("file_find_dif"); } return fsp; } } return NULL; }
files_struct *file_find_di_next(files_struct *start_fsp) { files_struct *fsp; for (fsp = start_fsp->next;fsp;fsp=fsp->next) { if (file_id_equal(&fsp->file_id, &start_fsp->file_id)) { return fsp; } } return NULL; }
static bool share_modes_identical(struct share_mode_entry *e1, struct share_mode_entry *e2) { /* We used to check for e1->share_access == e2->share_access here as well as the other fields but 2 different DOS or FCB opens sharing the same share mode entry may validly differ in fsp->share_access field. */ return (serverid_equal(&e1->pid, &e2->pid) && file_id_equal(&e1->id, &e2->id) && e1->share_file_id == e2->share_file_id ); }
files_struct *file_find_di_first(struct smbd_server_connection *sconn, struct file_id id) { files_struct *fsp; if (file_id_equal(&sconn->fsp_fi_cache.id, &id)) { /* Positive or negative cache hit. */ return sconn->fsp_fi_cache.fsp; } sconn->fsp_fi_cache.id = id; for (fsp=sconn->files;fsp;fsp=fsp->next) { if (file_id_equal(&fsp->file_id, &id)) { /* Setup positive cache. */ sconn->fsp_fi_cache.fsp = fsp; return fsp; } } /* Setup negative cache. */ sconn->fsp_fi_cache.fsp = NULL; return NULL; }
struct share_mode_lock *get_share_mode_lock( TALLOC_CTX *mem_ctx, struct file_id id, const char *servicepath, const struct smb_filename *smb_fname, const struct timespec *old_write_time) { struct share_mode_lock *lck; lck = talloc(mem_ctx, struct share_mode_lock); if (lck == NULL) { DEBUG(1, ("talloc failed\n")); return NULL; } if (the_lock == NULL) { the_lock = get_share_mode_lock_internal( lck, id, servicepath, smb_fname, old_write_time); if (the_lock == NULL) { goto fail; } talloc_set_destructor(the_lock, the_lock_destructor); the_lock_id = id; } else { if (!file_id_equal(&the_lock_id, &id)) { DEBUG(1, ("Can not lock two share modes " "simultaneously\n")); goto fail; } if (talloc_reference(lck, the_lock) == NULL) { DEBUG(1, ("talloc_reference failed\n")); goto fail; } } lck->data = the_lock->data; return lck; fail: TALLOC_FREE(lck); return NULL; }
static NTSTATUS close_remove_share_mode(files_struct *fsp, enum file_close_type close_type) { connection_struct *conn = fsp->conn; struct server_id self = messaging_server_id(conn->sconn->msg_ctx); bool delete_file = false; bool changed_user = false; struct share_mode_lock *lck = NULL; NTSTATUS status = NT_STATUS_OK; NTSTATUS tmp_status; struct file_id id; const struct security_unix_token *del_token = NULL; const struct security_token *del_nt_token = NULL; bool got_tokens = false; bool normal_close; int ret_flock, retries = 1; /* Ensure any pending write time updates are done. */ if (fsp->update_write_time_event) { update_write_time_handler(fsp->conn->sconn->ev_ctx, fsp->update_write_time_event, timeval_current(), (void *)fsp); } /* * Lock the share entries, and determine if we should delete * on close. If so delete whilst the lock is still in effect. * This prevents race conditions with the file being created. JRA. */ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); if (lck == NULL) { DEBUG(0, ("close_remove_share_mode: Could not get share mode " "lock for file %s\n", fsp_str_dbg(fsp))); return NT_STATUS_INVALID_PARAMETER; } if (fsp->write_time_forced) { DEBUG(10,("close_remove_share_mode: write time forced " "for file %s\n", fsp_str_dbg(fsp))); set_close_write_time(fsp, lck->data->changed_write_time); } else if (fsp->update_write_time_on_close) { /* Someone had a pending write. */ if (null_timespec(fsp->close_write_time)) { DEBUG(10,("close_remove_share_mode: update to current time " "for file %s\n", fsp_str_dbg(fsp))); /* Update to current time due to "normal" write. */ set_close_write_time(fsp, timespec_current()); } else { DEBUG(10,("close_remove_share_mode: write time pending " "for file %s\n", fsp_str_dbg(fsp))); /* Update to time set on close call. */ set_close_write_time(fsp, fsp->close_write_time); } } if (fsp->initial_delete_on_close && !is_delete_on_close_set(lck, fsp->name_hash)) { bool became_user = False; /* Initial delete on close was set and no one else * wrote a real delete on close. */ if (get_current_vuid(conn) != fsp->vuid) { become_user(conn, fsp->vuid); became_user = True; } fsp->delete_on_close = true; set_delete_on_close_lck(fsp, lck, get_current_nttok(conn), get_current_utok(conn)); if (became_user) { unbecome_user(); } } delete_file = is_delete_on_close_set(lck, fsp->name_hash); if (delete_file) { int i; /* See if others still have the file open via this pathname. If this is the case, then don't delete. If all opens are POSIX delete now. */ for (i=0; i<lck->data->num_share_modes; i++) { struct share_mode_entry *e = &lck->data->share_modes[i]; if (!is_valid_share_mode_entry(e)) { continue; } if (e->name_hash != fsp->name_hash) { continue; } if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) && (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) { continue; } if (serverid_equal(&self, &e->pid) && (e->share_file_id == fsp->fh->gen_id)) { continue; } if (share_mode_stale_pid(lck->data, i)) { continue; } delete_file = False; break; } } /* * NT can set delete_on_close of the last open * reference to a file. */ normal_close = (close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE); if (!normal_close || !delete_file) { status = NT_STATUS_OK; goto done; } /* * Ok, we have to delete the file */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set " "- deleting file.\n", fsp_str_dbg(fsp))); /* * Don't try to update the write time when we delete the file */ fsp->update_write_time_on_close = false; got_tokens = get_delete_on_close_token(lck, fsp->name_hash, &del_nt_token, &del_token); SMB_ASSERT(got_tokens); if (!unix_token_equal(del_token, get_current_utok(conn))) { /* Become the user who requested the delete. */ DEBUG(5,("close_remove_share_mode: file %s. " "Change user to uid %u\n", fsp_str_dbg(fsp), (unsigned int)del_token->uid)); if (!push_sec_ctx()) { smb_panic("close_remove_share_mode: file %s. failed to push " "sec_ctx.\n"); } set_sec_ctx(del_token->uid, del_token->gid, del_token->ngroups, del_token->groups, del_nt_token); changed_user = true; } /* We can only delete the file if the name we have is still valid and hasn't been renamed. */ tmp_status = vfs_stat_fsp(fsp); if (!NT_STATUS_IS_OK(tmp_status)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and stat failed with error %s\n", fsp_str_dbg(fsp), nt_errstr(tmp_status))); /* * Don't save the errno here, we ignore this error */ goto done; } id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st); if (!file_id_equal(&fsp->file_id, &id)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and dev and/or inode does not match\n", fsp_str_dbg(fsp))); DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, " "stat file_id %s\n", fsp_str_dbg(fsp), file_id_string_tos(&fsp->file_id), file_id_string_tos(&id))); /* * Don't save the errno here, we ignore this error */ goto done; } if ((conn->fs_capabilities & FILE_NAMED_STREAMS) && !is_ntfs_stream_smb_fname(fsp->fsp_name)) { status = delete_all_streams(conn, fsp->fsp_name->base_name); if (!NT_STATUS_IS_OK(status)) { DEBUG(5, ("delete_all_streams failed: %s\n", nt_errstr(status))); goto done; } } retry_delete: /* temporary files with delete on close set will not be deleted on a * cifs share using a netapp backend since they are opened with * read + write access mask. * close the file to allow the delete. */ if (fsp->can_write && !S_ISDIR(fsp->fsp_name->st.st_ex_mode) && fsp->fh->ref_count == 1 && retries) { status = fd_close(fsp); if (!NT_STATUS_IS_OK(status)) { DEBUG(3, ("close_remove_share_mode: Error %s closing %s\n", nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name))); goto skip_retry; } if (SMB_VFS_UNLINK(conn, fsp->fsp_name) != 0) { /* * This call can potentially fail as another smbd may * have had the file open with delete on close set and * deleted it when its last reference to this file * went away. Hence we log this but not at debug level * zero. */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and unlink failed with error %s\n", fsp_str_dbg(fsp), strerror(errno))); status = map_nt_error_from_unix(errno); retries = 0; goto retry_delete; } } else { if (SMB_VFS_UNLINK(conn, fsp->fsp_name) != 0) { /* * This call can potentially fail as another smbd may * have had the file open with delete on close set and * deleted it when its last reference to this file * went away. Hence we log this but not at debug level * zero. */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and unlink failed with error %s\n", fsp_str_dbg(fsp), strerror(errno))); status = map_nt_error_from_unix(errno); } } /* As we now have POSIX opens which can unlink * with other open files we may have taken * this code path with more than one share mode * entry - ensure we only delete once by resetting * the delete on close flag. JRA. */ skip_retry: fsp->delete_on_close = false; reset_delete_on_close_lck(fsp, lck); done: if (changed_user) { /* unbecome user. */ pop_sec_ctx(); } /* remove filesystem sharemodes */ ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, 0, 0); if (ret_flock == -1) { DEBUG(2, ("close_remove_share_mode: removing kernel flock for " "%s failed: %s\n", fsp_str_dbg(fsp), strerror(errno))); } if (!del_share_mode(lck, fsp)) { DEBUG(0, ("close_remove_share_mode: Could not delete share " "entry for file %s\n", fsp_str_dbg(fsp))); } TALLOC_FREE(lck); if (delete_file) { /* * Do the notification after we released the share * mode lock. Inside notify_fname we take out another * tdb lock. With ctdb also accessing our databases, * this can lead to deadlocks. Putting this notify * after the TALLOC_FREE(lck) above we avoid locking * two records simultaneously. Notifies are async and * informational only, so calling the notify_fname * without holding the share mode lock should not do * any harm. */ notify_fname(conn, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_FILE_NAME, fsp->fsp_name->base_name); } return status; }
static NTSTATUS close_remove_share_mode(files_struct *fsp, enum file_close_type close_type) { connection_struct *conn = fsp->conn; bool delete_file = false; bool changed_user = false; struct share_mode_lock *lck = NULL; NTSTATUS status = NT_STATUS_OK; NTSTATUS tmp_status; struct file_id id; /* Ensure any pending write time updates are done. */ if (fsp->update_write_time_event) { update_write_time_handler(smbd_event_context(), fsp->update_write_time_event, timeval_current(), (void *)fsp); } /* * Lock the share entries, and determine if we should delete * on close. If so delete whilst the lock is still in effect. * This prevents race conditions with the file being created. JRA. */ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL, NULL); if (lck == NULL) { DEBUG(0, ("close_remove_share_mode: Could not get share mode " "lock for file %s\n", fsp_str_dbg(fsp))); status = NT_STATUS_INVALID_PARAMETER; goto done; } if (fsp->write_time_forced) { DEBUG(10,("close_remove_share_mode: write time forced " "for file %s\n", fsp_str_dbg(fsp))); set_close_write_time(fsp, lck->changed_write_time); } else if (fsp->update_write_time_on_close) { /* Someone had a pending write. */ if (null_timespec(fsp->close_write_time)) { DEBUG(10,("close_remove_share_mode: update to current time " "for file %s\n", fsp_str_dbg(fsp))); /* Update to current time due to "normal" write. */ set_close_write_time(fsp, timespec_current()); } else { DEBUG(10,("close_remove_share_mode: write time pending " "for file %s\n", fsp_str_dbg(fsp))); /* Update to time set on close call. */ set_close_write_time(fsp, fsp->close_write_time); } } if (!del_share_mode(lck, fsp)) { DEBUG(0, ("close_remove_share_mode: Could not delete share " "entry for file %s\n", fsp_str_dbg(fsp))); } if (fsp->initial_delete_on_close && (lck->delete_token == NULL)) { bool became_user = False; /* Initial delete on close was set and no one else * wrote a real delete on close. */ if (current_user.vuid != fsp->vuid) { become_user(conn, fsp->vuid); became_user = True; } fsp->delete_on_close = true; set_delete_on_close_lck(lck, True, ¤t_user.ut); if (became_user) { unbecome_user(); } } delete_file = lck->delete_on_close; if (delete_file) { int i; /* See if others still have the file open. If this is the * case, then don't delete. If all opens are POSIX delete now. */ for (i=0; i<lck->num_share_modes; i++) { struct share_mode_entry *e = &lck->share_modes[i]; if (is_valid_share_mode_entry(e)) { if (fsp->posix_open && (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) { continue; } delete_file = False; break; } } } /* Notify any deferred opens waiting on this close. */ notify_deferred_opens(lck); reply_to_oplock_break_requests(fsp); /* * NT can set delete_on_close of the last open * reference to a file. */ if (!(close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) || !delete_file || (lck->delete_token == NULL)) { TALLOC_FREE(lck); return NT_STATUS_OK; } /* * Ok, we have to delete the file */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set " "- deleting file.\n", fsp_str_dbg(fsp))); /* * Don't try to update the write time when we delete the file */ fsp->update_write_time_on_close = false; if (!unix_token_equal(lck->delete_token, ¤t_user.ut)) { /* Become the user who requested the delete. */ DEBUG(5,("close_remove_share_mode: file %s. " "Change user to uid %u\n", fsp_str_dbg(fsp), (unsigned int)lck->delete_token->uid)); if (!push_sec_ctx()) { smb_panic("close_remove_share_mode: file %s. failed to push " "sec_ctx.\n"); } set_sec_ctx(lck->delete_token->uid, lck->delete_token->gid, lck->delete_token->ngroups, lck->delete_token->groups, NULL); changed_user = true; } /* We can only delete the file if the name we have is still valid and hasn't been renamed. */ tmp_status = vfs_stat_fsp(fsp); if (!NT_STATUS_IS_OK(tmp_status)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and stat failed with error %s\n", fsp_str_dbg(fsp), nt_errstr(tmp_status))); /* * Don't save the errno here, we ignore this error */ goto done; } id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st); if (!file_id_equal(&fsp->file_id, &id)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and dev and/or inode does not match\n", fsp_str_dbg(fsp))); DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, " "stat file_id %s\n", fsp_str_dbg(fsp), file_id_string_tos(&fsp->file_id), file_id_string_tos(&id))); /* * Don't save the errno here, we ignore this error */ goto done; } if ((conn->fs_capabilities & FILE_NAMED_STREAMS) && !is_ntfs_stream_smb_fname(fsp->fsp_name)) { status = delete_all_streams(conn, fsp->fsp_name->base_name); if (!NT_STATUS_IS_OK(status)) { DEBUG(5, ("delete_all_streams failed: %s\n", nt_errstr(status))); goto done; } } if (SMB_VFS_UNLINK(conn, fsp->fsp_name) != 0) { /* * This call can potentially fail as another smbd may * have had the file open with delete on close set and * deleted it when its last reference to this file * went away. Hence we log this but not at debug level * zero. */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and unlink failed with error %s\n", fsp_str_dbg(fsp), strerror(errno))); status = map_nt_error_from_unix(errno); } notify_fname(conn, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_FILE_NAME, fsp->fsp_name->base_name); /* As we now have POSIX opens which can unlink * with other open files we may have taken * this code path with more than one share mode * entry - ensure we only delete once by resetting * the delete on close flag. JRA. */ fsp->delete_on_close = false; set_delete_on_close_lck(lck, False, NULL); done: if (changed_user) { /* unbecome user. */ pop_sec_ctx(); } TALLOC_FREE(lck); return status; }
NTSTATUS leases_db_add(const struct GUID *client_guid, const struct smb2_lease_key *lease_key, const struct file_id *id, const char *servicepath, const char *base_name, const char *stream_name) { TDB_DATA db_key, db_value; DATA_BLOB blob; struct db_record *rec; NTSTATUS status; bool ok; struct leases_db_value new_value; struct leases_db_file new_file; struct leases_db_value *value = NULL; enum ndr_err_code ndr_err; if (!leases_db_init(false)) { return NT_STATUS_INTERNAL_ERROR; } ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key); if (!ok) { DEBUG(10, ("%s: leases_db_key failed\n", __func__)); return NT_STATUS_NO_MEMORY; } rec = dbwrap_fetch_locked(leases_db, talloc_tos(), db_key); TALLOC_FREE(db_key.dptr); if (rec == NULL) { return NT_STATUS_INTERNAL_ERROR; } db_value = dbwrap_record_get_value(rec); if (db_value.dsize != 0) { uint32_t i; DEBUG(10, ("%s: record exists\n", __func__)); value = talloc(talloc_tos(), struct leases_db_value); if (value == NULL) { status = NT_STATUS_NO_MEMORY; goto out; } blob.data = db_value.dptr; blob.length = db_value.dsize; ndr_err = ndr_pull_struct_blob_all( &blob, value, value, (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n", __func__, ndr_errstr(ndr_err))); status = ndr_map_error2ntstatus(ndr_err); goto out; } /* id must be unique. */ for (i = 0; i < value->num_files; i++) { if (file_id_equal(id, &value->files[i].id)) { status = NT_STATUS_OBJECT_NAME_COLLISION; goto out; } } value->files = talloc_realloc(value, value->files, struct leases_db_file, value->num_files + 1); if (value->files == NULL) { status = NT_STATUS_NO_MEMORY; goto out; } value->files[value->num_files].id = *id; value->files[value->num_files].servicepath = servicepath; value->files[value->num_files].base_name = base_name; value->files[value->num_files].stream_name = stream_name; value->num_files += 1; } else {
NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, struct smb_request *smb1req, struct smbXsrv_open *op, const DATA_BLOB old_cookie, TALLOC_CTX *mem_ctx, files_struct **result, DATA_BLOB *new_cookie) { struct share_mode_lock *lck; struct share_mode_entry *e; struct files_struct *fsp = NULL; NTSTATUS status; bool ok; int ret; int flags = 0; struct file_id file_id; struct smb_filename *smb_fname = NULL; enum ndr_err_code ndr_err; struct vfs_default_durable_cookie cookie; DATA_BLOB new_cookie_blob = data_blob_null; *result = NULL; *new_cookie = data_blob_null; if (!lp_durable_handles(SNUM(conn))) { return NT_STATUS_NOT_SUPPORTED; } /* * the checks for kernel oplocks * and similar things are done * in the vfs_default_durable_cookie() * call below. */ ZERO_STRUCT(cookie); ndr_err = ndr_pull_struct_blob(&old_cookie, talloc_tos(), &cookie, (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { status = ndr_map_error2ntstatus(ndr_err); return status; } if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) { return NT_STATUS_INVALID_PARAMETER; } if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) { return NT_STATUS_INVALID_PARAMETER; } if (!cookie.allow_reconnect) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } if (strcmp(cookie.servicepath, conn->connectpath) != 0) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } /* Create an smb_filename with stream_name == NULL. */ smb_fname = synthetic_smb_fname(talloc_tos(), cookie.base_name, NULL, NULL); if (smb_fname == NULL) { return NT_STATUS_NO_MEMORY; } ret = SMB_VFS_LSTAT(conn, smb_fname); if (ret == -1) { status = map_nt_error_from_unix_common(errno); DEBUG(1, ("Unable to lstat stream: %s => %s\n", smb_fname_str_dbg(smb_fname), nt_errstr(status))); return status; } if (!S_ISREG(smb_fname->st.st_ex_mode)) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st); if (!file_id_equal(&cookie.id, &file_id)) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } /* * 1. check entry in locking.tdb */ lck = get_existing_share_mode_lock(mem_ctx, file_id); if (lck == NULL) { DEBUG(5, ("vfs_default_durable_reconnect: share-mode lock " "not obtained from db\n")); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } if (lck->data->num_share_modes == 0) { DEBUG(1, ("vfs_default_durable_reconnect: Error: no share-mode " "entry in existing share mode lock\n")); TALLOC_FREE(lck); return NT_STATUS_INTERNAL_DB_ERROR; } if (lck->data->num_share_modes > 1) { /* * It can't be durable if there is more than one handle * on the file. */ DEBUG(5, ("vfs_default_durable_reconnect: more than one " "share-mode entry - can not be durable\n")); TALLOC_FREE(lck); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } e = &lck->data->share_modes[0]; if (!server_id_is_disconnected(&e->pid)) { DEBUG(5, ("vfs_default_durable_reconnect: denying durable " "reconnect for handle that was not marked " "disconnected (e.g. smbd or cluster node died)\n")); TALLOC_FREE(lck); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } if (e->share_file_id != op->global->open_persistent_id) { DEBUG(5, ("vfs_default_durable_reconnect: denying durable " "share_file_id changed %llu != %llu" "(e.g. another client had opened the file)\n", (unsigned long long)e->share_file_id, (unsigned long long)op->global->open_persistent_id)); TALLOC_FREE(lck); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } if ((e->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) && !CAN_WRITE(conn)) { DEBUG(5, ("vfs_default_durable_reconnect: denying durable " "share[%s] is not writeable anymore\n", lp_servicename(talloc_tos(), SNUM(conn)))); TALLOC_FREE(lck); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } /* * 2. proceed with opening file */ status = fsp_new(conn, conn, &fsp); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("vfs_default_durable_reconnect: failed to create " "new fsp: %s\n", nt_errstr(status))); TALLOC_FREE(lck); return status; } fsp->fh->private_options = e->private_options; fsp->fh->gen_id = smbXsrv_open_hash(op); fsp->file_id = file_id; fsp->file_pid = smb1req->smbpid; fsp->vuid = smb1req->vuid; fsp->open_time = e->time; fsp->access_mask = e->access_mask; fsp->share_access = e->share_access; fsp->can_read = ((fsp->access_mask & (FILE_READ_DATA)) != 0); fsp->can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0); fsp->fnum = op->local_id; /* * TODO: * Do we need to store the modified flag in the DB? */ fsp->modified = false; /* * no durables for directories */ fsp->is_directory = false; /* * For normal files, can_lock == !is_directory */ fsp->can_lock = true; /* * We do not support aio write behind for smb2 */ fsp->aio_write_behind = false; fsp->oplock_type = e->op_type; fsp->initial_allocation_size = cookie.initial_allocation_size; fsp->fh->position_information = cookie.position_information; fsp->update_write_time_triggered = cookie.update_write_time_triggered; fsp->update_write_time_on_close = cookie.update_write_time_on_close; fsp->write_time_forced = cookie.write_time_forced; fsp->close_write_time = cookie.close_write_time; status = fsp_set_smb_fname(fsp, smb_fname); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(lck); fsp_free(fsp); DEBUG(0, ("vfs_default_durable_reconnect: " "fsp_set_smb_fname failed: %s\n", nt_errstr(status))); return status; } op->compat = fsp; fsp->op = op; e->pid = messaging_server_id(conn->sconn->msg_ctx); e->op_mid = smb1req->mid; e->share_file_id = fsp->fh->gen_id; ok = brl_reconnect_disconnected(fsp); if (!ok) { status = NT_STATUS_INTERNAL_ERROR; DEBUG(1, ("vfs_default_durable_reconnect: " "failed to reopen brlocks: %s\n", nt_errstr(status))); TALLOC_FREE(lck); op->compat = NULL; fsp_free(fsp); return status; } /* * TODO: properly calculate open flags */ if (fsp->can_write && fsp->can_read) { flags = O_RDWR; } else if (fsp->can_write) { flags = O_WRONLY; } else if (fsp->can_read) { flags = O_RDONLY; } status = fd_open(conn, fsp, flags, 0 /* mode */); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(lck); DEBUG(1, ("vfs_default_durable_reconnect: failed to open " "file: %s\n", nt_errstr(status))); op->compat = NULL; fsp_free(fsp); return status; } /* * We now check the stat info stored in the cookie against * the current stat data from the file we just opened. * If any detail differs, we deny the durable reconnect, * because in that case it is very likely that someone * opened the file while the handle was disconnected, * which has to be interpreted as an oplock break. */ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st); if (ret == -1) { status = map_nt_error_from_unix_common(errno); DEBUG(1, ("Unable to fstat stream: %s => %s\n", smb_fname_str_dbg(smb_fname), nt_errstr(status))); ret = SMB_VFS_CLOSE(fsp); if (ret == -1) { DEBUG(0, ("vfs_default_durable_reconnect: " "SMB_VFS_CLOSE failed (%s) - leaking file " "descriptor\n", strerror(errno))); } TALLOC_FREE(lck); op->compat = NULL; fsp_free(fsp); return status; } if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) { ret = SMB_VFS_CLOSE(fsp); if (ret == -1) { DEBUG(0, ("vfs_default_durable_reconnect: " "SMB_VFS_CLOSE failed (%s) - leaking file " "descriptor\n", strerror(errno))); } TALLOC_FREE(lck); op->compat = NULL; fsp_free(fsp); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st); if (!file_id_equal(&cookie.id, &file_id)) { ret = SMB_VFS_CLOSE(fsp); if (ret == -1) { DEBUG(0, ("vfs_default_durable_reconnect: " "SMB_VFS_CLOSE failed (%s) - leaking file " "descriptor\n", strerror(errno))); } TALLOC_FREE(lck); op->compat = NULL; fsp_free(fsp); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } ok = vfs_default_durable_reconnect_check_stat(&cookie.stat_info, &fsp->fsp_name->st, fsp_str_dbg(fsp)); if (!ok) { ret = SMB_VFS_CLOSE(fsp); if (ret == -1) { DEBUG(0, ("vfs_default_durable_reconnect: " "SMB_VFS_CLOSE failed (%s) - leaking file " "descriptor\n", strerror(errno))); } TALLOC_FREE(lck); op->compat = NULL; fsp_free(fsp); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } status = set_file_oplock(fsp); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("vfs_default_durable_reconnect failed to set oplock " "after opening file: %s\n", nt_errstr(status))); ret = SMB_VFS_CLOSE(fsp); if (ret == -1) { DEBUG(0, ("vfs_default_durable_reconnect: " "SMB_VFS_CLOSE failed (%s) - leaking file " "descriptor\n", strerror(errno))); } TALLOC_FREE(lck); op->compat = NULL; fsp_free(fsp); return status; } status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(lck); DEBUG(1, ("vfs_default_durable_reconnect: " "vfs_default_durable_cookie - %s\n", nt_errstr(status))); op->compat = NULL; fsp_free(fsp); return status; } smb1req->chain_fsp = fsp; smb1req->smb2req->compat_chain_fsp = fsp; DEBUG(10, ("vfs_default_durable_reconnect: opened file '%s'\n", fsp_str_dbg(fsp))); /* * release the sharemode lock: this writes the changes */ lck->data->modified = true; TALLOC_FREE(lck); *result = fsp; *new_cookie = new_cookie_blob; return NT_STATUS_OK; }
NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp, const DATA_BLOB old_cookie, TALLOC_CTX *mem_ctx, DATA_BLOB *new_cookie) { struct connection_struct *conn = fsp->conn; NTSTATUS status; enum ndr_err_code ndr_err; struct vfs_default_durable_cookie cookie; DATA_BLOB new_cookie_blob = data_blob_null; struct share_mode_lock *lck; bool ok; *new_cookie = data_blob_null; ZERO_STRUCT(cookie); ndr_err = ndr_pull_struct_blob(&old_cookie, talloc_tos(), &cookie, (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { status = ndr_map_error2ntstatus(ndr_err); return status; } if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) { return NT_STATUS_INVALID_PARAMETER; } if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) { return NT_STATUS_INVALID_PARAMETER; } if (!file_id_equal(&fsp->file_id, &cookie.id)) { return NT_STATUS_INVALID_PARAMETER; } if (!BATCH_OPLOCK_TYPE(fsp->oplock_type)) { return NT_STATUS_NOT_SUPPORTED; } /* * For now let it be simple and do not keep * delete on close files durable open */ if (fsp->initial_delete_on_close) { return NT_STATUS_NOT_SUPPORTED; } if (fsp->delete_on_close) { return NT_STATUS_NOT_SUPPORTED; } if (!VALID_STAT(fsp->fsp_name->st)) { return NT_STATUS_NOT_SUPPORTED; } if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) { return NT_STATUS_NOT_SUPPORTED; } /* Ensure any pending write time updates are done. */ if (fsp->update_write_time_event) { update_write_time_handler(fsp->conn->sconn->ev_ctx, fsp->update_write_time_event, timeval_current(), (void *)fsp); } /* * The above checks are done in mark_share_mode_disconnected() too * but we want to avoid getting the lock if possible */ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); if (lck != NULL) { struct smb_file_time ft; ZERO_STRUCT(ft); if (fsp->write_time_forced) { ft.mtime = lck->data->changed_write_time; } else if (fsp->update_write_time_on_close) { if (null_timespec(fsp->close_write_time)) { ft.mtime = timespec_current(); } else { ft.mtime = fsp->close_write_time; } } if (!null_timespec(ft.mtime)) { round_timespec(conn->ts_res, &ft.mtime); file_ntimes(conn, fsp->fsp_name, &ft); } ok = mark_share_mode_disconnected(lck, fsp); if (!ok) { TALLOC_FREE(lck); } } if (lck != NULL) { ok = brl_mark_disconnected(fsp); if (!ok) { TALLOC_FREE(lck); } } if (lck == NULL) { return NT_STATUS_NOT_SUPPORTED; } TALLOC_FREE(lck); status = vfs_stat_fsp(fsp); if (!NT_STATUS_IS_OK(status)) { return status; } ZERO_STRUCT(cookie); cookie.allow_reconnect = true; cookie.id = fsp->file_id; cookie.servicepath = conn->connectpath; cookie.base_name = fsp->fsp_name->base_name; cookie.initial_allocation_size = fsp->initial_allocation_size; cookie.position_information = fsp->fh->position_information; cookie.update_write_time_triggered = fsp->update_write_time_triggered; cookie.update_write_time_on_close = fsp->update_write_time_on_close; cookie.write_time_forced = fsp->write_time_forced; cookie.close_write_time = fsp->close_write_time; cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev; cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino; cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode; cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink; cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid; cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid; cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev; cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size; cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime; cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime; cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime; cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime; cookie.stat_info.st_ex_calculated_birthtime = fsp->fsp_name->st.st_ex_calculated_birthtime; cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize; cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks; cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags; cookie.stat_info.st_ex_mask = fsp->fsp_name->st.st_ex_mask; ndr_err = ndr_push_struct_blob(&new_cookie_blob, mem_ctx, &cookie, (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { status = ndr_map_error2ntstatus(ndr_err); return status; } status = fd_close(fsp); if (!NT_STATUS_IS_OK(status)) { data_blob_free(&new_cookie_blob); return status; } *new_cookie = new_cookie_blob; return NT_STATUS_OK; }