/* rename_information level for streams */ static NTSTATUS pvfs_setfileinfo_rename_stream(struct pvfs_state *pvfs, struct ntvfs_request *req, struct pvfs_filename *name, int fd, DATA_BLOB *odb_locking_key, union smb_setfileinfo *info) { NTSTATUS status; struct odb_lock *lck = NULL; /* strangely, this gives a sharing violation, not invalid parameter */ if (info->rename_information.in.new_name[0] != ':') { return NT_STATUS_SHARING_VIOLATION; } status = pvfs_access_check_simple(pvfs, req, name, SEC_FILE_WRITE_ATTRIBUTE); if (!NT_STATUS_IS_OK(status)) { return status; } lck = odb_lock(req, pvfs->odb_context, odb_locking_key); if (lck == NULL) { DEBUG(0,("Unable to lock opendb for can_stat\n")); return NT_STATUS_INTERNAL_DB_CORRUPTION; } status = pvfs_stream_rename(pvfs, name, fd, info->rename_information.in.new_name+1, info->rename_information.in.overwrite); return status; }
/* destroy a pending open request */ static int pvfs_retry_destructor(struct pvfs_open_retry *r) { struct pvfs_state *pvfs = r->ntvfs->private_data; if (r->odb_locking_key.data) { struct odb_lock *lck; lck = odb_lock(r->req, pvfs->odb_context, &r->odb_locking_key); if (lck != NULL) { odb_remove_pending(lck, r); } talloc_free(lck); } return 0; }
/* cleanup a open directory handle */ static int pvfs_dir_handle_destructor(struct pvfs_file_handle *h) { int open_count; char *path = NULL; if (h->name->stream_name == NULL && pvfs_delete_on_close_set(h->pvfs, h, &open_count, &path) && open_count == 1) { NTSTATUS status; status = pvfs_xattr_unlink_hook(h->pvfs, path); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("Warning: xattr unlink hook failed for '%s' - %s\n", path, nt_errstr(status))); } if (rmdir(path) != 0) { DEBUG(0,("pvfs_dir_handle_destructor: failed to rmdir '%s' - %s\n", path, strerror(errno))); } } talloc_free(path); if (h->have_opendb_entry) { struct odb_lock *lck; NTSTATUS status; lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); if (lck == NULL) { DEBUG(0,("Unable to lock opendb for close\n")); return 0; } status = odb_close_file(lck, h); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("Unable to remove opendb entry for '%s' - %s\n", h->name->full_name, nt_errstr(status))); } talloc_free(lck); } return 0; }
/* create a new file */ static NTSTATUS pvfs_create_file(struct pvfs_state *pvfs, struct ntvfs_request *req, struct pvfs_filename *name, union smb_open *io) { struct pvfs_file *f; NTSTATUS status; struct ntvfs_handle *h; int flags, fd; struct odb_lock *lck; uint32_t create_options = io->generic.in.create_options; uint32_t share_access = io->generic.in.share_access; uint32_t access_mask = io->generic.in.access_mask; mode_t mode; uint32_t attrib; BOOL del_on_close; struct pvfs_filename *parent; uint32_t oplock_level = OPLOCK_NONE, oplock_granted; if ((io->ntcreatex.in.file_attr & FILE_ATTRIBUTE_READONLY) && (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) { return NT_STATUS_CANNOT_DELETE; } status = pvfs_access_check_create(pvfs, req, name, &access_mask); NT_STATUS_NOT_OK_RETURN(status); /* check that the parent isn't opened with delete on close set */ status = pvfs_resolve_parent(pvfs, req, name, &parent); if (NT_STATUS_IS_OK(status)) { DATA_BLOB locking_key; status = pvfs_locking_key(parent, req, &locking_key); NT_STATUS_NOT_OK_RETURN(status); status = odb_get_delete_on_close(pvfs->odb_context, &locking_key, &del_on_close, NULL, NULL); NT_STATUS_NOT_OK_RETURN(status); if (del_on_close) { return NT_STATUS_DELETE_PENDING; } } if (access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)) { flags = O_RDWR; } else { flags = O_RDONLY; } status = ntvfs_handle_new(pvfs->ntvfs, req, &h); NT_STATUS_NOT_OK_RETURN(status); f = talloc(h, struct pvfs_file); NT_STATUS_HAVE_NO_MEMORY(f); f->handle = talloc(f, struct pvfs_file_handle); NT_STATUS_HAVE_NO_MEMORY(f->handle); attrib = io->ntcreatex.in.file_attr | FILE_ATTRIBUTE_ARCHIVE; mode = pvfs_fileperms(pvfs, attrib); /* create the file */ fd = open(name->full_name, flags | O_CREAT | O_EXCL, mode); if (fd == -1) { return pvfs_map_errno(pvfs, errno); } pvfs_xattr_unlink_hook(pvfs, name->full_name); /* if this was a stream create then create the stream as well */ if (name->stream_name) { status = pvfs_stream_create(pvfs, name, fd); if (!NT_STATUS_IS_OK(status)) { close(fd); return status; } } /* re-resolve the open fd */ status = pvfs_resolve_name_fd(pvfs, fd, name); if (!NT_STATUS_IS_OK(status)) { close(fd); return status; } name->dos.attrib = attrib; status = pvfs_dosattrib_save(pvfs, name, fd); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } status = pvfs_open_setup_eas_acl(pvfs, req, name, fd, f, io); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } /* form the lock context used for byte range locking and opendb locking */ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } status = pvfs_brl_locking_handle(f, name, h, &f->brl_handle); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } /* grab a lock on the open file record */ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); if (lck == NULL) { DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", name->full_name)); /* we were supposed to do a blocking lock, so something is badly wrong! */ status = NT_STATUS_INTERNAL_DB_CORRUPTION; goto cleanup_delete; } if (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) { del_on_close = True; } else { del_on_close = False; } if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { oplock_level = OPLOCK_NONE; } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK) { oplock_level = OPLOCK_BATCH; } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_OPLOCK) { oplock_level = OPLOCK_EXCLUSIVE; } status = odb_open_file(lck, f->handle, name->stream_id, share_access, access_mask, del_on_close, name->full_name, oplock_level, &oplock_granted); talloc_free(lck); if (!NT_STATUS_IS_OK(status)) { /* bad news, we must have hit a race - we don't delete the file here as the most likely scenario is that someone else created the file at the same time */ close(fd); return status; } if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { oplock_granted = OPLOCK_BATCH; } f->ntvfs = h; f->pvfs = pvfs; f->pending_list = NULL; f->lock_count = 0; f->share_access = io->generic.in.share_access; f->access_mask = access_mask; f->impersonation = io->generic.in.impersonation; f->notify_buffer = NULL; f->search = NULL; f->handle->pvfs = pvfs; f->handle->name = talloc_steal(f->handle, name); f->handle->fd = fd; f->handle->create_options = io->generic.in.create_options; f->handle->seek_offset = 0; f->handle->position = 0; f->handle->mode = 0; f->handle->have_opendb_entry = True; f->handle->sticky_write_time = False; DLIST_ADD(pvfs->files.list, f); /* setup a destructor to avoid file descriptor leaks on abnormal termination */ talloc_set_destructor(f, pvfs_fnum_destructor); talloc_set_destructor(f->handle, pvfs_handle_destructor); io->generic.out.oplock_level = oplock_granted; io->generic.out.file.ntvfs = f->ntvfs; io->generic.out.create_action = NTCREATEX_ACTION_CREATED; io->generic.out.create_time = name->dos.create_time; io->generic.out.access_time = name->dos.access_time; io->generic.out.write_time = name->dos.write_time; io->generic.out.change_time = name->dos.change_time; io->generic.out.attrib = name->dos.attrib; io->generic.out.alloc_size = name->dos.alloc_size; io->generic.out.size = name->st.st_size; io->generic.out.file_type = FILE_TYPE_DISK; io->generic.out.ipc_state = 0; io->generic.out.is_directory = 0; /* success - keep the file handle */ status = ntvfs_handle_set_backend_data(h, pvfs->ntvfs, f); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } notify_trigger(pvfs->notify_context, NOTIFY_ACTION_ADDED, FILE_NOTIFY_CHANGE_FILE_NAME, name->full_name); return NT_STATUS_OK; cleanup_delete: close(fd); unlink(name->full_name); return status; }
/* destroy a struct pvfs_file_handle */ static int pvfs_handle_destructor(struct pvfs_file_handle *h) { int open_count; char *path = NULL; /* the write time is no longer sticky */ if (h->sticky_write_time) { NTSTATUS status; status = pvfs_dosattrib_load(h->pvfs, h->name, h->fd); if (NT_STATUS_IS_OK(status)) { h->name->dos.flags &= ~XATTR_ATTRIB_FLAG_STICKY_WRITE_TIME; pvfs_dosattrib_save(h->pvfs, h->name, h->fd); } } if ((h->create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) && h->name->stream_name) { NTSTATUS status; status = pvfs_stream_delete(h->pvfs, h->name, h->fd); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("Failed to delete stream '%s' on close of '%s'\n", h->name->stream_name, h->name->full_name)); } } if (h->fd != -1) { if (close(h->fd) != 0) { DEBUG(0,("pvfs_handle_destructor: close(%d) failed for %s - %s\n", h->fd, h->name->full_name, strerror(errno))); } h->fd = -1; } if (h->name->stream_name == NULL && pvfs_delete_on_close_set(h->pvfs, h, &open_count, &path) && open_count == 1) { NTSTATUS status; status = pvfs_xattr_unlink_hook(h->pvfs, path); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("Warning: xattr unlink hook failed for '%s' - %s\n", path, nt_errstr(status))); } if (unlink(path) != 0) { DEBUG(0,("pvfs_close: failed to delete '%s' - %s\n", path, strerror(errno))); } else { notify_trigger(h->pvfs->notify_context, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_FILE_NAME, path); } } talloc_free(path); if (h->have_opendb_entry) { struct odb_lock *lck; NTSTATUS status; lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); if (lck == NULL) { DEBUG(0,("Unable to lock opendb for close\n")); return 0; } status = odb_close_file(lck, h); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("Unable to remove opendb entry for '%s' - %s\n", h->name->full_name, nt_errstr(status))); } talloc_free(lck); } return 0; }
/* open a directory */ static NTSTATUS pvfs_open_directory(struct pvfs_state *pvfs, struct ntvfs_request *req, struct pvfs_filename *name, union smb_open *io) { struct pvfs_file *f; struct ntvfs_handle *h; NTSTATUS status; uint32_t create_action; uint32_t access_mask = io->generic.in.access_mask; struct odb_lock *lck; BOOL del_on_close; uint32_t create_options; uint32_t share_access; create_options = io->generic.in.create_options; share_access = io->generic.in.share_access; if (name->stream_name) { return NT_STATUS_NOT_A_DIRECTORY; } /* if the client says it must be a directory, and it isn't, then fail */ if (name->exists && !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) { return NT_STATUS_NOT_A_DIRECTORY; } switch (io->generic.in.open_disposition) { case NTCREATEX_DISP_OPEN_IF: break; case NTCREATEX_DISP_OPEN: if (!name->exists) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } break; case NTCREATEX_DISP_CREATE: if (name->exists) { return NT_STATUS_OBJECT_NAME_COLLISION; } break; case NTCREATEX_DISP_OVERWRITE_IF: case NTCREATEX_DISP_OVERWRITE: case NTCREATEX_DISP_SUPERSEDE: default: return NT_STATUS_INVALID_PARAMETER; } status = ntvfs_handle_new(pvfs->ntvfs, req, &h); NT_STATUS_NOT_OK_RETURN(status); f = talloc(h, struct pvfs_file); if (f == NULL) { return NT_STATUS_NO_MEMORY; } f->handle = talloc(f, struct pvfs_file_handle); if (f->handle == NULL) { return NT_STATUS_NO_MEMORY; } if (name->exists) { /* check the security descriptor */ status = pvfs_access_check(pvfs, req, name, &access_mask); } else { status = pvfs_access_check_create(pvfs, req, name, &access_mask); } if (!NT_STATUS_IS_OK(status)) { return status; } f->ntvfs = h; f->pvfs = pvfs; f->pending_list = NULL; f->lock_count = 0; f->share_access = io->generic.in.share_access; f->impersonation = io->generic.in.impersonation; f->access_mask = access_mask; f->brl_handle = NULL; f->notify_buffer = NULL; f->search = NULL; f->handle->pvfs = pvfs; f->handle->name = talloc_steal(f->handle, name); f->handle->fd = -1; f->handle->odb_locking_key = data_blob(NULL, 0); f->handle->create_options = io->generic.in.create_options; f->handle->seek_offset = 0; f->handle->position = 0; f->handle->mode = 0; f->handle->sticky_write_time = False; if ((create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) && pvfs_directory_empty(pvfs, f->handle->name)) { del_on_close = True; } else { del_on_close = False; } if (name->exists) { /* form the lock context used for opendb locking */ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); if (!NT_STATUS_IS_OK(status)) { return status; } /* get a lock on this file before the actual open */ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); if (lck == NULL) { DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", name->full_name)); /* we were supposed to do a blocking lock, so something is badly wrong! */ return NT_STATUS_INTERNAL_DB_CORRUPTION; } /* see if we are allowed to open at the same time as existing opens */ status = odb_open_file(lck, f->handle, f->handle->name->stream_id, share_access, access_mask, del_on_close, name->full_name, OPLOCK_NONE, NULL); if (!NT_STATUS_IS_OK(status)) { talloc_free(lck); return status; } f->handle->have_opendb_entry = True; } DLIST_ADD(pvfs->files.list, f); /* setup destructors to avoid leaks on abnormal termination */ talloc_set_destructor(f->handle, pvfs_dir_handle_destructor); talloc_set_destructor(f, pvfs_dir_fnum_destructor); if (!name->exists) { uint32_t attrib = io->generic.in.file_attr | FILE_ATTRIBUTE_DIRECTORY; mode_t mode = pvfs_fileperms(pvfs, attrib); if (mkdir(name->full_name, mode) == -1) { return pvfs_map_errno(pvfs,errno); } pvfs_xattr_unlink_hook(pvfs, name->full_name); status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname, 0, &name); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } status = pvfs_open_setup_eas_acl(pvfs, req, name, -1, f, io); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } /* form the lock context used for opendb locking */ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); if (!NT_STATUS_IS_OK(status)) { return status; } lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); if (lck == NULL) { DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", name->full_name)); /* we were supposed to do a blocking lock, so something is badly wrong! */ return NT_STATUS_INTERNAL_DB_CORRUPTION; } status = odb_open_file(lck, f->handle, f->handle->name->stream_id, share_access, access_mask, del_on_close, name->full_name, OPLOCK_NONE, NULL); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } f->handle->have_opendb_entry = True; create_action = NTCREATEX_ACTION_CREATED; notify_trigger(pvfs->notify_context, NOTIFY_ACTION_ADDED, FILE_NOTIFY_CHANGE_DIR_NAME, name->full_name); } else { create_action = NTCREATEX_ACTION_EXISTED; } if (!name->exists) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } /* the open succeeded, keep this handle permanently */ status = ntvfs_handle_set_backend_data(h, pvfs->ntvfs, f); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; } io->generic.out.oplock_level = OPLOCK_NONE; io->generic.out.file.ntvfs = h; io->generic.out.create_action = create_action; io->generic.out.create_time = name->dos.create_time; io->generic.out.access_time = name->dos.access_time; io->generic.out.write_time = name->dos.write_time; io->generic.out.change_time = name->dos.change_time; io->generic.out.attrib = name->dos.attrib; io->generic.out.alloc_size = name->dos.alloc_size; io->generic.out.size = name->st.st_size; io->generic.out.file_type = FILE_TYPE_DISK; io->generic.out.ipc_state = 0; io->generic.out.is_directory = 1; return NT_STATUS_OK; cleanup_delete: rmdir(name->full_name); return status; }
/* rename_information level */ static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs, struct ntvfs_request *req, struct pvfs_filename *name, int fd, DATA_BLOB *odb_locking_key, union smb_setfileinfo *info) { NTSTATUS status; struct pvfs_filename *name2; char *new_name, *p; struct odb_lock *lck = NULL; /* renames are only allowed within a directory */ if (strchr_m(info->rename_information.in.new_name, '\\') && (req->ctx->protocol != PROTOCOL_SMB2)) { return NT_STATUS_NOT_SUPPORTED; } /* handle stream renames specially */ if (name->stream_name) { return pvfs_setfileinfo_rename_stream(pvfs, req, name, fd, odb_locking_key, info); } /* w2k3 does not appear to allow relative rename. On SMB2, vista sends it sometimes, but I suspect it is just uninitialised memory */ if (info->rename_information.in.root_fid != 0 && (req->ctx->protocol != PROTOCOL_SMB2)) { return NT_STATUS_INVALID_PARAMETER; } /* construct the fully qualified windows name for the new file name */ if (req->ctx->protocol == PROTOCOL_SMB2) { /* SMB2 sends the full path of the new name */ new_name = talloc_asprintf(req, "\\%s", info->rename_information.in.new_name); } else { new_name = talloc_strdup(req, name->original_name); if (new_name == NULL) { return NT_STATUS_NO_MEMORY; } p = strrchr_m(new_name, '\\'); if (p == NULL) { return NT_STATUS_OBJECT_NAME_INVALID; } else { *p = 0; } new_name = talloc_asprintf(req, "%s\\%s", new_name, info->rename_information.in.new_name); } if (new_name == NULL) { return NT_STATUS_NO_MEMORY; } /* resolve the new name */ status = pvfs_resolve_name(pvfs, req, new_name, 0, &name2); if (!NT_STATUS_IS_OK(status)) { return status; } /* if the destination exists, then check the rename is allowed */ if (name2->exists) { if (strcmp(name2->full_name, name->full_name) == 0) { /* rename to same name is null-op */ return NT_STATUS_OK; } if (!info->rename_information.in.overwrite) { return NT_STATUS_OBJECT_NAME_COLLISION; } status = pvfs_can_delete(pvfs, req, name2, NULL); if (NT_STATUS_EQUAL(status, NT_STATUS_DELETE_PENDING)) { return NT_STATUS_ACCESS_DENIED; } if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { return NT_STATUS_ACCESS_DENIED; } if (!NT_STATUS_IS_OK(status)) { return status; } } status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); if (!NT_STATUS_IS_OK(status)) { return status; } lck = odb_lock(req, pvfs->odb_context, odb_locking_key); if (lck == NULL) { DEBUG(0,("Unable to lock opendb for can_stat\n")); return NT_STATUS_INTERNAL_DB_CORRUPTION; } status = pvfs_do_rename(pvfs, lck, name, name2->full_name); talloc_free(lck); NT_STATUS_NOT_OK_RETURN(status); if (NT_STATUS_IS_OK(status)) { name->full_name = talloc_steal(name, name2->full_name); name->original_name = talloc_steal(name, name2->original_name); } return NT_STATUS_OK; }