diff options
Diffstat (limited to 'source4/torture/raw')
33 files changed, 22430 insertions, 0 deletions
diff --git a/source4/torture/raw/acls.c b/source4/torture/raw/acls.c new file mode 100644 index 0000000000..95e7282895 --- /dev/null +++ b/source4/torture/raw/acls.c @@ -0,0 +1,1758 @@ +/* + Unix SMB/CIFS implementation. + + test security descriptor operations + + Copyright (C) Andrew Tridgell 2004 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "librpc/gen_ndr/lsa.h" +#include "libcli/util/clilsa.h" +#include "libcli/security/security.h" +#include "torture/util.h" +#include "librpc/gen_ndr/ndr_security.h" + +#define BASEDIR "\\testsd" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + +static bool test_sd(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\sd.txt"; + bool ret = true; + int fnum = -1; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_ace ace; + struct security_descriptor *sd; + struct dom_sid *test_sid; + + printf("TESTING SETFILEINFO EA_SET\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd = q.query_secdesc.out.sd; + + printf("add a new ACE to the DACL\n"); + + test_sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1234-5432"); + + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_STD_ALL; + ace.trustee = *test_sid; + + status = security_descriptor_dacl_add(sd, &ace); + CHECK_STATUS(status, NT_STATUS_OK); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = q.query_secdesc.in.secinfo_flags; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!security_acl_equal(q.query_secdesc.out.sd->dacl, sd->dacl)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + } + + printf("remove it again\n"); + + status = security_descriptor_dacl_del(sd, test_sid); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!security_acl_equal(q.query_secdesc.out.sd->dacl, sd->dacl)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + } + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + + +/* + test using nttrans create to create a file with an initial acl set +*/ +static bool test_nttrans_create(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\acl2.txt"; + bool ret = true; + int fnum = -1; + union smb_fileinfo q; + struct security_ace ace; + struct security_descriptor *sd; + struct dom_sid *test_sid; + + printf("testing nttrans create with sec_desc\n"); + + io.generic.level = RAW_OPEN_NTTRANS_CREATE; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + io.ntcreatex.in.sec_desc = NULL; + io.ntcreatex.in.ea_list = NULL; + + printf("creating normal file\n"); + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("querying ACL\n"); + + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd = q.query_secdesc.out.sd; + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + printf("adding a new ACE\n"); + test_sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1234-54321"); + + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_STD_ALL; + ace.trustee = *test_sid; + + status = security_descriptor_dacl_add(sd, &ace); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("creating a file with an initial ACL\n"); + + io.ntcreatex.in.sec_desc = sd; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + q.query_secdesc.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!security_acl_equal(q.query_secdesc.out.sd->dacl, sd->dacl)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + } + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +#define CHECK_ACCESS_FLAGS(_fnum, flags) do { \ + union smb_fileinfo _q; \ + _q.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; \ + _q.access_information.in.file.fnum = (_fnum); \ + status = smb_raw_fileinfo(cli->tree, tctx, &_q); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + if (_q.access_information.out.access_flags != (flags)) { \ + printf("(%s) Incorrect access_flags 0x%08x - should be 0x%08x\n", \ + __location__, _q.access_information.out.access_flags, (flags)); \ + ret = false; \ + goto done; \ + } \ +} while (0) + + +/* + test the behaviour of the well known SID_CREATOR_OWNER sid, and some generic + mapping bits +*/ +static bool test_creator_sid(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\creator.txt"; + bool ret = true; + int fnum = -1; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig, *sd2; + const char *owner_sid; + + printf("TESTING SID_CREATOR_OWNER\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_STD_READ_CONTROL | SEC_STD_WRITE_DAC | SEC_STD_WRITE_OWNER; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + printf("set a sec desc allowing no write by CREATOR_OWNER\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("try open for write\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for read\n"); + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for generic write\n"); + io.ntcreatex.in.access_mask = SEC_GENERIC_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for generic read\n"); + io.ntcreatex.in.access_mask = SEC_GENERIC_READ; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("set a sec desc allowing no write by owner\n"); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("check that sd has been mapped correctly\n"); + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + } + + printf("try open for write\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for read\n"); + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + SEC_FILE_READ_DATA| + SEC_FILE_READ_ATTRIBUTE); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + printf("try open for generic write\n"); + io.ntcreatex.in.access_mask = SEC_GENERIC_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for generic read\n"); + io.ntcreatex.in.access_mask = SEC_GENERIC_READ; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + SEC_RIGHTS_FILE_READ); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + printf("set a sec desc allowing generic read by owner\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_READ | SEC_STD_ALL, + 0, + NULL); + + set.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("check that generic read has been mapped correctly\n"); + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd2)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd2); + ret = false; + } + + + printf("try open for write\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for read\n"); + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + SEC_FILE_READ_DATA | + SEC_FILE_READ_ATTRIBUTE); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + printf("try open for generic write\n"); + io.ntcreatex.in.access_mask = SEC_GENERIC_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("try open for generic read\n"); + io.ntcreatex.in.access_mask = SEC_GENERIC_READ; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, SEC_RIGHTS_FILE_READ); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + + printf("put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + + +/* + test the mapping of the SEC_GENERIC_xx bits to SEC_STD_xx and + SEC_FILE_xx bits +*/ +static bool test_generic_bits(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\generic.txt"; + bool ret = true; + int fnum = -1, i; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig, *sd2; + const char *owner_sid; + const struct { + uint32_t gen_bits; + uint32_t specific_bits; + } file_mappings[] = { + { 0, 0 }, + { SEC_GENERIC_READ, SEC_RIGHTS_FILE_READ }, + { SEC_GENERIC_WRITE, SEC_RIGHTS_FILE_WRITE }, + { SEC_GENERIC_EXECUTE, SEC_RIGHTS_FILE_EXECUTE }, + { SEC_GENERIC_ALL, SEC_RIGHTS_FILE_ALL }, + { SEC_FILE_READ_DATA, SEC_FILE_READ_DATA }, + { SEC_FILE_READ_ATTRIBUTE, SEC_FILE_READ_ATTRIBUTE } + }; + const struct { + uint32_t gen_bits; + uint32_t specific_bits; + } dir_mappings[] = { + { 0, 0 }, + { SEC_GENERIC_READ, SEC_RIGHTS_DIR_READ }, + { SEC_GENERIC_WRITE, SEC_RIGHTS_DIR_WRITE }, + { SEC_GENERIC_EXECUTE, SEC_RIGHTS_DIR_EXECUTE }, + { SEC_GENERIC_ALL, SEC_RIGHTS_DIR_ALL } + }; + bool has_restore_privilege; + bool has_take_ownership_privilege; + + printf("TESTING FILE GENERIC BITS\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + printf("smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + printf("SEC_PRIV_RESTORE - %s\n", has_restore_privilege?"Yes":"No"); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_TAKE_OWNERSHIP)); + has_take_ownership_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + printf("smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + printf("SEC_PRIV_TAKE_OWNERSHIP - %s\n", has_take_ownership_privilege?"Yes":"No"); + + for (i=0;i<ARRAY_SIZE(file_mappings);i++) { + uint32_t expected_mask = + SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | + SEC_FILE_READ_ATTRIBUTE | + SEC_STD_DELETE; + uint32_t expected_mask_anon = SEC_FILE_READ_ATTRIBUTE; + + if (has_restore_privilege) { + expected_mask_anon |= SEC_STD_DELETE; + } + + printf("testing generic bits 0x%08x\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].specific_bits, + 0, + NULL); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd2)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd2); + ret = false; + } + + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + expected_mask | file_mappings[i].specific_bits); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + if (!has_take_ownership_privilege) { + continue; + } + + printf("testing generic bits 0x%08x (anonymous)\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].specific_bits, + 0, + NULL); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd2)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd2); + ret = false; + } + + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + expected_mask_anon | file_mappings[i].specific_bits); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + } + + printf("put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + + printf("TESTING DIR GENERIC BITS\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + printf("smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + printf("SEC_PRIV_RESTORE - %s\n", has_restore_privilege?"Yes":"No"); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_TAKE_OWNERSHIP)); + has_take_ownership_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + printf("smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + printf("SEC_PRIV_TAKE_OWNERSHIP - %s\n", has_take_ownership_privilege?"Yes":"No"); + + for (i=0;i<ARRAY_SIZE(dir_mappings);i++) { + uint32_t expected_mask = + SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | + SEC_FILE_READ_ATTRIBUTE | + SEC_STD_DELETE; + uint32_t expected_mask_anon = SEC_FILE_READ_ATTRIBUTE; + + if (has_restore_privilege) { + expected_mask_anon |= SEC_STD_DELETE; + } + + printf("testing generic bits 0x%08x\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + dir_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + dir_mappings[i].specific_bits, + 0, + NULL); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd2)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd2); + ret = false; + } + + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + expected_mask | dir_mappings[i].specific_bits); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + if (!has_take_ownership_privilege) { + continue; + } + + printf("testing generic bits 0x%08x (anonymous)\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].specific_bits, + 0, + NULL); + + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd2)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd2); + ret = false; + } + + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, + expected_mask_anon | dir_mappings[i].specific_bits); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + } + + printf("put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + + +/* + see what access bits the owner of a file always gets +*/ +static bool test_owner_bits(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\generic.txt"; + bool ret = true; + int fnum = -1, i; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig; + const char *owner_sid; + bool has_restore_privilege; + bool has_take_ownership_privilege; + uint32_t expected_bits; + + printf("TESTING FILE OWNER BITS\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + printf("smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + printf("SEC_PRIV_RESTORE - %s\n", has_restore_privilege?"Yes":"No"); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_TAKE_OWNERSHIP)); + has_take_ownership_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + printf("smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + printf("SEC_PRIV_TAKE_OWNERSHIP - %s\n", has_take_ownership_privilege?"Yes":"No"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + expected_bits = SEC_FILE_WRITE_DATA | SEC_FILE_READ_ATTRIBUTE; + + for (i=0;i<16;i++) { + uint32_t bit = (1<<i); + io.ntcreatex.in.access_mask = bit; + status = smb_raw_open(cli->tree, tctx, &io); + if (expected_bits & bit) { + if (!NT_STATUS_IS_OK(status)) { + printf("failed with access mask 0x%08x of expected 0x%08x\n", + bit, expected_bits); + } + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.ntcreatex.out.file.fnum, bit | SEC_FILE_READ_ATTRIBUTE); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + } + + printf("put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + return ret; +} + + + +/* + test the inheritance of ACL flags onto new files and directories +*/ +static bool test_inheritance(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *dname = BASEDIR "\\inheritance"; + const char *fname1 = BASEDIR "\\inheritance\\testfile"; + const char *fname2 = BASEDIR "\\inheritance\\testdir"; + bool ret = true; + int fnum=0, fnum2, i; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd2, *sd_orig=NULL, *sd_def; + const char *owner_sid; + const struct dom_sid *creator_owner; + const struct { + uint32_t parent_flags; + uint32_t file_flags; + uint32_t dir_flags; + } test_flags[] = { + { + 0, + 0, + 0 + }, + { + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_INHERIT_ONLY, + }, + { + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + SEC_ACE_FLAG_CONTAINER_INHERIT, + }, + { + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_INHERIT_ONLY, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + SEC_ACE_FLAG_CONTAINER_INHERIT, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + } + }; + + smbcli_rmdir(cli->tree, dname); + + printf("TESTING ACL INHERITANCE\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = dname; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + printf("owner_sid is %s\n", owner_sid); + + sd_def = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_ALL, + 0, + SID_NT_SYSTEM, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_ALL, + 0, + NULL); + + creator_owner = dom_sid_parse_talloc(tctx, SID_CREATOR_OWNER); + + for (i=0;i<ARRAY_SIZE(test_flags);i++) { + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA, + test_flags[i].parent_flags, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL | SEC_STD_ALL, + 0, + NULL); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + io.ntcreatex.in.fname = fname1; + io.ntcreatex.in.create_options = 0; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + q.query_secdesc.in.file.fnum = fnum2; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum2); + smbcli_unlink(cli->tree, fname1); + + if (!(test_flags[i].parent_flags & SEC_ACE_FLAG_OBJECT_INHERIT)) { + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd_def)) { + printf("Expected default sd at %d - got:\n", i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + } + goto check_dir; + } + + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 1 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + sd_orig->owner_sid)) { + printf("Bad sd in child file at %d\n", i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + ret = false; + goto check_dir; + } + + if (q.query_secdesc.out.sd->dacl->aces[0].flags != + test_flags[i].file_flags) { + printf("incorrect file_flags 0x%x - expected 0x%x for parent 0x%x with (i=%d)\n", + q.query_secdesc.out.sd->dacl->aces[0].flags, + test_flags[i].file_flags, + test_flags[i].parent_flags, + i); + ret = false; + } + + check_dir: + io.ntcreatex.in.fname = fname2; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + q.query_secdesc.in.file.fnum = fnum2; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum2); + smbcli_rmdir(cli->tree, fname2); + + if (!(test_flags[i].parent_flags & SEC_ACE_FLAG_CONTAINER_INHERIT) && + (!(test_flags[i].parent_flags & SEC_ACE_FLAG_OBJECT_INHERIT) || + (test_flags[i].parent_flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT))) { + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd_def)) { + printf("Expected default sd for dir at %d - got:\n", i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + } + continue; + } + + if ((test_flags[i].parent_flags & SEC_ACE_FLAG_CONTAINER_INHERIT) && + (test_flags[i].parent_flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT)) { + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 1 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + sd_orig->owner_sid) || + q.query_secdesc.out.sd->dacl->aces[0].flags != test_flags[i].dir_flags) { + printf("Bad sd in child dir at %d (parent 0x%x)\n", + i, test_flags[i].parent_flags); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + ret = false; + continue; + } + } else if (test_flags[i].parent_flags & SEC_ACE_FLAG_CONTAINER_INHERIT) { + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 2 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + sd_orig->owner_sid) || + q.query_secdesc.out.sd->dacl->aces[1].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[1].trustee, + creator_owner) || + q.query_secdesc.out.sd->dacl->aces[0].flags != 0 || + q.query_secdesc.out.sd->dacl->aces[1].flags != + (test_flags[i].dir_flags | SEC_ACE_FLAG_INHERIT_ONLY)) { + printf("Bad sd in child dir at %d (parent 0x%x)\n", + i, test_flags[i].parent_flags); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + ret = false; + continue; + } + } else { + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 1 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + creator_owner) || + q.query_secdesc.out.sd->dacl->aces[0].flags != test_flags[i].dir_flags) { + printf("Bad sd in child dir at %d (parent 0x%x)\n", + i, test_flags[i].parent_flags); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + ret = false; + continue; + } + } + } + + printf("testing access checks on inherited create with %s\n", fname1); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + SEC_ACE_FLAG_OBJECT_INHERIT, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL | SEC_STD_ALL, + 0, + NULL); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + io.ntcreatex.in.fname = fname1; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_ACCESS_FLAGS(fnum2, SEC_RIGHTS_FILE_ALL); + + q.query_secdesc.in.file.fnum = fnum2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, fnum2); + + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + 0, + NULL); + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd2)) { + printf("%s: security descriptors don't match!\n", __location__); + printf("got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + printf("expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd2); + ret = false; + } + + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + status = smb_raw_open(cli->tree, tctx, &io); + if (NT_STATUS_IS_OK(status)) { + printf("failed: w2k3 ACL bug (allowed open when ACL should deny)\n"); + ret = false; + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_ACCESS_FLAGS(fnum2, SEC_RIGHTS_FILE_ALL); + smbcli_close(cli->tree, fnum2); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + printf("trying without execute\n"); + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL & ~SEC_FILE_EXECUTE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("and with full permissions again\n"); + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_ACCESS_FLAGS(fnum2, SEC_FILE_WRITE_DATA | SEC_FILE_READ_ATTRIBUTE); + smbcli_close(cli->tree, fnum2); + + printf("put back original sd\n"); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_ACCESS_FLAGS(fnum2, SEC_FILE_WRITE_DATA | SEC_FILE_READ_ATTRIBUTE); + smbcli_close(cli->tree, fnum2); + + smbcli_unlink(cli->tree, fname1); + smbcli_rmdir(cli->tree, dname); + +done: + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + + smbcli_close(cli->tree, fnum); + return ret; +} + + +/* + test dynamic acl inheritance +*/ +static bool test_inheritance_dynamic(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + union smb_open io; + const char *dname = BASEDIR "\\inheritance"; + const char *fname1 = BASEDIR "\\inheritance\\testfile"; + bool ret = true; + int fnum=0, fnum2; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig=NULL; + const char *owner_sid; + + printf("TESTING DYNAMIC ACL INHERITANCE\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = dname; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.fnum = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + printf("owner_sid is %s\n", owner_sid); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_DELETE | SEC_FILE_READ_ATTRIBUTE, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + sd->type |= SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ; + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("create a file with an inherited acl\n"); + io.ntcreatex.in.fname = fname1; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + smbcli_close(cli->tree, fnum2); + + printf("try and access file with base rights - should be OK\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + smbcli_close(cli->tree, fnum2); + + printf("try and access file with extra rights - should be denied\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA | SEC_FILE_EXECUTE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("update parent sd\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_DELETE | SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + sd->type |= SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ; + + set.set_secdesc.in.sd = sd; + status = smb_raw_setfileinfo(cli->tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("try and access file with base rights - should be OK\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + smbcli_close(cli->tree, fnum2); + + + printf("try and access now - should be OK if dynamic inheritance works\n"); + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA | SEC_FILE_EXECUTE; + status = smb_raw_open(cli->tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("Server does not have dynamic inheritance\n"); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + printf("Server does have dynamic inheritance\n"); + } + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + smbcli_unlink(cli->tree, fname1); + +done: + printf("put back original sd\n"); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.fnum = fnum; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + status = smb_raw_setfileinfo(cli->tree, &set); + + smbcli_close(cli->tree, fnum); + smbcli_rmdir(cli->tree, dname); + + return ret; +} + +#define CHECK_STATUS_FOR_BIT_ACTION(status, bits, action) do { \ + if (!(bits & desired_64)) {\ + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); \ + action; \ + } else { \ + CHECK_STATUS(status, NT_STATUS_OK); \ + } \ +} while (0) + +#define CHECK_STATUS_FOR_BIT(status, bits, access) do { \ + if (NT_STATUS_IS_OK(status)) { \ + if (!(granted & access)) {\ + printf("(%s) %s but flags 0x%08X are not granted! granted[0x%08X] desired[0x%08X]\n", \ + __location__, nt_errstr(status), access, granted, desired); \ + ret = false; \ + goto done; \ + } \ + } else { \ + if (granted & access) {\ + printf("(%s) %s but flags 0x%08X are granted! granted[0x%08X] desired[0x%08X]\n", \ + __location__, nt_errstr(status), access, granted, desired); \ + ret = false; \ + goto done; \ + } \ + } \ + CHECK_STATUS_FOR_BIT_ACTION(status, bits, do {} while (0)); \ +} while (0) + +/* test what access mask is needed for getting and setting security_descriptors */ +static bool test_sd_get_set(struct torture_context *tctx, + struct smbcli_state *cli) +{ + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo fi; + union smb_setfileinfo si; + struct security_descriptor *sd; + struct security_descriptor *sd_owner = NULL; + struct security_descriptor *sd_group = NULL; + struct security_descriptor *sd_dacl = NULL; + struct security_descriptor *sd_sacl = NULL; + int fnum=0; + const char *fname = BASEDIR "\\sd_get_set.txt"; + uint64_t desired_64; + uint32_t desired = 0, granted; + int i = 0; +#define NO_BITS_HACK (((uint64_t)1)<<32) + uint64_t open_bits = + SEC_MASK_GENERIC | + SEC_FLAG_SYSTEM_SECURITY | + SEC_FLAG_MAXIMUM_ALLOWED | + SEC_STD_ALL | + SEC_FILE_ALL | + NO_BITS_HACK; + uint64_t get_owner_bits = SEC_MASK_GENERIC | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_READ_CONTROL; + uint64_t set_owner_bits = SEC_GENERIC_ALL | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_WRITE_OWNER; + uint64_t get_group_bits = SEC_MASK_GENERIC | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_READ_CONTROL; + uint64_t set_group_bits = SEC_GENERIC_ALL | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_WRITE_OWNER; + uint64_t get_dacl_bits = SEC_MASK_GENERIC | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_READ_CONTROL; + uint64_t set_dacl_bits = SEC_GENERIC_ALL | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_WRITE_DAC; + uint64_t get_sacl_bits = SEC_FLAG_SYSTEM_SECURITY; + uint64_t set_sacl_bits = SEC_FLAG_SYSTEM_SECURITY; + + printf("TESTING ACCESS MASKS FOR SD GET/SET\n"); + + /* first create a file with full access for everyone */ + sd = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, SID_BUILTIN_USERS, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + 0, + NULL); + sd->type |= SEC_DESC_SACL_PRESENT; + sd->sacl = NULL; + io.ntcreatex.level = RAW_OPEN_NTTRANS_CREATE; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_GENERIC_ALL; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + io.ntcreatex.in.sec_desc = sd; + io.ntcreatex.in.ea_list = NULL; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + status = smbcli_close(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * now try each access_mask bit and no bit at all in a loop + * and see what's allowed + * NOTE: if i == 32 it means access_mask = 0 (see NO_BITS_HACK above) + */ + for (i=0; i <= 32; i++) { + desired_64 = ((uint64_t)1) << i; + desired = (uint32_t)desired_64; + + /* first open the file with the desired access */ + io.ntcreatex.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.access_mask = desired; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS_FOR_BIT_ACTION(status, open_bits, goto next); + fnum = io.ntcreatex.out.file.fnum; + + /* then check what access was granted */ + fi.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; + fi.access_information.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, tctx, &fi); + CHECK_STATUS(status, NT_STATUS_OK); + granted = fi.access_information.out.access_flags; + + /* test the owner */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.fnum = fnum; + fi.query_secdesc.in.secinfo_flags = SECINFO_OWNER; + status = smb_raw_fileinfo(cli->tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_owner_bits, SEC_STD_READ_CONTROL); + if (fi.query_secdesc.out.sd) { + sd_owner = fi.query_secdesc.out.sd; + } else if (!sd_owner) { + sd_owner = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.fnum = fnum; + si.set_secdesc.in.secinfo_flags = SECINFO_OWNER; + si.set_secdesc.in.sd = sd_owner; + status = smb_raw_setfileinfo(cli->tree, &si); + CHECK_STATUS_FOR_BIT(status, set_owner_bits, SEC_STD_WRITE_OWNER); + + /* test the group */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.fnum = fnum; + fi.query_secdesc.in.secinfo_flags = SECINFO_GROUP; + status = smb_raw_fileinfo(cli->tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_group_bits, SEC_STD_READ_CONTROL); + if (fi.query_secdesc.out.sd) { + sd_group = fi.query_secdesc.out.sd; + } else if (!sd_group) { + sd_group = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.fnum = fnum; + si.set_secdesc.in.secinfo_flags = SECINFO_GROUP; + si.set_secdesc.in.sd = sd_group; + status = smb_raw_setfileinfo(cli->tree, &si); + CHECK_STATUS_FOR_BIT(status, set_group_bits, SEC_STD_WRITE_OWNER); + + /* test the DACL */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.fnum = fnum; + fi.query_secdesc.in.secinfo_flags = SECINFO_DACL; + status = smb_raw_fileinfo(cli->tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_dacl_bits, SEC_STD_READ_CONTROL); + if (fi.query_secdesc.out.sd) { + sd_dacl = fi.query_secdesc.out.sd; + } else if (!sd_dacl) { + sd_dacl = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.fnum = fnum; + si.set_secdesc.in.secinfo_flags = SECINFO_DACL; + si.set_secdesc.in.sd = sd_dacl; + status = smb_raw_setfileinfo(cli->tree, &si); + CHECK_STATUS_FOR_BIT(status, set_dacl_bits, SEC_STD_WRITE_DAC); + + /* test the SACL */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.fnum = fnum; + fi.query_secdesc.in.secinfo_flags = SECINFO_SACL; + status = smb_raw_fileinfo(cli->tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_sacl_bits, SEC_FLAG_SYSTEM_SECURITY); + if (fi.query_secdesc.out.sd) { + sd_sacl = fi.query_secdesc.out.sd; + } else if (!sd_sacl) { + sd_sacl = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.fnum = fnum; + si.set_secdesc.in.secinfo_flags = SECINFO_SACL; + si.set_secdesc.in.sd = sd_sacl; + status = smb_raw_setfileinfo(cli->tree, &si); + CHECK_STATUS_FOR_BIT(status, set_sacl_bits, SEC_FLAG_SYSTEM_SECURITY); + + /* close the handle */ + status = smbcli_close(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); +next: + continue; + } + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + basic testing of security descriptor calls +*/ +bool torture_raw_acls(struct torture_context *tctx, struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_sd(tctx, cli); + ret &= test_nttrans_create(tctx, cli); + ret &= test_creator_sid(tctx, cli); + ret &= test_generic_bits(tctx, cli); + ret &= test_owner_bits(tctx, cli); + ret &= test_inheritance(tctx, cli); + ret &= test_inheritance_dynamic(tctx, cli); + ret &= test_sd_get_set(tctx, cli); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} diff --git a/source4/torture/raw/chkpath.c b/source4/torture/raw/chkpath.c new file mode 100644 index 0000000000..2ed83d308c --- /dev/null +++ b/source4/torture/raw/chkpath.c @@ -0,0 +1,313 @@ +/* + Unix SMB/CIFS implementation. + chkpath individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define BASEDIR "\\rawchkpath" + +#define CHECK_STATUS(status, correct, dos_correct) do { \ + if (!NT_STATUS_EQUAL(status, correct) && !NT_STATUS_EQUAL(status, dos_correct)) { \ + printf("(%d) Incorrect status %s - should be %s\n", \ + __LINE__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + +static NTSTATUS single_search(struct smbcli_state *cli, + TALLOC_CTX *mem_ctx, const char *pattern) +{ + union smb_search_first io; + NTSTATUS status; + + io.t2ffirst.level = RAW_SEARCH_TRANS2; + io.t2ffirst.data_level = RAW_SEARCH_DATA_STANDARD; + io.t2ffirst.in.search_attrib = 0; + io.t2ffirst.in.max_count = 1; + io.t2ffirst.in.flags = FLAG_TRANS2_FIND_CLOSE; + io.t2ffirst.in.storage_type = 0; + io.t2ffirst.in.pattern = pattern; + + status = smb_raw_search_first(cli->tree, mem_ctx, + &io, NULL, NULL); + + return status; +} + +static bool test_path_ex(struct smbcli_state *cli, struct torture_context *tctx, + const char *path, const char *path_expected, + NTSTATUS expected, NTSTATUS dos_expected) +{ + union smb_chkpath io; + union smb_fileinfo finfo; + NTSTATUS status; + + io.chkpath.in.path = path; + status = smb_raw_chkpath(cli->tree, &io); + if (!NT_STATUS_EQUAL(status, expected) && !NT_STATUS_EQUAL(status, dos_expected)) { + printf("FAILED %-30s chkpath %s should be %s or %s\n", + path, nt_errstr(status), nt_errstr(expected), nt_errstr(dos_expected)); + return false; + } else { + printf("%-30s chkpath correct (%s)\n", path, nt_errstr(status)); + } + + if (NT_STATUS_EQUAL(expected, NT_STATUS_NOT_A_DIRECTORY)) { + expected = NT_STATUS_OK; + } + + ZERO_STRUCT(finfo); + finfo.generic.level = RAW_FILEINFO_NAME_INFO; + finfo.generic.in.file.path = path; + status = smb_raw_pathinfo(cli->tree, cli, &finfo); + if (!NT_STATUS_EQUAL(status, expected) && !NT_STATUS_EQUAL(status, dos_expected)) { + printf("FAILED: %-30s pathinfo %s should be %s or %s\n", + path, nt_errstr(status), nt_errstr(expected), nt_errstr(dos_expected)); + return false; + } + + if (!NT_STATUS_IS_OK(status)) { + printf("%-30s chkpath correct (%s)\n", path, nt_errstr(status)); + return true; + } + + if (path_expected && + (!finfo.name_info.out.fname.s || + strcmp(finfo.name_info.out.fname.s, path_expected) != 0)) { + if (tctx && torture_setting_bool(tctx, "samba4", false)) { + printf("IGNORE: %-30s => %-20s should be %s\n", + path, finfo.name_info.out.fname.s, path_expected); + return true; + } + printf("FAILED: %-30s => %-20s should be %s\n", + path, finfo.name_info.out.fname.s, path_expected); + return false; + } + printf("%-30s => %-20s correct\n", + path, finfo.name_info.out.fname.s); + + return true; +} + +static bool test_path(struct smbcli_state *cli, const char *path, + NTSTATUS expected, NTSTATUS dos_expected) +{ + return test_path_ex(cli, NULL, path, path, expected, dos_expected); +} + +static bool test_chkpath(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_chkpath io; + NTSTATUS status; + bool ret = true; + int fnum = -1; + int fnum1 = -1; + + io.chkpath.in.path = BASEDIR; + + status = smb_raw_chkpath(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK, NT_STATUS_OK); + + ret &= test_path(cli, BASEDIR "\\nodir", NT_STATUS_OBJECT_NAME_NOT_FOUND, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + fnum = create_complex_file(cli, tctx, BASEDIR "\\test.txt.."); + if (fnum == -1) { + printf("failed to open test.txt - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + ret &= test_path(cli, BASEDIR "\\test.txt..", NT_STATUS_NOT_A_DIRECTORY, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + if (!torture_set_file_attribute(cli->tree, BASEDIR, FILE_ATTRIBUTE_HIDDEN)) { + printf("failed to set basedir hidden\n"); + ret = false; + goto done; + } + + ret &= test_path_ex(cli, tctx, BASEDIR, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, ((char *)BASEDIR) + 1, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, ((char *)BASEDIR"\\\\") + 1, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, ((char *)BASEDIR"\\foo\\..") + 1, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, ((char *)BASEDIR"\\f\\o\\o\\..\\..\\..") + 1, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, ((char *)BASEDIR"\\foo\\\\..\\\\") + 1, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, BASEDIR"\\", BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, BASEDIR"\\\\..\\"BASEDIR, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, BASEDIR"\\\\\\", BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, "\\\\\\\\"BASEDIR"\\\\\\\\", BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, "\\\\\\\\"BASEDIR, BASEDIR, NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path_ex(cli, tctx, BASEDIR "\\foo\\..\\test.txt..", BASEDIR "\\test.txt..", + NT_STATUS_NOT_A_DIRECTORY, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path_ex(cli, tctx, "", "\\", NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path(cli, ".", NT_STATUS_OBJECT_NAME_INVALID, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\", NT_STATUS_OBJECT_NAME_INVALID, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "\\\\\\.\\", NT_STATUS_OBJECT_NAME_INVALID, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\.", NT_STATUS_OBJECT_PATH_NOT_FOUND, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "." BASEDIR, NT_STATUS_OBJECT_PATH_NOT_FOUND, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\.", NT_STATUS_OBJECT_NAME_INVALID, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\.\\test.txt..", NT_STATUS_OBJECT_PATH_NOT_FOUND, NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\.\\", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\.\\.", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\.\\.aaaaa", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "\\.\\", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "\\.\\\\", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "\\.\\\\\\\\\\\\", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + /* Note that the two following paths are identical but + give different NT status returns for chkpth and findfirst. */ + + printf("testing findfirst on %s\n", "\\.\\\\\\\\\\\\."); + status = single_search(cli, tctx, "\\.\\\\\\\\\\\\."); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRinvalidname)); + + ret &= test_path(cli, "\\.\\\\\\\\\\\\.", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + /* We expect this open to fail with the same error code as the chkpath below. */ + printf("testing Open on %s\n", "\\.\\\\\\\\\\\\."); + /* findfirst seems to fail with a different error. */ + fnum1 = smbcli_nt_create_full(cli->tree, "\\.\\\\\\\\\\\\.", + 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL, + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE, + NTCREATEX_DISP_OVERWRITE_IF, + 0, 0); + status = smbcli_nt_error(cli->tree); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + + ret &= test_path(cli, "\\.\\\\xxx", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "..\\..\\..", NT_STATUS_OBJECT_PATH_SYNTAX_BAD,NT_STATUS_DOS(ERRDOS,ERRinvalidpath)); + ret &= test_path(cli, "\\..", NT_STATUS_OBJECT_PATH_SYNTAX_BAD,NT_STATUS_DOS(ERRDOS,ERRinvalidpath)); + ret &= test_path(cli, "\\.\\\\\\\\\\\\xxx", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR"\\.\\", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR"\\.\\\\", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR"\\.\\nt", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR"\\.\\.\\nt", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR"\\nt", NT_STATUS_OK, NT_STATUS_OK); + ret &= test_path(cli, BASEDIR".\\foo", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR"xx\\foo", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\.", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, ".\\.\\.\\.\\foo\\.\\.\\", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR".\\.\\.\\.\\foo\\.\\.\\", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR".\\.\\.\\.\\foo\\..\\.\\", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR".", NT_STATUS_OBJECT_NAME_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "\\", NT_STATUS_OK,NT_STATUS_OK); + ret &= test_path(cli, "\\.", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, "\\..\\", NT_STATUS_OBJECT_PATH_SYNTAX_BAD,NT_STATUS_DOS(ERRDOS,ERRinvalidpath)); + ret &= test_path(cli, "\\..", NT_STATUS_OBJECT_PATH_SYNTAX_BAD,NT_STATUS_DOS(ERRDOS,ERRinvalidpath)); + ret &= test_path(cli, BASEDIR "\\.", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path_ex(cli, tctx, BASEDIR "\\..", "\\", NT_STATUS_OK,NT_STATUS_OK); + ret &= test_path(cli, BASEDIR "\\nt\\V S\\VB98\\vb600", NT_STATUS_OBJECT_NAME_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\nt\\V S\\VB98\\vb6.exe", NT_STATUS_NOT_A_DIRECTORY,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + /* We expect this open to fail with the same error code as the chkpath below. */ + printf("testing Open on %s\n", BASEDIR".\\.\\.\\.\\foo\\..\\.\\"); + /* findfirst seems to fail with a different error. */ + fnum1 = smbcli_nt_create_full(cli->tree, BASEDIR".\\.\\.\\.\\foo\\..\\.\\", + 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL, + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE, + NTCREATEX_DISP_OVERWRITE_IF, + 0, 0); + status = smbcli_nt_error(cli->tree); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + printf("testing findfirst on %s\n", BASEDIR".\\.\\.\\.\\foo\\..\\.\\"); + status = single_search(cli, tctx, BASEDIR".\\.\\.\\.\\foo\\..\\.\\"); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + /* We expect this open to fail with the same error code as the chkpath below. */ + /* findfirst seems to fail with a different error. */ + printf("testing Open on %s\n", BASEDIR "\\nt\\V S\\VB98\\vb6.exe\\3"); + fnum1 = smbcli_nt_create_full(cli->tree, BASEDIR "\\nt\\V S\\VB98\\vb6.exe\\3", + 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL, + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE, + NTCREATEX_DISP_OVERWRITE_IF, + 0, 0); + status = smbcli_nt_error(cli->tree); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + + ret &= test_path(cli, BASEDIR "\\nt\\V S\\VB98\\vb6.exe\\3", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\nt\\V S\\VB98\\vb6.exe\\3\\foo", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\nt\\3\\foo", NT_STATUS_OBJECT_PATH_NOT_FOUND,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\nt\\V S\\*\\vb6.exe\\3", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + ret &= test_path(cli, BASEDIR "\\nt\\V S\\*\\*\\vb6.exe\\3", NT_STATUS_OBJECT_NAME_INVALID,NT_STATUS_DOS(ERRDOS,ERRbadpath)); + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* + basic testing of chkpath calls +*/ +bool torture_raw_chkpath(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + int fnum; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + if (NT_STATUS_IS_ERR(smbcli_mkdir(cli->tree, BASEDIR "\\nt"))) { + printf("Failed to create " BASEDIR " - %s\n", smbcli_errstr(cli->tree)); + return false; + } + + if (NT_STATUS_IS_ERR(smbcli_mkdir(cli->tree, BASEDIR "\\nt\\V S"))) { + printf("Failed to create " BASEDIR " - %s\n", smbcli_errstr(cli->tree)); + return false; + } + + if (NT_STATUS_IS_ERR(smbcli_mkdir(cli->tree, BASEDIR "\\nt\\V S\\VB98"))) { + printf("Failed to create " BASEDIR " - %s\n", smbcli_errstr(cli->tree)); + return false; + } + + fnum = create_complex_file(cli, torture, BASEDIR "\\nt\\V S\\VB98\\vb6.exe"); + if (fnum == -1) { + printf("failed to open \\nt\\V S\\VB98\\vb6.exe - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + ret &= test_chkpath(cli, torture); + + done: + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} diff --git a/source4/torture/raw/close.c b/source4/torture/raw/close.c new file mode 100644 index 0000000000..01175836df --- /dev/null +++ b/source4/torture/raw/close.c @@ -0,0 +1,177 @@ +/* + Unix SMB/CIFS implementation. + RAW_CLOSE_* individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/time.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +/** + * basic testing of all RAW_CLOSE_* calls +*/ +bool torture_raw_close(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + union smb_close io; + union smb_flush io_flush; + int fnum; + const char *fname = "\\torture_close.txt"; + time_t basetime = (time(NULL) + 3*86400) & ~1; + union smb_fileinfo finfo, finfo2; + NTSTATUS status; + +#define REOPEN do { \ + fnum = create_complex_file(cli, torture, fname); \ + if (fnum == -1) { \ + printf("(%d) Failed to create %s\n", __LINE__, fname); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%d) Incorrect status %s - should be %s\n", \ + __LINE__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + REOPEN; + + io.close.level = RAW_CLOSE_CLOSE; + io.close.in.file.fnum = fnum; + io.close.in.write_time = basetime; + status = smb_raw_close(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_close(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("testing close.in.write_time\n"); + + /* the file should have the write time set */ + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, torture, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + + if (basetime != nt_time_to_unix(finfo.all_info.out.write_time)) { + printf("Incorrect write time on file - %s - %s\n", + timestring(torture, basetime), + nt_time_string(torture, finfo.all_info.out.write_time)); + dump_all_info(torture, &finfo); + ret = false; + } + + printf("testing other times\n"); + + /* none of the other times should be set to that time */ + if (nt_time_equal(&finfo.all_info.out.write_time, + &finfo.all_info.out.access_time) || + nt_time_equal(&finfo.all_info.out.write_time, + &finfo.all_info.out.create_time) || + nt_time_equal(&finfo.all_info.out.write_time, + &finfo.all_info.out.change_time)) { + printf("Incorrect times after close - only write time should be set\n"); + dump_all_info(torture, &finfo); + + if (!torture_setting_bool(torture, "samba3", false)) { + /* + * In Samba3 as of 3.0.23d we don't yet support all + * file times, so don't mark this as a critical + * failure + */ + ret = false; + } + } + + + smbcli_unlink(cli->tree, fname); + REOPEN; + + finfo2.generic.level = RAW_FILEINFO_ALL_INFO; + finfo2.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, torture, &finfo2); + CHECK_STATUS(status, NT_STATUS_OK); + + io.close.level = RAW_CLOSE_CLOSE; + io.close.in.file.fnum = fnum; + io.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* the file should have the write time set equal to access time */ + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, torture, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!nt_time_equal(&finfo.all_info.out.write_time, + &finfo2.all_info.out.write_time)) { + printf("Incorrect write time on file - 0 time should be ignored\n"); + dump_all_info(torture, &finfo); + ret = false; + } + + printf("testing splclose\n"); + + /* check splclose on a file */ + REOPEN; + io.splclose.level = RAW_CLOSE_SPLCLOSE; + io.splclose.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_DOS(ERRSRV, ERRerror)); + + printf("testing flush\n"); + smbcli_close(cli->tree, fnum); + + io_flush.flush.level = RAW_FLUSH_FLUSH; + io_flush.flush.in.file.fnum = fnum; + status = smb_raw_flush(cli->tree, &io_flush); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + io_flush.flush_all.level = RAW_FLUSH_ALL; + status = smb_raw_flush(cli->tree, &io_flush); + CHECK_STATUS(status, NT_STATUS_OK); + + REOPEN; + + io_flush.flush.level = RAW_FLUSH_FLUSH; + io_flush.flush.in.file.fnum = fnum; + status = smb_raw_flush(cli->tree, &io_flush); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Testing SMBexit\n"); + smb_raw_exit(cli->session); + + io_flush.flush.level = RAW_FLUSH_FLUSH; + io_flush.flush.in.file.fnum = fnum; + status = smb_raw_flush(cli->tree, &io_flush); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + return ret; +} diff --git a/source4/torture/raw/composite.c b/source4/torture/raw/composite.c new file mode 100644 index 0000000000..d73ac1327e --- /dev/null +++ b/source4/torture/raw/composite.c @@ -0,0 +1,424 @@ +/* + Unix SMB/CIFS implementation. + + libcli composite function testing + + Copyright (C) Andrew Tridgell 2005 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "lib/events/events.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "libcli/security/security.h" +#include "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "lib/cmdline/popt_common.h" +#include "torture/util.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +#define BASEDIR "\\composite" + +static void loadfile_complete(struct composite_context *c) +{ + int *count = talloc_get_type(c->async.private_data, int); + (*count)++; +} + +/* + test a simple savefile/loadfile combination +*/ +static bool test_loadfile(struct smbcli_state *cli, struct torture_context *tctx) +{ + const char *fname = BASEDIR "\\test.txt"; + NTSTATUS status; + struct smb_composite_savefile io1; + struct smb_composite_loadfile io2; + struct composite_context **c; + uint8_t *data; + size_t len = random() % 100000; + const int num_ops = 50; + int i; + int *count = talloc_zero(tctx, int); + + data = talloc_array(tctx, uint8_t, len); + + generate_random_buffer(data, len); + + io1.in.fname = fname; + io1.in.data = data; + io1.in.size = len; + + printf("testing savefile\n"); + + status = smb_composite_savefile(cli->tree, &io1); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) savefile failed: %s\n", __location__,nt_errstr(status)); + return false; + } + + io2.in.fname = fname; + + printf("testing parallel loadfile with %d ops\n", num_ops); + + c = talloc_array(tctx, struct composite_context *, num_ops); + + for (i=0;i<num_ops;i++) { + c[i] = smb_composite_loadfile_send(cli->tree, &io2); + c[i]->async.fn = loadfile_complete; + c[i]->async.private_data = count; + } + + printf("waiting for completion\n"); + while (*count != num_ops) { + event_loop_once(cli->transport->socket->event.ctx); + if (torture_setting_bool(tctx, "progress", true)) { + printf("(%s) count=%d\r", __location__, *count); + fflush(stdout); + } + } + printf("count=%d\n", *count); + + for (i=0;i<num_ops;i++) { + status = smb_composite_loadfile_recv(c[i], tctx); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) loadfile[%d] failed - %s\n", __location__, i, nt_errstr(status)); + return false; + } + + if (io2.out.size != len) { + printf("(%s) wrong length in returned data - %d should be %d\n",__location__, + io2.out.size, (int)len); + return false; + } + + if (memcmp(io2.out.data, data, len) != 0) { + printf("(%s) wrong data in loadfile!\n",__location__); + return false; + } + } + + talloc_free(data); + + return true; +} + +/* + test a simple savefile/loadfile combination +*/ +static bool test_fetchfile(struct smbcli_state *cli, struct torture_context *tctx) +{ + const char *fname = BASEDIR "\\test.txt"; + NTSTATUS status; + struct smb_composite_savefile io1; + struct smb_composite_fetchfile io2; + struct composite_context **c; + uint8_t *data; + int i; + size_t len = random() % 10000; + extern int torture_numops; + struct event_context *event_ctx; + int *count = talloc_zero(tctx, int); + bool ret = true; + + data = talloc_array(tctx, uint8_t, len); + + generate_random_buffer(data, len); + + io1.in.fname = fname; + io1.in.data = data; + io1.in.size = len; + + printf("testing savefile\n"); + + status = smb_composite_savefile(cli->tree, &io1); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) savefile failed: %s\n",__location__, nt_errstr(status)); + return false; + } + + io2.in.dest_host = torture_setting_string(tctx, "host", NULL); + io2.in.ports = lp_smb_ports(tctx->lp_ctx); + io2.in.called_name = torture_setting_string(tctx, "host", NULL); + io2.in.service = torture_setting_string(tctx, "share", NULL); + io2.in.service_type = "A:"; + + io2.in.credentials = cmdline_credentials; + io2.in.workgroup = lp_workgroup(tctx->lp_ctx); + io2.in.filename = fname; + io2.in.resolve_ctx = lp_resolve_context(tctx->lp_ctx); + lp_smbcli_options(tctx->lp_ctx, &io2.in.options); + + printf("testing parallel fetchfile with %d ops\n", torture_numops); + + event_ctx = cli->transport->socket->event.ctx; + c = talloc_array(tctx, struct composite_context *, torture_numops); + + for (i=0; i<torture_numops; i++) { + c[i] = smb_composite_fetchfile_send(&io2, event_ctx); + c[i]->async.fn = loadfile_complete; + c[i]->async.private_data = count; + } + + printf("waiting for completion\n"); + + while (*count != torture_numops) { + event_loop_once(event_ctx); + if (torture_setting_bool(tctx, "progress", true)) { + printf("(%s) count=%d\r", __location__, *count); + fflush(stdout); + } + } + printf("count=%d\n", *count); + + for (i=0;i<torture_numops;i++) { + status = smb_composite_fetchfile_recv(c[i], tctx); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) loadfile[%d] failed - %s\n", __location__, i, + nt_errstr(status)); + ret = false; + continue; + } + + if (io2.out.size != len) { + printf("(%s) wrong length in returned data - %d " + "should be %d\n", __location__, + io2.out.size, (int)len); + ret = false; + continue; + } + + if (memcmp(io2.out.data, data, len) != 0) { + printf("(%s) wrong data in loadfile!\n", __location__); + ret = false; + continue; + } + } + + return ret; +} + +/* + test setfileacl +*/ +static bool test_appendacl(struct smbcli_state *cli, struct torture_context *tctx) +{ + struct smb_composite_appendacl **io; + struct smb_composite_appendacl **io_orig; + struct composite_context **c; + struct event_context *event_ctx; + + struct security_descriptor *test_sd; + struct security_ace *ace; + struct dom_sid *test_sid; + + const int num_ops = 50; + int *count = talloc_zero(tctx, int); + struct smb_composite_savefile io1; + + NTSTATUS status; + int i; + + io_orig = talloc_array(tctx, struct smb_composite_appendacl *, num_ops); + + printf ("creating %d empty files and getting their acls with appendacl\n", num_ops); + + for (i = 0; i < num_ops; i++) { + io1.in.fname = talloc_asprintf(io_orig, BASEDIR "\\test%d.txt", i); + io1.in.data = NULL; + io1.in.size = 0; + + status = smb_composite_savefile(cli->tree, &io1); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) savefile failed: %s\n", __location__, nt_errstr(status)); + return false; + } + + io_orig[i] = talloc (io_orig, struct smb_composite_appendacl); + io_orig[i]->in.fname = talloc_steal(io_orig[i], io1.in.fname); + io_orig[i]->in.sd = security_descriptor_initialise(io_orig[i]); + status = smb_composite_appendacl(cli->tree, io_orig[i], io_orig[i]); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) appendacl failed: %s\n", __location__, nt_errstr(status)); + return false; + } + } + + + /* fill Security Descriptor with aces to be added */ + + test_sd = security_descriptor_initialise(tctx); + test_sid = dom_sid_parse_talloc (tctx, "S-1-5-32-1234-5432"); + + ace = talloc_zero(tctx, struct security_ace); + + ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace->flags = 0; + ace->access_mask = SEC_STD_ALL; + ace->trustee = *test_sid; + + status = security_descriptor_dacl_add(test_sd, ace); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) appendacl failed: %s\n", __location__, nt_errstr(status)); + return false; + } + + /* set parameters for appendacl async call */ + + printf("testing parallel appendacl with %d ops\n", num_ops); + + c = talloc_array(tctx, struct composite_context *, num_ops); + io = talloc_array(tctx, struct smb_composite_appendacl *, num_ops); + + for (i=0; i < num_ops; i++) { + io[i] = talloc (io, struct smb_composite_appendacl); + io[i]->in.sd = test_sd; + io[i]->in.fname = talloc_asprintf(io[i], BASEDIR "\\test%d.txt", i); + + c[i] = smb_composite_appendacl_send(cli->tree, io[i]); + c[i]->async.fn = loadfile_complete; + c[i]->async.private_data = count; + } + + event_ctx = tctx->ev; + printf("waiting for completion\n"); + while (*count != num_ops) { + event_loop_once(event_ctx); + if (torture_setting_bool(tctx, "progress", true)) { + printf("(%s) count=%d\r", __location__, *count); + fflush(stdout); + } + } + printf("count=%d\n", *count); + + for (i=0; i < num_ops; i++) { + status = smb_composite_appendacl_recv(c[i], io[i]); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) appendacl[%d] failed - %s\n", __location__, i, nt_errstr(status)); + return false; + } + + security_descriptor_dacl_add(io_orig[i]->out.sd, ace); + if (!security_acl_equal(io_orig[i]->out.sd->dacl, io[i]->out.sd->dacl)) { + printf("(%s) appendacl[%d] failed - needed acl isn't set\n", __location__, i); + return false; + } + } + + + talloc_free (ace); + talloc_free (test_sid); + talloc_free (test_sd); + + return true; +} + +/* test a query FS info by asking for share's GUID */ +static bool test_fsinfo(struct smbcli_state *cli, struct torture_context *tctx) +{ + char *guid = NULL; + NTSTATUS status; + struct smb_composite_fsinfo io1; + struct composite_context **c; + + int i; + extern int torture_numops; + struct event_context *event_ctx; + int *count = talloc_zero(tctx, int); + bool ret = true; + + io1.in.dest_host = torture_setting_string(tctx, "host", NULL); + io1.in.dest_ports = lp_smb_ports(tctx->lp_ctx); + io1.in.called_name = torture_setting_string(tctx, "host", NULL); + io1.in.service = torture_setting_string(tctx, "share", NULL); + io1.in.service_type = "A:"; + io1.in.credentials = cmdline_credentials; + io1.in.workgroup = lp_workgroup(tctx->lp_ctx); + io1.in.level = RAW_QFS_OBJECTID_INFORMATION; + + printf("testing parallel queryfsinfo [Object ID] with %d ops\n", torture_numops); + + event_ctx = tctx->ev; + c = talloc_array(tctx, struct composite_context *, torture_numops); + + for (i=0; i<torture_numops; i++) { + c[i] = smb_composite_fsinfo_send(cli->tree,&io1); + c[i]->async.fn = loadfile_complete; + c[i]->async.private_data = count; + } + + printf("waiting for completion\n"); + + while (*count < torture_numops) { + event_loop_once(event_ctx); + if (torture_setting_bool(tctx, "progress", true)) { + printf("(%s) count=%d\r", __location__, *count); + fflush(stdout); + } + } + printf("count=%d\n", *count); + + for (i=0;i<torture_numops;i++) { + status = smb_composite_fsinfo_recv(c[i], tctx); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) fsinfo[%d] failed - %s\n", __location__, i, nt_errstr(status)); + ret = false; + continue; + } + + if (io1.out.fsinfo->generic.level != RAW_QFS_OBJECTID_INFORMATION) { + printf("(%s) wrong level in returned info - %d " + "should be %d\n", __location__, + io1.out.fsinfo->generic.level, RAW_QFS_OBJECTID_INFORMATION); + ret = false; + continue; + } + + guid=GUID_string(tctx, &io1.out.fsinfo->objectid_information.out.guid); + printf("[%d] GUID: %s\n", i, guid); + + + } + + return ret; +} + + +/* + basic testing of libcli composite calls +*/ +bool torture_raw_composite(struct torture_context *tctx, + struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_fetchfile(cli, tctx); + ret &= test_loadfile(cli, tctx); + ret &= test_appendacl(cli, tctx); + ret &= test_fsinfo(cli, tctx); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} diff --git a/source4/torture/raw/context.c b/source4/torture/raw/context.c new file mode 100644 index 0000000000..15e736fff5 --- /dev/null +++ b/source4/torture/raw/context.c @@ -0,0 +1,902 @@ +/* + Unix SMB/CIFS implementation. + test suite for session setup operations + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "lib/cmdline/popt_common.h" +#include "lib/events/events.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "auth/credentials/credentials.h" +#include "param/param.h" + +#define BASEDIR "\\rawcontext" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%s) Incorrect value %s=%d - should be %d\n", \ + __location__, #v, v, correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_NOT_VALUE(v, correct) do { \ + if ((v) == (correct)) { \ + printf("(%s) Incorrect value %s=%d - should not be %d\n", \ + __location__, #v, v, correct); \ + ret = false; \ + goto done; \ + }} while (0) + + +/* + test session ops +*/ +static bool test_session(struct smbcli_state *cli, struct torture_context *tctx) +{ + NTSTATUS status; + bool ret = true; + struct smbcli_session *session; + struct smbcli_session *session2; + struct smbcli_session *session3; + struct smbcli_session *session4; + struct cli_credentials *anon_creds; + struct smbcli_session *sessions[15]; + struct composite_context *composite_contexts[15]; + struct smbcli_tree *tree; + struct smb_composite_sesssetup setup; + struct smb_composite_sesssetup setups[15]; + union smb_open io; + union smb_write wr; + union smb_close cl; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + uint8_t c = 1; + int i; + + printf("TESTING SESSION HANDLING\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("create a second security context on the same transport\n"); + session = smbcli_session_init(cli->transport, tctx, false); + + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; /* ignored in secondary session setup, except by our libs, which care about the extended security bit */ + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + + setup.in.credentials = cmdline_credentials; + + status = smb_composite_sesssetup(session, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + + session->vuid = setup.out.vuid; + + printf("create a third security context on the same transport, with vuid set\n"); + session2 = smbcli_session_init(cli->transport, tctx, false); + + session2->vuid = session->vuid; + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; /* ignored in secondary session setup, except by our libs, which care about the extended security bit */ + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + + setup.in.credentials = cmdline_credentials; + + status = smb_composite_sesssetup(session2, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + + session2->vuid = setup.out.vuid; + printf("vuid1=%d vuid2=%d vuid3=%d\n", cli->session->vuid, session->vuid, session2->vuid); + + if (cli->transport->negotiate.capabilities & CAP_EXTENDED_SECURITY) { + /* Samba4 currently fails this - we need to determine if this insane behaviour is important */ + if (session2->vuid == session->vuid) { + printf("server allows the user to re-use an existing vuid in session setup \n"); + } + } else { + CHECK_NOT_VALUE(session2->vuid, session->vuid); + } + talloc_free(session2); + + if (cli->transport->negotiate.capabilities & CAP_EXTENDED_SECURITY) { + printf("create a fourth security context on the same transport, without extended security\n"); + session3 = smbcli_session_init(cli->transport, tctx, false); + + session3->vuid = session->vuid; + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities &= ~CAP_EXTENDED_SECURITY; /* force a non extended security login (should fail) */ + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + + setup.in.credentials = cmdline_credentials; + + + status = smb_composite_sesssetup(session3, &setup); + CHECK_STATUS(status, NT_STATUS_LOGON_FAILURE); + + printf("create a fouth anonymous security context on the same transport, without extended security\n"); + session4 = smbcli_session_init(cli->transport, tctx, false); + + session4->vuid = session->vuid; + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities &= ~CAP_EXTENDED_SECURITY; /* force a non extended security login (should fail) */ + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + + anon_creds = cli_credentials_init(tctx); + cli_credentials_set_conf(anon_creds, tctx->lp_ctx); + cli_credentials_set_anonymous(anon_creds); + + setup.in.credentials = anon_creds; + + status = smb_composite_sesssetup(session3, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + + talloc_free(session4); + } + + printf("use the same tree as the existing connection\n"); + tree = smbcli_tree_init(session, tctx, false); + tree->tid = cli->tree->tid; + + printf("create a file using the new vuid\n"); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("write using the old vuid\n"); + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("write with the new vuid\n"); + status = smb_raw_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("logoff the new vuid\n"); + status = smb_raw_ulogoff(session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the new vuid should not now be accessible\n"); + status = smb_raw_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("second logoff for the new vuid should fail\n"); + status = smb_raw_ulogoff(session); + CHECK_STATUS(status, NT_STATUS_DOS(ERRSRV, ERRbaduid)); + talloc_free(session); + + printf("the fnum should have been auto-closed\n"); + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("create %d secondary security contexts on the same transport\n", + (int)ARRAY_SIZE(sessions)); + for (i=0; i <ARRAY_SIZE(sessions); i++) { + setups[i].in.sesskey = cli->transport->negotiate.sesskey; + setups[i].in.capabilities = cli->transport->negotiate.capabilities; /* ignored in secondary session setup, except by our libs, which care about the extended security bit */ + setups[i].in.workgroup = lp_workgroup(tctx->lp_ctx); + + setups[i].in.credentials = cmdline_credentials; + + sessions[i] = smbcli_session_init(cli->transport, tctx, false); + composite_contexts[i] = smb_composite_sesssetup_send(sessions[i], &setups[i]); + + } + + + printf("finishing %d secondary security contexts on the same transport\n", + (int)ARRAY_SIZE(sessions)); + for (i=0; i< ARRAY_SIZE(sessions); i++) { + status = smb_composite_sesssetup_recv(composite_contexts[i]); + CHECK_STATUS(status, NT_STATUS_OK); + sessions[i]->vuid = setups[i].out.vuid; + printf("VUID: %d\n", sessions[i]->vuid); + status = smb_raw_ulogoff(sessions[i]); + CHECK_STATUS(status, NT_STATUS_OK); + } + + + talloc_free(tree); + +done: + return ret; +} + + +/* + test tree ops +*/ +static bool test_tree(struct smbcli_state *cli, struct torture_context *tctx) +{ + NTSTATUS status; + bool ret = true; + const char *share, *host; + struct smbcli_tree *tree; + union smb_tcon tcon; + union smb_open io; + union smb_write wr; + union smb_close cl; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + uint8_t c = 1; + + printf("TESTING TREE HANDLING\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + share = torture_setting_string(tctx, "share", NULL); + host = torture_setting_string(tctx, "host", NULL); + + printf("create a second tree context on the same session\n"); + tree = smbcli_tree_init(cli->session, tctx, false); + + tcon.generic.level = RAW_TCON_TCONX; + tcon.tconx.in.flags = 0; + tcon.tconx.in.password = data_blob(NULL, 0); + tcon.tconx.in.path = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + tcon.tconx.in.device = "A:"; + status = smb_raw_tcon(tree, tctx, &tcon); + CHECK_STATUS(status, NT_STATUS_OK); + + + tree->tid = tcon.tconx.out.tid; + printf("tid1=%d tid2=%d\n", cli->tree->tid, tree->tid); + + printf("try a tconx with a bad device type\n"); + tcon.tconx.in.device = "FOO"; + status = smb_raw_tcon(tree, tctx, &tcon); + CHECK_STATUS(status, NT_STATUS_BAD_DEVICE_TYPE); + + + printf("create a file using the new tid\n"); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("write using the old tid\n"); + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("write with the new tid\n"); + status = smb_raw_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("disconnect the new tid\n"); + status = smb_tree_disconnect(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the new tid should not now be accessible\n"); + status = smb_raw_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("the fnum should have been auto-closed\n"); + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + /* close down the new tree */ + talloc_free(tree); + +done: + return ret; +} + +/* + test tree with ulogoff + this demonstrates that a tcon isn't autoclosed by a ulogoff + the tcon can be reused using any other valid session later +*/ +static bool test_tree_ulogoff(struct smbcli_state *cli, struct torture_context *tctx) +{ + NTSTATUS status; + bool ret = true; + const char *share, *host; + struct smbcli_session *session1; + struct smbcli_session *session2; + struct smb_composite_sesssetup setup; + struct smbcli_tree *tree; + union smb_tcon tcon; + union smb_open io; + union smb_write wr; + int fnum1, fnum2; + const char *fname1 = BASEDIR "\\test1.txt"; + const char *fname2 = BASEDIR "\\test2.txt"; + uint8_t c = 1; + + printf("TESTING TREE with ulogoff\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + share = torture_setting_string(tctx, "share", NULL); + host = torture_setting_string(tctx, "host", NULL); + + printf("create the first new sessions\n"); + session1 = smbcli_session_init(cli->transport, tctx, false); + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + setup.in.credentials = cmdline_credentials; + status = smb_composite_sesssetup(session1, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + session1->vuid = setup.out.vuid; + printf("vuid1=%d\n", session1->vuid); + + printf("create a tree context on the with vuid1\n"); + tree = smbcli_tree_init(session1, tctx, false); + tcon.generic.level = RAW_TCON_TCONX; + tcon.tconx.in.flags = 0; + tcon.tconx.in.password = data_blob(NULL, 0); + tcon.tconx.in.path = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + tcon.tconx.in.device = "A:"; + status = smb_raw_tcon(tree, tctx, &tcon); + CHECK_STATUS(status, NT_STATUS_OK); + tree->tid = tcon.tconx.out.tid; + printf("tid=%d\n", tree->tid); + + printf("create a file using vuid1\n"); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + status = smb_raw_open(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum1 = io.ntcreatex.out.file.fnum; + + printf("write using vuid1\n"); + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum1; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + status = smb_raw_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("ulogoff the vuid1\n"); + status = smb_raw_ulogoff(session1); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("create the second new sessions\n"); + session2 = smbcli_session_init(cli->transport, tctx, false); + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + setup.in.credentials = cmdline_credentials; + status = smb_composite_sesssetup(session2, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + session2->vuid = setup.out.vuid; + printf("vuid2=%d\n", session2->vuid); + + printf("use the existing tree with vuid2\n"); + tree->session = session2; + + printf("create a file using vuid2\n"); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname2; + status = smb_raw_open(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + printf("write using vuid2\n"); + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum2; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + status = smb_raw_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("ulogoff the vuid2\n"); + status = smb_raw_ulogoff(session2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* this also demonstrates that SMBtdis doesn't need a valid vuid */ + printf("disconnect the existing tree connection\n"); + status = smb_tree_disconnect(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("disconnect the existing tree connection\n"); + status = smb_tree_disconnect(tree); + CHECK_STATUS(status, NT_STATUS_DOS(ERRSRV,ERRinvnid)); + + /* close down the new tree */ + talloc_free(tree); + +done: + return ret; +} + +/* + test pid ops + this test demonstrates that exit() only sees the PID + used for the open() calls +*/ +static bool test_pid_exit_only_sees_open(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_write wr; + union smb_close cl; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + uint8_t c = 1; + uint16_t pid1, pid2; + + printf("TESTING PID HANDLING exit() only cares about open() PID\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + pid1 = cli->session->pid; + pid2 = pid1 + 1; + + printf("pid1=%d pid2=%d\n", pid1, pid2); + + printf("create a file using pid1\n"); + cli->session->pid = pid1; + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("write using pid2\n"); + cli->session->pid = pid2; + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("exit pid2\n"); + cli->session->pid = pid2; + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the fnum should still be accessible via pid2\n"); + cli->session->pid = pid2; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("exit pid2\n"); + cli->session->pid = pid2; + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the fnum should still be accessible via pid1 and pid2\n"); + cli->session->pid = pid1; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + cli->session->pid = pid2; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("exit pid1\n"); + cli->session->pid = pid1; + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the fnum should not now be accessible via pid1 or pid2\n"); + cli->session->pid = pid1; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + cli->session->pid = pid2; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("the fnum should have been auto-closed\n"); + cli->session->pid = pid1; + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + +done: + return ret; +} + +/* + test pid ops with 2 sessions +*/ +static bool test_pid_2sess(struct smbcli_state *cli, struct torture_context *tctx) +{ + NTSTATUS status; + bool ret = true; + struct smbcli_session *session; + struct smb_composite_sesssetup setup; + union smb_open io; + union smb_write wr; + union smb_close cl; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + uint8_t c = 1; + uint16_t vuid1, vuid2; + + printf("TESTING PID HANDLING WITH 2 SESSIONS\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("create a second security context on the same transport\n"); + session = smbcli_session_init(cli->transport, tctx, false); + + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; /* ignored in secondary session setup, except by our libs, which care about the extended security bit */ + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + + setup.in.credentials = cmdline_credentials; + + status = smb_composite_sesssetup(session, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + session->vuid = setup.out.vuid; + + vuid1 = cli->session->vuid; + vuid2 = session->vuid; + + printf("vuid1=%d vuid2=%d\n", vuid1, vuid2); + + printf("create a file using the vuid1\n"); + cli->session->vuid = vuid1; + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("write using the vuid1 (fnum=%d)\n", fnum); + cli->session->vuid = vuid1; + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("exit the pid with vuid2\n"); + cli->session->vuid = vuid2; + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the fnum should still be accessible\n"); + cli->session->vuid = vuid1; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("exit the pid with vuid1\n"); + cli->session->vuid = vuid1; + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the fnum should not now be accessible\n"); + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("the fnum should have been auto-closed\n"); + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + +done: + return ret; +} + +/* + test pid ops with 2 tcons +*/ +static bool test_pid_2tcon(struct smbcli_state *cli, struct torture_context *tctx) +{ + NTSTATUS status; + bool ret = true; + const char *share, *host; + struct smbcli_tree *tree; + union smb_tcon tcon; + union smb_open io; + union smb_write wr; + union smb_close cl; + int fnum1, fnum2; + const char *fname1 = BASEDIR "\\test1.txt"; + const char *fname2 = BASEDIR "\\test2.txt"; + uint8_t c = 1; + uint16_t tid1, tid2; + + printf("TESTING PID HANDLING WITH 2 TCONS\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + share = torture_setting_string(tctx, "share", NULL); + host = torture_setting_string(tctx, "host", NULL); + + printf("create a second tree context on the same session\n"); + tree = smbcli_tree_init(cli->session, tctx, false); + + tcon.generic.level = RAW_TCON_TCONX; + tcon.tconx.in.flags = 0; + tcon.tconx.in.password = data_blob(NULL, 0); + tcon.tconx.in.path = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + tcon.tconx.in.device = "A:"; + status = smb_raw_tcon(tree, tctx, &tcon); + CHECK_STATUS(status, NT_STATUS_OK); + + tree->tid = tcon.tconx.out.tid; + + tid1 = cli->tree->tid; + tid2 = tree->tid; + printf("tid1=%d tid2=%d\n", tid1, tid2); + + printf("create a file using the tid1\n"); + cli->tree->tid = tid1; + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum1 = io.ntcreatex.out.file.fnum; + + printf("write using the tid1\n"); + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum1; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("create a file using the tid2\n"); + cli->tree->tid = tid2; + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname2; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + printf("write using the tid2\n"); + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum2; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = 1; + wr.writex.in.data = &c; + + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, 1); + + printf("exit the pid\n"); + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the fnum1 on tid1 should not be accessible\n"); + cli->tree->tid = tid1; + wr.writex.in.file.fnum = fnum1; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("the fnum1 on tid1 should have been auto-closed\n"); + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum1; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("the fnum2 on tid2 should not be accessible\n"); + cli->tree->tid = tid2; + wr.writex.in.file.fnum = fnum2; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("the fnum2 on tid2 should have been auto-closed\n"); + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum2; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + +done: + return ret; +} + + +/* + basic testing of session/tree context calls +*/ +static bool torture_raw_context_int(struct torture_context *tctx, + struct smbcli_state *cli) +{ + bool ret = true; + + ret &= test_session(cli, tctx); + ret &= test_tree(cli, tctx); + ret &= test_tree_ulogoff(cli, tctx); + ret &= test_pid_exit_only_sees_open(cli, tctx); + ret &= test_pid_2sess(cli, tctx); + ret &= test_pid_2tcon(cli, tctx); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} +/* + basic testing of session/tree context calls +*/ +bool torture_raw_context(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + if (lp_use_spnego(torture->lp_ctx)) { + ret &= torture_raw_context_int(torture, cli); + lp_set_cmdline(torture->lp_ctx, "use spnego", "False"); + } + + ret &= torture_raw_context_int(torture, cli); + + return ret; +} diff --git a/source4/torture/raw/eas.c b/source4/torture/raw/eas.c new file mode 100644 index 0000000000..bc4473b17a --- /dev/null +++ b/source4/torture/raw/eas.c @@ -0,0 +1,490 @@ +/* + Unix SMB/CIFS implementation. + + test DOS extended attributes + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Guenter Kukkukk 2005 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "param/param.h" + +#define BASEDIR "\\testeas" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +static bool maxeadebug; /* need that here, to allow no file delete in debug case */ + +static bool check_ea(struct smbcli_state *cli, + const char *fname, const char *eaname, const char *value) +{ + NTSTATUS status = torture_check_ea(cli, fname, eaname, value); + return NT_STATUS_IS_OK(status); +} + +static bool test_eas(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + union smb_setfileinfo setfile; + union smb_open io; + const char *fname = BASEDIR "\\ea.txt"; + bool ret = true; + int fnum = -1; + + printf("TESTING SETFILEINFO EA_SET\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + ret &= check_ea(cli, fname, "EAONE", NULL); + + printf("Adding first two EAs\n"); + setfile.generic.level = RAW_SFILEINFO_EA_SET; + setfile.generic.in.file.fnum = fnum; + setfile.ea_set.in.num_eas = 2; + setfile.ea_set.in.eas = talloc_array(mem_ctx, struct ea_struct, 2); + setfile.ea_set.in.eas[0].flags = 0; + setfile.ea_set.in.eas[0].name.s = "EAONE"; + setfile.ea_set.in.eas[0].value = data_blob_string_const("VALUE1"); + setfile.ea_set.in.eas[1].flags = 0; + setfile.ea_set.in.eas[1].name.s = "SECONDEA"; + setfile.ea_set.in.eas[1].value = data_blob_string_const("ValueTwo"); + + status = smb_raw_setfileinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + ret &= check_ea(cli, fname, "EAONE", "VALUE1"); + ret &= check_ea(cli, fname, "SECONDEA", "ValueTwo"); + + printf("Modifying 2nd EA\n"); + setfile.ea_set.in.num_eas = 1; + setfile.ea_set.in.eas[0].name.s = "SECONDEA"; + setfile.ea_set.in.eas[0].value = data_blob_string_const(" Changed Value"); + status = smb_raw_setfileinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + ret &= check_ea(cli, fname, "EAONE", "VALUE1"); + ret &= check_ea(cli, fname, "SECONDEA", " Changed Value"); + + printf("Setting a NULL EA\n"); + setfile.ea_set.in.eas[0].value = data_blob(NULL, 0); + setfile.ea_set.in.eas[0].name.s = "NULLEA"; + status = smb_raw_setfileinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + ret &= check_ea(cli, fname, "EAONE", "VALUE1"); + ret &= check_ea(cli, fname, "SECONDEA", " Changed Value"); + ret &= check_ea(cli, fname, "NULLEA", NULL); + + printf("Deleting first EA\n"); + setfile.ea_set.in.eas[0].flags = 0; + setfile.ea_set.in.eas[0].name.s = "EAONE"; + setfile.ea_set.in.eas[0].value = data_blob(NULL, 0); + status = smb_raw_setfileinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + ret &= check_ea(cli, fname, "EAONE", NULL); + ret &= check_ea(cli, fname, "SECONDEA", " Changed Value"); + + printf("Deleting second EA\n"); + setfile.ea_set.in.eas[0].flags = 0; + setfile.ea_set.in.eas[0].name.s = "SECONDEA"; + setfile.ea_set.in.eas[0].value = data_blob(NULL, 0); + status = smb_raw_setfileinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + ret &= check_ea(cli, fname, "EAONE", NULL); + ret &= check_ea(cli, fname, "SECONDEA", NULL); + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + + +/* + * Helper function to retrieve the max. ea size for one ea name + */ +static int test_one_eamax(struct smbcli_state *cli, const int fnum, + const char *eaname, DATA_BLOB eablob, + const int eastart, const int eadebug) +{ + NTSTATUS status; + struct ea_struct eastruct; + union smb_setfileinfo setfile; + int i, high, low, maxeasize; + + setfile.generic.level = RAW_SFILEINFO_EA_SET; + setfile.generic.in.file.fnum = fnum; + setfile.ea_set.in.num_eas = 1; + setfile.ea_set.in.eas = &eastruct; + setfile.ea_set.in.eas->flags = 0; + setfile.ea_set.in.eas->name.s = eaname; + setfile.ea_set.in.eas->value = eablob; + + maxeasize = eablob.length; + i = eastart; + low = 0; + high = maxeasize; + + do { + if (eadebug) { + printf ("Testing EA size: %d\n", i); + } + setfile.ea_set.in.eas->value.length = i; + + status = smb_raw_setfileinfo(cli->tree, &setfile); + + if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + if (eadebug) { + printf ("[%s] EA size %d succeeded! " + "(high=%d low=%d)\n", + eaname, i, high, low); + } + low = i; + if (low == maxeasize) { + printf ("Max. EA size for \"%s\"=%d " + "[but could be possibly larger]\n", + eaname, low); + break; + } + if (high - low == 1 && high != maxeasize) { + printf ("Max. EA size for \"%s\"=%d\n", + eaname, low); + break; + } + i += (high - low + 1) / 2; + } else { + if (eadebug) { + printf ("[%s] EA size %d failed! " + "(high=%d low=%d) [%s]\n", + eaname, i, high, low, + nt_errstr(status)); + } + high = i; + if (high - low <= 1) { + printf ("Max. EA size for \"%s\"=%d\n", + eaname, low); + break; + } + i -= (high - low + 1) / 2; + } + } while (true); + + return low; +} + +/* + * Test for maximum ea size - more than one ea name is checked. + * + * Additional parameters can be passed, to allow further testing: + * + * default + * maxeasize 65536 limit the max. size for a single EA name + * maxeanames 101 limit of the number of tested names + * maxeastart 1 this EA size is used to test for the 1st EA (atm) + * maxeadebug 0 if set true, further debug output is done - in addition + * the testfile is not deleted for further inspection! + * + * Set some/all of these options on the cmdline with: + * --option torture:maxeasize=1024 --option torture:maxeadebug=1 ... + * + */ +static bool test_max_eas(struct smbcli_state *cli, struct torture_context *tctx) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\ea_max.txt"; + int fnum = -1; + bool ret = true; + bool err = false; + + int i, j, k, last, total; + DATA_BLOB eablob; + char *eaname = NULL; + int maxeasize; + int maxeanames; + int maxeastart; + + printf("TESTING SETFILEINFO MAX. EA_SET\n"); + + maxeasize = torture_setting_int(tctx, "maxeasize", 65536); + maxeanames = torture_setting_int(tctx, "maxeanames", 101); + maxeastart = torture_setting_int(tctx, "maxeastart", 1); + maxeadebug = torture_setting_int(tctx, "maxeadebug", 0); + + /* Do some sanity check on possibly passed parms */ + if (maxeasize <= 0) { + printf("Invalid parameter 'maxeasize=%d'",maxeasize); + err = true; + } + if (maxeanames <= 0) { + printf("Invalid parameter 'maxeanames=%d'",maxeanames); + err = true; + } + if (maxeastart <= 0) { + printf("Invalid parameter 'maxeastart=%d'",maxeastart); + err = true; + } + if (maxeadebug < 0) { + printf("Invalid parameter 'maxeadebug=%d'",maxeadebug); + err = true; + } + if (err) { + printf("\n\n"); + goto done; + } + if (maxeastart > maxeasize) { + maxeastart = maxeasize; + printf ("'maxeastart' outside range - corrected to %d\n", + maxeastart); + } + printf("MAXEA parms: maxeasize=%d maxeanames=%d maxeastart=%d" + " maxeadebug=%d\n", maxeasize, maxeanames, maxeastart, + maxeadebug); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + eablob = data_blob_talloc(tctx, NULL, maxeasize); + if (eablob.data == NULL) { + goto done; + } + /* + * Fill in some EA data - the offset could be easily checked + * during a hexdump. + */ + for (i = 0, k = 0; i < eablob.length / 4; i++, k+=4) { + eablob.data[k] = k & 0xff; + eablob.data[k+1] = (k >> 8) & 0xff; + eablob.data[k+2] = (k >> 16) & 0xff; + eablob.data[k+3] = (k >> 24) & 0xff; + } + + i = eablob.length % 4; + if (i-- > 0) { + eablob.data[k] = k & 0xff; + if (i-- > 0) { + eablob.data[k+1] = (k >> 8) & 0xff; + if (i-- > 0) { + eablob.data[k+2] = (k >> 16) & 0xff; + } + } + } + /* + * Filesystems might allow max. EAs data for different EA names. + * So more than one EA name should be checked. + */ + total = 0; + last = maxeastart; + + for (i = 0; i < maxeanames; i++) { + if (eaname != NULL) { + talloc_free(eaname); + } + eaname = talloc_asprintf(tctx, "MAX%d", i); + if(eaname == NULL) { + goto done; + } + j = test_one_eamax(cli, fnum, eaname, eablob, last, maxeadebug); + if (j <= 0) { + break; + } + total += j; + last = j; + } + + printf("Total EA size:%d\n", total); + if (i == maxeanames) { + printf ("NOTE: More EAs could be available!\n"); + } + if (total == 0) { + ret = false; + } +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* + test using NTTRANS CREATE to create a file with an initial EA set +*/ +static bool test_nttrans_create(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\ea2.txt"; + bool ret = true; + int fnum = -1; + struct ea_struct eas[3]; + struct smb_ea_list ea_list; + + printf("TESTING NTTRANS CREATE WITH EAS\n"); + + io.generic.level = RAW_OPEN_NTTRANS_CREATE; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + ea_list.num_eas = 3; + ea_list.eas = eas; + + eas[0].flags = 0; + eas[0].name.s = "1st EA"; + eas[0].value = data_blob_string_const("Value One"); + + eas[1].flags = 0; + eas[1].name.s = "2nd EA"; + eas[1].value = data_blob_string_const("Second Value"); + + eas[2].flags = 0; + eas[2].name.s = "and 3rd"; + eas[2].value = data_blob_string_const("final value"); + + io.ntcreatex.in.ea_list = &ea_list; + io.ntcreatex.in.sec_desc = NULL; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + ret &= check_ea(cli, fname, "EAONE", NULL); + ret &= check_ea(cli, fname, "1st EA", "Value One"); + ret &= check_ea(cli, fname, "2nd EA", "Second Value"); + ret &= check_ea(cli, fname, "and 3rd", "final value"); + + smbcli_close(cli->tree, fnum); + + printf("Trying to add EAs on non-create\n"); + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.fname = fname; + + ea_list.num_eas = 1; + eas[0].flags = 0; + eas[0].name.s = "Fourth EA"; + eas[0].value = data_blob_string_const("Value Four"); + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + ret &= check_ea(cli, fname, "1st EA", "Value One"); + ret &= check_ea(cli, fname, "2nd EA", "Second Value"); + ret &= check_ea(cli, fname, "and 3rd", "final value"); + ret &= check_ea(cli, fname, "Fourth EA", NULL); + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* + basic testing of EA calls +*/ +bool torture_raw_eas(struct torture_context *torture, struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_eas(cli, torture); + ret &= test_nttrans_create(cli, torture); + + smb_raw_exit(cli->session); + + return ret; +} + +/* + test max EA size +*/ +bool torture_max_eas(struct torture_context *torture) +{ + struct smbcli_state *cli; + bool ret = true; + + if (!torture_open_connection(&cli, torture, 0)) { + return false; + } + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_max_eas(cli, torture); + + smb_raw_exit(cli->session); + if (!maxeadebug) { + /* in no ea debug case, all files are gone now */ + smbcli_deltree(cli->tree, BASEDIR); + } + + torture_close_connection(cli); + return ret; +} diff --git a/source4/torture/raw/ioctl.c b/source4/torture/raw/ioctl.c new file mode 100644 index 0000000000..4cb366f807 --- /dev/null +++ b/source4/torture/raw/ioctl.c @@ -0,0 +1,171 @@ +/* + Unix SMB/CIFS implementation. + ioctl individual test suite + Copyright (C) Andrew Tridgell 2003 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/ioctl.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define BASEDIR "\\rawioctl" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%d) Incorrect status %s - should be %s\n", \ + __LINE__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + +/* test some ioctls */ +static bool test_ioctl(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + union smb_ioctl ctl; + int fnum; + NTSTATUS status; + bool ret = true; + const char *fname = BASEDIR "\\test.dat"; + + printf("TESTING IOCTL FUNCTIONS\n"); + + fnum = create_complex_file(cli, mem_ctx, fname); + if (fnum == -1) { + printf("Failed to create test.dat - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying 0xFFFF\n"); + ctl.ioctl.level = RAW_IOCTL_IOCTL; + ctl.ioctl.in.file.fnum = fnum; + ctl.ioctl.in.request = 0xFFFF; + + status = smb_raw_ioctl(cli->tree, mem_ctx, &ctl); + CHECK_STATUS(status, NT_STATUS_DOS(ERRSRV, ERRerror)); + + printf("Trying QUERY_JOB_INFO\n"); + ctl.ioctl.level = RAW_IOCTL_IOCTL; + ctl.ioctl.in.file.fnum = fnum; + ctl.ioctl.in.request = IOCTL_QUERY_JOB_INFO; + + status = smb_raw_ioctl(cli->tree, mem_ctx, &ctl); + CHECK_STATUS(status, NT_STATUS_DOS(ERRSRV, ERRerror)); + + printf("Trying bad handle\n"); + ctl.ioctl.in.file.fnum = fnum+1; + status = smb_raw_ioctl(cli->tree, mem_ctx, &ctl); + CHECK_STATUS(status, NT_STATUS_DOS(ERRSRV, ERRerror)); + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* test some filesystem control functions */ +static bool test_fsctl(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + int fnum; + NTSTATUS status; + bool ret = true; + const char *fname = BASEDIR "\\test.dat"; + union smb_ioctl nt; + + printf("\nTESTING FSCTL FUNCTIONS\n"); + + fnum = create_complex_file(cli, mem_ctx, fname); + if (fnum == -1) { + printf("Failed to create test.dat - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("trying sparse file\n"); + nt.ioctl.level = RAW_IOCTL_NTIOCTL; + nt.ntioctl.in.function = FSCTL_SET_SPARSE; + nt.ntioctl.in.file.fnum = fnum; + nt.ntioctl.in.fsctl = true; + nt.ntioctl.in.filter = 0; + nt.ntioctl.in.max_data = 0; + nt.ntioctl.in.blob = data_blob(NULL, 0); + + status = smb_raw_ioctl(cli->tree, mem_ctx, &nt); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("trying batch oplock\n"); + nt.ioctl.level = RAW_IOCTL_NTIOCTL; + nt.ntioctl.in.function = FSCTL_REQUEST_BATCH_OPLOCK; + nt.ntioctl.in.file.fnum = fnum; + nt.ntioctl.in.fsctl = true; + nt.ntioctl.in.filter = 0; + nt.ntioctl.in.max_data = 0; + nt.ntioctl.in.blob = data_blob(NULL, 0); + + status = smb_raw_ioctl(cli->tree, mem_ctx, &nt); + if (NT_STATUS_IS_OK(status)) { + printf("Server supports batch oplock upgrades on open files\n"); + } else { + printf("Server does not support batch oplock upgrades on open files\n"); + } + + printf("Trying bad handle\n"); + nt.ntioctl.in.file.fnum = fnum+1; + status = smb_raw_ioctl(cli->tree, mem_ctx, &nt); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + +#if 0 + nt.ntioctl.in.file.fnum = fnum; + for (i=0;i<100;i++) { + nt.ntioctl.in.function = FSCTL_FILESYSTEM + (i<<2); + status = smb_raw_ioctl(cli->tree, mem_ctx, &nt); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + printf("filesystem fsctl 0x%x - %s\n", + i, nt_errstr(status)); + } + } +#endif + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* + basic testing of some ioctl calls +*/ +bool torture_raw_ioctl(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_ioctl(cli, torture); + ret &= test_fsctl(cli, torture); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} diff --git a/source4/torture/raw/lock.c b/source4/torture/raw/lock.c new file mode 100644 index 0000000000..cd8d606795 --- /dev/null +++ b/source4/torture/raw/lock.c @@ -0,0 +1,1336 @@ +/* + Unix SMB/CIFS implementation. + test suite for various lock operations + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "lib/cmdline/popt_common.h" +#include "param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define BASEDIR "\\testlock" + + +/* + test SMBlock and SMBunlock ops +*/ +static bool test_lock(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_lock io; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_LOCK_LOCK\n"); + io.generic.level = RAW_LOCK_LOCK; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying 0/0 lock\n"); + io.lock.level = RAW_LOCK_LOCK; + io.lock.in.file.fnum = fnum; + io.lock.in.count = 0; + io.lock.in.offset = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid--; + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying 0/1 lock\n"); + io.lock.level = RAW_LOCK_LOCK; + io.lock.in.file.fnum = fnum; + io.lock.in.count = 1; + io.lock.in.offset = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + cli->session->pid--; + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying 0xEEFFFFFF lock\n"); + io.lock.level = RAW_LOCK_LOCK; + io.lock.in.file.fnum = fnum; + io.lock.in.count = 4000; + io.lock.in.offset = 0xEEFFFFFF; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + cli->session->pid--; + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying 0xEF000000 lock\n"); + io.lock.level = RAW_LOCK_LOCK; + io.lock.in.file.fnum = fnum; + io.lock.in.count = 4000; + io.lock.in.offset = 0xEEFFFFFF; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + cli->session->pid--; + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying max lock\n"); + io.lock.level = RAW_LOCK_LOCK; + io.lock.in.file.fnum = fnum; + io.lock.in.count = 4000; + io.lock.in.offset = 0xEF000000; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + cli->session->pid--; + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying wrong pid unlock\n"); + io.lock.level = RAW_LOCK_LOCK; + io.lock.in.file.fnum = fnum; + io.lock.in.count = 4002; + io.lock.in.offset = 10001; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + cli->session->pid++; + io.lock.level = RAW_LOCK_UNLOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + cli->session->pid--; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test locking&X ops +*/ +static bool test_lockx(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[1]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_LOCK_LOCKX\n"); + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 10; + lock[0].count = 1; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + + printf("Trying 0xEEFFFFFF lock\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 4000; + lock[0].offset = 0xEEFFFFFF; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying 0xEF000000 lock\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 4000; + lock[0].offset = 0xEF000000; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying zero lock\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 0; + lock[0].offset = ~0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying max lock\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 0; + lock[0].offset = ~0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying 2^63\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 1; + lock[0].offset = 1; + lock[0].offset <<= 63; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying 2^63 - 1\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 1; + lock[0].offset = 1; + lock[0].offset <<= 63; + lock[0].offset--; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + printf("Trying max lock 2\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].count = 1; + lock[0].offset = ~0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid++; + lock[0].count = 2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + lock[0].pid--; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + lock[0].count = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test high pid +*/ +static bool test_pidhigh(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[1]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + uint8_t c = 1; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing high pid\n"); + io.generic.level = RAW_LOCK_LOCKX; + + cli->session->pid = 1; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + if (smbcli_write(cli->tree, fnum, 0, &c, 0, 1) != 1) { + printf("Failed to write 1 byte - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 0; + lock[0].count = 0xFFFFFFFF; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + if (smbcli_read(cli->tree, fnum, &c, 0, 1) != 1) { + printf("Failed to read 1 byte - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + cli->session->pid |= 0x10000; + + cli->session->pid = 2; + + if (smbcli_read(cli->tree, fnum, &c, 0, 1) == 1) { + printf("pid is incorrect handled for read with lock!\n"); + ret = false; + goto done; + } + + cli->session->pid = 0x10001; + + if (smbcli_read(cli->tree, fnum, &c, 0, 1) != 1) { + printf("High pid is used on this server!\n"); + ret = false; + } else { + printf("High pid is not used on this server (correct)\n"); + } + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test locking&X async operation +*/ +static bool test_async(struct torture_context *tctx, + struct smbcli_state *cli) +{ + struct smbcli_session *session; + struct smb_composite_sesssetup setup; + struct smbcli_tree *tree; + union smb_tcon tcon; + const char *host, *share; + union smb_lock io; + struct smb_lock_entry lock[2]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\test.txt"; + time_t t; + struct smbcli_request *req; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing LOCKING_ANDX_CANCEL_LOCK\n"); + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + t = time(NULL); + + printf("testing cancel by CANCEL_LOCK\n"); + + /* setup a timed lock */ + io.lockx.in.timeout = 10000; + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + /* cancel the wrong range */ + lock[0].offset = 0; + io.lockx.in.timeout = 0; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRcancelviolation)); + + /* cancel with the wrong bits set */ + lock[0].offset = 100; + io.lockx.in.timeout = 0; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRcancelviolation)); + + /* cancel the right range */ + lock[0].offset = 100; + io.lockx.in.timeout = 0; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK | LOCKING_ANDX_LARGE_FILES; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* receive the failed lock request */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + if (time(NULL) > t+2) { + printf("lock cancel was not immediate (%s)\n", __location__); + ret = false; + goto done; + } + + printf("testing cancel by unlock\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.timeout = 5000; + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + t = time(NULL); + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + if (time(NULL) > t+2) { + printf("lock cancel by unlock was not immediate (%s) - took %d secs\n", + __location__, (int)(time(NULL)-t)); + ret = false; + goto done; + } + + printf("testing cancel by close\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + t = time(NULL); + io.lockx.in.timeout = 10000; + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + status = smbcli_close(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + if (time(NULL) > t+2) { + printf("lock cancel by close was not immediate (%s)\n", __location__); + ret = false; + goto done; + } + + printf("create a new sessions\n"); + session = smbcli_session_init(cli->transport, tctx, false); + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; + setup.in.workgroup = lp_workgroup(tctx->lp_ctx); + setup.in.credentials = cmdline_credentials; + status = smb_composite_sesssetup(session, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + session->vuid = setup.out.vuid; + + printf("create new tree context\n"); + share = torture_setting_string(tctx, "share", NULL); + host = torture_setting_string(tctx, "host", NULL); + tree = smbcli_tree_init(session, tctx, false); + tcon.generic.level = RAW_TCON_TCONX; + tcon.tconx.in.flags = 0; + tcon.tconx.in.password = data_blob(NULL, 0); + tcon.tconx.in.path = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + tcon.tconx.in.device = "A:"; + status = smb_raw_tcon(tree, tctx, &tcon); + CHECK_STATUS(status, NT_STATUS_OK); + tree->tid = tcon.tconx.out.tid; + + printf("testing cancel by exit\n"); + fname = BASEDIR "\\test_exit.txt"; + fnum = smbcli_open(tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to reopen %s - %s\n", fname, smbcli_errstr(tree)); + ret = false; + goto done; + } + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + status = smb_raw_lock(tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + io.lockx.in.timeout = 10000; + t = time(NULL); + req = smb_raw_lock_send(tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + status = smb_raw_exit(session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + if (time(NULL) > t+2) { + printf("lock cancel by exit was not immediate (%s)\n", __location__); + ret = false; + goto done; + } + + printf("testing cancel by ulogoff\n"); + fname = BASEDIR "\\test_ulogoff.txt"; + fnum = smbcli_open(tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to reopen %s - %s\n", fname, smbcli_errstr(tree)); + ret = false; + goto done; + } + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + status = smb_raw_lock(tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + io.lockx.in.timeout = 10000; + t = time(NULL); + req = smb_raw_lock_send(tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + status = smb_raw_ulogoff(session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + if (NT_STATUS_EQUAL(NT_STATUS_FILE_LOCK_CONFLICT, status)) { + printf("lock not canceled by ulogoff - %s (ignored because of vfs_vifs fails it)\n", + nt_errstr(status)); + smb_tree_disconnect(tree); + smb_raw_exit(session); + goto done; + } + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + if (time(NULL) > t+2) { + printf("lock cancel by ulogoff was not immediate (%s)\n", __location__); + ret = false; + goto done; + } + + printf("testing cancel by tdis\n"); + tree->session = cli->session; + + fname = BASEDIR "\\test_tdis.txt"; + fnum = smbcli_open(tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to reopen %s - %s\n", fname, smbcli_errstr(tree)); + ret = false; + goto done; + } + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_lock(tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + io.lockx.in.timeout = 10000; + t = time(NULL); + req = smb_raw_lock_send(tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + status = smb_tree_disconnect(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + if (time(NULL) > t+2) { + printf("lock cancel by tdis was not immediate (%s)\n", __location__); + ret = false; + goto done; + } + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test NT_STATUS_LOCK_NOT_GRANTED vs. NT_STATUS_FILE_LOCK_CONFLICT +*/ +static bool test_errorcode(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + union smb_open op; + struct smb_lock_entry lock[2]; + NTSTATUS status; + bool ret = true; + int fnum, fnum2; + const char *fname; + struct smbcli_request *req; + time_t start; + int t; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing LOCK_NOT_GRANTED vs. FILE_LOCK_CONFLICT\n"); + + printf("testing with timeout = 0\n"); + fname = BASEDIR "\\test0.txt"; + t = 0; + + /* + * the first run is with t = 0, + * the second with t > 0 (=1) + */ +next_run: + /* + * use the DENY_DOS mode, that creates two fnum's of one low-level file handle, + * this demonstrates that the cache is per fnum + */ + op.openx.level = RAW_OPEN_OPENX; + op.openx.in.fname = fname; + op.openx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + op.openx.in.open_mode = OPENX_MODE_ACCESS_RDWR | OPENX_MODE_DENY_DOS; + op.openx.in.open_func = OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE; + op.openx.in.search_attrs = 0; + op.openx.in.file_attrs = 0; + op.openx.in.write_time = 0; + op.openx.in.size = 0; + op.openx.in.timeout = 0; + + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.openx.out.file.fnum; + + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = op.openx.out.file.fnum; + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = t; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * demonstrate that the first conflicting lock on each handle give LOCK_NOT_GRANTED + * this also demonstrates that the error code cache is per file handle + * (LOCK_NOT_GRANTED is only be used when timeout is 0!) + */ + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + + /* demonstrate that each following conflict gives FILE_LOCK_CONFLICT */ + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* demonstrate that the smbpid doesn't matter */ + lock[0].pid++; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + lock[0].pid--; + + /* + * demonstrate the a successful lock with count = 0 and the same offset, + * doesn't reset the error cache + */ + lock[0].offset = 100; + lock[0].count = 0; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* + * demonstrate the a successful lock with count = 0 and outside the locked range, + * doesn't reset the error cache + */ + lock[0].offset = 110; + lock[0].count = 0; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 99; + lock[0].count = 0; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* demonstrate that a changing count doesn't reset the error cache */ + lock[0].offset = 100; + lock[0].count = 5; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 100; + lock[0].count = 15; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* + * demonstrate the a lock with count = 0 and inside the locked range, + * fails and resets the error cache + */ + lock[0].offset = 101; + lock[0].count = 0; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* demonstrate the a changing offset, resets the error cache */ + lock[0].offset = 105; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 95; + lock[0].count = 9; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* + * demonstrate the a successful lock in a different range, + * doesn't reset the cache, the failing lock on the 2nd handle + * resets the resets the cache + */ + lock[0].offset = 120; + lock[0].count = 15; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.file.fnum = fnum; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, (t?NT_STATUS_FILE_LOCK_CONFLICT:NT_STATUS_LOCK_NOT_GRANTED)); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* end of the loop */ + if (t == 0) { + smb_raw_exit(cli->session); + printf("testing with timeout > 0 (=1)\n"); + fname = BASEDIR "\\test1.txt"; + t = 1; + goto next_run; + } + + /* + * the following 3 test sections demonstrate that + * the cache is only set when the error is reported + * to the client (after the timeout went by) + */ + smb_raw_exit(cli->session); + printf("testing a conflict while a lock is pending\n"); + fname = BASEDIR "\\test2.txt"; + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to reopen %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + start = time(NULL); + io.lockx.in.timeout = 1000; + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + io.lockx.in.timeout = 0; + lock[0].offset = 105; + lock[0].count = 10; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + if (time(NULL) < start+1) { + printf("lock comes back to early (%s)\n", __location__); + ret = false; + goto done; + } + + smbcli_close(cli->tree, fnum); + fname = BASEDIR "\\test3.txt"; + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to reopen %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + start = time(NULL); + io.lockx.in.timeout = 1000; + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + io.lockx.in.timeout = 0; + lock[0].offset = 105; + lock[0].count = 10; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + lock[0].offset = 100; + lock[0].count = 10; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + if (time(NULL) < start+1) { + printf("lock comes back to early (%s)\n", __location__); + ret = false; + goto done; + } + + smbcli_close(cli->tree, fnum); + fname = BASEDIR "\\test4.txt"; + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to reopen %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + start = time(NULL); + io.lockx.in.timeout = 1000; + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + printf("Failed to setup timed lock (%s)\n", __location__); + ret = false; + goto done; + } + + io.lockx.in.timeout = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + if (time(NULL) < start+1) { + printf("lock comes back to early (%s)\n", __location__); + ret = false; + goto done; + } + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test LOCKING_ANDX_CHANGE_LOCKTYPE +*/ +static bool test_changetype(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[2]; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t c = 0; + const char *fname = BASEDIR "\\test.txt"; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing LOCKING_ANDX_CHANGE_LOCKTYPE\n"); + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + if (smbcli_write(cli->tree, fnum, 0, &c, 100, 1) == 1) { + printf("allowed write on read locked region (%s)\n", __location__); + ret = false; + goto done; + } + + /* windows server don't seem to support this */ + io.lockx.in.mode = LOCKING_ANDX_CHANGE_LOCKTYPE; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks)); + + if (smbcli_write(cli->tree, fnum, 0, &c, 100, 1) == 1) { + printf("allowed write after lock change (%s)\n", __location__); + ret = false; + goto done; + } + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + basic testing of lock calls +*/ +struct torture_suite *torture_raw_lock(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "LOCK"); + + torture_suite_add_1smb_test(suite, "lockx", test_lockx); + torture_suite_add_1smb_test(suite, "lock", test_lock); + torture_suite_add_1smb_test(suite, "pidhigh", test_pidhigh); + torture_suite_add_1smb_test(suite, "async", test_async); + torture_suite_add_1smb_test(suite, "errorcode", test_errorcode); + torture_suite_add_1smb_test(suite, "changetype", test_changetype); + + return suite; +} diff --git a/source4/torture/raw/lockbench.c b/source4/torture/raw/lockbench.c new file mode 100644 index 0000000000..21541d003b --- /dev/null +++ b/source4/torture/raw/lockbench.c @@ -0,0 +1,414 @@ +/* + Unix SMB/CIFS implementation. + + locking benchmark + + Copyright (C) Andrew Tridgell 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "lib/events/events.h" +#include "lib/cmdline/popt_common.h" +#include "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/resolve/resolve.h" +#include "param/param.h" + +#define BASEDIR "\\benchlock" +#define FNAME BASEDIR "\\lock.dat" + +static int nprocs; +static int lock_failed; +static int num_connected; + +enum lock_stage {LOCK_INITIAL, LOCK_LOCK, LOCK_UNLOCK}; + +struct benchlock_state { + struct torture_context *tctx; + struct event_context *ev; + struct smbcli_tree *tree; + TALLOC_CTX *mem_ctx; + int client_num; + int fnum; + enum lock_stage stage; + int lock_offset; + int unlock_offset; + int count; + int lastcount; + struct smbcli_request *req; + struct smb_composite_connect reconnect; + struct timed_event *te; + + /* these are used for reconnections */ + const char **dest_ports; + const char *dest_host; + const char *called_name; + const char *service_type; +}; + +static void lock_completion(struct smbcli_request *); + +/* + send the next lock request +*/ +static void lock_send(struct benchlock_state *state) +{ + union smb_lock io; + struct smb_lock_entry lock; + + switch (state->stage) { + case LOCK_INITIAL: + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + state->lock_offset = 0; + state->unlock_offset = 0; + lock.offset = state->lock_offset; + break; + case LOCK_LOCK: + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + state->lock_offset = (state->lock_offset+1)%(nprocs+1); + lock.offset = state->lock_offset; + break; + case LOCK_UNLOCK: + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + lock.offset = state->unlock_offset; + state->unlock_offset = (state->unlock_offset+1)%(nprocs+1); + break; + } + + lock.count = 1; + lock.pid = state->tree->session->pid; + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 100000; + io.lockx.in.locks = &lock; + io.lockx.in.file.fnum = state->fnum; + + state->req = smb_raw_lock_send(state->tree, &io); + if (state->req == NULL) { + DEBUG(0,("Failed to setup lock\n")); + lock_failed++; + } + state->req->async.private = state; + state->req->async.fn = lock_completion; +} + +static void reopen_connection(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data); + + +static void reopen_file(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct benchlock_state *state = (struct benchlock_state *)private_data; + + /* reestablish our open file */ + state->fnum = smbcli_open(state->tree, FNAME, O_RDWR|O_CREAT, DENY_NONE); + if (state->fnum == -1) { + printf("Failed to open %s on connection %d\n", FNAME, state->client_num); + exit(1); + } + + num_connected++; + + DEBUG(0,("reconnect to %s finished (%u connected)\n", state->dest_host, + num_connected)); + + state->stage = LOCK_INITIAL; + lock_send(state); +} + +/* + complete an async reconnect + */ +static void reopen_connection_complete(struct composite_context *ctx) +{ + struct benchlock_state *state = (struct benchlock_state *)ctx->async.private_data; + NTSTATUS status; + struct smb_composite_connect *io = &state->reconnect; + + status = smb_composite_connect_recv(ctx, state->mem_ctx); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + return; + } + + talloc_free(state->tree); + state->tree = io->out.tree; + + /* do the reopen as a separate event */ + event_add_timed(state->ev, state->mem_ctx, timeval_zero(), reopen_file, state); +} + + + +/* + reopen a connection + */ +static void reopen_connection(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct benchlock_state *state = (struct benchlock_state *)private_data; + struct composite_context *ctx; + struct smb_composite_connect *io = &state->reconnect; + char *host, *share; + + state->te = NULL; + + if (!torture_get_conn_index(state->client_num, state->mem_ctx, state->tctx, &host, &share)) { + DEBUG(0,("Can't find host/share for reconnect?!\n")); + exit(1); + } + + io->in.dest_host = state->dest_host; + io->in.dest_ports = state->dest_ports; + io->in.called_name = state->called_name; + io->in.service = share; + io->in.service_type = state->service_type; + io->in.credentials = cmdline_credentials; + io->in.fallback_to_anonymous = false; + io->in.workgroup = lp_workgroup(state->tctx->lp_ctx); + lp_smbcli_options(state->tctx->lp_ctx, &io->in.options); + + /* kill off the remnants of the old connection */ + talloc_free(state->tree); + state->tree = NULL; + + ctx = smb_composite_connect_send(io, state->mem_ctx, + lp_resolve_context(state->tctx->lp_ctx), + state->ev); + if (ctx == NULL) { + DEBUG(0,("Failed to setup async reconnect\n")); + exit(1); + } + + ctx->async.fn = reopen_connection_complete; + ctx->async.private_data = state; +} + + +/* + called when a lock completes +*/ +static void lock_completion(struct smbcli_request *req) +{ + struct benchlock_state *state = (struct benchlock_state *)req->async.private; + NTSTATUS status = smbcli_request_simple_recv(req); + state->req = NULL; + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE) || + NT_STATUS_EQUAL(status, NT_STATUS_LOCAL_DISCONNECT)) { + talloc_free(state->tree); + state->tree = NULL; + num_connected--; + DEBUG(0,("reopening connection to %s\n", state->dest_host)); + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + } else { + DEBUG(0,("Lock failed - %s\n", nt_errstr(status))); + lock_failed++; + } + return; + } + + switch (state->stage) { + case LOCK_INITIAL: + state->stage = LOCK_LOCK; + break; + case LOCK_LOCK: + state->stage = LOCK_UNLOCK; + break; + case LOCK_UNLOCK: + state->stage = LOCK_LOCK; + break; + } + + state->count++; + lock_send(state); +} + + +static void echo_completion(struct smbcli_request *req) +{ + struct benchlock_state *state = (struct benchlock_state *)req->async.private; + NTSTATUS status = smbcli_request_simple_recv(req); + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE) || + NT_STATUS_EQUAL(status, NT_STATUS_LOCAL_DISCONNECT)) { + talloc_free(state->tree); + state->tree = NULL; + num_connected--; + DEBUG(0,("reopening connection to %s\n", state->dest_host)); + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + } +} + +static void report_rate(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct benchlock_state *state = talloc_get_type(private_data, + struct benchlock_state); + int i; + for (i=0;i<nprocs;i++) { + printf("%5u ", (unsigned)(state[i].count - state[i].lastcount)); + state[i].lastcount = state[i].count; + } + printf("\r"); + fflush(stdout); + event_add_timed(ev, state, timeval_current_ofs(1, 0), report_rate, state); + + /* send an echo on each interface to ensure it stays alive - this helps + with IP takeover */ + for (i=0;i<nprocs;i++) { + struct smb_echo p; + struct smbcli_request *req; + + if (!state[i].tree) { + continue; + } + + p.in.repeat_count = 1; + p.in.size = 0; + p.in.data = NULL; + req = smb_raw_echo_send(state[i].tree->session->transport, &p); + req->async.private = &state[i]; + req->async.fn = echo_completion; + } +} + +/* + benchmark locking calls +*/ +bool torture_bench_lock(struct torture_context *torture) +{ + bool ret = true; + TALLOC_CTX *mem_ctx = talloc_new(torture); + int i; + int timelimit = torture_setting_int(torture, "timelimit", 10); + struct timeval tv; + struct benchlock_state *state; + int total = 0, minops=0; + struct smbcli_state *cli; + bool progress; + + progress = torture_setting_bool(torture, "progress", true); + + nprocs = torture_setting_int(torture, "nprocs", 4); + + state = talloc_zero_array(mem_ctx, struct benchlock_state, nprocs); + + printf("Opening %d connections\n", nprocs); + for (i=0;i<nprocs;i++) { + state[i].tctx = torture; + state[i].mem_ctx = talloc_new(state); + state[i].client_num = i; + state[i].ev = torture->ev; + if (!torture_open_connection_ev(&cli, i, torture, torture->ev)) { + return false; + } + talloc_steal(mem_ctx, state); + state[i].tree = cli->tree; + state[i].dest_host = talloc_strdup(state[i].mem_ctx, + cli->tree->session->transport->socket->hostname); + state[i].dest_ports = talloc_array(state[i].mem_ctx, + const char *, 2); + state[i].dest_ports[0] = talloc_asprintf(state[i].dest_ports, + "%u", + cli->tree->session->transport->socket->port); + state[i].dest_ports[1] = NULL; + state[i].called_name = talloc_strdup(state[i].mem_ctx, + cli->tree->session->transport->called.name); + state[i].service_type = talloc_strdup(state[i].mem_ctx, + cli->tree->device); + } + + num_connected = i; + + if (!torture_setup_dir(cli, BASEDIR)) { + goto failed; + } + + for (i=0;i<nprocs;i++) { + state[i].fnum = smbcli_open(state[i].tree, + FNAME, + O_RDWR|O_CREAT, DENY_NONE); + if (state[i].fnum == -1) { + printf("Failed to open %s on connection %d\n", FNAME, i); + goto failed; + } + + state[i].stage = LOCK_INITIAL; + lock_send(&state[i]); + } + + tv = timeval_current(); + + if (progress) { + event_add_timed(torture->ev, state, timeval_current_ofs(1, 0), report_rate, state); + } + + printf("Running for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + event_loop_once(torture->ev); + + if (lock_failed) { + DEBUG(0,("locking failed\n")); + goto failed; + } + } + + printf("%.2f ops/second\n", total/timeval_elapsed(&tv)); + minops = state[0].count; + for (i=0;i<nprocs;i++) { + printf("[%d] %u ops\n", i, state[i].count); + if (state[i].count < minops) minops = state[i].count; + } + if (minops < 0.5*total/nprocs) { + printf("Failed: unbalanced locking\n"); + goto failed; + } + + for (i=0;i<nprocs;i++) { + talloc_free(state[i].req); + smb_raw_exit(state[i].tree->session); + } + + smbcli_deltree(state[0].tree, BASEDIR); + talloc_free(mem_ctx); + printf("\n"); + return ret; + +failed: + talloc_free(mem_ctx); + return false; +} diff --git a/source4/torture/raw/lookuprate.c b/source4/torture/raw/lookuprate.c new file mode 100644 index 0000000000..782cb1b31b --- /dev/null +++ b/source4/torture/raw/lookuprate.c @@ -0,0 +1,320 @@ +/* + File lookup rate test. + + Copyright (C) James Peach 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "param/param.h" +#include "system/filesys.h" +#include "torture/smbtorture.h" +#include "torture/basic/proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "lib/cmdline/popt_common.h" +#include "auth/credentials/credentials.h" + +#define BASEDIR "\\lookuprate" +#define MISSINGNAME BASEDIR "\\foo" + +#define FUZZ_PERCENT 10 + +#define usec_to_sec(s) ((s) / 1000000) +#define sec_to_usec(s) ((s) * 1000000) + +struct rate_record +{ + unsigned dirent_count; + unsigned querypath_persec; + unsigned findfirst_persec; +}; + +static struct rate_record records[] = +{ + { 0, 0, 0 }, /* Base (optimal) lookup rate. */ + { 100, 0, 0}, + { 1000, 0, 0}, + { 10000, 0, 0}, + { 100000, 0, 0} +}; + +typedef NTSTATUS lookup_function(struct smbcli_tree *tree, const char * path); + +/* Test whether rhs is within fuzz% of lhs. */ +static bool fuzzily_equal(unsigned lhs, unsigned rhs, int percent) +{ + double fuzz = (double)lhs * (double)percent/100.0; + + if (((double)rhs >= ((double)lhs - fuzz)) && + ((double)rhs <= ((double)lhs + fuzz))) { + return true; + } + + return false; + +} + +static NTSTATUS fill_directory(struct smbcli_tree *tree, + const char * path, unsigned count) +{ + NTSTATUS status; + char *fname = NULL; + unsigned i; + unsigned current; + + struct timeval start; + struct timeval now; + + status = smbcli_mkdir(tree, path); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + printf("filling directory %s with %u files... ", path, count); + fflush(stdout); + + current = random(); + start = timeval_current(); + + for (i = 0; i < count; ++i) { + int fnum; + + ++current; + fname = talloc_asprintf(NULL, "%s\\fill%u", + path, current); + + fnum = smbcli_open(tree, fname, O_RDONLY|O_CREAT, + OPENX_MODE_DENY_NONE); + if (fnum < 0) { + talloc_free(fname); + return smbcli_nt_error(tree); + } + + smbcli_close(tree, fnum); + talloc_free(fname); + } + + if (count) { + double rate; + now = timeval_current(); + rate = (double)count / usec_to_sec((double)usec_time_diff(&now, &start)); + printf("%u/sec\n", (unsigned)rate); + } else { + printf("done\n"); + } + + return NT_STATUS_OK; +} + +static NTSTATUS squash_lookup_error(NTSTATUS status) +{ + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + + /* We don't care if the file isn't there. */ + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) { + return NT_STATUS_OK; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_OK; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_FILE)) { + return NT_STATUS_OK; + } + + return status; +} + +/* Look up a pathname using TRANS2_QUERY_PATH_INFORMATION. */ +static NTSTATUS querypath_lookup(struct smbcli_tree *tree, const char * path) +{ + NTSTATUS status; + time_t ftimes[3]; + size_t fsize; + uint16_t fmode; + + status = smbcli_qpathinfo(tree, path, &ftimes[0], &ftimes[1], &ftimes[2], + &fsize, &fmode); + + return squash_lookup_error(status); +} + +/* Look up a pathname using TRANS2_FIND_FIRST2. */ +static NTSTATUS findfirst_lookup(struct smbcli_tree *tree, const char * path) +{ + NTSTATUS status = NT_STATUS_OK; + + if (smbcli_list(tree, path, 0, NULL, NULL) < 0) { + status = smbcli_nt_error(tree); + } + + return squash_lookup_error(status); +} + +static NTSTATUS lookup_rate_convert(struct smbcli_tree *tree, + lookup_function lookup, const char * path, unsigned * rate) +{ + NTSTATUS status; + + struct timeval start; + struct timeval now; + unsigned count = 0; + int64_t elapsed = 0; + +#define LOOKUP_PERIOD_SEC (2) + + start = timeval_current(); + while (elapsed < sec_to_usec(LOOKUP_PERIOD_SEC)) { + + status = lookup(tree, path); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ++count; + now = timeval_current(); + elapsed = usec_time_diff(&now, &start); + } + +#undef LOOKUP_PERIOD_SEC + + *rate = (unsigned)((double)count / (double)usec_to_sec(elapsed)); + return NT_STATUS_OK; +} + +static bool remove_working_directory(struct smbcli_tree *tree, + const char * path) +{ + int tries; + + /* Using smbcli_deltree to delete a very large number of files + * doesn't work against all servers. Work around this by + * retrying. + */ + for (tries = 0; tries < 5; ) { + int ret; + + ret = smbcli_deltree(tree, BASEDIR); + if (ret == -1) { + tries++; + printf("(%s) failed to deltree %s: %s\n", + __location__, BASEDIR, + smbcli_errstr(tree)); + continue; + } + + return true; + } + + return false; + +} + +/* Verify that looking up a file name takes constant time. + * + * This test samples the lookup rate for a non-existant filename in a + * directory, while varying the number of files in the directory. The + * lookup rate should continue to approximate the lookup rate for the + * empty directory case. + */ +bool torture_bench_lookup(struct torture_context *torture) +{ + NTSTATUS status; + bool result = false; + + int i, tries; + struct smbcli_state *cli = NULL; + + if (!torture_open_connection(&cli, torture, 0)) { + goto done; + } + + remove_working_directory(cli->tree, BASEDIR); + + for (i = 0; i < ARRAY_SIZE(records); ++i) { + printf("testing lookup rate with %u directory entries\n", + records[i].dirent_count); + + status = fill_directory(cli->tree, BASEDIR, + records[i].dirent_count); + if (!NT_STATUS_IS_OK(status)) { + printf("failed to fill directory: %s\n", nt_errstr(status)); + goto done; + } + + status = lookup_rate_convert(cli->tree, querypath_lookup, + MISSINGNAME, &records[i].querypath_persec); + if (!NT_STATUS_IS_OK(status)) { + printf("querypathinfo of %s failed: %s\n", + MISSINGNAME, nt_errstr(status)); + goto done; + } + + status = lookup_rate_convert(cli->tree, findfirst_lookup, + MISSINGNAME, &records[i].findfirst_persec); + if (!NT_STATUS_IS_OK(status)) { + printf("findfirst of %s failed: %s\n", + MISSINGNAME, nt_errstr(status)); + goto done; + } + + printf("entries = %u, querypath = %u/sec, findfirst = %u/sec\n", + records[i].dirent_count, + records[i].querypath_persec, + records[i].findfirst_persec); + + if (!remove_working_directory(cli->tree, BASEDIR)) { + goto done; + } + } + + /* Ok. We have run all our tests. Walk through the records we + * accumulated and figure out whether the lookups took constant + * time of not. + */ + for (i = 0; i < ARRAY_SIZE(records); ++i) { + if (!fuzzily_equal(records[0].querypath_persec, + records[i].querypath_persec, + FUZZ_PERCENT)) { + printf("querypath rate for %d entries differed by " + "more than %d%% from base rate\n", + records[i].dirent_count, FUZZ_PERCENT); + result = false; + } + + if (!fuzzily_equal(records[0].findfirst_persec, + records[i].findfirst_persec, + FUZZ_PERCENT)) { + printf("findfirst rate for %d entries differed by " + "more than %d%% from base rate\n", + records[i].dirent_count, FUZZ_PERCENT); + result = false; + } + } + +done: + if (cli) { + remove_working_directory(cli->tree, BASEDIR); + talloc_free(cli); + } + + return result; +} + +/* vim: set sts=8 sw=8 : */ diff --git a/source4/torture/raw/missing.txt b/source4/torture/raw/missing.txt new file mode 100644 index 0000000000..0f4104b596 --- /dev/null +++ b/source4/torture/raw/missing.txt @@ -0,0 +1,160 @@ +- RAW-CONTEXT passes on nt4 but TCON doesn't !!?? + +- all messaging commands + +- writebraw + +- writebmpx + +- acl ops + +- readbmpx + +- rap commands + +- rpc commands + +- SMBcopy + +- SMBtcon + +- SMBecho + +- SMBfunique + +- SMBsearch vs SMBffirst? + +- SMBfclose + +- SMBkeepalive + +- secondary trans2 and nttrans + +- trans2 ioctl + +- trans2 session setup + +- trans2 DFS ops + +- unix ops + +-------------- +done: + +mkdir +rmdir +open +create +close +flush +unlink +mv +getatr +setatr +read +write +lock +unlock +ctemp +mknew +chkpath +exit +lseek +tconX +tdis +negprot +dskattr +search +lockread +writeunlock +readbraw +setattrE +getattrE +lockingX +ioctl +openX +readX +writeX +sesssetupX +trans2 +findclose +ulogoffX +nttrans +ntcreateX +ntcancel +trans2_open +trans2_findfirst +trans2_findnext? +trans2_qfsinfo +trans2_setfsinfo +trans2_qpathinfo +trans2_setpathinfo +trans2_qfileinfo +trans2_setfileinfo +trans2_fsctl +trans2_mkdir +trans2_findnext +NTrename +SMB_QFS_ALLOCATION +SMB_QFS_VOLUME +SMB_QFS_VOLUME_INFO +SMB_QFS_SIZE_INFO +SMB_QFS_DEVICE_INFO +SMB_QFS_ATTRIBUTE_INFO +SMB_QFS_VOLUME_INFORMATION +SMB_QFS_SIZE_INFORMATION +SMB_QFS_DEVICE_INFORMATION +SMB_QFS_ATTRIBUTE_INFORMATION +SMB_QFS_QUOTA_INFORMATION +SMB_QFS_FULL_SIZE_INFORMATION +SMB_QFS_OBJECTID_INFORMATION +SMB_QFILEINFO_STANDARD +SMB_QFILEINFO_EA_SIZE +SMB_QFILEINFO_ALL_EAS +SMB_QFILEINFO_IS_NAME_VALID +SMB_QFILEINFO_BASIC_INFO +SMB_QFILEINFO_STANDARD_INFO +SMB_QFILEINFO_EA_INFO +SMB_QFILEINFO_NAME_INFO +SMB_QFILEINFO_ALL_INFO +SMB_QFILEINFO_ALT_NAME_INFO +SMB_QFILEINFO_STREAM_INFO +SMB_QFILEINFO_COMPRESSION_INFO +SMB_QFILEINFO_BASIC_INFORMATION +SMB_QFILEINFO_STANDARD_INFORMATION +SMB_QFILEINFO_INTERNAL_INFORMATION +SMB_QFILEINFO_EA_INFORMATION +SMB_QFILEINFO_ACCESS_INFORMATION +SMB_QFILEINFO_NAME_INFORMATION +SMB_QFILEINFO_POSITION_INFORMATION +SMB_QFILEINFO_MODE_INFORMATION +SMB_QFILEINFO_ALIGNMENT_INFORMATION +SMB_QFILEINFO_ALL_INFORMATION +SMB_QFILEINFO_ALT_NAME_INFORMATION +SMB_QFILEINFO_STREAM_INFORMATION +SMB_QFILEINFO_COMPRESSION_INFORMATION +SMB_QFILEINFO_NETWORK_OPEN_INFORMATION +SMB_QFILEINFO_ATTRIBUTE_TAG_INFORMATION +SMB_SFILEINFO_STANDARD +SMB_SFILEINFO_EA_SET +SMB_SFILEINFO_BASIC_INFO +SMB_SFILEINFO_DISPOSITION_INFO +SMB_SFILEINFO_ALLOCATION_INFO +SMB_SFILEINFO_END_OF_FILE_INFO +SMB_SFILEINFO_UNIX_BASIC +SMB_SFILEINFO_UNIX_LINK +SMB_SFILEINFO_BASIC_INFORMATION +SMB_SFILEINFO_RENAME_INFORMATION +SMB_SFILEINFO_DISPOSITION_INFORMATION +SMB_SFILEINFO_POSITION_INFORMATION +SMB_SFILEINFO_MODE_INFORMATION +SMB_SFILEINFO_ALLOCATION_INFORMATION +SMB_SFILEINFO_END_OF_FILE_INFORMATION +SMB_FIND_STANDARD +SMB_FIND_EA_SIZE +SMB_FIND_DIRECTORY_INFO +SMB_FIND_FULL_DIRECTORY_INFO +SMB_FIND_NAME_INFO +SMB_FIND_BOTH_DIRECTORY_INFO +SMB_FIND_261 +SMB_FIND_262 diff --git a/source4/torture/raw/mkdir.c b/source4/torture/raw/mkdir.c new file mode 100644 index 0000000000..cea959347d --- /dev/null +++ b/source4/torture/raw/mkdir.c @@ -0,0 +1,175 @@ +/* + Unix SMB/CIFS implementation. + RAW_MKDIR_* and RAW_RMDIR_* individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "param/param.h" + +#define BASEDIR "\\mkdirtest" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +/* + test mkdir ops +*/ +static bool test_mkdir(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_mkdir md; + struct smb_rmdir rd; + const char *path = BASEDIR "\\mkdir.dir"; + NTSTATUS status; + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + /* + basic mkdir + */ + md.mkdir.level = RAW_MKDIR_MKDIR; + md.mkdir.in.path = path; + + status = smb_raw_mkdir(cli->tree, &md); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("testing mkdir collision\n"); + + /* 2nd create */ + status = smb_raw_mkdir(cli->tree, &md); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + + /* basic rmdir */ + rd.in.path = path; + status = smb_raw_rmdir(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_rmdir(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + printf("testing mkdir collision with file\n"); + + /* name collision with a file */ + smbcli_close(cli->tree, create_complex_file(cli, tctx, path)); + status = smb_raw_mkdir(cli->tree, &md); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + + printf("testing rmdir with file\n"); + + /* delete a file with rmdir */ + status = smb_raw_rmdir(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY); + + smbcli_unlink(cli->tree, path); + + printf("testing invalid dir\n"); + + /* create an invalid dir */ + md.mkdir.in.path = "..\\..\\.."; + status = smb_raw_mkdir(cli->tree, &md); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + + printf("testing t2mkdir\n"); + + /* try a t2mkdir - need to work out why this fails! */ + md.t2mkdir.level = RAW_MKDIR_T2MKDIR; + md.t2mkdir.in.path = path; + md.t2mkdir.in.num_eas = 0; + status = smb_raw_mkdir(cli->tree, &md); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_rmdir(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("testing t2mkdir bad path\n"); + md.t2mkdir.in.path = talloc_asprintf(tctx, "%s\\bad_path\\bad_path", + BASEDIR); + status = smb_raw_mkdir(cli->tree, &md); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_NOT_FOUND); + + printf("testing t2mkdir with EAs\n"); + + /* with EAs */ + md.t2mkdir.level = RAW_MKDIR_T2MKDIR; + md.t2mkdir.in.path = path; + md.t2mkdir.in.num_eas = 3; + md.t2mkdir.in.eas = talloc_array(tctx, struct ea_struct, md.t2mkdir.in.num_eas); + md.t2mkdir.in.eas[0].flags = 0; + md.t2mkdir.in.eas[0].name.s = "EAONE"; + md.t2mkdir.in.eas[0].value = data_blob_talloc(tctx, "blah", 4); + md.t2mkdir.in.eas[1].flags = 0; + md.t2mkdir.in.eas[1].name.s = "EA TWO"; + md.t2mkdir.in.eas[1].value = data_blob_talloc(tctx, "foo bar", 7); + md.t2mkdir.in.eas[2].flags = 0; + md.t2mkdir.in.eas[2].name.s = "EATHREE"; + md.t2mkdir.in.eas[2].value = data_blob_talloc(tctx, "xx1", 3); + status = smb_raw_mkdir(cli->tree, &md); + + if (torture_setting_bool(tctx, "samba3", false) + && NT_STATUS_EQUAL(status, NT_STATUS_EAS_NOT_SUPPORTED)) { + d_printf("EAS not supported -- not treating as fatal\n"); + } + else { + /* + * In Samba3, don't see this error as fatal + */ + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_check_ea(cli, path, "EAONE", "blah"); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_check_ea(cli, path, "EA TWO", "foo bar"); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_check_ea(cli, path, "EATHREE", "xx1"); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_rmdir(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + } + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + basic testing of all RAW_MKDIR_* calls +*/ +bool torture_raw_mkdir(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + + if (!test_mkdir(cli, torture)) { + ret = false; + } + + return ret; +} diff --git a/source4/torture/raw/mux.c b/source4/torture/raw/mux.c new file mode 100644 index 0000000000..5b5db3a557 --- /dev/null +++ b/source4/torture/raw/mux.c @@ -0,0 +1,361 @@ +/* + Unix SMB/CIFS implementation. + basic raw test suite for multiplexing + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/filesys.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define BASEDIR "\\test_mux" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + +/* + test the delayed reply to a open that leads to a sharing violation +*/ +static bool test_mux_open(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + union smb_open io; + NTSTATUS status; + int fnum1, fnum2; + bool ret = true; + struct smbcli_request *req1, *req2; + struct timeval tv; + double d; + + printf("testing multiplexed open/open/close\n"); + + printf("send first open\n"); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR "\\open.dat"; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum1 = io.ntcreatex.out.file.fnum; + + printf("send 2nd open, non-conflicting\n"); + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + tv = timeval_current(); + + printf("send 3rd open, conflicting\n"); + io.ntcreatex.in.share_access = 0; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + d = timeval_elapsed(&tv); + if (d < 0.5 || d > 1.5) { + printf("bad timeout for conflict - %.2f should be 1.0\n", d); + } else { + printf("open delay %.2f\n", d); + } + + printf("send async open, conflicting\n"); + tv = timeval_current(); + req1 = smb_raw_open_send(cli->tree, &io); + + printf("send 2nd async open, conflicting\n"); + tv = timeval_current(); + req2 = smb_raw_open_send(cli->tree, &io); + + printf("close first sync open\n"); + smbcli_close(cli->tree, fnum1); + + printf("cancel 2nd async open (should be ignored)\n"); + smb_raw_ntcancel(req2); + + d = timeval_elapsed(&tv); + if (d > 0.25) { + printf("bad timeout after cancel - %.2f should be <0.25\n", d); + ret = false; + } + + printf("close the 2nd sync open\n"); + smbcli_close(cli->tree, fnum2); + + printf("see if the 1st async open now succeeded\n"); + status = smb_raw_open_recv(req1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + d = timeval_elapsed(&tv); + if (d > 0.25) { + printf("bad timeout for async conflict - %.2f should be <0.25\n", d); + ret = false; + } else { + printf("async open delay %.2f\n", d); + } + + printf("2nd async open should have timed out\n"); + status = smb_raw_open_recv(req2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + d = timeval_elapsed(&tv); + if (d < 0.8) { + printf("bad timeout for async conflict - %.2f should be 1.0\n", d); + } + + printf("close the 1st async open\n"); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + +done: + return ret; +} + + +/* + test a write that hits a byte range lock and send the close after the write +*/ +static bool test_mux_write(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + union smb_write io; + NTSTATUS status; + int fnum; + bool ret = true; + struct smbcli_request *req; + + printf("testing multiplexed lock/write/close\n"); + + fnum = smbcli_open(cli->tree, BASEDIR "\\write.dat", O_RDWR | O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("open failed in mux_write - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + cli->session->pid = 1; + + /* lock a range */ + if (NT_STATUS_IS_ERR(smbcli_lock(cli->tree, fnum, 0, 4, 0, WRITE_LOCK))) { + printf("lock failed in mux_write - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + cli->session->pid = 2; + + /* send an async write */ + io.generic.level = RAW_WRITE_WRITEX; + io.writex.in.file.fnum = fnum; + io.writex.in.offset = 0; + io.writex.in.wmode = 0; + io.writex.in.remaining = 0; + io.writex.in.count = 4; + io.writex.in.data = (const uint8_t *)&fnum; + req = smb_raw_write_send(cli->tree, &io); + + /* unlock the range */ + cli->session->pid = 1; + smbcli_unlock(cli->tree, fnum, 0, 4); + + /* and recv the async write reply */ + status = smb_raw_write_recv(req, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + smbcli_close(cli->tree, fnum); + +done: + return ret; +} + + +/* + test a lock that conflicts with an existing lock +*/ +static bool test_mux_lock(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + union smb_lock io; + NTSTATUS status; + int fnum; + bool ret = true; + struct smbcli_request *req; + struct smb_lock_entry lock[1]; + struct timeval t; + + printf("TESTING MULTIPLEXED LOCK/LOCK/UNLOCK\n"); + + fnum = smbcli_open(cli->tree, BASEDIR "\\write.dat", O_RDWR | O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("open failed in mux_write - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("establishing a lock\n"); + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = 0; + io.lockx.in.timeout = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.ulock_cnt = 0; + lock[0].pid = 1; + lock[0].offset = 0; + lock[0].count = 4; + io.lockx.in.locks = &lock[0]; + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("the second lock will conflict with the first\n"); + lock[0].pid = 2; + io.lockx.in.timeout = 1000; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + printf("this will too, but we'll unlock while waiting\n"); + t = timeval_current(); + req = smb_raw_lock_send(cli->tree, &io); + + printf("unlock the first range\n"); + lock[0].pid = 1; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.timeout = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("recv the async reply\n"); + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("async lock took %.2f msec\n", timeval_elapsed(&t) * 1000); + if (timeval_elapsed(&t) > 0.1) { + printf("failed to trigger early lock retry\n"); + return false; + } + + printf("reopening with an exit\n"); + smb_raw_exit(cli->session); + fnum = smbcli_open(cli->tree, BASEDIR "\\write.dat", O_RDWR | O_CREAT, DENY_NONE); + + printf("Now trying with a cancel\n"); + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = 0; + io.lockx.in.timeout = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.ulock_cnt = 0; + lock[0].pid = 1; + lock[0].offset = 0; + lock[0].count = 4; + io.lockx.in.locks = &lock[0]; + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + lock[0].pid = 2; + io.lockx.in.timeout = 1000; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + req = smb_raw_lock_send(cli->tree, &io); + + /* cancel the blocking lock */ + smb_raw_ntcancel(req); + + printf("sending 2nd cancel\n"); + /* the 2nd cancel is totally harmless, but tests the server trying to + cancel an already cancelled request */ + smb_raw_ntcancel(req); + + printf("sent 2nd cancel\n"); + + lock[0].pid = 1; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.timeout = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + printf("cancel a lock using exit to close file\n"); + lock[0].pid = 1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.timeout = 1000; + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + t = timeval_current(); + lock[0].pid = 2; + req = smb_raw_lock_send(cli->tree, &io); + + smb_raw_exit(cli->session); + smb_raw_exit(cli->session); + smb_raw_exit(cli->session); + smb_raw_exit(cli->session); + + printf("recv the async reply\n"); + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + printf("async lock exit took %.2f msec\n", timeval_elapsed(&t) * 1000); + if (timeval_elapsed(&t) > 0.1) { + printf("failed to trigger early lock failure\n"); + return false; + } + +done: + return ret; +} + + + +/* + basic testing of multiplexing notify +*/ +bool torture_raw_mux(struct torture_context *torture, struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_mux_open(cli, torture); + ret &= test_mux_write(cli, torture); + ret &= test_mux_lock(cli, torture); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} diff --git a/source4/torture/raw/notify.c b/source4/torture/raw/notify.c new file mode 100644 index 0000000000..25c91eb88b --- /dev/null +++ b/source4/torture/raw/notify.c @@ -0,0 +1,1312 @@ +/* + Unix SMB/CIFS implementation. + basic raw test suite for change notify + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "system/filesys.h" +#include "torture/util.h" +#include "param/param.h" + +#define BASEDIR "\\test_notify" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%d) Incorrect status %s - should be %s\n", \ + __LINE__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%d) wrong value for %s 0x%x should be 0x%x\n", \ + __LINE__, #v, (int)v, (int)correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_WSTR(field, value, flags) do { \ + if (!field.s || strcmp(field.s, value) || wire_bad_flags(&field, flags, cli->transport)) { \ + printf("(%d) %s [%s] != %s\n", __LINE__, #field, field.s, value); \ + ret = false; \ + goto done; \ + }} while (0) + + +/* + basic testing of change notify on directories +*/ +static bool test_notify_dir(struct smbcli_state *cli, struct smbcli_state *cli2, + TALLOC_CTX *mem_ctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + union smb_close cl; + int i, count, fnum, fnum2; + struct smbcli_request *req, *req2; + extern int torture_numops; + + printf("TESTING CHANGE NOTIFY ON DIRECTRIES\n"); + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.recursive = true; + + printf("testing notify cancel\n"); + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + smb_raw_ntcancel(req); + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + printf("testing notify mkdir\n"); + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + smbcli_mkdir(cli2->tree, BASEDIR "\\subdir-name"); + + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name", STR_UNICODE); + + printf("testing notify rmdir\n"); + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + smbcli_rmdir(cli2->tree, BASEDIR "\\subdir-name"); + + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name", STR_UNICODE); + + printf("testing notify mkdir - rmdir - mkdir - rmdir\n"); + + smbcli_mkdir(cli2->tree, BASEDIR "\\subdir-name"); + smbcli_rmdir(cli2->tree, BASEDIR "\\subdir-name"); + smbcli_mkdir(cli2->tree, BASEDIR "\\subdir-name"); + smbcli_rmdir(cli2->tree, BASEDIR "\\subdir-name"); + msleep(200); + req = smb_raw_changenotify_send(cli->tree, ¬ify); + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 4); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[1].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[1].name, "subdir-name", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[2].action, NOTIFY_ACTION_ADDED); + CHECK_WSTR(notify.nttrans.out.changes[2].name, "subdir-name", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[3].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[3].name, "subdir-name", STR_UNICODE); + + count = torture_numops; + printf("testing buffered notify on create of %d files\n", count); + for (i=0;i<count;i++) { + char *fname = talloc_asprintf(cli, BASEDIR "\\test%d.txt", i); + int fnum3 = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, DENY_NONE); + if (fnum3 == -1) { + printf("Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + talloc_free(fname); + smbcli_close(cli->tree, fnum3); + } + + /* (1st notify) setup a new notify on a different directory handle. + This new notify won't see the events above. */ + notify.nttrans.in.file.fnum = fnum2; + req2 = smb_raw_changenotify_send(cli->tree, ¬ify); + + /* (2nd notify) whereas this notify will see the above buffered events, + and it directly returns the buffered events */ + notify.nttrans.in.file.fnum = fnum; + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + status = smbcli_unlink(cli->tree, BASEDIR "\\nonexistant.txt"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* (1st unlink) as the 2nd notify directly returns, + this unlink is only seen by the 1st notify and + the 3rd notify (later) */ + printf("testing notify on unlink for the first file\n"); + status = smbcli_unlink(cli2->tree, BASEDIR "\\test0.txt"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* receive the reply from the 2nd notify */ + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, count); + for (i=1;i<count;i++) { + CHECK_VAL(notify.nttrans.out.changes[i].action, NOTIFY_ACTION_ADDED); + } + CHECK_WSTR(notify.nttrans.out.changes[0].name, "test0.txt", STR_UNICODE); + + printf("and now from the 1st notify\n"); + status = smb_raw_changenotify_recv(req2, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "test0.txt", STR_UNICODE); + + printf("(3rd notify) this notify will only see the 1st unlink\n"); + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + status = smbcli_unlink(cli->tree, BASEDIR "\\nonexistant.txt"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + printf("testing notify on wildcard unlink for %d files\n", count-1); + /* (2nd unlink) do a wildcard unlink */ + status = smbcli_unlink(cli2->tree, BASEDIR "\\test*.txt"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* receive the 3rd notify */ + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "test0.txt", STR_UNICODE); + + /* and we now see the rest of the unlink calls on both directory handles */ + notify.nttrans.in.file.fnum = fnum; + sleep(3); + req = smb_raw_changenotify_send(cli->tree, ¬ify); + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, count-1); + for (i=0;i<notify.nttrans.out.num_changes;i++) { + CHECK_VAL(notify.nttrans.out.changes[i].action, NOTIFY_ACTION_REMOVED); + } + notify.nttrans.in.file.fnum = fnum2; + req = smb_raw_changenotify_send(cli->tree, ¬ify); + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, count-1); + for (i=0;i<notify.nttrans.out.num_changes;i++) { + CHECK_VAL(notify.nttrans.out.changes[i].action, NOTIFY_ACTION_REMOVED); + } + + printf("testing if a close() on the dir handle triggers the notify reply\n"); + + notify.nttrans.in.file.fnum = fnum; + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 0); + +done: + smb_raw_exit(cli->session); + return ret; +} + +/* + * Check notify reply for a rename action. Not sure if this is a valid thing + * to do, but depending on timing between inotify and messaging we get the + * add/remove/modify in any order. This routines tries to find the action/name + * pair in any of the three following notify_changes. + */ + +static bool check_rename_reply(struct smbcli_state *cli, + int line, + struct notify_changes *actions, + uint32_t action, const char *name) +{ + int i; + + for (i=0; i<3; i++) { + if (actions[i].action == action) { + if ((actions[i].name.s == NULL) + || (strcmp(actions[i].name.s, name) != 0) + || (wire_bad_flags(&actions[i].name, STR_UNICODE, + cli->transport))) { + printf("(%d) name [%s] != %s\n", line, + actions[i].name.s, name); + return false; + } + return true; + } + } + + printf("(%d) expected action %d, not found\n", line, action); + return false; +} + +/* + testing of recursive change notify +*/ +static bool test_notify_recursive(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req1, *req2; + + printf("TESTING CHANGE NOTIFY WITH RECURSION\n"); + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, on file or directory name + changes. Setup both with and without recursion */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_CREATION; + notify.nttrans.in.file.fnum = fnum; + + notify.nttrans.in.recursive = true; + req1 = smb_raw_changenotify_send(cli->tree, ¬ify); + + notify.nttrans.in.recursive = false; + req2 = smb_raw_changenotify_send(cli->tree, ¬ify); + + /* cancel initial requests so the buffer is setup */ + smb_raw_ntcancel(req1); + status = smb_raw_changenotify_recv(req1, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + smb_raw_ntcancel(req2); + status = smb_raw_changenotify_recv(req2, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + smbcli_mkdir(cli->tree, BASEDIR "\\subdir-name"); + smbcli_mkdir(cli->tree, BASEDIR "\\subdir-name\\subname1"); + smbcli_close(cli->tree, + smbcli_open(cli->tree, BASEDIR "\\subdir-name\\subname2", O_CREAT, 0)); + smbcli_rename(cli->tree, BASEDIR "\\subdir-name\\subname1", BASEDIR "\\subdir-name\\subname1-r"); + smbcli_rename(cli->tree, BASEDIR "\\subdir-name\\subname2", BASEDIR "\\subname2-r"); + smbcli_rename(cli->tree, BASEDIR "\\subname2-r", BASEDIR "\\subname3-r"); + + notify.nttrans.in.completion_filter = 0; + notify.nttrans.in.recursive = true; + msleep(200); + req1 = smb_raw_changenotify_send(cli->tree, ¬ify); + + smbcli_rmdir(cli->tree, BASEDIR "\\subdir-name\\subname1-r"); + smbcli_rmdir(cli->tree, BASEDIR "\\subdir-name"); + smbcli_unlink(cli->tree, BASEDIR "\\subname3-r"); + + notify.nttrans.in.recursive = false; + req2 = smb_raw_changenotify_send(cli->tree, ¬ify); + + status = smb_raw_changenotify_recv(req1, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, 11); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[1].action, NOTIFY_ACTION_ADDED); + CHECK_WSTR(notify.nttrans.out.changes[1].name, "subdir-name\\subname1", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[2].action, NOTIFY_ACTION_ADDED); + CHECK_WSTR(notify.nttrans.out.changes[2].name, "subdir-name\\subname2", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[3].action, NOTIFY_ACTION_OLD_NAME); + CHECK_WSTR(notify.nttrans.out.changes[3].name, "subdir-name\\subname1", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[4].action, NOTIFY_ACTION_NEW_NAME); + CHECK_WSTR(notify.nttrans.out.changes[4].name, "subdir-name\\subname1-r", STR_UNICODE); + + ret &= check_rename_reply( + cli, __LINE__, ¬ify.nttrans.out.changes[5], + NOTIFY_ACTION_ADDED, "subname2-r"); + ret &= check_rename_reply( + cli, __LINE__, ¬ify.nttrans.out.changes[5], + NOTIFY_ACTION_REMOVED, "subdir-name\\subname2"); + ret &= check_rename_reply( + cli, __LINE__, ¬ify.nttrans.out.changes[5], + NOTIFY_ACTION_MODIFIED, "subname2-r"); + + ret &= check_rename_reply( + cli, __LINE__, ¬ify.nttrans.out.changes[8], + NOTIFY_ACTION_OLD_NAME, "subname2-r"); + ret &= check_rename_reply( + cli, __LINE__, ¬ify.nttrans.out.changes[8], + NOTIFY_ACTION_NEW_NAME, "subname3-r"); + ret &= check_rename_reply( + cli, __LINE__, ¬ify.nttrans.out.changes[8], + NOTIFY_ACTION_MODIFIED, "subname3-r"); + + if (!ret) { + goto done; + } + + status = smb_raw_changenotify_recv(req2, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, 3); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name\\subname1-r", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[1].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[1].name, "subdir-name", STR_UNICODE); + CHECK_VAL(notify.nttrans.out.changes[2].action, NOTIFY_ACTION_REMOVED); + CHECK_WSTR(notify.nttrans.out.changes[2].name, "subname3-r", STR_UNICODE); + +done: + smb_raw_exit(cli->session); + return ret; +} + +/* + testing of change notify mask change +*/ +static bool test_notify_mask_change(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req1, *req2; + + printf("TESTING CHANGE NOTIFY WITH MASK CHANGE\n"); + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, on file or directory name + changes. Setup both with and without recursion */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_ATTRIBUTES; + notify.nttrans.in.file.fnum = fnum; + + notify.nttrans.in.recursive = true; + req1 = smb_raw_changenotify_send(cli->tree, ¬ify); + + notify.nttrans.in.recursive = false; + req2 = smb_raw_changenotify_send(cli->tree, ¬ify); + + /* cancel initial requests so the buffer is setup */ + smb_raw_ntcancel(req1); + status = smb_raw_changenotify_recv(req1, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + smb_raw_ntcancel(req2); + status = smb_raw_changenotify_recv(req2, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + notify.nttrans.in.recursive = true; + req1 = smb_raw_changenotify_send(cli->tree, ¬ify); + + /* Set to hidden then back again. */ + smbcli_close(cli->tree, smbcli_open(cli->tree, BASEDIR "\\tname1", O_CREAT, 0)); + smbcli_setatr(cli->tree, BASEDIR "\\tname1", FILE_ATTRIBUTE_HIDDEN, 0); + smbcli_unlink(cli->tree, BASEDIR "\\tname1"); + + status = smb_raw_changenotify_recv(req1, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "tname1", STR_UNICODE); + + /* Now try and change the mask to include other events. + * This should not work - once the mask is set on a directory + * fnum it seems to be fixed until the fnum is closed. */ + + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_CREATION; + notify.nttrans.in.recursive = true; + req1 = smb_raw_changenotify_send(cli->tree, ¬ify); + + notify.nttrans.in.recursive = false; + req2 = smb_raw_changenotify_send(cli->tree, ¬ify); + + smbcli_mkdir(cli->tree, BASEDIR "\\subdir-name"); + smbcli_mkdir(cli->tree, BASEDIR "\\subdir-name\\subname1"); + smbcli_close(cli->tree, + smbcli_open(cli->tree, BASEDIR "\\subdir-name\\subname2", O_CREAT, 0)); + smbcli_rename(cli->tree, BASEDIR "\\subdir-name\\subname1", BASEDIR "\\subdir-name\\subname1-r"); + smbcli_rename(cli->tree, BASEDIR "\\subdir-name\\subname2", BASEDIR "\\subname2-r"); + smbcli_rename(cli->tree, BASEDIR "\\subname2-r", BASEDIR "\\subname3-r"); + + smbcli_rmdir(cli->tree, BASEDIR "\\subdir-name\\subname1-r"); + smbcli_rmdir(cli->tree, BASEDIR "\\subdir-name"); + smbcli_unlink(cli->tree, BASEDIR "\\subname3-r"); + + status = smb_raw_changenotify_recv(req1, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subname2-r", STR_UNICODE); + + status = smb_raw_changenotify_recv(req2, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_VAL(notify.nttrans.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subname3-r", STR_UNICODE); + + if (!ret) { + goto done; + } + +done: + smb_raw_exit(cli->session); + return ret; +} + + +/* + testing of mask bits for change notify +*/ +static bool test_notify_mask(struct smbcli_state *cli, struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum, fnum2; + uint32_t mask; + int i; + char c = 1; + struct timeval tv; + NTTIME t; + + printf("TESTING CHANGE NOTIFY COMPLETION FILTERS\n"); + + tv = timeval_current_ofs(1000, 0); + t = timeval_to_nttime(&tv); + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.recursive = true; + +#define NOTIFY_MASK_TEST(setup, op, cleanup, Action, expected, nchanges) \ + do { for (mask=i=0;i<32;i++) { \ + struct smbcli_request *req; \ + status = smb_raw_open(cli->tree, tctx, &io); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + fnum = io.ntcreatex.out.file.fnum; \ + setup \ + notify.nttrans.in.file.fnum = fnum; \ + notify.nttrans.in.completion_filter = (1<<i); \ + req = smb_raw_changenotify_send(cli->tree, ¬ify); \ + op \ + msleep(200); smb_raw_ntcancel(req); \ + status = smb_raw_changenotify_recv(req, tctx, ¬ify); \ + cleanup \ + smbcli_close(cli->tree, fnum); \ + if (NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) continue; \ + CHECK_STATUS(status, NT_STATUS_OK); \ + /* special case to cope with file rename behaviour */ \ + if (nchanges == 2 && notify.nttrans.out.num_changes == 1 && \ + notify.nttrans.out.changes[0].action == NOTIFY_ACTION_MODIFIED && \ + ((expected) & FILE_NOTIFY_CHANGE_ATTRIBUTES) && \ + Action == NOTIFY_ACTION_OLD_NAME) { \ + printf("(rename file special handling OK)\n"); \ + } else if (nchanges != notify.nttrans.out.num_changes) { \ + printf("ERROR: nchanges=%d expected=%d action=%d filter=0x%08x\n", \ + notify.nttrans.out.num_changes, \ + nchanges, \ + notify.nttrans.out.changes[0].action, \ + notify.nttrans.in.completion_filter); \ + ret = false; \ + } else if (notify.nttrans.out.changes[0].action != Action) { \ + printf("ERROR: nchanges=%d action=%d expectedAction=%d filter=0x%08x\n", \ + notify.nttrans.out.num_changes, \ + notify.nttrans.out.changes[0].action, \ + Action, \ + notify.nttrans.in.completion_filter); \ + ret = false; \ + } else if (strcmp(notify.nttrans.out.changes[0].name.s, "tname1") != 0) { \ + printf("ERROR: nchanges=%d action=%d filter=0x%08x name=%s\n", \ + notify.nttrans.out.num_changes, \ + notify.nttrans.out.changes[0].action, \ + notify.nttrans.in.completion_filter, \ + notify.nttrans.out.changes[0].name.s); \ + ret = false; \ + } \ + mask |= (1<<i); \ + } \ + if ((expected) != mask) { \ + if (((expected) & ~mask) != 0) { \ + printf("ERROR: trigger on too few bits. mask=0x%08x expected=0x%08x\n", \ + mask, expected); \ + ret = false; \ + } else { \ + printf("WARNING: trigger on too many bits. mask=0x%08x expected=0x%08x\n", \ + mask, expected); \ + } \ + } \ + } while (0) + + printf("testing mkdir\n"); + NOTIFY_MASK_TEST(;, + smbcli_mkdir(cli->tree, BASEDIR "\\tname1");, + smbcli_rmdir(cli->tree, BASEDIR "\\tname1");, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_DIR_NAME, 1); + + printf("testing create file\n"); + NOTIFY_MASK_TEST(;, + smbcli_close(cli->tree, smbcli_open(cli->tree, BASEDIR "\\tname1", O_CREAT, 0));, + smbcli_unlink(cli->tree, BASEDIR "\\tname1");, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_FILE_NAME, 1); + + printf("testing unlink\n"); + NOTIFY_MASK_TEST( + smbcli_close(cli->tree, smbcli_open(cli->tree, BASEDIR "\\tname1", O_CREAT, 0));, + smbcli_unlink(cli->tree, BASEDIR "\\tname1");, + ;, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_FILE_NAME, 1); + + printf("testing rmdir\n"); + NOTIFY_MASK_TEST( + smbcli_mkdir(cli->tree, BASEDIR "\\tname1");, + smbcli_rmdir(cli->tree, BASEDIR "\\tname1");, + ;, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_DIR_NAME, 1); + + printf("testing rename file\n"); + NOTIFY_MASK_TEST( + smbcli_close(cli->tree, smbcli_open(cli->tree, BASEDIR "\\tname1", O_CREAT, 0));, + smbcli_rename(cli->tree, BASEDIR "\\tname1", BASEDIR "\\tname2");, + smbcli_unlink(cli->tree, BASEDIR "\\tname2");, + NOTIFY_ACTION_OLD_NAME, + FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_CREATION, 2); + + printf("testing rename dir\n"); + NOTIFY_MASK_TEST( + smbcli_mkdir(cli->tree, BASEDIR "\\tname1");, + smbcli_rename(cli->tree, BASEDIR "\\tname1", BASEDIR "\\tname2");, + smbcli_rmdir(cli->tree, BASEDIR "\\tname2");, + NOTIFY_ACTION_OLD_NAME, + FILE_NOTIFY_CHANGE_DIR_NAME, 2); + + printf("testing set path attribute\n"); + NOTIFY_MASK_TEST( + smbcli_close(cli->tree, smbcli_open(cli->tree, BASEDIR "\\tname1", O_CREAT, 0));, + smbcli_setatr(cli->tree, BASEDIR "\\tname1", FILE_ATTRIBUTE_HIDDEN, 0);, + smbcli_unlink(cli->tree, BASEDIR "\\tname1");, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, 1); + + printf("testing set path write time\n"); + NOTIFY_MASK_TEST( + smbcli_close(cli->tree, smbcli_open(cli->tree, BASEDIR "\\tname1", O_CREAT, 0));, + smbcli_setatr(cli->tree, BASEDIR "\\tname1", FILE_ATTRIBUTE_NORMAL, 1000);, + smbcli_unlink(cli->tree, BASEDIR "\\tname1");, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_LAST_WRITE, 1); + + printf("testing set file attribute\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, BASEDIR "\\tname1");, + smbcli_fsetatr(cli->tree, fnum2, FILE_ATTRIBUTE_HIDDEN, 0, 0, 0, 0);, + (smbcli_close(cli->tree, fnum2), smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, 1); + + if (torture_setting_bool(tctx, "samba3", false)) { + printf("Samba3 does not yet support create times " + "everywhere\n"); + } + else { + printf("testing set file create time\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, + BASEDIR "\\tname1");, + smbcli_fsetatr(cli->tree, fnum2, 0, t, 0, 0, 0);, + (smbcli_close(cli->tree, fnum2), + smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_CREATION, 1); + } + + printf("testing set file access time\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, BASEDIR "\\tname1");, + smbcli_fsetatr(cli->tree, fnum2, 0, 0, t, 0, 0);, + (smbcli_close(cli->tree, fnum2), smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_LAST_ACCESS, 1); + + printf("testing set file write time\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, BASEDIR "\\tname1");, + smbcli_fsetatr(cli->tree, fnum2, 0, 0, 0, t, 0);, + (smbcli_close(cli->tree, fnum2), smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_LAST_WRITE, 1); + + printf("testing set file change time\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, BASEDIR "\\tname1");, + smbcli_fsetatr(cli->tree, fnum2, 0, 0, 0, 0, t);, + (smbcli_close(cli->tree, fnum2), smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + 0, 1); + + + printf("testing write\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, BASEDIR "\\tname1");, + smbcli_write(cli->tree, fnum2, 1, &c, 10000, 1);, + (smbcli_close(cli->tree, fnum2), smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + 0, 1); + + printf("testing truncate\n"); + NOTIFY_MASK_TEST( + fnum2 = create_complex_file(cli, tctx, BASEDIR "\\tname1");, + smbcli_ftruncate(cli->tree, fnum2, 10000);, + (smbcli_close(cli->tree, fnum2), smbcli_unlink(cli->tree, BASEDIR "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_ATTRIBUTES, 1); + +done: + smb_raw_exit(cli->session); + return ret; +} + +/* + basic testing of change notify on files +*/ +static bool test_notify_file(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_close cl; + union smb_notify notify; + struct smbcli_request *req; + int fnum; + const char *fname = BASEDIR "\\file.txt"; + + printf("TESTING CHANGE NOTIFY ON FILES\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_STREAM_NAME; + notify.nttrans.in.recursive = false; + + printf("testing if notifies on file handles are invalid (should be)\n"); + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + cl.close.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + cl.close.in.write_time = 0; + status = smb_raw_close(cli->tree, &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_unlink(cli->tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb_raw_exit(cli->session); + return ret; +} + +/* + basic testing of change notifies followed by a tdis +*/ +static bool test_notify_tdis(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req; + struct smbcli_state *cli = NULL; + + printf("TESTING CHANGE NOTIFY FOLLOWED BY TDIS\n"); + + if (!torture_open_connection(&cli, tctx, 0)) { + return false; + } + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.recursive = true; + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + status = smbcli_tdis(cli); + CHECK_STATUS(status, NT_STATUS_OK); + cli->tree = NULL; + + status = smb_raw_changenotify_recv(req, tctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 0); + +done: + torture_close_connection(cli); + return ret; +} + +/* + basic testing of change notifies followed by a exit +*/ +static bool test_notify_exit(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req; + struct smbcli_state *cli = NULL; + + printf("TESTING CHANGE NOTIFY FOLLOWED BY EXIT\n"); + + if (!torture_open_connection(&cli, tctx, 0)) { + return false; + } + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.recursive = true; + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + status = smb_raw_exit(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_changenotify_recv(req, tctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 0); + +done: + torture_close_connection(cli); + return ret; +} + +/* + basic testing of change notifies followed by a ulogoff +*/ +static bool test_notify_ulogoff(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req; + struct smbcli_state *cli = NULL; + + printf("TESTING CHANGE NOTIFY FOLLOWED BY ULOGOFF\n"); + + if (!torture_open_connection(&cli, tctx, 0)) { + return false; + } + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.recursive = true; + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + status = smb_raw_ulogoff(cli->session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_changenotify_recv(req, tctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 0); + +done: + torture_close_connection(cli); + return ret; +} + +static void tcp_dis_handler(struct smbcli_transport *t, void *p) +{ + struct smbcli_state *cli = (struct smbcli_state *)p; + smbcli_transport_dead(cli->transport, NT_STATUS_LOCAL_DISCONNECT); + cli->transport = NULL; + cli->tree = NULL; +} +/* + basic testing of change notifies followed by tcp disconnect +*/ +static bool test_notify_tcp_dis(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req; + struct smbcli_state *cli = NULL; + + printf("TESTING CHANGE NOTIFY FOLLOWED BY TCP DISCONNECT\n"); + + if (!torture_open_connection(&cli, tctx, 0)) { + return false; + } + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.recursive = true; + + req = smb_raw_changenotify_send(cli->tree, ¬ify); + + smbcli_transport_idle_handler(cli->transport, tcp_dis_handler, 250, cli); + + status = smb_raw_changenotify_recv(req, tctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_LOCAL_DISCONNECT); + +done: + torture_close_connection(cli); + return ret; +} + +/* + test setting up two change notify requests on one handle +*/ +static bool test_notify_double(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + int fnum; + struct smbcli_request *req1, *req2; + + printf("TESTING CHANGE NOTIFY TWICE ON ONE DIRECTORY\n"); + + /* + get a handle on the directory + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + /* ask for a change notify, + on file or directory name changes */ + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 1000; + notify.nttrans.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.nttrans.in.file.fnum = fnum; + notify.nttrans.in.recursive = true; + + req1 = smb_raw_changenotify_send(cli->tree, ¬ify); + req2 = smb_raw_changenotify_send(cli->tree, ¬ify); + + smbcli_mkdir(cli->tree, BASEDIR "\\subdir-name"); + + status = smb_raw_changenotify_recv(req1, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name", STR_UNICODE); + + smbcli_mkdir(cli->tree, BASEDIR "\\subdir-name2"); + + status = smb_raw_changenotify_recv(req2, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.nttrans.out.num_changes, 1); + CHECK_WSTR(notify.nttrans.out.changes[0].name, "subdir-name2", STR_UNICODE); + +done: + smb_raw_exit(cli->session); + return ret; +} + + +/* + test multiple change notifies at different depths and with/without recursion +*/ +static bool test_notify_tree(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + bool ret = true; + union smb_notify notify; + union smb_open io; + struct smbcli_request *req; + struct timeval tv; + struct { + const char *path; + bool recursive; + uint32_t filter; + int expected; + int fnum; + int counted; + } dirs[] = { + {BASEDIR "\\abc", true, FILE_NOTIFY_CHANGE_NAME, 30 }, + {BASEDIR "\\zqy", true, FILE_NOTIFY_CHANGE_NAME, 8 }, + {BASEDIR "\\atsy", true, FILE_NOTIFY_CHANGE_NAME, 4 }, + {BASEDIR "\\abc\\foo", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\abc\\blah", true, FILE_NOTIFY_CHANGE_NAME, 13 }, + {BASEDIR "\\abc\\blah", false, FILE_NOTIFY_CHANGE_NAME, 7 }, + {BASEDIR "\\abc\\blah\\a", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\abc\\blah\\b", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\abc\\blah\\c", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\abc\\fooblah", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\zqy\\xx", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\zqy\\yyy", true, FILE_NOTIFY_CHANGE_NAME, 2 }, + {BASEDIR "\\zqy\\..", true, FILE_NOTIFY_CHANGE_NAME, 40 }, + {BASEDIR, true, FILE_NOTIFY_CHANGE_NAME, 40 }, + {BASEDIR, false,FILE_NOTIFY_CHANGE_NAME, 6 }, + {BASEDIR "\\atsy", false,FILE_NOTIFY_CHANGE_NAME, 4 }, + {BASEDIR "\\abc", true, FILE_NOTIFY_CHANGE_NAME, 24 }, + {BASEDIR "\\abc", false,FILE_NOTIFY_CHANGE_FILE_NAME, 0 }, + {BASEDIR "\\abc", true, FILE_NOTIFY_CHANGE_FILE_NAME, 0 }, + {BASEDIR "\\abc", true, FILE_NOTIFY_CHANGE_NAME, 24 }, + }; + int i; + NTSTATUS status; + bool all_done = false; + + printf("TESTING CHANGE NOTIFY FOR DIFFERENT DEPTHS\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_ALL; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + + notify.nttrans.level = RAW_NOTIFY_NTTRANS; + notify.nttrans.in.buffer_size = 20000; + + /* + setup the directory tree, and the notify buffer on each directory + */ + for (i=0;i<ARRAY_SIZE(dirs);i++) { + io.ntcreatex.in.fname = dirs[i].path; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + dirs[i].fnum = io.ntcreatex.out.file.fnum; + + notify.nttrans.in.completion_filter = dirs[i].filter; + notify.nttrans.in.file.fnum = dirs[i].fnum; + notify.nttrans.in.recursive = dirs[i].recursive; + req = smb_raw_changenotify_send(cli->tree, ¬ify); + smb_raw_ntcancel(req); + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + } + + /* trigger 2 events in each dir */ + for (i=0;i<ARRAY_SIZE(dirs);i++) { + char *path = talloc_asprintf(mem_ctx, "%s\\test.dir", dirs[i].path); + smbcli_mkdir(cli->tree, path); + smbcli_rmdir(cli->tree, path); + talloc_free(path); + } + + /* give a bit of time for the events to propogate */ + tv = timeval_current(); + + do { + /* count events that have happened in each dir */ + for (i=0;i<ARRAY_SIZE(dirs);i++) { + notify.nttrans.in.file.fnum = dirs[i].fnum; + req = smb_raw_changenotify_send(cli->tree, ¬ify); + smb_raw_ntcancel(req); + notify.nttrans.out.num_changes = 0; + status = smb_raw_changenotify_recv(req, mem_ctx, ¬ify); + dirs[i].counted += notify.nttrans.out.num_changes; + } + + all_done = true; + + for (i=0;i<ARRAY_SIZE(dirs);i++) { + if (dirs[i].counted != dirs[i].expected) { + all_done = false; + } + } + } while (!all_done && timeval_elapsed(&tv) < 20); + + printf("took %.4f seconds to propogate all events\n", timeval_elapsed(&tv)); + + for (i=0;i<ARRAY_SIZE(dirs);i++) { + if (dirs[i].counted != dirs[i].expected) { + printf("ERROR: i=%d expected %d got %d for '%s'\n", + i, dirs[i].expected, dirs[i].counted, dirs[i].path); + ret = false; + } + } + + /* + run from the back, closing and deleting + */ + for (i=ARRAY_SIZE(dirs)-1;i>=0;i--) { + smbcli_close(cli->tree, dirs[i].fnum); + smbcli_rmdir(cli->tree, dirs[i].path); + } + +done: + smb_raw_exit(cli->session); + return ret; +} + +/* + basic testing of change notify +*/ +bool torture_raw_notify(struct torture_context *torture, + struct smbcli_state *cli, + struct smbcli_state *cli2) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_notify_dir(cli, cli2, torture); + ret &= test_notify_mask(cli, torture); + ret &= test_notify_recursive(cli, torture); + ret &= test_notify_mask_change(cli, torture); + ret &= test_notify_file(cli, torture); + ret &= test_notify_tdis(torture); + ret &= test_notify_exit(torture); + ret &= test_notify_ulogoff(torture); + ret &= test_notify_tcp_dis(torture); + ret &= test_notify_double(cli, torture); + ret &= test_notify_tree(cli, torture); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} diff --git a/source4/torture/raw/offline.c b/source4/torture/raw/offline.c new file mode 100644 index 0000000000..f2f0bf5d27 --- /dev/null +++ b/source4/torture/raw/offline.c @@ -0,0 +1,514 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2008 + + 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 <http://www.gnu.org/licenses/>. +*/ + +/* + test offline files + */ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "lib/events/events.h" +#include "lib/cmdline/popt_common.h" +#include "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/resolve/resolve.h" +#include "param/param.h" + +#define BASEDIR "\\testoffline" + +static int nconnections; +static int numstates; +static int num_connected; +static int test_failed; +extern int torture_numops; +extern int torture_entries; +static bool test_finished; + +enum offline_op {OP_LOADFILE, OP_SAVEFILE, OP_SETOFFLINE, OP_GETOFFLINE, OP_ENDOFLIST}; + +static double latencies[OP_ENDOFLIST]; +static double worst_latencies[OP_ENDOFLIST]; + +#define FILE_SIZE 8192 + + +struct offline_state { + struct torture_context *tctx; + struct event_context *ev; + struct smbcli_tree *tree; + TALLOC_CTX *mem_ctx; + int client; + int fnum; + uint32_t count; + uint32_t lastcount; + uint32_t fnumber; + uint32_t offline_count; + uint32_t online_count; + char *fname; + struct smb_composite_loadfile *loadfile; + struct smb_composite_savefile *savefile; + struct smbcli_request *req; + enum offline_op op; + struct timeval tv_start; +}; + +static void test_offline(struct offline_state *state); + + +static char *filename(TALLOC_CTX *ctx, int i) +{ + char *s = talloc_asprintf(ctx, BASEDIR "\\file%u.dat", i); + return s; +} + + +/* + called when a loadfile completes + */ +static void loadfile_callback(struct composite_context *ctx) +{ + struct offline_state *state = ctx->async.private_data; + NTSTATUS status; + int i; + + status = smb_composite_loadfile_recv(ctx, state->mem_ctx); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to read file '%s' - %s\n", + state->loadfile->in.fname, nt_errstr(status)); + test_failed++; + } + + /* check the data is correct */ + if (state->loadfile->out.size != FILE_SIZE) { + printf("Wrong file size %u - expected %u\n", + state->loadfile->out.size, FILE_SIZE); + test_failed++; + return; + } + + for (i=0;i<FILE_SIZE;i++) { + if (state->loadfile->out.data[i] != 1+(state->fnumber % 255)) { + printf("Bad data in file %u (got %u expected %u)\n", + state->fnumber, + state->loadfile->out.data[i], + 1+(state->fnumber % 255)); + test_failed++; + return; + } + } + + talloc_steal(state->loadfile, state->loadfile->out.data); + + state->count++; + talloc_free(state->loadfile); + state->loadfile = NULL; + + if (!test_finished) { + test_offline(state); + } +} + + +/* + called when a savefile completes + */ +static void savefile_callback(struct composite_context *ctx) +{ + struct offline_state *state = ctx->async.private_data; + NTSTATUS status; + + status = smb_composite_savefile_recv(ctx); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to save file '%s' - %s\n", + state->savefile->in.fname, nt_errstr(status)); + test_failed++; + } + + state->count++; + talloc_free(state->savefile); + state->savefile = NULL; + + if (!test_finished) { + test_offline(state); + } +} + + +/* + called when a setoffline completes + */ +static void setoffline_callback(struct smbcli_request *req) +{ + struct offline_state *state = req->async.private; + NTSTATUS status; + + status = smbcli_request_simple_recv(req); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to set offline file '%s' - %s\n", + state->fname, nt_errstr(status)); + test_failed++; + } + + state->req = NULL; + state->count++; + + if (!test_finished) { + test_offline(state); + } +} + + +/* + called when a getoffline completes + */ +static void getoffline_callback(struct smbcli_request *req) +{ + struct offline_state *state = req->async.private; + NTSTATUS status; + union smb_fileinfo io; + + io.getattr.level = RAW_FILEINFO_GETATTR; + + status = smb_raw_pathinfo_recv(req, state->mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to get offline file '%s' - %s\n", + state->fname, nt_errstr(status)); + test_failed++; + } + + if (io.getattr.out.attrib & FILE_ATTRIBUTE_OFFLINE) { + state->offline_count++; + } else { + state->online_count++; + } + + state->req = NULL; + state->count++; + + if (!test_finished) { + test_offline(state); + } +} + + +/* + send the next offline file fetch request +*/ +static void test_offline(struct offline_state *state) +{ + struct composite_context *ctx; + double lat; + + lat = timeval_elapsed(&state->tv_start); + if (latencies[state->op] < lat) { + latencies[state->op] = lat; + } + + state->op = (enum offline_op) (random() % OP_ENDOFLIST); + + state->fnumber = random() % torture_numops; + talloc_free(state->fname); + state->fname = filename(state->mem_ctx, state->fnumber); + + state->tv_start = timeval_current(); + + switch (state->op) { + case OP_LOADFILE: + state->loadfile = talloc_zero(state->mem_ctx, struct smb_composite_loadfile); + state->loadfile->in.fname = state->fname; + + ctx = smb_composite_loadfile_send(state->tree, state->loadfile); + if (ctx == NULL) { + printf("Failed to setup loadfile for %s\n", state->fname); + test_failed = true; + } + + talloc_steal(state->loadfile, ctx); + + ctx->async.fn = loadfile_callback; + ctx->async.private_data = state; + break; + + case OP_SAVEFILE: + state->savefile = talloc_zero(state->mem_ctx, struct smb_composite_savefile); + + state->savefile->in.fname = state->fname; + state->savefile->in.data = talloc_size(state->savefile, FILE_SIZE); + state->savefile->in.size = FILE_SIZE; + memset(state->savefile->in.data, 1+(state->fnumber%255), FILE_SIZE); + + ctx = smb_composite_savefile_send(state->tree, state->savefile); + if (ctx == NULL) { + printf("Failed to setup savefile for %s\n", state->fname); + test_failed = true; + } + + talloc_steal(state->savefile, ctx); + + ctx->async.fn = savefile_callback; + ctx->async.private_data = state; + break; + + case OP_SETOFFLINE: { + union smb_setfileinfo io; + ZERO_STRUCT(io); + io.setattr.level = RAW_SFILEINFO_SETATTR; + io.setattr.in.attrib = FILE_ATTRIBUTE_OFFLINE; + io.setattr.in.file.path = state->fname; + /* make the file 1 hour old, to get past mininum age restrictions + for HSM systems */ + io.setattr.in.write_time = time(NULL) - 60*60; + + state->req = smb_raw_setpathinfo_send(state->tree, &io); + if (state->req == NULL) { + printf("Failed to setup setoffline for %s\n", state->fname); + test_failed = true; + } + + state->req->async.fn = setoffline_callback; + state->req->async.private = state; + break; + } + + case OP_GETOFFLINE: { + union smb_fileinfo io; + ZERO_STRUCT(io); + io.getattr.level = RAW_FILEINFO_GETATTR; + io.getattr.in.file.path = state->fname; + + state->req = smb_raw_pathinfo_send(state->tree, &io); + if (state->req == NULL) { + printf("Failed to setup getoffline for %s\n", state->fname); + test_failed = true; + } + + state->req->async.fn = getoffline_callback; + state->req->async.private = state; + break; + } + + default: + printf("bad operation??\n"); + break; + } +} + + + + +static void echo_completion(struct smbcli_request *req) +{ + struct offline_state *state = (struct offline_state *)req->async.private; + NTSTATUS status = smbcli_request_simple_recv(req); + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE) || + NT_STATUS_EQUAL(status, NT_STATUS_LOCAL_DISCONNECT)) { + talloc_free(state->tree); + state->tree = NULL; + num_connected--; + DEBUG(0,("lost connection\n")); + test_failed++; + } +} + +static void report_rate(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct offline_state *state = talloc_get_type(private_data, + struct offline_state); + int i; + uint32_t total=0, total_offline=0, total_online=0; + for (i=0;i<numstates;i++) { + total += state[i].count - state[i].lastcount; + if (timeval_elapsed(&state[i].tv_start) > latencies[state[i].op]) { + latencies[state[i].op] = timeval_elapsed(&state[i].tv_start); + } + state[i].lastcount = state[i].count; + total_online += state[i].online_count; + total_offline += state[i].offline_count; + } + printf("ops/s=%4u offline=%5u online=%4u set_lat=%.1f/%.1f get_lat=%.1f/%.1f save_lat=%.1f/%.1f load_lat=%.1f/%.1f\n", + total, total_offline, total_online, + latencies[OP_SETOFFLINE], + worst_latencies[OP_SETOFFLINE], + latencies[OP_GETOFFLINE], + worst_latencies[OP_GETOFFLINE], + latencies[OP_SAVEFILE], + worst_latencies[OP_SAVEFILE], + latencies[OP_LOADFILE], + worst_latencies[OP_LOADFILE]); + fflush(stdout); + event_add_timed(ev, state, timeval_current_ofs(1, 0), report_rate, state); + + for (i=0;i<OP_ENDOFLIST;i++) { + if (latencies[i] > worst_latencies[i]) { + worst_latencies[i] = latencies[i]; + } + latencies[i] = 0; + } + + /* send an echo on each interface to ensure it stays alive - this helps + with IP takeover */ + for (i=0;i<numstates;i++) { + struct smb_echo p; + struct smbcli_request *req; + + if (!state[i].tree) { + continue; + } + + p.in.repeat_count = 1; + p.in.size = 0; + p.in.data = NULL; + req = smb_raw_echo_send(state[i].tree->session->transport, &p); + req->async.private = &state[i]; + req->async.fn = echo_completion; + } +} + +/* + test offline file handling +*/ +bool torture_test_offline(struct torture_context *torture) +{ + bool ret = true; + TALLOC_CTX *mem_ctx = talloc_new(torture); + int i; + int timelimit = torture_setting_int(torture, "timelimit", 10); + struct timeval tv; + struct offline_state *state; + struct smbcli_state *cli; + bool progress; + progress = torture_setting_bool(torture, "progress", true); + + nconnections = torture_setting_int(torture, "nprocs", 4); + numstates = nconnections * torture_entries; + + state = talloc_zero_array(mem_ctx, struct offline_state, numstates); + + printf("Opening %d connections with %d simultaneous operations and %u files\n", nconnections, numstates, torture_numops); + for (i=0;i<nconnections;i++) { + state[i].tctx = torture; + state[i].mem_ctx = talloc_new(state); + state[i].ev = torture->ev; + if (!torture_open_connection_ev(&cli, i, torture, torture->ev)) { + return false; + } + state[i].tree = cli->tree; + state[i].client = i; + /* allow more time for offline files */ + state[i].tree->session->transport->options.request_timeout = 200; + } + + /* the others are repeats on the earlier connections */ + for (i=nconnections;i<numstates;i++) { + state[i].tctx = torture; + state[i].mem_ctx = talloc_new(state); + state[i].ev = torture->ev; + state[i].tree = state[i % nconnections].tree; + state[i].client = i; + } + + num_connected = i; + + if (!torture_setup_dir(cli, BASEDIR)) { + goto failed; + } + + /* pre-create files */ + printf("Pre-creating %u files ....\n", torture_numops); + for (i=0;i<torture_numops;i++) { + int fnum; + char *fname = filename(mem_ctx, i); + char buf[FILE_SIZE]; + NTSTATUS status; + + memset(buf, 1+(i % 255), sizeof(buf)); + + fnum = smbcli_open(state[0].tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to open %s on connection %d\n", fname, i); + goto failed; + } + + if (smbcli_write(state[0].tree, fnum, 0, buf, 0, sizeof(buf)) != sizeof(buf)) { + printf("Failed to write file of size %u\n", FILE_SIZE); + goto failed; + } + + status = smbcli_close(state[0].tree, fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + goto failed; + } + + talloc_free(fname); + } + + /* start the async ops */ + for (i=0;i<numstates;i++) { + state[i].tv_start = timeval_current(); + test_offline(&state[i]); + } + + tv = timeval_current(); + + if (progress) { + event_add_timed(torture->ev, state, timeval_current_ofs(1, 0), report_rate, state); + } + + printf("Running for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + event_loop_once(torture->ev); + + if (test_failed) { + DEBUG(0,("test failed\n")); + goto failed; + } + } + + printf("\nWaiting for completion\n"); + test_finished = true; + for (i=0;i<numstates;i++) { + while (state[i].loadfile || + state[i].savefile || + state[i].req) { + event_loop_once(torture->ev); + } + } + + printf("worst latencies: set_lat=%.1f get_lat=%.1f save_lat=%.1f load_lat=%.1f\n", + worst_latencies[OP_SETOFFLINE], + worst_latencies[OP_GETOFFLINE], + worst_latencies[OP_SAVEFILE], + worst_latencies[OP_LOADFILE]); + + smbcli_deltree(state[0].tree, BASEDIR); + talloc_free(mem_ctx); + printf("\n"); + return ret; + +failed: + talloc_free(mem_ctx); + return false; +} diff --git a/source4/torture/raw/open.c b/source4/torture/raw/open.c new file mode 100644 index 0000000000..f3494ea3d0 --- /dev/null +++ b/source4/torture/raw/open.c @@ -0,0 +1,1575 @@ +/* + Unix SMB/CIFS implementation. + RAW_OPEN_* individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/security.h" +#include "lib/events/events.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "auth/credentials/credentials.h" +#include "lib/cmdline/popt_common.h" +#include "param/param.h" + +/* enum for whether reads/writes are possible on a file */ +enum rdwr_mode {RDWR_NONE, RDWR_RDONLY, RDWR_WRONLY, RDWR_RDWR}; + +#define BASEDIR "\\rawopen" + +/* + check if a open file can be read/written +*/ +static enum rdwr_mode check_rdwr(struct smbcli_tree *tree, int fnum) +{ + uint8_t c = 1; + bool can_read = (smbcli_read(tree, fnum, &c, 0, 1) == 1); + bool can_write = (smbcli_write(tree, fnum, 0, &c, 0, 1) == 1); + if ( can_read && can_write) return RDWR_RDWR; + if ( can_read && !can_write) return RDWR_RDONLY; + if (!can_read && can_write) return RDWR_WRONLY; + return RDWR_NONE; +} + +/* + describe a RDWR mode as a string +*/ +static const char *rdwr_string(enum rdwr_mode m) +{ + switch (m) { + case RDWR_NONE: return "NONE"; + case RDWR_RDONLY: return "RDONLY"; + case RDWR_WRONLY: return "WRONLY"; + case RDWR_RDWR: return "RDWR"; + } + return "-"; +} + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CREATE_FILE do { \ + fnum = create_complex_file(cli, tctx, fname); \ + if (fnum == -1) { \ + printf("(%s) Failed to create %s - %s\n", __location__, fname, smbcli_errstr(cli->tree)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_RDWR(fnum, correct) do { \ + enum rdwr_mode m = check_rdwr(cli->tree, fnum); \ + if (m != correct) { \ + printf("(%s) Incorrect readwrite mode %s - expected %s\n", \ + __location__, rdwr_string(m), rdwr_string(correct)); \ + ret = false; \ + }} while (0) + +#define CHECK_TIME(t, field) do { \ + time_t t1, t2; \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFO; \ + finfo.all_info.in.file.path = fname; \ + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + t1 = t & ~1; \ + t2 = nt_time_to_unix(finfo.all_info.out.field) & ~1; \ + if (abs(t1-t2) > 2) { \ + printf("(%s) wrong time for field %s %s - %s\n", \ + __location__, #field, \ + timestring(tctx, t1), \ + timestring(tctx, t2)); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + +#define CHECK_NTTIME(t, field) do { \ + NTTIME t2; \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFO; \ + finfo.all_info.in.file.path = fname; \ + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + t2 = finfo.all_info.out.field; \ + if (t != t2) { \ + printf("(%s) wrong time for field %s %s - %s\n", \ + __location__, #field, \ + nt_time_string(tctx, t), \ + nt_time_string(tctx, t2)); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + +#define CHECK_ALL_INFO(v, field) do { \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFO; \ + finfo.all_info.in.file.path = fname; \ + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + if ((v) != (finfo.all_info.out.field)) { \ + printf("(%s) wrong value for field %s 0x%x - 0x%x\n", \ + __location__, #field, (int)v, (int)(finfo.all_info.out.field)); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%s) wrong value for %s 0x%x - should be 0x%x\n", \ + __location__, #v, (int)(v), (int)correct); \ + ret = false; \ + }} while (0) + +#define SET_ATTRIB(sattrib) do { \ + union smb_setfileinfo sfinfo; \ + ZERO_STRUCT(sfinfo.basic_info.in); \ + sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; \ + sfinfo.basic_info.in.file.path = fname; \ + sfinfo.basic_info.in.attrib = sattrib; \ + status = smb_raw_setpathinfo(cli->tree, &sfinfo); \ + if (!NT_STATUS_IS_OK(status)) { \ + printf("(%s) Failed to set attrib 0x%x on %s\n", \ + __location__, sattrib, fname); \ + }} while (0) + +/* + test RAW_OPEN_OPEN +*/ +static bool test_open(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname = BASEDIR "\\torture_open.txt"; + NTSTATUS status; + int fnum = -1, fnum2; + bool ret = true; + + printf("Checking RAW_OPEN_OPEN\n"); + + io.openold.level = RAW_OPEN_OPEN; + io.openold.in.fname = fname; + io.openold.in.open_mode = OPEN_FLAGS_FCB; + io.openold.in.search_attrs = 0; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + fnum = io.openold.out.file.fnum; + + smbcli_unlink(cli->tree, fname); + CREATE_FILE; + smbcli_close(cli->tree, fnum); + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openold.out.file.fnum; + CHECK_RDWR(fnum, RDWR_RDWR); + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.openold.out.file.fnum; + CHECK_RDWR(fnum2, RDWR_RDWR); + smbcli_close(cli->tree, fnum2); + smbcli_close(cli->tree, fnum); + + /* check the read/write modes */ + io.openold.level = RAW_OPEN_OPEN; + io.openold.in.fname = fname; + io.openold.in.search_attrs = 0; + + io.openold.in.open_mode = OPEN_FLAGS_OPEN_READ; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openold.out.file.fnum; + CHECK_RDWR(fnum, RDWR_RDONLY); + smbcli_close(cli->tree, fnum); + + io.openold.in.open_mode = OPEN_FLAGS_OPEN_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openold.out.file.fnum; + CHECK_RDWR(fnum, RDWR_WRONLY); + smbcli_close(cli->tree, fnum); + + io.openold.in.open_mode = OPEN_FLAGS_OPEN_RDWR; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openold.out.file.fnum; + CHECK_RDWR(fnum, RDWR_RDWR); + smbcli_close(cli->tree, fnum); + + /* check the share modes roughly - not a complete matrix */ + io.openold.in.open_mode = OPEN_FLAGS_OPEN_RDWR | OPEN_FLAGS_DENY_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openold.out.file.fnum; + CHECK_RDWR(fnum, RDWR_RDWR); + + if (io.openold.in.open_mode != io.openold.out.rmode) { + printf("(%s) rmode should equal open_mode - 0x%x 0x%x\n", + __location__, io.openold.out.rmode, io.openold.in.open_mode); + } + + io.openold.in.open_mode = OPEN_FLAGS_OPEN_RDWR | OPEN_FLAGS_DENY_NONE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.openold.in.open_mode = OPEN_FLAGS_OPEN_READ | OPEN_FLAGS_DENY_NONE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.openold.out.file.fnum; + CHECK_RDWR(fnum2, RDWR_RDONLY); + smbcli_close(cli->tree, fnum); + smbcli_close(cli->tree, fnum2); + + + /* check the returned write time */ + io.openold.level = RAW_OPEN_OPEN; + io.openold.in.fname = fname; + io.openold.in.search_attrs = 0; + io.openold.in.open_mode = OPEN_FLAGS_OPEN_READ; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openold.out.file.fnum; + + /* check other reply fields */ + CHECK_TIME(io.openold.out.write_time, write_time); + CHECK_ALL_INFO(io.openold.out.size, size); + CHECK_ALL_INFO(io.openold.out.attrib, attrib & ~FILE_ATTRIBUTE_NONINDEXED); + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + test RAW_OPEN_OPENX +*/ +static bool test_openx(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname = BASEDIR "\\torture_openx.txt"; + const char *fname_exe = BASEDIR "\\torture_openx.exe"; + NTSTATUS status; + int fnum = -1, fnum2; + bool ret = true; + int i; + struct timeval tv; + struct { + uint16_t open_func; + bool with_file; + NTSTATUS correct_status; + } open_funcs[] = { + { OPENX_OPEN_FUNC_OPEN, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_OPEN, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE, false, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_FAIL, true, NT_STATUS_DOS(ERRDOS, ERRbadaccess) }, + { OPENX_OPEN_FUNC_FAIL, false, NT_STATUS_DOS(ERRDOS, ERRbadaccess) }, + { OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE, true, NT_STATUS_OBJECT_NAME_COLLISION }, + { OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE, false, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_TRUNC, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_TRUNC, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { OPENX_OPEN_FUNC_TRUNC | OPENX_OPEN_FUNC_CREATE, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_TRUNC | OPENX_OPEN_FUNC_CREATE, false, NT_STATUS_OK }, + }; + + printf("Checking RAW_OPEN_OPENX\n"); + smbcli_unlink(cli->tree, fname); + + io.openx.level = RAW_OPEN_OPENX; + io.openx.in.fname = fname; + io.openx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + io.openx.in.open_mode = OPENX_MODE_ACCESS_RDWR; + io.openx.in.search_attrs = 0; + io.openx.in.file_attrs = 0; + io.openx.in.write_time = 0; + io.openx.in.size = 1024*1024; + io.openx.in.timeout = 0; + + /* check all combinations of open_func */ + for (i=0; i<ARRAY_SIZE(open_funcs); i++) { + if (open_funcs[i].with_file) { + fnum = create_complex_file(cli, tctx, fname); + if (fnum == -1) { + d_printf("Failed to create file %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum); + } + io.openx.in.open_func = open_funcs[i].open_func; + status = smb_raw_open(cli->tree, tctx, &io); + if (!NT_STATUS_EQUAL(status, open_funcs[i].correct_status)) { + printf("(%s) incorrect status %s should be %s (i=%d with_file=%d open_func=0x%x)\n", + __location__, nt_errstr(status), nt_errstr(open_funcs[i].correct_status), + i, (int)open_funcs[i].with_file, (int)open_funcs[i].open_func); + ret = false; + } + if (NT_STATUS_IS_OK(status)) { + smbcli_close(cli->tree, io.openx.out.file.fnum); + } + if (open_funcs[i].with_file) { + smbcli_unlink(cli->tree, fname); + } + } + + smbcli_unlink(cli->tree, fname); + + /* check the basic return fields */ + io.openx.in.open_func = OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openx.out.file.fnum; + + CHECK_ALL_INFO(io.openx.out.size, size); + CHECK_TIME(io.openx.out.write_time, write_time); + CHECK_ALL_INFO(io.openx.out.attrib, attrib & ~FILE_ATTRIBUTE_NONINDEXED); + CHECK_VAL(io.openx.out.access, OPENX_MODE_ACCESS_RDWR); + CHECK_VAL(io.openx.out.ftype, 0); + CHECK_VAL(io.openx.out.devstate, 0); + CHECK_VAL(io.openx.out.action, OPENX_ACTION_CREATED); + CHECK_VAL(io.openx.out.size, 1024*1024); + CHECK_ALL_INFO(io.openx.in.size, size); + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + /* check the fields when the file already existed */ + fnum2 = create_complex_file(cli, tctx, fname); + if (fnum2 == -1) { + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum2); + + io.openx.in.open_func = OPENX_OPEN_FUNC_OPEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openx.out.file.fnum; + + CHECK_ALL_INFO(io.openx.out.size, size); + CHECK_TIME(io.openx.out.write_time, write_time); + CHECK_VAL(io.openx.out.action, OPENX_ACTION_EXISTED); + CHECK_VAL(io.openx.out.unknown, 0); + CHECK_ALL_INFO(io.openx.out.attrib, attrib & ~FILE_ATTRIBUTE_NONINDEXED); + smbcli_close(cli->tree, fnum); + + /* now check the search attrib for hidden files - win2003 ignores this? */ + SET_ATTRIB(FILE_ATTRIBUTE_HIDDEN); + CHECK_ALL_INFO(FILE_ATTRIBUTE_HIDDEN, attrib); + + io.openx.in.search_attrs = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.openx.out.file.fnum); + + io.openx.in.search_attrs = 0; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.openx.out.file.fnum); + + SET_ATTRIB(FILE_ATTRIBUTE_NORMAL); + smbcli_unlink(cli->tree, fname); + + /* and check attrib on create */ + io.openx.in.open_func = OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE; + io.openx.in.search_attrs = 0; + io.openx.in.file_attrs = FILE_ATTRIBUTE_SYSTEM; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + if (torture_setting_bool(tctx, "samba3", false)) { + CHECK_ALL_INFO(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE, + attrib & ~(FILE_ATTRIBUTE_NONINDEXED| + FILE_ATTRIBUTE_SPARSE)); + } + else { + CHECK_ALL_INFO(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE, + attrib & ~(FILE_ATTRIBUTE_NONINDEXED)); + } + smbcli_close(cli->tree, io.openx.out.file.fnum); + smbcli_unlink(cli->tree, fname); + + /* check timeout on create - win2003 ignores the timeout! */ + io.openx.in.open_func = OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE; + io.openx.in.file_attrs = 0; + io.openx.in.open_mode = OPENX_MODE_ACCESS_RDWR | OPENX_MODE_DENY_ALL; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openx.out.file.fnum; + + io.openx.in.timeout = 20000; + tv = timeval_current(); + io.openx.in.open_mode = OPENX_MODE_ACCESS_RDWR | OPENX_MODE_DENY_NONE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + if (timeval_elapsed(&tv) > 3.0) { + printf("(%s) Incorrect timing in openx with timeout - waited %.2f seconds\n", + __location__, timeval_elapsed(&tv)); + ret = false; + } + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + /* now this is a really weird one - open for execute implies create?! */ + io.openx.in.fname = fname; + io.openx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + io.openx.in.open_mode = OPENX_MODE_ACCESS_EXEC | OPENX_MODE_DENY_NONE; + io.openx.in.search_attrs = 0; + io.openx.in.open_func = OPENX_OPEN_FUNC_FAIL; + io.openx.in.file_attrs = 0; + io.openx.in.write_time = 0; + io.openx.in.size = 0; + io.openx.in.timeout = 0; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.openx.out.file.fnum); + + /* check the extended return flag */ + io.openx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO | OPENX_FLAGS_EXTENDED_RETURN; + io.openx.in.open_func = OPENX_OPEN_FUNC_OPEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(io.openx.out.access_mask, SEC_STD_ALL); + smbcli_close(cli->tree, io.openx.out.file.fnum); + + io.openx.in.fname = "\\A.+,;=[].B"; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* Check the mapping for open exec. */ + + /* First create an .exe file. */ + smbcli_unlink(cli->tree, fname_exe); + fnum = create_complex_file(cli, tctx, fname_exe); + smbcli_close(cli->tree, fnum); + + io.openx.level = RAW_OPEN_OPENX; + io.openx.in.fname = fname_exe; + io.openx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + io.openx.in.open_mode = OPENX_MODE_ACCESS_EXEC | OPENX_MODE_DENY_NONE; + io.openx.in.search_attrs = 0; + io.openx.in.file_attrs = 0; + io.openx.in.write_time = 0; + io.openx.in.size = 0; + io.openx.in.timeout = 0; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Can we read and write ? */ + CHECK_RDWR(io.openx.out.file.fnum, RDWR_RDONLY); + smbcli_close(cli->tree, io.openx.out.file.fnum); + smbcli_unlink(cli->tree, fname); + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname_exe); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + test RAW_OPEN_T2OPEN + + many thanks to kukks for a sniff showing how this works with os2->w2k +*/ +static bool test_t2open(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname1 = BASEDIR "\\torture_t2open_yes.txt"; + const char *fname2 = BASEDIR "\\torture_t2open_no.txt"; + const char *fname = BASEDIR "\\torture_t2open_3.txt"; + NTSTATUS status; + int fnum; + bool ret = true; + int i; + struct { + uint16_t open_func; + bool with_file; + NTSTATUS correct_status; + } open_funcs[] = { + { OPENX_OPEN_FUNC_OPEN, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_OPEN, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE, false, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_FAIL, true, NT_STATUS_OBJECT_NAME_COLLISION }, + { OPENX_OPEN_FUNC_FAIL, false, NT_STATUS_OBJECT_NAME_COLLISION }, + { OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE, true, NT_STATUS_OBJECT_NAME_COLLISION }, + { OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE, false, NT_STATUS_OBJECT_NAME_COLLISION }, + { OPENX_OPEN_FUNC_TRUNC, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_TRUNC, false, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_TRUNC | OPENX_OPEN_FUNC_CREATE, true, NT_STATUS_OK }, + { OPENX_OPEN_FUNC_TRUNC | OPENX_OPEN_FUNC_CREATE, false, NT_STATUS_OK }, + }; + + fnum = create_complex_file(cli, tctx, fname1); + if (fnum == -1) { + d_printf("Failed to create file %s - %s\n", fname1, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum); + + printf("Checking RAW_OPEN_T2OPEN\n"); + + io.t2open.level = RAW_OPEN_T2OPEN; + io.t2open.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + io.t2open.in.open_mode = OPENX_MODE_DENY_NONE | OPENX_MODE_ACCESS_RDWR; + io.t2open.in.open_func = OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE; + io.t2open.in.search_attrs = 0; + io.t2open.in.file_attrs = 0; + io.t2open.in.write_time = 0; + io.t2open.in.size = 0; + io.t2open.in.timeout = 0; + + io.t2open.in.num_eas = 3; + io.t2open.in.eas = talloc_array(tctx, struct ea_struct, io.t2open.in.num_eas); + io.t2open.in.eas[0].flags = 0; + io.t2open.in.eas[0].name.s = ".CLASSINFO"; + io.t2open.in.eas[0].value = data_blob_talloc(tctx, "first value", 11); + io.t2open.in.eas[1].flags = 0; + io.t2open.in.eas[1].name.s = "EA TWO"; + io.t2open.in.eas[1].value = data_blob_talloc(tctx, "foo", 3); + io.t2open.in.eas[2].flags = 0; + io.t2open.in.eas[2].name.s = "X THIRD"; + io.t2open.in.eas[2].value = data_blob_talloc(tctx, "xy", 2); + + /* check all combinations of open_func */ + for (i=0; i<ARRAY_SIZE(open_funcs); i++) { + again: + if (open_funcs[i].with_file) { + io.t2open.in.fname = fname1; + } else { + io.t2open.in.fname = fname2; + } + io.t2open.in.open_func = open_funcs[i].open_func; + status = smb_raw_open(cli->tree, tctx, &io); + if ((io.t2open.in.num_eas != 0) + && NT_STATUS_EQUAL(status, NT_STATUS_EAS_NOT_SUPPORTED) + && torture_setting_bool(tctx, "samba3", false)) { + printf("(%s) EAs not supported, not treating as fatal " + "in Samba3 test\n", __location__); + io.t2open.in.num_eas = 0; + goto again; + } + + if (!NT_STATUS_EQUAL(status, open_funcs[i].correct_status)) { + printf("(%s) incorrect status %s should be %s (i=%d with_file=%d open_func=0x%x)\n", + __location__, nt_errstr(status), nt_errstr(open_funcs[i].correct_status), + i, (int)open_funcs[i].with_file, (int)open_funcs[i].open_func); + ret = false; + } + if (NT_STATUS_IS_OK(status)) { + smbcli_close(cli->tree, io.t2open.out.file.fnum); + } + } + + smbcli_unlink(cli->tree, fname1); + smbcli_unlink(cli->tree, fname2); + + /* check the basic return fields */ + io.t2open.in.open_func = OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE; + io.t2open.in.write_time = 0; + io.t2open.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.t2open.out.file.fnum; + + CHECK_ALL_INFO(io.t2open.out.size, size); +#if 0 + /* windows appears to leak uninitialised memory here */ + CHECK_VAL(io.t2open.out.write_time, 0); +#endif + CHECK_ALL_INFO(io.t2open.out.attrib, attrib & ~FILE_ATTRIBUTE_NONINDEXED); + CHECK_VAL(io.t2open.out.access, OPENX_MODE_DENY_NONE | OPENX_MODE_ACCESS_RDWR); + CHECK_VAL(io.t2open.out.ftype, 0); + CHECK_VAL(io.t2open.out.devstate, 0); + CHECK_VAL(io.t2open.out.action, OPENX_ACTION_CREATED); + smbcli_close(cli->tree, fnum); + + status = torture_check_ea(cli, fname, ".CLASSINFO", "first value"); + CHECK_STATUS(status, io.t2open.in.num_eas + ? NT_STATUS_OK : NT_STATUS_EAS_NOT_SUPPORTED); + status = torture_check_ea(cli, fname, "EA TWO", "foo"); + CHECK_STATUS(status, io.t2open.in.num_eas + ? NT_STATUS_OK : NT_STATUS_EAS_NOT_SUPPORTED); + status = torture_check_ea(cli, fname, "X THIRD", "xy"); + CHECK_STATUS(status, io.t2open.in.num_eas + ? NT_STATUS_OK : NT_STATUS_EAS_NOT_SUPPORTED); + + /* now check the search attrib for hidden files - win2003 ignores this? */ + SET_ATTRIB(FILE_ATTRIBUTE_HIDDEN); + CHECK_ALL_INFO(FILE_ATTRIBUTE_HIDDEN, attrib); + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.t2open.out.file.fnum); + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.t2open.out.file.fnum); + + SET_ATTRIB(FILE_ATTRIBUTE_NORMAL); + smbcli_unlink(cli->tree, fname); + + /* and check attrib on create */ + io.t2open.in.open_func = OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE; + io.t2open.in.file_attrs = FILE_ATTRIBUTE_SYSTEM; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* check timeout on create - win2003 ignores the timeout! */ + io.t2open.in.open_func = OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE; + io.t2open.in.file_attrs = 0; + io.t2open.in.timeout = 20000; + io.t2open.in.open_mode = OPENX_MODE_ACCESS_RDWR | OPENX_MODE_DENY_ALL; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + test RAW_OPEN_NTCREATEX +*/ +static bool test_ntcreatex(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname = BASEDIR "\\torture_ntcreatex.txt"; + const char *dname = BASEDIR "\\torture_ntcreatex.dir"; + NTSTATUS status; + int fnum = -1; + bool ret = true; + int i; + struct { + uint32_t open_disp; + bool with_file; + NTSTATUS correct_status; + } open_funcs[] = { + { NTCREATEX_DISP_SUPERSEDE, true, NT_STATUS_OK }, + { NTCREATEX_DISP_SUPERSEDE, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { NTCREATEX_DISP_CREATE, true, NT_STATUS_OBJECT_NAME_COLLISION }, + { NTCREATEX_DISP_CREATE, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN_IF, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN_IF, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { NTCREATEX_DISP_OVERWRITE_IF, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE_IF, false, NT_STATUS_OK }, + { 6, true, NT_STATUS_INVALID_PARAMETER }, + { 6, false, NT_STATUS_INVALID_PARAMETER }, + }; + + printf("Checking RAW_OPEN_NTCREATEX\n"); + + /* reasonable default parameters */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 1024*1024; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* test the open disposition */ + for (i=0; i<ARRAY_SIZE(open_funcs); i++) { + if (open_funcs[i].with_file) { + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR|O_TRUNC, DENY_NONE); + if (fnum == -1) { + d_printf("Failed to create file %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum); + } + io.ntcreatex.in.open_disposition = open_funcs[i].open_disp; + status = smb_raw_open(cli->tree, tctx, &io); + if (!NT_STATUS_EQUAL(status, open_funcs[i].correct_status)) { + printf("(%s) incorrect status %s should be %s (i=%d with_file=%d open_disp=%d)\n", + __location__, nt_errstr(status), nt_errstr(open_funcs[i].correct_status), + i, (int)open_funcs[i].with_file, (int)open_funcs[i].open_disp); + ret = false; + } + if (NT_STATUS_IS_OK(status) || open_funcs[i].with_file) { + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + smbcli_unlink(cli->tree, fname); + } + } + + /* basic field testing */ + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + + /* check fields when the file already existed */ + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + fnum = create_complex_file(cli, tctx, fname); + if (fnum == -1) { + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum); + + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + + /* create a directory */ + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.fname = dname; + fname = dname; + + smbcli_rmdir(cli->tree, fname); + smbcli_unlink(cli->tree, fname); + + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_VAL(io.ntcreatex.out.attrib & ~FILE_ATTRIBUTE_NONINDEXED, + FILE_ATTRIBUTE_DIRECTORY); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.is_directory, 1); + CHECK_VAL(io.ntcreatex.out.size, 0); + CHECK_VAL(io.ntcreatex.out.alloc_size, 0); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + smbcli_unlink(cli->tree, fname); + + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + test RAW_OPEN_NTTRANS_CREATE +*/ +static bool test_nttrans_create(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname = BASEDIR "\\torture_ntcreatex.txt"; + const char *dname = BASEDIR "\\torture_ntcreatex.dir"; + NTSTATUS status; + int fnum = -1; + bool ret = true; + int i; + uint32_t ok_mask, not_supported_mask, invalid_parameter_mask; + uint32_t not_a_directory_mask, unexpected_mask; + struct { + uint32_t open_disp; + bool with_file; + NTSTATUS correct_status; + } open_funcs[] = { + { NTCREATEX_DISP_SUPERSEDE, true, NT_STATUS_OK }, + { NTCREATEX_DISP_SUPERSEDE, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { NTCREATEX_DISP_CREATE, true, NT_STATUS_OBJECT_NAME_COLLISION }, + { NTCREATEX_DISP_CREATE, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN_IF, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN_IF, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { NTCREATEX_DISP_OVERWRITE_IF, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE_IF, false, NT_STATUS_OK }, + { 6, true, NT_STATUS_INVALID_PARAMETER }, + { 6, false, NT_STATUS_INVALID_PARAMETER }, + }; + + printf("Checking RAW_OPEN_NTTRANS_CREATE\n"); + + /* reasonable default parameters */ + io.generic.level = RAW_OPEN_NTTRANS_CREATE; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 1024*1024; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + io.ntcreatex.in.sec_desc = NULL; + io.ntcreatex.in.ea_list = NULL; + + /* test the open disposition */ + for (i=0; i<ARRAY_SIZE(open_funcs); i++) { + if (open_funcs[i].with_file) { + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR|O_TRUNC, DENY_NONE); + if (fnum == -1) { + d_printf("Failed to create file %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum); + } + io.ntcreatex.in.open_disposition = open_funcs[i].open_disp; + status = smb_raw_open(cli->tree, tctx, &io); + if (!NT_STATUS_EQUAL(status, open_funcs[i].correct_status)) { + printf("(%s) incorrect status %s should be %s (i=%d with_file=%d open_disp=%d)\n", + __location__, nt_errstr(status), nt_errstr(open_funcs[i].correct_status), + i, (int)open_funcs[i].with_file, (int)open_funcs[i].open_disp); + ret = false; + } + if (NT_STATUS_IS_OK(status) || open_funcs[i].with_file) { + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + smbcli_unlink(cli->tree, fname); + } + } + + /* basic field testing */ + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + + /* check fields when the file already existed */ + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + fnum = create_complex_file(cli, tctx, fname); + if (fnum == -1) { + ret = false; + goto done; + } + smbcli_close(cli->tree, fnum); + + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + smbcli_close(cli->tree, fnum); + + /* check no-recall - don't pull a file from tape on a HSM */ + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_NO_RECALL; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + smbcli_close(cli->tree, fnum); + + /* Check some create options (these all should be ignored) */ + for (i=0; i < 32; i++) { + uint32_t create_option = (1 << i) & NTCREATEX_OPTIONS_MUST_IGNORE_MASK; + if (create_option == 0) { + continue; + } + io.ntcreatex.in.create_options = create_option; + status = smb_raw_open(cli->tree, tctx, &io); + if (!NT_STATUS_IS_OK(status)) { + printf("ntcreatex create option 0x%08x gave %s - should give NT_STATUS_OK\n", + create_option, nt_errstr(status)); + } + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + smbcli_close(cli->tree, fnum); + } + + io.ntcreatex.in.file_attr = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + + /* Check for options that should return NOT_SUPPORTED, OK or INVALID_PARAMETER */ + ok_mask = 0; + not_supported_mask = 0; + invalid_parameter_mask = 0; + not_a_directory_mask = 0; + unexpected_mask = 0; + for (i=0; i < 32; i++) { + uint32_t create_option = 1<<i; + if (create_option & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) { + continue; + } + io.ntcreatex.in.create_options = create_option; + status = smb_raw_open(cli->tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + not_supported_mask |= create_option; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + ok_mask |= create_option; + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + invalid_parameter_mask |= create_option; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) { + not_a_directory_mask |= 1<<i; + } else { + unexpected_mask |= 1<<i; + printf("create option 0x%08x returned %s\n", create_option, nt_errstr(status)); + } + } + + CHECK_VAL(ok_mask, 0x00efcfce); + CHECK_VAL(not_a_directory_mask, 0x00000001); + CHECK_VAL(not_supported_mask, 0x00002000); + CHECK_VAL(invalid_parameter_mask, 0xff100030); + CHECK_VAL(unexpected_mask, 0x00000000); + + smbcli_unlink(cli->tree, fname); + + + /* create a directory */ + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.fname = dname; + fname = dname; + + smbcli_rmdir(cli->tree, fname); + smbcli_unlink(cli->tree, fname); + + io.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + CHECK_VAL(io.ntcreatex.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_NTTIME(io.ntcreatex.out.create_time, create_time); + CHECK_NTTIME(io.ntcreatex.out.access_time, access_time); + CHECK_NTTIME(io.ntcreatex.out.write_time, write_time); + CHECK_NTTIME(io.ntcreatex.out.change_time, change_time); + CHECK_ALL_INFO(io.ntcreatex.out.attrib, attrib); + CHECK_VAL(io.ntcreatex.out.attrib & ~FILE_ATTRIBUTE_NONINDEXED, + FILE_ATTRIBUTE_DIRECTORY); + CHECK_ALL_INFO(io.ntcreatex.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.ntcreatex.out.size, size); + CHECK_ALL_INFO(io.ntcreatex.out.is_directory, directory); + CHECK_VAL(io.ntcreatex.out.is_directory, 1); + CHECK_VAL(io.ntcreatex.out.size, 0); + CHECK_VAL(io.ntcreatex.out.alloc_size, 0); + CHECK_VAL(io.ntcreatex.out.file_type, FILE_TYPE_DISK); + smbcli_unlink(cli->tree, fname); + + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + +/* + test RAW_OPEN_NTCREATEX with an already opened and byte range locked file + + I've got an application that does a similar sequence of ntcreate&x, + locking&x and another ntcreate&x with + open_disposition==NTCREATEX_DISP_OVERWRITE_IF. Windows 2003 allows the + second open. +*/ +static bool test_ntcreatex_brlocked(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io, io1; + union smb_lock io2; + struct smb_lock_entry lock[1]; + const char *fname = BASEDIR "\\torture_ntcreatex.txt"; + NTSTATUS status; + bool ret = true; + + printf("Testing ntcreatex with a byte range locked file\n"); + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = 0x2019f; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_IMPERSONATION; + io.ntcreatex.in.security_flags = NTCREATEX_SECURITY_DYNAMIC | + NTCREATEX_SECURITY_ALL; + io.ntcreatex.in.fname = fname; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io2.lockx.level = RAW_LOCK_LOCKX; + io2.lockx.in.file.fnum = io.ntcreatex.out.file.fnum; + io2.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io2.lockx.in.timeout = 0; + io2.lockx.in.ulock_cnt = 0; + io2.lockx.in.lock_cnt = 1; + lock[0].pid = cli->session->pid; + lock[0].offset = 0; + lock[0].count = 0x1; + io2.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + + io1.generic.level = RAW_OPEN_NTCREATEX; + io1.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io1.ntcreatex.in.root_fid = 0; + io1.ntcreatex.in.access_mask = 0x20196; + io1.ntcreatex.in.alloc_size = 0; + io1.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io1.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io1.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io1.ntcreatex.in.create_options = 0; + io1.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_IMPERSONATION; + io1.ntcreatex.in.security_flags = NTCREATEX_SECURITY_DYNAMIC | + NTCREATEX_SECURITY_ALL; + io1.ntcreatex.in.fname = fname; + + status = smb_raw_open(cli->tree, tctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + smbcli_close(cli->tree, io1.ntcreatex.out.file.fnum); + smbcli_unlink(cli->tree, fname); + return ret; +} + +/* + test RAW_OPEN_MKNEW +*/ +static bool test_mknew(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + const char *fname = BASEDIR "\\torture_mknew.txt"; + NTSTATUS status; + int fnum = -1; + bool ret = true; + time_t basetime = (time(NULL) + 3600*24*3) & ~1; + union smb_fileinfo finfo; + + printf("Checking RAW_OPEN_MKNEW\n"); + + io.mknew.level = RAW_OPEN_MKNEW; + io.mknew.in.attrib = 0; + io.mknew.in.write_time = 0; + io.mknew.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.mknew.out.file.fnum; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + /* make sure write_time works */ + io.mknew.in.write_time = basetime; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.mknew.out.file.fnum; + CHECK_TIME(basetime, write_time); + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + /* make sure file_attrs works */ + io.mknew.in.attrib = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.mknew.out.file.fnum; + CHECK_ALL_INFO(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_ARCHIVE, + attrib & ~FILE_ATTRIBUTE_NONINDEXED); + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + test RAW_OPEN_CREATE +*/ +static bool test_create(struct smbcli_state *cli, struct torture_context *tctx) +{ + union smb_open io; + const char *fname = BASEDIR "\\torture_create.txt"; + NTSTATUS status; + int fnum = -1; + bool ret = true; + time_t basetime = (time(NULL) + 3600*24*3) & ~1; + union smb_fileinfo finfo; + + printf("Checking RAW_OPEN_CREATE\n"); + + io.create.level = RAW_OPEN_CREATE; + io.create.in.attrib = 0; + io.create.in.write_time = 0; + io.create.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.create.out.file.fnum; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, io.create.out.file.fnum); + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + /* make sure write_time works */ + io.create.in.write_time = basetime; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.create.out.file.fnum; + CHECK_TIME(basetime, write_time); + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + /* make sure file_attrs works */ + io.create.in.attrib = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.create.out.file.fnum; + CHECK_ALL_INFO(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_ARCHIVE, + attrib & ~FILE_ATTRIBUTE_NONINDEXED); + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +/* + test RAW_OPEN_CTEMP +*/ +static bool test_ctemp(struct smbcli_state *cli, TALLOC_CTX *tctx) +{ + union smb_open io; + NTSTATUS status; + int fnum = -1; + bool ret = true; + time_t basetime = (time(NULL) + 3600*24*3) & ~1; + union smb_fileinfo finfo; + const char *name, *fname = NULL; + + printf("Checking RAW_OPEN_CTEMP\n"); + + io.ctemp.level = RAW_OPEN_CTEMP; + io.ctemp.in.attrib = FILE_ATTRIBUTE_HIDDEN; + io.ctemp.in.write_time = basetime; + io.ctemp.in.directory = BASEDIR; + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ctemp.out.file.fnum; + + name = io.ctemp.out.name; + + finfo.generic.level = RAW_FILEINFO_NAME_INFO; + finfo.generic.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + + fname = finfo.name_info.out.fname.s; + d_printf("ctemp name=%s real name=%s\n", name, fname); + +done: + smbcli_close(cli->tree, fnum); + if (fname) { + smbcli_unlink(cli->tree, fname); + } + + return ret; +} + + +/* + test chained RAW_OPEN_OPENX_READX +*/ +static bool test_chained(struct smbcli_state *cli, TALLOC_CTX *tctx) +{ + union smb_open io; + const char *fname = BASEDIR "\\torture_chained.txt"; + NTSTATUS status; + int fnum = -1; + bool ret = true; + const char *buf = "test"; + char buf2[4]; + + printf("Checking RAW_OPEN_OPENX chained with READX\n"); + smbcli_unlink(cli->tree, fname); + + fnum = create_complex_file(cli, tctx, fname); + + smbcli_write(cli->tree, fnum, 0, buf, 0, sizeof(buf)); + + smbcli_close(cli->tree, fnum); + + io.openxreadx.level = RAW_OPEN_OPENX_READX; + io.openxreadx.in.fname = fname; + io.openxreadx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + io.openxreadx.in.open_mode = OPENX_MODE_ACCESS_RDWR; + io.openxreadx.in.open_func = OPENX_OPEN_FUNC_OPEN; + io.openxreadx.in.search_attrs = 0; + io.openxreadx.in.file_attrs = 0; + io.openxreadx.in.write_time = 0; + io.openxreadx.in.size = 1024*1024; + io.openxreadx.in.timeout = 0; + + io.openxreadx.in.offset = 0; + io.openxreadx.in.mincnt = sizeof(buf); + io.openxreadx.in.maxcnt = sizeof(buf); + io.openxreadx.in.remaining = 0; + io.openxreadx.out.data = (uint8_t *)buf2; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openxreadx.out.file.fnum; + + if (memcmp(buf, buf2, sizeof(buf)) != 0) { + d_printf("wrong data in reply buffer\n"); + ret = false; + } + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + +/* + test RAW_OPEN_OPENX without a leading slash on the path. + NetApp filers are known to fail on this. + +*/ +static bool test_no_leading_slash(struct smbcli_state *cli, TALLOC_CTX *tctx) +{ + union smb_open io; + const char *fname = BASEDIR "\\torture_no_leading_slash.txt"; + NTSTATUS status; + int fnum = -1; + bool ret = true; + const char *buf = "test"; + + printf("Checking RAW_OPEN_OPENX without leading slash on path\n"); + smbcli_unlink(cli->tree, fname); + + /* Create the file */ + fnum = create_complex_file(cli, tctx, fname); + smbcli_write(cli->tree, fnum, 0, buf, 0, sizeof(buf)); + smbcli_close(cli->tree, fnum); + + /* Prepare to open the file using path without leading slash */ + io.openx.level = RAW_OPEN_OPENX; + io.openx.in.fname = fname + 1; + io.openx.in.flags = OPENX_FLAGS_ADDITIONAL_INFO; + io.openx.in.open_mode = OPENX_MODE_ACCESS_RDWR; + io.openx.in.open_func = OPENX_OPEN_FUNC_OPEN; + io.openx.in.search_attrs = 0; + io.openx.in.file_attrs = 0; + io.openx.in.write_time = 0; + io.openx.in.size = 1024*1024; + io.openx.in.timeout = 0; + + status = smb_raw_open(cli->tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.openx.out.file.fnum; + +done: + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + +/* A little torture test to expose a race condition in Samba 3.0.20 ... :-) */ + +static bool test_raw_open_multi(struct torture_context *tctx) +{ + struct smbcli_state *cli; + TALLOC_CTX *mem_ctx = talloc_init("torture_test_oplock_multi"); + const char *fname = "\\test_oplock.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smbcli_state **clients; + struct smbcli_request **requests; + union smb_open *ios; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + int i, num_files = 3; + int num_ok = 0; + int num_collision = 0; + + clients = talloc_array(mem_ctx, struct smbcli_state *, num_files); + requests = talloc_array(mem_ctx, struct smbcli_request *, num_files); + ios = talloc_array(mem_ctx, union smb_open, num_files); + if ((tctx->ev == NULL) || (clients == NULL) || (requests == NULL) || + (ios == NULL)) { + DEBUG(0, ("talloc failed\n")); + return false; + } + + if (!torture_open_connection_share(mem_ctx, &cli, tctx, host, share, tctx->ev)) { + return false; + } + + cli->tree->session->transport->options.request_timeout = 60; + + for (i=0; i<num_files; i++) { + if (!torture_open_connection_share(mem_ctx, &(clients[i]), + tctx, host, share, tctx->ev)) { + DEBUG(0, ("Could not open %d'th connection\n", i)); + return false; + } + clients[i]->tree->session->transport->options.request_timeout = 60; + } + + /* cleanup */ + smbcli_unlink(cli->tree, fname); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + io.ntcreatex.in.flags = 0; + + for (i=0; i<num_files; i++) { + ios[i] = io; + requests[i] = smb_raw_open_send(clients[i]->tree, &ios[i]); + if (requests[i] == NULL) { + DEBUG(0, ("could not send %d'th request\n", i)); + return false; + } + } + + DEBUG(10, ("waiting for replies\n")); + while (1) { + bool unreplied = false; + for (i=0; i<num_files; i++) { + if (requests[i] == NULL) { + continue; + } + if (requests[i]->state < SMBCLI_REQUEST_DONE) { + unreplied = true; + break; + } + status = smb_raw_open_recv(requests[i], mem_ctx, + &ios[i]); + + DEBUG(0, ("File %d returned status %s\n", i, + nt_errstr(status))); + + if (NT_STATUS_IS_OK(status)) { + num_ok += 1; + } + + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_COLLISION)) { + num_collision += 1; + } + + requests[i] = NULL; + } + if (!unreplied) { + break; + } + + if (event_loop_once(tctx->ev) != 0) { + DEBUG(0, ("event_loop_once failed\n")); + return false; + } + } + + if ((num_ok != 1) || (num_ok + num_collision != num_files)) { + ret = false; + } + + for (i=0; i<num_files; i++) { + torture_close_connection(clients[i]); + } + talloc_free(mem_ctx); + return ret; +} + +/* basic testing of all RAW_OPEN_* calls +*/ +bool torture_raw_open(struct torture_context *torture, struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_ntcreatex_brlocked(cli, torture); + ret &= test_open(cli, torture); + ret &= test_raw_open_multi(torture); + ret &= test_openx(cli, torture); + ret &= test_ntcreatex(cli, torture); + ret &= test_nttrans_create(cli, torture); + ret &= test_t2open(cli, torture); + ret &= test_mknew(cli, torture); + ret &= test_create(cli, torture); + ret &= test_ctemp(cli, torture); + ret &= test_chained(cli, torture); + ret &= test_no_leading_slash(cli, torture); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} diff --git a/source4/torture/raw/openbench.c b/source4/torture/raw/openbench.c new file mode 100644 index 0000000000..26b862c33f --- /dev/null +++ b/source4/torture/raw/openbench.c @@ -0,0 +1,484 @@ +/* + Unix SMB/CIFS implementation. + + open benchmark + + Copyright (C) Andrew Tridgell 2007 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "lib/events/events.h" +#include "lib/cmdline/popt_common.h" +#include "libcli/composite/composite.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/resolve/resolve.h" +#include "param/param.h" + +#define BASEDIR "\\benchopen" + +static int nprocs; +static int open_failed; +static int close_failed; +static char **fnames; +static int num_connected; +static struct timed_event *report_te; + +struct benchopen_state { + struct torture_context *tctx; + TALLOC_CTX *mem_ctx; + struct event_context *ev; + struct smbcli_state *cli; + struct smbcli_tree *tree; + int client_num; + int close_fnum; + int open_fnum; + int close_file_num; + int open_file_num; + int pending_file_num; + int next_file_num; + int count; + int lastcount; + union smb_open open_parms; + int open_retries; + union smb_close close_parms; + struct smbcli_request *req_open; + struct smbcli_request *req_close; + struct smb_composite_connect reconnect; + struct timed_event *te; + + /* these are used for reconnections */ + const char **dest_ports; + const char *dest_host; + const char *called_name; + const char *service_type; +}; + +static void next_open(struct benchopen_state *state); +static void reopen_connection(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data); + + +/* + complete an async reconnect + */ +static void reopen_connection_complete(struct composite_context *ctx) +{ + struct benchopen_state *state = (struct benchopen_state *)ctx->async.private_data; + NTSTATUS status; + struct smb_composite_connect *io = &state->reconnect; + + status = smb_composite_connect_recv(ctx, state->mem_ctx); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + return; + } + + state->tree = io->out.tree; + + num_connected++; + + DEBUG(0,("[%u] reconnect to %s finished (%u connected)\n", + state->client_num, state->dest_host, num_connected)); + + state->open_fnum = -1; + state->close_fnum = -1; + next_open(state); +} + + + +/* + reopen a connection + */ +static void reopen_connection(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct benchopen_state *state = (struct benchopen_state *)private_data; + struct composite_context *ctx; + struct smb_composite_connect *io = &state->reconnect; + char *host, *share; + + state->te = NULL; + + if (!torture_get_conn_index(state->client_num, state->mem_ctx, state->tctx, &host, &share)) { + DEBUG(0,("Can't find host/share for reconnect?!\n")); + exit(1); + } + + io->in.dest_host = state->dest_host; + io->in.dest_ports = state->dest_ports; + io->in.called_name = state->called_name; + io->in.service = share; + io->in.service_type = state->service_type; + io->in.credentials = cmdline_credentials; + io->in.fallback_to_anonymous = false; + io->in.workgroup = lp_workgroup(state->tctx->lp_ctx); + lp_smbcli_options(state->tctx->lp_ctx, &io->in.options); + + /* kill off the remnants of the old connection */ + talloc_free(state->tree); + state->tree = NULL; + state->open_fnum = -1; + state->close_fnum = -1; + + ctx = smb_composite_connect_send(io, state->mem_ctx, + lp_resolve_context(state->tctx->lp_ctx), + state->ev); + if (ctx == NULL) { + DEBUG(0,("Failed to setup async reconnect\n")); + exit(1); + } + + ctx->async.fn = reopen_connection_complete; + ctx->async.private_data = state; +} + +static void open_completed(struct smbcli_request *req); +static void close_completed(struct smbcli_request *req); + + +static void next_open(struct benchopen_state *state) +{ + state->count++; + + state->pending_file_num = state->next_file_num; + state->next_file_num = (state->next_file_num+1) % (3*nprocs); + + DEBUG(2,("[%d] opening %u\n", state->client_num, state->pending_file_num)); + state->open_parms.ntcreatex.level = RAW_OPEN_NTCREATEX; + state->open_parms.ntcreatex.in.flags = 0; + state->open_parms.ntcreatex.in.root_fid = 0; + state->open_parms.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + state->open_parms.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + state->open_parms.ntcreatex.in.alloc_size = 0; + state->open_parms.ntcreatex.in.share_access = 0; + state->open_parms.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + state->open_parms.ntcreatex.in.create_options = 0; + state->open_parms.ntcreatex.in.impersonation = 0; + state->open_parms.ntcreatex.in.security_flags = 0; + state->open_parms.ntcreatex.in.fname = fnames[state->pending_file_num]; + + state->req_open = smb_raw_open_send(state->tree, &state->open_parms); + state->req_open->async.fn = open_completed; + state->req_open->async.private = state; +} + + +static void next_close(struct benchopen_state *state) +{ + if (state->close_fnum == -1) { + return; + } + DEBUG(2,("[%d] closing %d (fnum[%d])\n", + state->client_num, state->close_file_num, state->close_fnum)); + state->close_parms.close.level = RAW_CLOSE_CLOSE; + state->close_parms.close.in.file.fnum = state->close_fnum; + state->close_parms.close.in.write_time = 0; + + state->req_close = smb_raw_close_send(state->tree, &state->close_parms); + state->req_close->async.fn = close_completed; + state->req_close->async.private = state; +} + +/* + called when a open completes +*/ +static void open_completed(struct smbcli_request *req) +{ + struct benchopen_state *state = (struct benchopen_state *)req->async.private; + TALLOC_CTX *tmp_ctx = talloc_new(state->mem_ctx); + NTSTATUS status; + + status = smb_raw_open_recv(req, tmp_ctx, &state->open_parms); + + talloc_free(tmp_ctx); + + state->req_open = NULL; + + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE) || + NT_STATUS_EQUAL(status, NT_STATUS_LOCAL_DISCONNECT)) { + talloc_free(state->tree); + talloc_free(state->cli); + state->tree = NULL; + state->cli = NULL; + num_connected--; + DEBUG(0,("[%u] reopening connection to %s\n", + state->client_num, state->dest_host)); + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + return; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + DEBUG(2,("[%d] retrying open %d\n", + state->client_num, state->pending_file_num)); + state->open_retries++; + state->req_open = smb_raw_open_send(state->tree, &state->open_parms); + state->req_open->async.fn = open_completed; + state->req_open->async.private = state; + return; + } + + if (!NT_STATUS_IS_OK(status)) { + open_failed++; + DEBUG(0,("[%u] open failed %d - %s\n", + state->client_num, state->pending_file_num, + nt_errstr(status))); + return; + } + + state->close_file_num = state->open_file_num; + state->close_fnum = state->open_fnum; + state->open_file_num = state->pending_file_num; + state->open_fnum = state->open_parms.ntcreatex.out.file.fnum; + + DEBUG(2,("[%d] open completed %d (fnum[%d])\n", + state->client_num, state->open_file_num, state->open_fnum)); + + if (state->close_fnum != -1) { + next_close(state); + } + + next_open(state); +} + +/* + called when a close completes +*/ +static void close_completed(struct smbcli_request *req) +{ + struct benchopen_state *state = (struct benchopen_state *)req->async.private; + NTSTATUS status = smbcli_request_simple_recv(req); + + state->req_close = NULL; + + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE) || + NT_STATUS_EQUAL(status, NT_STATUS_LOCAL_DISCONNECT)) { + talloc_free(state->tree); + talloc_free(state->cli); + state->tree = NULL; + state->cli = NULL; + num_connected--; + DEBUG(0,("[%u] reopening connection to %s\n", + state->client_num, state->dest_host)); + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + return; + } + + if (!NT_STATUS_IS_OK(status)) { + close_failed++; + DEBUG(0,("[%u] close failed %d (fnum[%d]) - %s\n", + state->client_num, state->close_file_num, + state->close_fnum, + nt_errstr(status))); + return; + } + + DEBUG(2,("[%d] close completed %d (fnum[%d])\n", + state->client_num, state->close_file_num, + state->close_fnum)); +} + +static void echo_completion(struct smbcli_request *req) +{ + struct benchopen_state *state = (struct benchopen_state *)req->async.private; + NTSTATUS status = smbcli_request_simple_recv(req); + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE) || + NT_STATUS_EQUAL(status, NT_STATUS_LOCAL_DISCONNECT)) { + talloc_free(state->tree); + state->tree = NULL; + num_connected--; + DEBUG(0,("[%u] reopening connection to %s\n", + state->client_num, state->dest_host)); + talloc_free(state->te); + state->te = event_add_timed(state->ev, state->mem_ctx, + timeval_current_ofs(1,0), + reopen_connection, state); + } +} + +static void report_rate(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct benchopen_state *state = talloc_get_type(private_data, + struct benchopen_state); + int i; + for (i=0;i<nprocs;i++) { + printf("%5u ", (unsigned)(state[i].count - state[i].lastcount)); + state[i].lastcount = state[i].count; + } + printf("\r"); + fflush(stdout); + report_te = event_add_timed(ev, state, timeval_current_ofs(1, 0), + report_rate, state); + + /* send an echo on each interface to ensure it stays alive - this helps + with IP takeover */ + for (i=0;i<nprocs;i++) { + struct smb_echo p; + struct smbcli_request *req; + + if (!state[i].tree) { + continue; + } + + p.in.repeat_count = 1; + p.in.size = 0; + p.in.data = NULL; + req = smb_raw_echo_send(state[i].tree->session->transport, &p); + req->async.private = &state[i]; + req->async.fn = echo_completion; + } +} + +/* + benchmark open calls +*/ +bool torture_bench_open(struct torture_context *torture) +{ + bool ret = true; + TALLOC_CTX *mem_ctx = talloc_new(torture); + int i; + int timelimit = torture_setting_int(torture, "timelimit", 10); + struct timeval tv; + struct benchopen_state *state; + int total = 0; + int total_retries = 0; + int minops = 0; + bool progress=false; + + progress = torture_setting_bool(torture, "progress", true); + + nprocs = torture_setting_int(torture, "nprocs", 4); + + state = talloc_zero_array(mem_ctx, struct benchopen_state, nprocs); + + printf("Opening %d connections\n", nprocs); + for (i=0;i<nprocs;i++) { + state[i].tctx = torture; + state[i].mem_ctx = talloc_new(state); + state[i].client_num = i; + state[i].ev = torture->ev; + if (!torture_open_connection_ev(&state[i].cli, i, torture, torture->ev)) { + return false; + } + talloc_steal(mem_ctx, state); + state[i].tree = state[i].cli->tree; + state[i].dest_host = talloc_strdup(state[i].mem_ctx, + state[i].cli->tree->session->transport->socket->hostname); + state[i].dest_ports = talloc_array(state[i].mem_ctx, + const char *, 2); + state[i].dest_ports[0] = talloc_asprintf(state[i].dest_ports, + "%u", state[i].cli->tree->session->transport->socket->port); + state[i].dest_ports[1] = NULL; + state[i].called_name = talloc_strdup(state[i].mem_ctx, + state[i].cli->tree->session->transport->called.name); + state[i].service_type = talloc_strdup(state[i].mem_ctx, + state[i].cli->tree->device); + } + + num_connected = i; + + if (!torture_setup_dir(state[0].cli, BASEDIR)) { + goto failed; + } + + fnames = talloc_array(mem_ctx, char *, 3*nprocs); + for (i=0;i<3*nprocs;i++) { + fnames[i] = talloc_asprintf(fnames, "%s\\file%d.dat", BASEDIR, i); + } + + for (i=0;i<nprocs;i++) { + /* all connections start with the same file */ + state[i].next_file_num = 0; + state[i].open_fnum = -1; + state[i].close_fnum = -1; + next_open(&state[i]); + } + + tv = timeval_current(); + + if (progress) { + report_te = event_add_timed(torture->ev, state, timeval_current_ofs(1, 0), + report_rate, state); + } + + printf("Running for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + event_loop_once(torture->ev); + + if (open_failed) { + DEBUG(0,("open failed\n")); + goto failed; + } + if (close_failed) { + DEBUG(0,("open failed\n")); + goto failed; + } + } + + talloc_free(report_te); + if (progress) { + for (i=0;i<nprocs;i++) { + printf(" "); + } + printf("\r"); + } + + minops = state[0].count; + for (i=0;i<nprocs;i++) { + total += state[i].count; + total_retries += state[i].open_retries; + printf("[%d] %u ops (%u retries)\n", + i, state[i].count, state[i].open_retries); + if (state[i].count < minops) minops = state[i].count; + } + printf("%.2f ops/second (%d retries)\n", + total/timeval_elapsed(&tv), total_retries); + if (minops < 0.5*total/nprocs) { + printf("Failed: unbalanced open\n"); + goto failed; + } + + for (i=0;i<nprocs;i++) { + talloc_free(state[i].req_open); + talloc_free(state[i].req_close); + smb_raw_exit(state[i].tree->session); + } + + smbcli_deltree(state[0].tree, BASEDIR); + talloc_free(mem_ctx); + return ret; + +failed: + talloc_free(mem_ctx); + return false; +} diff --git a/source4/torture/raw/oplock.c b/source4/torture/raw/oplock.c new file mode 100644 index 0000000000..fd8d292980 --- /dev/null +++ b/source4/torture/raw/oplock.c @@ -0,0 +1,3030 @@ +/* + Unix SMB/CIFS implementation. + basic raw test suite for oplocks + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "lib/events/events.h" +#include "param/param.h" +#include "lib/cmdline/popt_common.h" +#include "libcli/resolve/resolve.h" + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +#define CHECK_RANGE(v, min, max) do { \ + if ((v) < (min) || (v) > (max)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got %d - should be between %d and %d\n", \ + __location__, #v, (int)v, (int)min, (int)max); \ + ret = false; \ + }} while (0) + +#define CHECK_STRMATCH(v, correct) do { \ + if (!v || strstr((v),(correct)) == NULL) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got '%s' - should be '%s'\n", \ + __location__, #v, v?v:"NULL", correct); \ + ret = false; \ + } \ +} while (0) + +#define CHECK_STATUS(tctx, status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + +static struct { + int fnum; + uint8_t level; + int count; + int failures; +} break_info; + +#define BASEDIR "\\test_oplock" + +/* + a handler function for oplock break requests. Ack it as a break to level II if possible +*/ +static bool oplock_handler_ack_to_given(struct smbcli_transport *transport, + uint16_t tid, uint16_t fnum, + uint8_t level, void *private) +{ + struct smbcli_tree *tree = (struct smbcli_tree *)private; + const char *name; + + break_info.fnum = fnum; + break_info.level = level; + break_info.count++; + + switch (level) { + case OPLOCK_BREAK_TO_LEVEL_II: + name = "level II"; + break; + case OPLOCK_BREAK_TO_NONE: + name = "none"; + break; + default: + name = "unknown"; + break_info.failures++; + } + printf("Acking to %s [0x%02X] in oplock handler\n", + name, level); + + return smbcli_oplock_ack(tree, fnum, level); +} + +/* + a handler function for oplock break requests. Ack it as a break to none +*/ +static bool oplock_handler_ack_to_none(struct smbcli_transport *transport, + uint16_t tid, uint16_t fnum, + uint8_t level, void *private) +{ + struct smbcli_tree *tree = (struct smbcli_tree *)private; + break_info.fnum = fnum; + break_info.level = level; + break_info.count++; + + printf("Acking to none in oplock handler\n"); + + return smbcli_oplock_ack(tree, fnum, OPLOCK_BREAK_TO_NONE); +} + +/* + a handler function for oplock break requests. Let it timeout +*/ +static bool oplock_handler_timeout(struct smbcli_transport *transport, + uint16_t tid, uint16_t fnum, + uint8_t level, void *private) +{ + break_info.fnum = fnum; + break_info.level = level; + break_info.count++; + + printf("Let oplock break timeout\n"); + return true; +} + +static void oplock_handler_close_recv(struct smbcli_request *req) +{ + NTSTATUS status; + status = smbcli_request_simple_recv(req); + if (!NT_STATUS_IS_OK(status)) { + printf("close failed in oplock_handler_close\n"); + break_info.failures++; + } +} + +/* + a handler function for oplock break requests - close the file +*/ +static bool oplock_handler_close(struct smbcli_transport *transport, uint16_t tid, + uint16_t fnum, uint8_t level, void *private) +{ + union smb_close io; + struct smbcli_tree *tree = (struct smbcli_tree *)private; + struct smbcli_request *req; + + break_info.fnum = fnum; + break_info.level = level; + break_info.count++; + + io.close.level = RAW_CLOSE_CLOSE; + io.close.in.file.fnum = fnum; + io.close.in.write_time = 0; + req = smb_raw_close_send(tree, &io); + if (req == NULL) { + printf("failed to send close in oplock_handler_close\n"); + return false; + } + + req->async.fn = oplock_handler_close_recv; + req->async.private = NULL; + + return true; +} + +static bool open_connection_no_level2_oplocks(struct torture_context *tctx, + struct smbcli_state **c) +{ + NTSTATUS status; + + struct smbcli_options options; + + lp_smbcli_options(tctx->lp_ctx, &options); + + options.use_level2_oplocks = false; + + status = smbcli_full_connection(tctx, c, + torture_setting_string(tctx, "host", NULL), + lp_smb_ports(tctx->lp_ctx), + torture_setting_string(tctx, "share", NULL), + NULL, cmdline_credentials, + lp_resolve_context(tctx->lp_ctx), + tctx->ev, &options); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to open connection - %s\n", nt_errstr(status)); + return false; + } + + return true; +} + +static bool test_raw_oplock_exclusive1(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_exclusive1.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_unlink unl; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE1: open a file with an exclusive oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + torture_comment(tctx, "a 2nd open should not cause a break\n"); + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "unlink it - should also be no break\n"); + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_exclusive2(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_exclusive2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_unlink unl; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE2: open a file with an exclusive oplock (share mode: all)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + torture_comment(tctx, "a 2nd open should cause a break to level 2\n"); + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + ZERO_STRUCT(break_info); + + /* now we have 2 level II oplocks... */ + torture_comment(tctx, "try to unlink it - should not cause a break, but a sharing violation\n"); + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "close 1st handle\n"); + smbcli_close(cli1->tree, fnum); + + torture_comment(tctx, "try to unlink it - should not cause a break, but a sharing violation\n"); + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "close 1st handle\n"); + smbcli_close(cli2->tree, fnum2); + + torture_comment(tctx, "unlink it\n"); + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_exclusive3(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_exclusive3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sfi; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE3: open a file with an exclusive oplock (share mode: none)\n"); + + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + torture_comment(tctx, "setpathinfo EOF should trigger a break to none\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sfi.generic.in.file.path = fname; + sfi.end_of_file_info.in.size = 100; + + status = smb_raw_setpathinfo(cli2->tree, &sfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_NONE); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_exclusive4(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_exclusive4.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE4: open with exclusive oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + torture_comment(tctx, "second open with attributes only shouldn't cause oplock break\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, NO_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_exclusive5(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_exclusive5.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE5: open with exclusive oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and NTCREATEX_DISP_OVERWRITE_IF dispostion causes oplock break\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_exclusive6(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname1 = BASEDIR "\\test_exclusive6_1.dat"; + const char *fname2 = BASEDIR "\\test_exclusive6_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_rename rn; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "EXCLUSIVE6: open a file with an exclusive oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | NTCREATEX_FLAGS_REQUEST_OPLOCK; + + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + torture_comment(tctx, "rename should not generate a break but get a sharing violation\n"); + ZERO_STRUCT(rn); + rn.generic.level = RAW_RENAME_RENAME; + rn.rename.in.pattern1 = fname1; + rn.rename.in.pattern2 = fname2; + rn.rename.in.attrib = 0; + + printf("trying rename while first file open\n"); + status = smb_raw_rename(cli2->tree, &rn); + + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch1(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch1.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_unlink unl; + uint16_t fnum=0; + char c = 0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH1: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "unlink should generate a break\n"); + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "2nd unlink should not generate a break\n"); + ZERO_STRUCT(break_info); + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + + CHECK_VAL(break_info.count, 0); + + torture_comment(tctx, "writing should generate a self break to none\n"); + smbcli_write(cli1->tree, fnum, 0, &c, 0, 1); + msleep(100); + smbcli_write(cli1->tree, fnum, 0, &c, 1, 1); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_NONE); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch2(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_unlink unl; + uint16_t fnum=0; + char c = 0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH2: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "unlink should generate a break, which we ack as break to none\n"); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_none, cli1->tree); + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "2nd unlink should not generate a break\n"); + ZERO_STRUCT(break_info); + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + + CHECK_VAL(break_info.count, 0); + + torture_comment(tctx, "writing should not generate a break\n"); + smbcli_write(cli1->tree, fnum, 0, &c, 0, 1); + msleep(100); + smbcli_write(cli1->tree, fnum, 0, &c, 1, 1); + + CHECK_VAL(break_info.count, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch3(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_unlink unl; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH3: if we close on break then the unlink can succeed\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_close, cli1->tree); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + unl.unlink.in.pattern = fname; + unl.unlink.in.attrib = 0; + ZERO_STRUCT(break_info); + status = smb_raw_unlink(cli2->tree, &unl); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch4(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch4.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_read rd; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH4: a self read should not cause a break\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + rd.read.level = RAW_READ_READ; + rd.read.in.file.fnum = fnum; + rd.read.in.count = 1; + rd.read.in.offset = 0; + rd.read.in.remaining = 0; + status = smb_raw_read(cli1->tree, &rd); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch5(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch5.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH5: a 2nd open should give a break\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch6(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch6.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + char c = 0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH6: a 2nd open should give a break to level II if the first open allowed shared read\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli2->tree); + + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_WRITE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + ZERO_STRUCT(break_info); + + torture_comment(tctx, "write should trigger a break to none on both\n"); + smbcli_write(cli1->tree, fnum, 0, &c, 0, 1); + msleep(100); + smbcli_write(cli1->tree, fnum, 0, &c, 1, 1); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch7(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch7.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH7: a 2nd open should get an oplock when we close instead of ack\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_close, cli1->tree); + + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum2); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli2->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch8(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch8.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH8: open with batch oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + torture_comment(tctx, "second open with attributes only shouldn't cause oplock break\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, NO_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch9(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch9.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + char c = 0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH9: open with attributes only can create file\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "Subsequent normal open should break oplock on attribute only open to level II\n"); + + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + smbcli_close(cli2->tree, fnum2); + + torture_comment(tctx, "third oplocked open should grant level2 without break\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli2->tree); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "write should trigger a break to none on both\n"); + smbcli_write(cli2->tree, fnum2, 0, &c, 0, 1); + + /* Now the oplock break request comes in. But right now we can't + * answer it. Do another write */ + + msleep(100); + smbcli_write(cli2->tree, fnum2, 0, &c, 1, 1); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch10(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch10.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH10: Open with oplock after a non-oplock open should grant level2\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.ntcreatex.out.oplock_level, 0); + + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli2->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + + torture_comment(tctx, "write should trigger a break to none\n"); + { + union smb_write wr; + wr.write.level = RAW_WRITE_WRITE; + wr.write.in.file.fnum = fnum; + wr.write.in.count = 1; + wr.write.in.offset = 0; + wr.write.in.remaining = 0; + wr.write.in.data = (const uint8_t *)"x"; + status = smb_raw_write(cli1->tree, &wr); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + } + + /* Now the oplock break request comes in. But right now we can't + * answer it. Do another write */ + + msleep(100); + + { + union smb_write wr; + wr.write.level = RAW_WRITE_WRITE; + wr.write.in.file.fnum = fnum; + wr.write.in.count = 1; + wr.write.in.offset = 0; + wr.write.in.remaining = 0; + wr.write.in.data = (const uint8_t *)"x"; + status = smb_raw_write(cli1->tree, &wr); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + } + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum2); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch11(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch11.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sfi; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* Test if a set-eof on pathname breaks an exclusive oplock. */ + torture_comment(tctx, "BATCH11: Test if setpathinfo set EOF breaks oplocks.\n"); + + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sfi.generic.in.file.path = fname; + sfi.end_of_file_info.in.size = 100; + + status = smb_raw_setpathinfo(cli2->tree, &sfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch12(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch12.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sfi; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* Test if a set-allocation size on pathname breaks an exclusive oplock. */ + torture_comment(tctx, "BATCH12: Test if setpathinfo allocation size breaks oplocks.\n"); + + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(sfi); + sfi.generic.level = SMB_SFILEINFO_ALLOCATION_INFORMATION; + sfi.generic.in.file.path = fname; + sfi.allocation_info.in.alloc_size = 65536 * 8; + + status = smb_raw_setpathinfo(cli2->tree, &sfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch13(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch13.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH13: open with batch oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and NTCREATEX_DISP_OVERWRITE dispostion causes oplock break\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch14(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch14.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH14: open with batch oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and NTCREATEX_DISP_SUPERSEDE dispostion causes oplock break\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch15(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch15.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* Test if a qpathinfo all info on pathname breaks a batch oplock. */ + torture_comment(tctx, "BATCH15: Test if qpathinfo all info breaks a batch oplock (should not).\n"); + + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.path = fname; + + status = smb_raw_pathinfo(cli2->tree, tctx, &qfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch16(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch16.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH16: open with batch oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and NTCREATEX_DISP_OVERWRITE_IF dispostion causes oplock break\n"); + + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.access_mask = SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|SEC_STD_SYNCHRONIZE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch17(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname1 = BASEDIR "\\test_batch17_1.dat"; + const char *fname2 = BASEDIR "\\test_batch17_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_rename rn; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "BATCH17: open a file with an batch oplock (share mode: none)\n"); + + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "rename should trigger a break\n"); + ZERO_STRUCT(rn); + rn.generic.level = RAW_RENAME_RENAME; + rn.rename.in.pattern1 = fname1; + rn.rename.in.pattern2 = fname2; + rn.rename.in.attrib = 0; + + printf("trying rename while first file open\n"); + status = smb_raw_rename(cli2->tree, &rn); + + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch18(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname1 = BASEDIR "\\test_batch18_1.dat"; + const char *fname2 = BASEDIR "\\test_batch18_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_rename rn; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "BATCH18: open a file with an batch oplock (share mode: none)\n"); + + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "ntrename should trigger a break\n"); + ZERO_STRUCT(rn); + rn.generic.level = RAW_RENAME_NTRENAME; + rn.ntrename.in.attrib = 0; + rn.ntrename.in.flags = RENAME_FLAG_RENAME; + rn.ntrename.in.old_name = fname1; + rn.ntrename.in.new_name = fname2; + printf("trying rename while first file open\n"); + status = smb_raw_rename(cli2->tree, &rn); + + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch19(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname1 = BASEDIR "\\test_batch19_1.dat"; + const char *fname2 = BASEDIR "\\test_batch19_2.dat"; + const char *fname3 = BASEDIR "\\test_batch19_3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + union smb_setfileinfo sfi; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + smbcli_unlink(cli1->tree, fname3); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "BATCH19: open a file with an batch oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "setpathinfo rename info should not trigger a break nor a violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.path = fname1; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname2+strlen(BASEDIR)+1; + + status = smb_raw_setpathinfo(cli2->tree, &sfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname2); + + torture_comment(tctx, "setfileinfo rename info should not trigger a break nor a violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.fnum = fnum; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname3+strlen(BASEDIR)+1; + + status = smb_raw_setfileinfo(cli1->tree, &sfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname3); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +/**************************************************** + Called from raw-rename - we need oplock handling for + this test so this is why it's in oplock.c, not rename.c +****************************************************/ + +bool test_trans2rename(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname1 = BASEDIR "\\test_trans2rename_1.dat"; + const char *fname2 = BASEDIR "\\test_trans2rename_2.dat"; + const char *fname3 = BASEDIR "\\test_trans2rename_3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + union smb_setfileinfo sfi; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + smbcli_unlink(cli1->tree, fname3); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "open a file with an exclusive oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + torture_comment(tctx, "setpathinfo rename info should not trigger a break nor a violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.path = fname1; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname2+strlen(BASEDIR)+1; + + status = smb_raw_setpathinfo(cli2->tree, &sfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname2); + + torture_comment(tctx, "setfileinfo rename info should not trigger a break nor a violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.fnum = fnum; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname3+strlen(BASEDIR)+1; + + status = smb_raw_setfileinfo(cli1->tree, &sfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname3); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +/**************************************************** + Called from raw-rename - we need oplock handling for + this test so this is why it's in oplock.c, not rename.c +****************************************************/ + +bool test_nttransrename(struct torture_context *tctx, struct smbcli_state *cli1) +{ + const char *fname1 = BASEDIR "\\test_nttransrename_1.dat"; + const char *fname2 = BASEDIR "\\test_nttransrename_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi, qpi; + union smb_rename rn; + uint16_t fnum=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = 0;/* ask for no access at all */; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "nttrans_rename: open a file with an exclusive oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, EXCLUSIVE_OPLOCK_RETURN); + + torture_comment(tctx, "nttrans_rename: should not trigger a break nor a share mode violation\n"); + ZERO_STRUCT(rn); + rn.generic.level = RAW_RENAME_NTTRANS; + rn.nttrans.in.file.fnum = fnum; + rn.nttrans.in.flags = 0; + rn.nttrans.in.new_name = fname2+strlen(BASEDIR)+1; + + status = smb_raw_rename(cli1->tree, &rn); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + /* w2k3 does nothing, it doesn't rename the file */ + torture_comment(tctx, "nttrans_rename: the server should have done nothing\n"); + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname1); + + ZERO_STRUCT(qpi); + qpi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qpi.generic.in.file.path = fname1; + + status = smb_raw_pathinfo(cli1->tree, tctx, &qpi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qpi.all_info.out.fname.s, fname1); + + ZERO_STRUCT(qpi); + qpi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qpi.generic.in.file.path = fname2; + + status = smb_raw_pathinfo(cli1->tree, tctx, &qpi); + CHECK_STATUS(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + torture_comment(tctx, "nttrans_rename: after closing the file the file is still not renamed\n"); + status = smbcli_close(cli1->tree, fnum); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + + ZERO_STRUCT(qpi); + qpi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qpi.generic.in.file.path = fname1; + + status = smb_raw_pathinfo(cli1->tree, tctx, &qpi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qpi.all_info.out.fname.s, fname1); + + ZERO_STRUCT(qpi); + qpi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qpi.generic.in.file.path = fname2; + + status = smb_raw_pathinfo(cli1->tree, tctx, &qpi); + CHECK_STATUS(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + torture_comment(tctx, "nttrans_rename: rename with an invalid handle gives NT_STATUS_INVALID_HANDLE\n"); + ZERO_STRUCT(rn); + rn.generic.level = RAW_RENAME_NTTRANS; + rn.nttrans.in.file.fnum = fnum+1; + rn.nttrans.in.flags = 0; + rn.nttrans.in.new_name = fname2+strlen(BASEDIR)+1; + + status = smb_raw_rename(cli1->tree, &rn); + + CHECK_STATUS(tctx, status, NT_STATUS_INVALID_HANDLE); + +done: + smb_raw_exit(cli1->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + + +static bool test_raw_oplock_batch20(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname1 = BASEDIR "\\test_batch20_1.dat"; + const char *fname2 = BASEDIR "\\test_batch20_2.dat"; + const char *fname3 = BASEDIR "\\test_batch20_3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + union smb_setfileinfo sfi; + uint16_t fnum=0,fnum2=0; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname1); + smbcli_unlink(cli1->tree, fname2); + smbcli_unlink(cli1->tree, fname3); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname1; + + torture_comment(tctx, "BATCH20: open a file with an batch oplock (share mode: all)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "setpathinfo rename info should not trigger a break nor a violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.path = fname1; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname2+strlen(BASEDIR)+1; + + status = smb_raw_setpathinfo(cli2->tree, &sfi); + + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname2); + + torture_comment(tctx, "open a file with the new name an batch oplock (share mode: all)\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.fname = fname2; + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + + torture_comment(tctx, "setfileinfo rename info should not trigger a break nor a violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.fnum = fnum; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname3+strlen(BASEDIR)+1; + + status = smb_raw_setfileinfo(cli1->tree, &sfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli1->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname3); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_ALL_INFORMATION; + qfi.generic.in.file.fnum = fnum2; + + status = smb_raw_fileinfo(cli2->tree, tctx, &qfi); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_STRMATCH(qfi.all_info.out.fname.s, fname3); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch21(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch21.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb_echo e; + uint16_t fnum=0; + char c = 0; + ssize_t wr; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH21: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "writing should not generate a break\n"); + wr = smbcli_write(cli1->tree, fnum, 0, &c, 0, 1); + CHECK_VAL(wr, 1); + CHECK_STATUS(tctx, smbcli_nt_error(cli1->tree), NT_STATUS_OK); + + ZERO_STRUCT(e); + e.in.repeat_count = 1; + status = smb_raw_echo(cli1->transport, &e); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + + CHECK_VAL(break_info.count, 0); + + smbcli_close(cli1->tree, fnum); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch22(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch22.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0; + struct timeval tv; + int timeout = torture_setting_int(tctx, "oplocktimeout", 30); + int te; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "BATCH22 disabled against samba3\n"); + } + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH22: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + torture_comment(tctx, "a 2nd open shoud not succeed after the oplock break timeout\n"); + tv = timeval_current(); + smbcli_oplock_handler(cli1->transport, oplock_handler_timeout, cli1->tree); + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_SHARING_VIOLATION); + te = (int)timeval_elapsed(&tv); + CHECK_RANGE(te, timeout - 1, timeout + 15); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 2nd open shoud succeed after the oplock release without break\n"); + tv = timeval_current(); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + te = (int)timeval_elapsed(&tv); + /* it should come in without delay */ + CHECK_RANGE(te+1, 0, timeout); + fnum2 = io.ntcreatex.out.file.fnum; + + CHECK_VAL(break_info.count, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli1->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch23(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch23.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum=0, fnum2=0,fnum3=0; + struct smbcli_state *cli3 = NULL; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "BATCH23 disabled against samba3\n"); + } + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + ret = open_connection_no_level2_oplocks(tctx, &cli3); + CHECK_VAL(ret, true); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH23: a open and ask for a batch oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli2->tree); + smbcli_oplock_handler(cli3->transport, oplock_handler_ack_to_given, cli3->tree); + + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_WRITE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli1->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 2nd open without level2 oplock support should generate a break to level2\n"); + status = smb_raw_open(cli3->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum3 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, NO_OPLOCK_RETURN); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 3rd open with level2 oplock support should not generate a break\n"); + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + + CHECK_VAL(break_info.count, 0); + + smbcli_close(cli1->tree, fnum); + smbcli_close(cli2->tree, fnum2); + smbcli_close(cli3->tree, fnum3); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smb_raw_exit(cli3->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +static bool test_raw_oplock_batch24(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2) +{ + const char *fname = BASEDIR "\\test_batch24.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + uint16_t fnum2=0,fnum3=0; + struct smbcli_state *cli3 = NULL; + + if (!torture_setup_dir(cli1, BASEDIR)) { + return false; + } + + /* cleanup */ + smbcli_unlink(cli1->tree, fname); + + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + + ret = open_connection_no_level2_oplocks(tctx, &cli3); + CHECK_VAL(ret, true); + + /* + base ntcreatex parms + */ + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + torture_comment(tctx, "BATCH24: a open without level support and ask for a batch oplock\n"); + ZERO_STRUCT(break_info); + smbcli_oplock_handler(cli1->transport, oplock_handler_ack_to_given, cli1->tree); + smbcli_oplock_handler(cli2->transport, oplock_handler_ack_to_given, cli2->tree); + smbcli_oplock_handler(cli3->transport, oplock_handler_ack_to_given, cli3->tree); + + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_WRITE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + status = smb_raw_open(cli3->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum3 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, BATCH_OPLOCK_RETURN); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 2nd open with level2 oplock support should generate a break to none\n"); + status = smb_raw_open(cli2->tree, tctx, &io); + CHECK_STATUS(tctx, status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + CHECK_VAL(io.ntcreatex.out.oplock_level, LEVEL_II_OPLOCK_RETURN); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.fnum, fnum3); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_NONE); + CHECK_VAL(break_info.failures, 0); + + smbcli_close(cli3->tree, fnum3); + smbcli_close(cli2->tree, fnum2); + +done: + smb_raw_exit(cli1->session); + smb_raw_exit(cli2->session); + smb_raw_exit(cli3->session); + smbcli_deltree(cli1->tree, BASEDIR); + return ret; +} + +/* + basic testing of oplocks +*/ +struct torture_suite *torture_raw_oplock(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "OPLOCK"); + + torture_suite_add_2smb_test(suite, "EXCLUSIVE1", test_raw_oplock_exclusive1); + torture_suite_add_2smb_test(suite, "EXCLUSIVE2", test_raw_oplock_exclusive2); + torture_suite_add_2smb_test(suite, "EXCLUSIVE3", test_raw_oplock_exclusive3); + torture_suite_add_2smb_test(suite, "EXCLUSIVE4", test_raw_oplock_exclusive4); + torture_suite_add_2smb_test(suite, "EXCLUSIVE5", test_raw_oplock_exclusive5); + torture_suite_add_2smb_test(suite, "EXCLUSIVE6", test_raw_oplock_exclusive6); + torture_suite_add_2smb_test(suite, "BATCH1", test_raw_oplock_batch1); + torture_suite_add_2smb_test(suite, "BATCH2", test_raw_oplock_batch2); + torture_suite_add_2smb_test(suite, "BATCH3", test_raw_oplock_batch3); + torture_suite_add_2smb_test(suite, "BATCH4", test_raw_oplock_batch4); + torture_suite_add_2smb_test(suite, "BATCH5", test_raw_oplock_batch5); + torture_suite_add_2smb_test(suite, "BATCH6", test_raw_oplock_batch6); + torture_suite_add_2smb_test(suite, "BATCH7", test_raw_oplock_batch7); + torture_suite_add_2smb_test(suite, "BATCH8", test_raw_oplock_batch8); + torture_suite_add_2smb_test(suite, "BATCH9", test_raw_oplock_batch9); + torture_suite_add_2smb_test(suite, "BATCH10", test_raw_oplock_batch10); + torture_suite_add_2smb_test(suite, "BATCH11", test_raw_oplock_batch11); + torture_suite_add_2smb_test(suite, "BATCH12", test_raw_oplock_batch12); + torture_suite_add_2smb_test(suite, "BATCH13", test_raw_oplock_batch13); + torture_suite_add_2smb_test(suite, "BATCH14", test_raw_oplock_batch14); + torture_suite_add_2smb_test(suite, "BATCH15", test_raw_oplock_batch15); + torture_suite_add_2smb_test(suite, "BATCH16", test_raw_oplock_batch16); + torture_suite_add_2smb_test(suite, "BATCH17", test_raw_oplock_batch17); + torture_suite_add_2smb_test(suite, "BATCH18", test_raw_oplock_batch18); + torture_suite_add_2smb_test(suite, "BATCH19", test_raw_oplock_batch19); + torture_suite_add_2smb_test(suite, "BATCH20", test_raw_oplock_batch20); + torture_suite_add_2smb_test(suite, "BATCH21", test_raw_oplock_batch21); + torture_suite_add_2smb_test(suite, "BATCH22", test_raw_oplock_batch22); + torture_suite_add_2smb_test(suite, "BATCH23", test_raw_oplock_batch23); + torture_suite_add_2smb_test(suite, "BATCH24", test_raw_oplock_batch24); + + return suite; +} + +/* + stress testing of oplocks +*/ +bool torture_bench_oplock(struct torture_context *torture) +{ + struct smbcli_state **cli; + bool ret = true; + TALLOC_CTX *mem_ctx = talloc_new(torture); + int torture_nprocs = torture_setting_int(torture, "nprocs", 4); + int i, count=0; + int timelimit = torture_setting_int(torture, "timelimit", 10); + union smb_open io; + struct timeval tv; + + cli = talloc_array(mem_ctx, struct smbcli_state *, torture_nprocs); + + torture_comment(torture, "Opening %d connections\n", torture_nprocs); + for (i=0;i<torture_nprocs;i++) { + if (!torture_open_connection_ev(&cli[i], i, torture, torture->ev)) { + return false; + } + talloc_steal(mem_ctx, cli[i]); + smbcli_oplock_handler(cli[i]->transport, oplock_handler_close, + cli[i]->tree); + } + + if (!torture_setup_dir(cli[0], BASEDIR)) { + ret = false; + goto done; + } + + io.ntcreatex.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = BASEDIR "\\test.dat"; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + + tv = timeval_current(); + + /* + we open the same file with SHARE_ACCESS_NONE from all the + connections in a round robin fashion. Each open causes an + oplock break on the previous connection, which is answered + by the oplock_handler_close() to close the file. + + This measures how fast we can pass on oplocks, and stresses + the oplock handling code + */ + torture_comment(torture, "Running for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + for (i=0;i<torture_nprocs;i++) { + NTSTATUS status; + + status = smb_raw_open(cli[i]->tree, mem_ctx, &io); + CHECK_STATUS(torture, status, NT_STATUS_OK); + count++; + } + + if (torture_setting_bool(torture, "progress", true)) { + torture_comment(torture, "%.2f ops/second\r", count/timeval_elapsed(&tv)); + } + } + + torture_comment(torture, "%.2f ops/second\n", count/timeval_elapsed(&tv)); + + smb_raw_exit(cli[torture_nprocs-1]->session); + +done: + smb_raw_exit(cli[0]->session); + smbcli_deltree(cli[0]->tree, BASEDIR); + talloc_free(mem_ctx); + return ret; +} + + +static struct hold_oplock_info { + const char *fname; + bool close_on_break; + uint32_t share_access; + uint16_t fnum; +} hold_info[] = { + { BASEDIR "\\notshared_close", true, + NTCREATEX_SHARE_ACCESS_NONE, }, + { BASEDIR "\\notshared_noclose", false, + NTCREATEX_SHARE_ACCESS_NONE, }, + { BASEDIR "\\shared_close", true, + NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, }, + { BASEDIR "\\shared_noclose", false, + NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, }, +}; + +static bool oplock_handler_hold(struct smbcli_transport *transport, + uint16_t tid, uint16_t fnum, uint8_t level, + void *private) +{ + struct smbcli_tree *tree = (struct smbcli_tree *)private; + struct hold_oplock_info *info; + int i; + + for (i=0;i<ARRAY_SIZE(hold_info);i++) { + if (hold_info[i].fnum == fnum) break; + } + + if (i == ARRAY_SIZE(hold_info)) { + printf("oplock break for unknown fnum %u\n", fnum); + return false; + } + + info = &hold_info[i]; + + if (info->close_on_break) { + printf("oplock break on %s - closing\n", + info->fname); + oplock_handler_close(transport, tid, fnum, level, private); + return true; + } + + printf("oplock break on %s - acking break\n", info->fname); + + return smbcli_oplock_ack(tree, fnum, OPLOCK_BREAK_TO_NONE); +} + + +/* + used for manual testing of oplocks - especially interaction with + other filesystems (such as NFS and local access) +*/ +bool torture_hold_oplock(struct torture_context *torture, + struct smbcli_state *cli) +{ + struct event_context *ev = + (struct event_context *)cli->transport->socket->event.ctx; + int i; + + printf("Setting up open files with oplocks in %s\n", BASEDIR); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + smbcli_oplock_handler(cli->transport, oplock_handler_hold, cli->tree); + + /* setup the files */ + for (i=0;i<ARRAY_SIZE(hold_info);i++) { + union smb_open io; + NTSTATUS status; + char c = 1; + + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = hold_info[i].share_access; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = hold_info[i].fname; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED | + NTCREATEX_FLAGS_REQUEST_OPLOCK | + NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + printf("opening %s\n", hold_info[i].fname); + + status = smb_raw_open(cli->tree, cli, &io); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to open %s - %s\n", + hold_info[i].fname, nt_errstr(status)); + return false; + } + + if (io.ntcreatex.out.oplock_level != BATCH_OPLOCK_RETURN) { + printf("Oplock not granted for %s - expected %d but got %d\n", + hold_info[i].fname, BATCH_OPLOCK_RETURN, + io.ntcreatex.out.oplock_level); + return false; + } + hold_info[i].fnum = io.ntcreatex.out.file.fnum; + + /* make the file non-zero size */ + if (smbcli_write(cli->tree, hold_info[i].fnum, 0, &c, 0, 1) != 1) { + printf("Failed to write to file\n"); + return false; + } + } + + printf("Waiting for oplock events\n"); + event_loop_wait(ev); + + return true; +} diff --git a/source4/torture/raw/pingpong.c b/source4/torture/raw/pingpong.c new file mode 100755 index 0000000000..ca56f190a9 --- /dev/null +++ b/source4/torture/raw/pingpong.c @@ -0,0 +1,267 @@ +/* + Unix SMB/CIFS implementation. + + ping pong test + + Copyright (C) Ronnie Sahlberg 2007 + + Significantly based on and borrowed from lockbench.c by + Copyright (C) Andrew Tridgell 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +/* + filename is specified by + --option=torture:filename=... + + number of locks is specified by + --option=torture:num_locks=... + + locktimeout is specified in ms by + --option=torture:locktimeout=... + + default is 100 seconds + if set to 0 pingpong will instead loop trying the lock operation + over and over until it completes. + + reading from the file can be enabled with + --option=torture:read=true + + writing to the file can be enabled with + --option=torture:write=true + +*/ +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "param/param.h" + +static void lock_byte(struct smbcli_state *cli, int fd, int offset, int lock_timeout) +{ + union smb_lock io; + struct smb_lock_entry lock; + NTSTATUS status; + +try_again: + ZERO_STRUCT(lock); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + + lock.count = 1; + lock.offset = offset; + lock.pid = cli->tree->session->pid; + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = lock_timeout; + io.lockx.in.locks = &lock; + io.lockx.in.file.fnum = fd; + + status = smb_raw_lock(cli->tree, &io); + + /* If we dont use timeouts and we got file lock conflict + just try the lock again. + */ + if (lock_timeout==0) { + if ( (NT_STATUS_EQUAL(NT_STATUS_FILE_LOCK_CONFLICT, status)) + ||(NT_STATUS_EQUAL(NT_STATUS_LOCK_NOT_GRANTED, status)) ) { + goto try_again; + } + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Lock failed\n")); + exit(1); + } +} + +static void unlock_byte(struct smbcli_state *cli, int fd, int offset) +{ + union smb_lock io; + struct smb_lock_entry lock; + NTSTATUS status; + + ZERO_STRUCT(lock); + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + + lock.count = 1; + lock.offset = offset; + lock.pid = cli->tree->session->pid; + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 100000; + io.lockx.in.locks = &lock; + io.lockx.in.file.fnum = fd; + + status = smb_raw_lock(cli->tree, &io); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unlock failed\n")); + exit(1); + } +} + +static void write_byte(struct smbcli_state *cli, int fd, uint8_t c, int offset) +{ + union smb_write io; + NTSTATUS status; + + io.generic.level = RAW_WRITE_WRITEX; + io.writex.in.file.fnum = fd; + io.writex.in.offset = offset; + io.writex.in.wmode = 0; + io.writex.in.remaining = 0; + io.writex.in.count = 1; + io.writex.in.data = &c; + + status = smb_raw_write(cli->tree, &io); + if (!NT_STATUS_IS_OK(status)) { + printf("write failed\n"); + exit(1); + } +} + +static void read_byte(struct smbcli_state *cli, int fd, uint8_t *c, int offset) +{ + union smb_read io; + NTSTATUS status; + + io.generic.level = RAW_READ_READX; + io.readx.in.file.fnum = fd; + io.readx.in.mincnt = 1; + io.readx.in.maxcnt = 1; + io.readx.in.offset = offset; + io.readx.in.remaining = 0; + io.readx.in.read_for_execute = false; + io.readx.out.data = c; + + status = smb_raw_read(cli->tree, &io); + if (!NT_STATUS_IS_OK(status)) { + printf("read failed\n"); + exit(1); + } +} + + +static struct timeval tp1, tp2; + +static void start_timer(void) +{ + gettimeofday(&tp1, NULL); +} + +static double end_timer(void) +{ + gettimeofday(&tp2, NULL); + return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) - + (tp1.tv_sec + (tp1.tv_usec*1.0e-6)); +} + +/* + ping pong +*/ +bool torture_ping_pong(struct torture_context *torture) +{ + const char *fn; + int num_locks; + TALLOC_CTX *mem_ctx = talloc_new(torture); + static bool do_reads; + static bool do_writes; + int lock_timeout; + int fd; + struct smbcli_state *cli; + int i; + uint8_t incr=0, last_incr=0; + uint8_t *val; + int count, loops; + + fn = torture_setting_string(torture, "filename", NULL); + if (fn == NULL) { + DEBUG(0,("You must specify the filename using --option=torture:filename=...\n")); + return false; + } + + num_locks = torture_setting_int(torture, "num_locks", -1); + if (num_locks == -1) { + DEBUG(0,("You must specify num_locks using --option=torture:num_locks=...\n")); + return false; + } + + do_reads = torture_setting_bool(torture, "read", false); + do_writes = torture_setting_bool(torture, "write", false); + lock_timeout = torture_setting_int(torture, "lock_timeout", 100000); + + if (!torture_open_connection(&cli, torture, 0)) { + DEBUG(0,("Could not open connection\n")); + return false; + } + + fd = smbcli_open(cli->tree, fn, O_RDWR|O_CREAT, DENY_NONE); + if (fd == -1) { + printf("Failed to open %s\n", fn); + exit(1); + } + + write_byte(cli, fd, 0, num_locks); + lock_byte(cli, fd, 0, lock_timeout); + + + start_timer(); + val = talloc_zero_array(mem_ctx, uint8_t, num_locks); + i = 0; + count = 0; + loops = 0; + while (1) { + lock_byte(cli, fd, (i+1)%num_locks, lock_timeout); + + if (do_reads) { + uint8_t c; + read_byte(cli, fd, &c, i); + incr = c-val[i]; + val[i] = c; + } + + if (do_writes) { + uint8_t c = val[i] + 1; + write_byte(cli, fd, c, i); + } + + unlock_byte(cli, fd, i); + + i = (i+1)%num_locks; + count++; + if (loops>num_locks && incr!=last_incr) { + last_incr = incr; + printf("data increment = %u\n", incr); + fflush(stdout); + } + if (end_timer() > 1.0) { + printf("%8u locks/sec\r", + (unsigned)(2*count/end_timer())); + fflush(stdout); + start_timer(); + count=0; + } + loops++; + } + + talloc_free(mem_ctx); + return true; +} + diff --git a/source4/torture/raw/qfileinfo.c b/source4/torture/raw/qfileinfo.c new file mode 100644 index 0000000000..c7673be526 --- /dev/null +++ b/source4/torture/raw/qfileinfo.c @@ -0,0 +1,865 @@ +/* + Unix SMB/CIFS implementation. + RAW_FILEINFO_* individual test suite + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "librpc/rpc/dcerpc.h" +#include "torture/rpc/rpc.h" +#include "torture/raw/proto.h" +#include "param/param.h" + +static struct { + const char *name; + enum smb_fileinfo_level level; + uint_t only_paths:1; + uint_t only_handles:1; + uint32_t capability_mask; + uint_t expected_ipc_access_denied:1; + NTSTATUS expected_ipc_fnum_status; + NTSTATUS fnum_status, fname_status; + union smb_fileinfo fnum_finfo, fname_finfo; +} levels[] = { + { .name = "GETATTR", + .level = RAW_FILEINFO_GETATTR, + .only_paths = 1, + .only_handles = 0, + .expected_ipc_access_denied = 1}, + { .name ="GETATTRE", + .level = RAW_FILEINFO_GETATTRE, + .only_paths = 0, + .only_handles = 1 }, + { .name ="STANDARD", + .level = RAW_FILEINFO_STANDARD, }, + { .name ="EA_SIZE", + .level = RAW_FILEINFO_EA_SIZE }, + { .name ="ALL_EAS", + .level = RAW_FILEINFO_ALL_EAS, + .expected_ipc_fnum_status = NT_STATUS_ACCESS_DENIED, + }, + { .name ="IS_NAME_VALID", + .level = RAW_FILEINFO_IS_NAME_VALID, + .only_paths = 1, + .only_handles = 0 }, + { .name ="BASIC_INFO", + .level = RAW_FILEINFO_BASIC_INFO }, + { .name ="STANDARD_INFO", + .level = RAW_FILEINFO_STANDARD_INFO }, + { .name ="EA_INFO", + .level = RAW_FILEINFO_EA_INFO }, + { .name ="NAME_INFO", + .level = RAW_FILEINFO_NAME_INFO }, + { .name ="ALL_INFO", + .level = RAW_FILEINFO_ALL_INFO }, + { .name ="ALT_NAME_INFO", + .level = RAW_FILEINFO_ALT_NAME_INFO, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name ="STREAM_INFO", + .level = RAW_FILEINFO_STREAM_INFO, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name ="COMPRESSION_INFO", + .level = RAW_FILEINFO_COMPRESSION_INFO, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name ="UNIX_BASIC_INFO", + .level = RAW_FILEINFO_UNIX_BASIC, + .only_paths = 0, + .only_handles = 0, + .capability_mask = CAP_UNIX}, + { .name ="UNIX_LINK_INFO", + .level = RAW_FILEINFO_UNIX_LINK, + .only_paths = 0, + .only_handles = 0, + .capability_mask = CAP_UNIX}, + { .name ="BASIC_INFORMATION", + .level = RAW_FILEINFO_BASIC_INFORMATION }, + { .name ="STANDARD_INFORMATION", + .level = RAW_FILEINFO_STANDARD_INFORMATION }, + { .name ="INTERNAL_INFORMATION", + .level = RAW_FILEINFO_INTERNAL_INFORMATION }, + { .name ="EA_INFORMATION", + .level = RAW_FILEINFO_EA_INFORMATION }, + { .name = "ACCESS_INFORMATION", + .level = RAW_FILEINFO_ACCESS_INFORMATION }, + { .name = "NAME_INFORMATION", + .level = RAW_FILEINFO_NAME_INFORMATION }, + { .name ="POSITION_INFORMATION", + .level = RAW_FILEINFO_POSITION_INFORMATION }, + { .name ="MODE_INFORMATION", + .level = RAW_FILEINFO_MODE_INFORMATION }, + { .name ="ALIGNMENT_INFORMATION", + .level = RAW_FILEINFO_ALIGNMENT_INFORMATION }, + { .name ="ALL_INFORMATION", + .level = RAW_FILEINFO_ALL_INFORMATION }, + { .name ="ALT_NAME_INFORMATION", + .level = RAW_FILEINFO_ALT_NAME_INFORMATION, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name ="STREAM_INFORMATION", + .level = RAW_FILEINFO_STREAM_INFORMATION, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name = "COMPRESSION_INFORMATION", + .level = RAW_FILEINFO_COMPRESSION_INFORMATION, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name ="NETWORK_OPEN_INFORMATION", + .level = RAW_FILEINFO_NETWORK_OPEN_INFORMATION, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { .name = "ATTRIBUTE_TAG_INFORMATION", + .level = RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION, + .expected_ipc_fnum_status = NT_STATUS_INVALID_PARAMETER + }, + { NULL } +}; + +/* + compare a dos time (2 second resolution) to a nt time +*/ +static int dos_nt_time_cmp(time_t t, NTTIME nt) +{ + time_t t2 = nt_time_to_unix(nt); + if (abs(t2 - t) <= 2) return 0; + return t2 - t; +} + + +/* + find a level in the levels[] table +*/ +static union smb_fileinfo *fnum_find(const char *name) +{ + int i; + for (i=0; levels[i].name; i++) { + if (NT_STATUS_IS_OK(levels[i].fnum_status) && + strcmp(name, levels[i].name) == 0 && + !levels[i].only_paths) { + return &levels[i].fnum_finfo; + } + } + return NULL; +} + +/* + find a level in the levels[] table +*/ +static union smb_fileinfo *fname_find(bool is_ipc, const char *name) +{ + int i; + if (is_ipc) { + return NULL; + } + for (i=0; levels[i].name; i++) { + if (NT_STATUS_IS_OK(levels[i].fname_status) && + strcmp(name, levels[i].name) == 0 && + !levels[i].only_handles) { + return &levels[i].fname_finfo; + } + } + return NULL; +} + +/* local macros to make the code below more readable */ +#define VAL_EQUAL(n1, v1, n2, v2) do {if (s1->n1.out.v1 != s2->n2.out.v2) { \ + printf("%s/%s [%u] != %s/%s [%u] at %s(%d)\n", \ + #n1, #v1, (uint_t)s1->n1.out.v1, \ + #n2, #v2, (uint_t)s2->n2.out.v2, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +#define STR_EQUAL(n1, v1, n2, v2) do {if (strcmp_safe(s1->n1.out.v1.s, s2->n2.out.v2.s) || \ + s1->n1.out.v1.private_length != s2->n2.out.v2.private_length) { \ + printf("%s/%s [%s/%d] != %s/%s [%s/%d] at %s(%d)\n", \ + #n1, #v1, s1->n1.out.v1.s, s1->n1.out.v1.private_length, \ + #n2, #v2, s2->n2.out.v2.s, s2->n2.out.v2.private_length, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +#define STRUCT_EQUAL(n1, v1, n2, v2) do {if (memcmp(&s1->n1.out.v1,&s2->n2.out.v2,sizeof(s1->n1.out.v1))) { \ + printf("%s/%s != %s/%s at %s(%d)\n", \ + #n1, #v1, \ + #n2, #v2, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +/* used to find hints on unknown values - and to make sure + we zero-fill */ +#if 0 /* unused */ +#define VAL_UNKNOWN(n1, v1) do {if (s1->n1.out.v1 != 0) { \ + printf("%s/%s non-zero unknown - %u (0x%x) at %s(%d)\n", \ + #n1, #v1, \ + (uint_t)s1->n1.out.v1, \ + (uint_t)s1->n1.out.v1, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) +#endif + +/* basic testing of all RAW_FILEINFO_* calls + for each call we test that it succeeds, and where possible test + for consistency between the calls. +*/ +static bool torture_raw_qfileinfo_internals(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smbcli_tree *tree, + int fnum, const char *fname, + bool is_ipc) +{ + int i; + bool ret = true; + int count; + union smb_fileinfo *s1, *s2; + NTTIME correct_time; + uint64_t correct_size; + uint32_t correct_attrib; + const char *correct_name; + bool skip_streams = false; + + /* scan all the fileinfo and pathinfo levels */ + for (i=0; levels[i].name; i++) { + if (!levels[i].only_paths) { + levels[i].fnum_finfo.generic.level = levels[i].level; + levels[i].fnum_finfo.generic.in.file.fnum = fnum; + levels[i].fnum_status = smb_raw_fileinfo(tree, mem_ctx, + &levels[i].fnum_finfo); + } + + if (!levels[i].only_handles) { + levels[i].fname_finfo.generic.level = levels[i].level; + levels[i].fname_finfo.generic.in.file.path = talloc_strdup(mem_ctx, fname); + levels[i].fname_status = smb_raw_pathinfo(tree, mem_ctx, + &levels[i].fname_finfo); + } + } + + /* check for completely broken levels */ + for (count=i=0; levels[i].name; i++) { + uint32_t cap = tree->session->transport->negotiate.capabilities; + /* see if this server claims to support this level */ + if ((cap & levels[i].capability_mask) != levels[i].capability_mask) { + continue; + } + + if (is_ipc) { + if (levels[i].expected_ipc_access_denied && NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, levels[i].fname_status)) { + } else if (!levels[i].only_handles && !NT_STATUS_EQUAL(NT_STATUS_INVALID_DEVICE_REQUEST, levels[i].fname_status)) { + printf("ERROR: fname level %s failed, expected NT_STATUS_INVALID_DEVICE_REQUEST - %s\n", + levels[i].name, nt_errstr(levels[i].fname_status)); + count++; + } + if (!levels[i].only_paths && !NT_STATUS_EQUAL(levels[i].expected_ipc_fnum_status, levels[i].fnum_status)) { + printf("ERROR: fnum level %s failed, expected %s - %s\n", + levels[i].name, nt_errstr(levels[i].expected_ipc_fnum_status), + nt_errstr(levels[i].fnum_status)); + count++; + } + } else { + if (!levels[i].only_paths && !NT_STATUS_IS_OK(levels[i].fnum_status)) { + printf("ERROR: fnum level %s failed - %s\n", + levels[i].name, nt_errstr(levels[i].fnum_status)); + count++; + } + if (!levels[i].only_handles && !NT_STATUS_IS_OK(levels[i].fname_status)) { + printf("ERROR: fname level %s failed - %s\n", + levels[i].name, nt_errstr(levels[i].fname_status)); + count++; + } + } + + } + + if (count != 0) { + ret = false; + printf("%d levels failed\n", count); + if (count > 35) { + torture_fail(torture, "too many level failures - giving up"); + } + } + + /* see if we can do streams */ + s1 = fnum_find("STREAM_INFO"); + if (!s1 || s1->stream_info.out.num_streams == 0) { + if (!is_ipc) { + printf("STREAM_INFO broken (%d) - skipping streams checks\n", + s1 ? s1->stream_info.out.num_streams : -1); + } + skip_streams = true; + } + + + /* this code is incredibly repititive but doesn't lend itself to loops, so + we use lots of macros to make it less painful */ + + /* first off we check the levels that are supposed to be aliases. It will be quite rare for + this code to fail, but we need to check it for completeness */ + + + +#define ALIAS_CHECK(sname1, sname2) \ + do { \ + s1 = fnum_find(sname1); s2 = fnum_find(sname2); \ + if (s1 && s2) { INFO_CHECK } \ + s1 = fname_find(is_ipc, sname1); s2 = fname_find(is_ipc, sname2); \ + if (s1 && s2) { INFO_CHECK } \ + s1 = fnum_find(sname1); s2 = fname_find(is_ipc, sname2); \ + if (s1 && s2) { INFO_CHECK } \ + } while (0) + +#define INFO_CHECK \ + STRUCT_EQUAL(basic_info, create_time, basic_info, create_time); \ + STRUCT_EQUAL(basic_info, access_time, basic_info, access_time); \ + STRUCT_EQUAL(basic_info, write_time, basic_info, write_time); \ + STRUCT_EQUAL(basic_info, change_time, basic_info, change_time); \ + VAL_EQUAL (basic_info, attrib, basic_info, attrib); + + ALIAS_CHECK("BASIC_INFO", "BASIC_INFORMATION"); + +#undef INFO_CHECK +#define INFO_CHECK \ + VAL_EQUAL(standard_info, alloc_size, standard_info, alloc_size); \ + VAL_EQUAL(standard_info, size, standard_info, size); \ + VAL_EQUAL(standard_info, nlink, standard_info, nlink); \ + VAL_EQUAL(standard_info, delete_pending, standard_info, delete_pending); \ + VAL_EQUAL(standard_info, directory, standard_info, directory); + + ALIAS_CHECK("STANDARD_INFO", "STANDARD_INFORMATION"); + +#undef INFO_CHECK +#define INFO_CHECK \ + VAL_EQUAL(ea_info, ea_size, ea_info, ea_size); + + ALIAS_CHECK("EA_INFO", "EA_INFORMATION"); + +#undef INFO_CHECK +#define INFO_CHECK \ + STR_EQUAL(name_info, fname, name_info, fname); + + ALIAS_CHECK("NAME_INFO", "NAME_INFORMATION"); + +#undef INFO_CHECK +#define INFO_CHECK \ + STRUCT_EQUAL(all_info, create_time, all_info, create_time); \ + STRUCT_EQUAL(all_info, access_time, all_info, access_time); \ + STRUCT_EQUAL(all_info, write_time, all_info, write_time); \ + STRUCT_EQUAL(all_info, change_time, all_info, change_time); \ + VAL_EQUAL(all_info, attrib, all_info, attrib); \ + VAL_EQUAL(all_info, alloc_size, all_info, alloc_size); \ + VAL_EQUAL(all_info, size, all_info, size); \ + VAL_EQUAL(all_info, nlink, all_info, nlink); \ + VAL_EQUAL(all_info, delete_pending, all_info, delete_pending); \ + VAL_EQUAL(all_info, directory, all_info, directory); \ + VAL_EQUAL(all_info, ea_size, all_info, ea_size); \ + STR_EQUAL(all_info, fname, all_info, fname); + + ALIAS_CHECK("ALL_INFO", "ALL_INFORMATION"); + +#undef INFO_CHECK +#define INFO_CHECK \ + VAL_EQUAL(compression_info, compressed_size,compression_info, compressed_size); \ + VAL_EQUAL(compression_info, format, compression_info, format); \ + VAL_EQUAL(compression_info, unit_shift, compression_info, unit_shift); \ + VAL_EQUAL(compression_info, chunk_shift, compression_info, chunk_shift); \ + VAL_EQUAL(compression_info, cluster_shift, compression_info, cluster_shift); + + ALIAS_CHECK("COMPRESSION_INFO", "COMPRESSION_INFORMATION"); + + +#undef INFO_CHECK +#define INFO_CHECK \ + STR_EQUAL(alt_name_info, fname, alt_name_info, fname); + + ALIAS_CHECK("ALT_NAME_INFO", "ALT_NAME_INFORMATION"); + + +#define TIME_CHECK_NT(sname, stype, tfield) do { \ + s1 = fnum_find(sname); \ + if (s1 && memcmp(&s1->stype.out.tfield, &correct_time, sizeof(correct_time)) != 0) { \ + printf("(%d) handle %s/%s incorrect - %s should be %s\n", __LINE__, #stype, #tfield, \ + nt_time_string(mem_ctx, s1->stype.out.tfield), \ + nt_time_string(mem_ctx, correct_time)); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && memcmp(&s1->stype.out.tfield, &correct_time, sizeof(correct_time)) != 0) { \ + printf("(%d) path %s/%s incorrect - %s should be %s\n", __LINE__, #stype, #tfield, \ + nt_time_string(mem_ctx, s1->stype.out.tfield), \ + nt_time_string(mem_ctx, correct_time)); \ + ret = false; \ + }} while (0) + +#define TIME_CHECK_DOS(sname, stype, tfield) do { \ + s1 = fnum_find(sname); \ + if (s1 && dos_nt_time_cmp(s1->stype.out.tfield, correct_time) != 0) { \ + printf("(%d) handle %s/%s incorrect - %s should be %s\n", __LINE__, #stype, #tfield, \ + timestring(mem_ctx, s1->stype.out.tfield), \ + nt_time_string(mem_ctx, correct_time)); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && dos_nt_time_cmp(s1->stype.out.tfield, correct_time) != 0) { \ + printf("(%d) path %s/%s incorrect - %s should be %s\n", __LINE__, #stype, #tfield, \ + timestring(mem_ctx, s1->stype.out.tfield), \ + nt_time_string(mem_ctx, correct_time)); \ + ret = false; \ + }} while (0) + +#if 0 /* unused */ +#define TIME_CHECK_UNX(sname, stype, tfield) do { \ + s1 = fnum_find(sname); \ + if (s1 && unx_nt_time_cmp(s1->stype.out.tfield, correct_time) != 0) { \ + printf("(%d) handle %s/%s incorrect - %s should be %s\n", __LINE__, #stype, #tfield, \ + timestring(mem_ctx, s1->stype.out.tfield), \ + nt_time_string(mem_ctx, correct_time)); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && unx_nt_time_cmp(s1->stype.out.tfield, correct_time) != 0) { \ + printf("(%d) path %s/%s incorrect - %s should be %s\n", __LINE__, #stype, #tfield, \ + timestring(mem_ctx, s1->stype.out.tfield), \ + nt_time_string(mem_ctx, correct_time)); \ + ret = false; \ + }} while (0) +#endif + + /* now check that all the times that are supposed to be equal are correct */ + s1 = fnum_find("BASIC_INFO"); + correct_time = s1->basic_info.out.create_time; + torture_comment(torture, "create_time: %s\n", nt_time_string(mem_ctx, correct_time)); + + TIME_CHECK_NT ("BASIC_INFO", basic_info, create_time); + TIME_CHECK_NT ("BASIC_INFORMATION", basic_info, create_time); + TIME_CHECK_DOS("GETATTRE", getattre, create_time); + TIME_CHECK_DOS("STANDARD", standard, create_time); + TIME_CHECK_DOS("EA_SIZE", ea_size, create_time); + TIME_CHECK_NT ("ALL_INFO", all_info, create_time); + TIME_CHECK_NT ("NETWORK_OPEN_INFORMATION", network_open_information, create_time); + + s1 = fnum_find("BASIC_INFO"); + correct_time = s1->basic_info.out.access_time; + torture_comment(torture, "access_time: %s\n", nt_time_string(mem_ctx, correct_time)); + + TIME_CHECK_NT ("BASIC_INFO", basic_info, access_time); + TIME_CHECK_NT ("BASIC_INFORMATION", basic_info, access_time); + TIME_CHECK_DOS("GETATTRE", getattre, access_time); + TIME_CHECK_DOS("STANDARD", standard, access_time); + TIME_CHECK_DOS("EA_SIZE", ea_size, access_time); + TIME_CHECK_NT ("ALL_INFO", all_info, access_time); + TIME_CHECK_NT ("NETWORK_OPEN_INFORMATION", network_open_information, access_time); + + s1 = fnum_find("BASIC_INFO"); + correct_time = s1->basic_info.out.write_time; + torture_comment(torture, "write_time : %s\n", nt_time_string(mem_ctx, correct_time)); + + TIME_CHECK_NT ("BASIC_INFO", basic_info, write_time); + TIME_CHECK_NT ("BASIC_INFORMATION", basic_info, write_time); + TIME_CHECK_DOS("GETATTR", getattr, write_time); + TIME_CHECK_DOS("GETATTRE", getattre, write_time); + TIME_CHECK_DOS("STANDARD", standard, write_time); + TIME_CHECK_DOS("EA_SIZE", ea_size, write_time); + TIME_CHECK_NT ("ALL_INFO", all_info, write_time); + TIME_CHECK_NT ("NETWORK_OPEN_INFORMATION", network_open_information, write_time); + + s1 = fnum_find("BASIC_INFO"); + correct_time = s1->basic_info.out.change_time; + torture_comment(torture, "change_time: %s\n", nt_time_string(mem_ctx, correct_time)); + + TIME_CHECK_NT ("BASIC_INFO", basic_info, change_time); + TIME_CHECK_NT ("BASIC_INFORMATION", basic_info, change_time); + TIME_CHECK_NT ("ALL_INFO", all_info, change_time); + TIME_CHECK_NT ("NETWORK_OPEN_INFORMATION", network_open_information, change_time); + + +#define SIZE_CHECK(sname, stype, tfield) do { \ + s1 = fnum_find(sname); \ + if (s1 && s1->stype.out.tfield != correct_size) { \ + printf("(%d) handle %s/%s incorrect - %u should be %u\n", __LINE__, #stype, #tfield, \ + (uint_t)s1->stype.out.tfield, \ + (uint_t)correct_size); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && s1->stype.out.tfield != correct_size) { \ + printf("(%d) path %s/%s incorrect - %u should be %u\n", __LINE__, #stype, #tfield, \ + (uint_t)s1->stype.out.tfield, \ + (uint_t)correct_size); \ + ret = false; \ + }} while (0) + + s1 = fnum_find("STANDARD_INFO"); + correct_size = s1->standard_info.out.size; + torture_comment(torture, "size: %u\n", (uint_t)correct_size); + + SIZE_CHECK("GETATTR", getattr, size); + SIZE_CHECK("GETATTRE", getattre, size); + SIZE_CHECK("STANDARD", standard, size); + SIZE_CHECK("EA_SIZE", ea_size, size); + SIZE_CHECK("STANDARD_INFO", standard_info, size); + SIZE_CHECK("STANDARD_INFORMATION", standard_info, size); + SIZE_CHECK("ALL_INFO", all_info, size); + SIZE_CHECK("ALL_INFORMATION", all_info, size); + SIZE_CHECK("COMPRESSION_INFO", compression_info, compressed_size); + SIZE_CHECK("COMPRESSION_INFORMATION", compression_info, compressed_size); + SIZE_CHECK("NETWORK_OPEN_INFORMATION", network_open_information, size); + if (!skip_streams) { + SIZE_CHECK("STREAM_INFO", stream_info, streams[0].size); + SIZE_CHECK("STREAM_INFORMATION", stream_info, streams[0].size); + } + + + s1 = fnum_find("STANDARD_INFO"); + correct_size = s1->standard_info.out.alloc_size; + torture_comment(torture, "alloc_size: %u\n", (uint_t)correct_size); + + SIZE_CHECK("GETATTRE", getattre, alloc_size); + SIZE_CHECK("STANDARD", standard, alloc_size); + SIZE_CHECK("EA_SIZE", ea_size, alloc_size); + SIZE_CHECK("STANDARD_INFO", standard_info, alloc_size); + SIZE_CHECK("STANDARD_INFORMATION", standard_info, alloc_size); + SIZE_CHECK("ALL_INFO", all_info, alloc_size); + SIZE_CHECK("ALL_INFORMATION", all_info, alloc_size); + SIZE_CHECK("NETWORK_OPEN_INFORMATION", network_open_information, alloc_size); + if (!skip_streams) { + SIZE_CHECK("STREAM_INFO", stream_info, streams[0].alloc_size); + SIZE_CHECK("STREAM_INFORMATION", stream_info, streams[0].alloc_size); + } + +#define ATTRIB_CHECK(sname, stype, tfield) do { \ + s1 = fnum_find(sname); \ + if (s1 && s1->stype.out.tfield != correct_attrib) { \ + printf("(%d) handle %s/%s incorrect - 0x%x should be 0x%x\n", __LINE__, #stype, #tfield, \ + (uint_t)s1->stype.out.tfield, \ + (uint_t)correct_attrib); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && s1->stype.out.tfield != correct_attrib) { \ + printf("(%d) path %s/%s incorrect - 0x%x should be 0x%x\n", __LINE__, #stype, #tfield, \ + (uint_t)s1->stype.out.tfield, \ + (uint_t)correct_attrib); \ + ret = false; \ + }} while (0) + + s1 = fnum_find("BASIC_INFO"); + correct_attrib = s1->basic_info.out.attrib; + torture_comment(torture, "attrib: 0x%x\n", (uint_t)correct_attrib); + + ATTRIB_CHECK("GETATTR", getattr, attrib); + if (!is_ipc) { + ATTRIB_CHECK("GETATTRE", getattre, attrib); + ATTRIB_CHECK("STANDARD", standard, attrib); + ATTRIB_CHECK("EA_SIZE", ea_size, attrib); + } + ATTRIB_CHECK("BASIC_INFO", basic_info, attrib); + ATTRIB_CHECK("BASIC_INFORMATION", basic_info, attrib); + ATTRIB_CHECK("ALL_INFO", all_info, attrib); + ATTRIB_CHECK("ALL_INFORMATION", all_info, attrib); + ATTRIB_CHECK("NETWORK_OPEN_INFORMATION", network_open_information, attrib); + ATTRIB_CHECK("ATTRIBUTE_TAG_INFORMATION", attribute_tag_information, attrib); + + correct_name = fname; + torture_comment(torture, "name: %s\n", correct_name); + +#define NAME_CHECK(sname, stype, tfield, flags) do { \ + s1 = fnum_find(sname); \ + if (s1 && (strcmp_safe(s1->stype.out.tfield.s, correct_name) != 0 || \ + wire_bad_flags(&s1->stype.out.tfield, flags, tree->session->transport))) { \ + printf("(%d) handle %s/%s incorrect - '%s/%d'\n", __LINE__, #stype, #tfield, \ + s1->stype.out.tfield.s, s1->stype.out.tfield.private_length); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && (strcmp_safe(s1->stype.out.tfield.s, correct_name) != 0 || \ + wire_bad_flags(&s1->stype.out.tfield, flags, tree->session->transport))) { \ + printf("(%d) path %s/%s incorrect - '%s/%d'\n", __LINE__, #stype, #tfield, \ + s1->stype.out.tfield.s, s1->stype.out.tfield.private_length); \ + ret = false; \ + }} while (0) + + NAME_CHECK("NAME_INFO", name_info, fname, STR_UNICODE); + NAME_CHECK("NAME_INFORMATION", name_info, fname, STR_UNICODE); + + /* the ALL_INFO file name is the full path on the filesystem */ + s1 = fnum_find("ALL_INFO"); + if (s1 && !s1->all_info.out.fname.s) { + torture_fail(torture, "ALL_INFO didn't give a filename"); + } + if (s1 && s1->all_info.out.fname.s) { + char *p = strrchr(s1->all_info.out.fname.s, '\\'); + if (!p) { + printf("Not a full path in all_info/fname? - '%s'\n", + s1->all_info.out.fname.s); + ret = false; + } else { + if (strcmp_safe(correct_name, p) != 0) { + printf("incorrect basename in all_info/fname - '%s'\n", + s1->all_info.out.fname.s); + ret = false; + } + } + if (wire_bad_flags(&s1->all_info.out.fname, STR_UNICODE, tree->session->transport)) { + printf("Should not null terminate all_info/fname\n"); + ret = false; + } + } + + s1 = fnum_find("ALT_NAME_INFO"); + if (s1) { + correct_name = s1->alt_name_info.out.fname.s; + torture_comment(torture, "alt_name: %s\n", correct_name); + + NAME_CHECK("ALT_NAME_INFO", alt_name_info, fname, STR_UNICODE); + NAME_CHECK("ALT_NAME_INFORMATION", alt_name_info, fname, STR_UNICODE); + + /* and make sure we can open by alternate name */ + smbcli_close(tree, fnum); + fnum = smbcli_nt_create_full(tree, correct_name, 0, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL, + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE, + NTCREATEX_DISP_OVERWRITE_IF, + 0, 0); + if (fnum == -1) { + printf("Unable to open by alt_name - %s\n", smbcli_errstr(tree)); + ret = false; + } + + if (!skip_streams) { + correct_name = "::$DATA"; + torture_comment(torture, "stream_name: %s\n", correct_name); + + NAME_CHECK("STREAM_INFO", stream_info, streams[0].stream_name, STR_UNICODE); + NAME_CHECK("STREAM_INFORMATION", stream_info, streams[0].stream_name, STR_UNICODE); + } + } + + /* make sure the EAs look right */ + s1 = fnum_find("ALL_EAS"); + s2 = fnum_find("ALL_INFO"); + if (s1) { + for (i=0;i<s1->all_eas.out.num_eas;i++) { + printf(" flags=%d %s=%*.*s\n", + s1->all_eas.out.eas[i].flags, + s1->all_eas.out.eas[i].name.s, + (int)s1->all_eas.out.eas[i].value.length, + (int)s1->all_eas.out.eas[i].value.length, + s1->all_eas.out.eas[i].value.data); + } + } + if (s1 && s2) { + if (s1->all_eas.out.num_eas == 0) { + if (s2->all_info.out.ea_size != 0) { + printf("ERROR: num_eas==0 but fnum all_info.out.ea_size == %d\n", + s2->all_info.out.ea_size); + } + } else { + if (s2->all_info.out.ea_size != + ea_list_size(s1->all_eas.out.num_eas, s1->all_eas.out.eas)) { + printf("ERROR: ea_list_size=%d != fnum all_info.out.ea_size=%d\n", + (int)ea_list_size(s1->all_eas.out.num_eas, s1->all_eas.out.eas), + (int)s2->all_info.out.ea_size); + } + } + } + s2 = fname_find(is_ipc, "ALL_EAS"); + if (s2) { + VAL_EQUAL(all_eas, num_eas, all_eas, num_eas); + for (i=0;i<s1->all_eas.out.num_eas;i++) { + VAL_EQUAL(all_eas, eas[i].flags, all_eas, eas[i].flags); + STR_EQUAL(all_eas, eas[i].name, all_eas, eas[i].name); + VAL_EQUAL(all_eas, eas[i].value.length, all_eas, eas[i].value.length); + } + } + +#define VAL_CHECK(sname1, stype1, tfield1, sname2, stype2, tfield2) do { \ + s1 = fnum_find(sname1); s2 = fnum_find(sname2); \ + if (s1 && s2 && s1->stype1.out.tfield1 != s2->stype2.out.tfield2) { \ + printf("(%d) handle %s/%s != %s/%s - 0x%x vs 0x%x\n", __LINE__, \ + #stype1, #tfield1, #stype2, #tfield2, \ + s1->stype1.out.tfield1, s2->stype2.out.tfield2); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname1); s2 = fname_find(is_ipc, sname2); \ + if (s1 && s2 && s1->stype1.out.tfield1 != s2->stype2.out.tfield2) { \ + printf("(%d) path %s/%s != %s/%s - 0x%x vs 0x%x\n", __LINE__, \ + #stype1, #tfield1, #stype2, #tfield2, \ + s1->stype1.out.tfield1, s2->stype2.out.tfield2); \ + ret = false; \ + } \ + s1 = fnum_find(sname1); s2 = fname_find(is_ipc, sname2); \ + if (s1 && s2 && s1->stype1.out.tfield1 != s2->stype2.out.tfield2) { \ + printf("(%d) handle %s/%s != path %s/%s - 0x%x vs 0x%x\n", __LINE__, \ + #stype1, #tfield1, #stype2, #tfield2, \ + s1->stype1.out.tfield1, s2->stype2.out.tfield2); \ + ret = false; \ + } \ + s1 = fname_find(is_ipc, sname1); s2 = fnum_find(sname2); \ + if (s1 && s2 && s1->stype1.out.tfield1 != s2->stype2.out.tfield2) { \ + printf("(%d) path %s/%s != handle %s/%s - 0x%x vs 0x%x\n", __LINE__, \ + #stype1, #tfield1, #stype2, #tfield2, \ + s1->stype1.out.tfield1, s2->stype2.out.tfield2); \ + ret = false; \ + }} while (0) + + VAL_CHECK("STANDARD_INFO", standard_info, delete_pending, + "ALL_INFO", all_info, delete_pending); + VAL_CHECK("STANDARD_INFO", standard_info, directory, + "ALL_INFO", all_info, directory); + VAL_CHECK("STANDARD_INFO", standard_info, nlink, + "ALL_INFO", all_info, nlink); + s1 = fnum_find("BASIC_INFO"); + if (s1 && is_ipc) { + if (s1->basic_info.out.attrib != FILE_ATTRIBUTE_NORMAL) { + printf("(%d) attrib basic_info/nlink incorrect - %d should be %d\n", __LINE__, s1->basic_info.out.attrib, FILE_ATTRIBUTE_NORMAL); + ret = false; + } + } + s1 = fnum_find("STANDARD_INFO"); + if (s1 && is_ipc) { + if (s1->standard_info.out.nlink != 1) { + printf("(%d) nlinks standard_info/nlink incorrect - %d should be 1\n", __LINE__, s1->standard_info.out.nlink); + ret = false; + } + if (s1->standard_info.out.delete_pending != 1) { + printf("(%d) nlinks standard_info/delete_pending incorrect - %d should be 1\n", __LINE__, s1->standard_info.out.delete_pending); + ret = false; + } + } + VAL_CHECK("EA_INFO", ea_info, ea_size, + "ALL_INFO", all_info, ea_size); + if (!is_ipc) { + VAL_CHECK("EA_SIZE", ea_size, ea_size, + "ALL_INFO", all_info, ea_size); + } + +#define NAME_PATH_CHECK(sname, stype, field) do { \ + s1 = fname_find(is_ipc, sname); s2 = fnum_find(sname); \ + if (s1 && s2) { \ + VAL_EQUAL(stype, field, stype, field); \ + } \ +} while (0) + + + s1 = fnum_find("INTERNAL_INFORMATION"); + if (s1) { + torture_comment(torture, "file_id=%.0f\n", (double)s1->internal_information.out.file_id); + } + + NAME_PATH_CHECK("INTERNAL_INFORMATION", internal_information, file_id); + NAME_PATH_CHECK("POSITION_INFORMATION", position_information, position); + if (s1 && s2) { + printf("fnum pos = %.0f, fname pos = %.0f\n", + (double)s2->position_information.out.position, + (double)s1->position_information.out.position ); + } + NAME_PATH_CHECK("MODE_INFORMATION", mode_information, mode); + NAME_PATH_CHECK("ALIGNMENT_INFORMATION", alignment_information, alignment_requirement); + NAME_PATH_CHECK("ATTRIBUTE_TAG_INFORMATION", attribute_tag_information, attrib); + NAME_PATH_CHECK("ATTRIBUTE_TAG_INFORMATION", attribute_tag_information, reparse_tag); + +#if 0 + /* these are expected to differ */ + NAME_PATH_CHECK("ACCESS_INFORMATION", access_information, access_flags); +#endif + +#if 0 /* unused */ +#define UNKNOWN_CHECK(sname, stype, tfield) do { \ + s1 = fnum_find(sname); \ + if (s1 && s1->stype.out.tfield != 0) { \ + printf("(%d) handle %s/%s unknown != 0 (0x%x)\n", __LINE__, \ + #stype, #tfield, \ + (uint_t)s1->stype.out.tfield); \ + } \ + s1 = fname_find(is_ipc, sname); \ + if (s1 && s1->stype.out.tfield != 0) { \ + printf("(%d) path %s/%s unknown != 0 (0x%x)\n", __LINE__, \ + #stype, #tfield, \ + (uint_t)s1->stype.out.tfield); \ + }} while (0) +#endif + /* now get a bit fancier .... */ + + /* when we set the delete disposition then the link count should drop + to 0 and delete_pending should be 1 */ + + return ret; +} + +/* basic testing of all RAW_FILEINFO_* calls + for each call we test that it succeeds, and where possible test + for consistency between the calls. +*/ +bool torture_raw_qfileinfo(struct torture_context *torture, + struct smbcli_state *cli) +{ + int fnum; + bool ret; + const char *fname = "\\torture_qfileinfo.txt"; + + fnum = create_complex_file(cli, torture, fname); + if (fnum == -1) { + printf("ERROR: open of %s failed (%s)\n", fname, smbcli_errstr(cli->tree)); + return false; + } + + ret = torture_raw_qfileinfo_internals(torture, torture, cli->tree, fnum, fname, false /* is_ipc */); + + smbcli_close(cli->tree, fnum); + smbcli_unlink(cli->tree, fname); + + return ret; +} + +bool torture_raw_qfileinfo_pipe(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + int fnum; + const char *fname = "\\lsass"; + struct dcerpc_pipe *p; + struct smbcli_tree *ipc_tree; + NTSTATUS status; + + if (!(p = dcerpc_pipe_init(torture, cli->tree->session->transport->socket->event.ctx, + lp_iconv_convenience(torture->lp_ctx)))) { + return false; + } + + status = dcerpc_pipe_open_smb(p, cli->tree, fname); + torture_assert_ntstatus_ok(torture, status, "dcerpc_pipe_open_smb failed"); + + ipc_tree = dcerpc_smb_tree(p->conn); + fnum = dcerpc_smb_fnum(p->conn); + + ret = torture_raw_qfileinfo_internals(torture, torture, ipc_tree, fnum, fname, true /* is_ipc */); + + talloc_free(p); + return ret; +} diff --git a/source4/torture/raw/qfsinfo.c b/source4/torture/raw/qfsinfo.c new file mode 100644 index 0000000000..c1c77edba9 --- /dev/null +++ b/source4/torture/raw/qfsinfo.c @@ -0,0 +1,296 @@ +/* + Unix SMB/CIFS implementation. + RAW_QFS_* individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "torture/util.h" + + +static struct { + const char *name; + enum smb_fsinfo_level level; + uint32_t capability_mask; + NTSTATUS status; + union smb_fsinfo fsinfo; +} levels[] = { + {"DSKATTR", RAW_QFS_DSKATTR, }, + {"ALLOCATION", RAW_QFS_ALLOCATION, }, + {"VOLUME", RAW_QFS_VOLUME, }, + {"VOLUME_INFO", RAW_QFS_VOLUME_INFO, }, + {"SIZE_INFO", RAW_QFS_SIZE_INFO, }, + {"DEVICE_INFO", RAW_QFS_DEVICE_INFO, }, + {"ATTRIBUTE_INFO", RAW_QFS_ATTRIBUTE_INFO, }, + {"UNIX_INFO", RAW_QFS_UNIX_INFO, CAP_UNIX}, + {"VOLUME_INFORMATION", RAW_QFS_VOLUME_INFORMATION, }, + {"SIZE_INFORMATION", RAW_QFS_SIZE_INFORMATION, }, + {"DEVICE_INFORMATION", RAW_QFS_DEVICE_INFORMATION, }, + {"ATTRIBUTE_INFORMATION", RAW_QFS_ATTRIBUTE_INFORMATION, }, + {"QUOTA_INFORMATION", RAW_QFS_QUOTA_INFORMATION, }, + {"FULL_SIZE_INFORMATION", RAW_QFS_FULL_SIZE_INFORMATION, }, +#if 0 + /* w2k3 seems to no longer support this */ + {"OBJECTID_INFORMATION", RAW_QFS_OBJECTID_INFORMATION, }, +#endif + { NULL, } +}; + + +/* + find a level in the levels[] table +*/ +static union smb_fsinfo *find(const char *name) +{ + int i; + for (i=0; levels[i].name; i++) { + if (strcmp(name, levels[i].name) == 0 && + NT_STATUS_IS_OK(levels[i].status)) { + return &levels[i].fsinfo; + } + } + return NULL; +} + +/* local macros to make the code below more readable */ +#define VAL_EQUAL(n1, v1, n2, v2) do {if (s1->n1.out.v1 != s2->n2.out.v2) { \ + printf("%s/%s [%u] != %s/%s [%u] at %s(%d)\n", \ + #n1, #v1, (uint_t)s1->n1.out.v1, \ + #n2, #v2, (uint_t)s2->n2.out.v2, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +#define VAL_APPROX_EQUAL(n1, v1, n2, v2) do {if (abs((int)(s1->n1.out.v1) - (int)(s2->n2.out.v2)) > 0.1*s1->n1.out.v1) { \ + printf("%s/%s [%u] != %s/%s [%u] at %s(%d)\n", \ + #n1, #v1, (uint_t)s1->n1.out.v1, \ + #n2, #v2, (uint_t)s2->n2.out.v2, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +#define STR_EQUAL(n1, v1, n2, v2) do { \ + if (strcmp_safe(s1->n1.out.v1, s2->n2.out.v2)) { \ + printf("%s/%s [%s] != %s/%s [%s] at %s(%d)\n", \ + #n1, #v1, s1->n1.out.v1, \ + #n2, #v2, s2->n2.out.v2, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +#define STRUCT_EQUAL(n1, v1, n2, v2) do {if (memcmp(&s1->n1.out.v1,&s2->n2.out.v2,sizeof(s1->n1.out.v1))) { \ + printf("%s/%s != %s/%s at %s(%d)\n", \ + #n1, #v1, \ + #n2, #v2, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +/* used to find hints on unknown values - and to make sure + we zero-fill */ +#define VAL_UNKNOWN(n1, v1) do {if (s1->n1.out.v1 != 0) { \ + printf("%s/%s non-zero unknown - %u (0x%x) at %s(%d)\n", \ + #n1, #v1, \ + (uint_t)s1->n1.out.v1, \ + (uint_t)s1->n1.out.v1, \ + __FILE__, __LINE__); \ + ret = false; \ +}} while(0) + +/* basic testing of all RAW_QFS_* calls + for each call we test that it succeeds, and where possible test + for consistency between the calls. + + Some of the consistency tests assume that the target filesystem is + quiescent, which is sometimes hard to achieve +*/ +bool torture_raw_qfsinfo(struct torture_context *torture, + struct smbcli_state *cli) +{ + int i; + bool ret = true; + int count; + union smb_fsinfo *s1, *s2; + + /* scan all the levels, pulling the results */ + for (i=0; levels[i].name; i++) { + torture_comment(torture, "Running level %s\n", levels[i].name); + levels[i].fsinfo.generic.level = levels[i].level; + levels[i].status = smb_raw_fsinfo(cli->tree, torture, &levels[i].fsinfo); + } + + /* check for completely broken levels */ + for (count=i=0; levels[i].name; i++) { + uint32_t cap = cli->transport->negotiate.capabilities; + /* see if this server claims to support this level */ + if ((cap & levels[i].capability_mask) != levels[i].capability_mask) { + continue; + } + + if (!NT_STATUS_IS_OK(levels[i].status)) { + printf("ERROR: level %s failed - %s\n", + levels[i].name, nt_errstr(levels[i].status)); + count++; + } + } + + if (count != 0) { + torture_comment(torture, "%d levels failed\n", count); + torture_assert(torture, count > 13, "too many level failures - giving up"); + } + + torture_comment(torture, "check for correct aliases\n"); + s1 = find("SIZE_INFO"); + s2 = find("SIZE_INFORMATION"); + if (s1 && s2) { + VAL_EQUAL(size_info, total_alloc_units, size_info, total_alloc_units); + VAL_APPROX_EQUAL(size_info, avail_alloc_units, size_info, avail_alloc_units); + VAL_EQUAL(size_info, sectors_per_unit, size_info, sectors_per_unit); + VAL_EQUAL(size_info, bytes_per_sector, size_info, bytes_per_sector); + } + + s1 = find("DEVICE_INFO"); + s2 = find("DEVICE_INFORMATION"); + if (s1 && s2) { + VAL_EQUAL(device_info, device_type, device_info, device_type); + VAL_EQUAL(device_info, characteristics, device_info, characteristics); + } + + s1 = find("VOLUME_INFO"); + s2 = find("VOLUME_INFORMATION"); + if (s1 && s2) { + STRUCT_EQUAL(volume_info, create_time, volume_info, create_time); + VAL_EQUAL (volume_info, serial_number, volume_info, serial_number); + STR_EQUAL (volume_info, volume_name.s, volume_info, volume_name.s); + torture_comment(torture, "volume_info.volume_name = '%s'\n", s1->volume_info.out.volume_name.s); + } + + s1 = find("ATTRIBUTE_INFO"); + s2 = find("ATTRIBUTE_INFORMATION"); + if (s1 && s2) { + VAL_EQUAL(attribute_info, fs_attr, + attribute_info, fs_attr); + VAL_EQUAL(attribute_info, max_file_component_length, + attribute_info, max_file_component_length); + STR_EQUAL(attribute_info, fs_type.s, attribute_info, fs_type.s); + torture_comment(torture, "attribute_info.fs_type = '%s'\n", s1->attribute_info.out.fs_type.s); + } + + torture_comment(torture, "check for consistent disk sizes\n"); + s1 = find("DSKATTR"); + s2 = find("ALLOCATION"); + if (s1 && s2) { + double size1, size2; + double scale = s1->dskattr.out.blocks_per_unit * s1->dskattr.out.block_size; + size1 = 1.0 * + s1->dskattr.out.units_total * + s1->dskattr.out.blocks_per_unit * + s1->dskattr.out.block_size / scale; + size2 = 1.0 * + s2->allocation.out.sectors_per_unit * + s2->allocation.out.total_alloc_units * + s2->allocation.out.bytes_per_sector / scale; + if (abs(size1 - size2) > 1) { + printf("Inconsistent total size in DSKATTR and ALLOCATION - size1=%.0f size2=%.0f\n", + size1, size2); + ret = false; + } + torture_comment(torture, "total disk = %.0f MB\n", size1*scale/1.0e6); + } + + torture_comment(torture, "check consistent free disk space\n"); + s1 = find("DSKATTR"); + s2 = find("ALLOCATION"); + if (s1 && s2) { + double size1, size2; + double scale = s1->dskattr.out.blocks_per_unit * s1->dskattr.out.block_size; + size1 = 1.0 * + s1->dskattr.out.units_free * + s1->dskattr.out.blocks_per_unit * + s1->dskattr.out.block_size / scale; + size2 = 1.0 * + s2->allocation.out.sectors_per_unit * + s2->allocation.out.avail_alloc_units * + s2->allocation.out.bytes_per_sector / scale; + if (abs(size1 - size2) > 1) { + printf("Inconsistent avail size in DSKATTR and ALLOCATION - size1=%.0f size2=%.0f\n", + size1, size2); + ret = false; + } + torture_comment(torture, "free disk = %.0f MB\n", size1*scale/1.0e6); + } + + torture_comment(torture, "volume info consistency\n"); + s1 = find("VOLUME"); + s2 = find("VOLUME_INFO"); + if (s1 && s2) { + VAL_EQUAL(volume, serial_number, volume_info, serial_number); + STR_EQUAL(volume, volume_name.s, volume_info, volume_name.s); + } + + /* disk size consistency - notice that 'avail_alloc_units' maps to the caller + available allocation units, not the total */ + s1 = find("SIZE_INFO"); + s2 = find("FULL_SIZE_INFORMATION"); + if (s1 && s2) { + VAL_EQUAL(size_info, total_alloc_units, full_size_information, total_alloc_units); + VAL_APPROX_EQUAL(size_info, avail_alloc_units, full_size_information, call_avail_alloc_units); + VAL_EQUAL(size_info, sectors_per_unit, full_size_information, sectors_per_unit); + VAL_EQUAL(size_info, bytes_per_sector, full_size_information, bytes_per_sector); + } + + printf("check for non-zero unknown fields\n"); + s1 = find("QUOTA_INFORMATION"); + if (s1) { + VAL_UNKNOWN(quota_information, unknown[0]); + VAL_UNKNOWN(quota_information, unknown[1]); + VAL_UNKNOWN(quota_information, unknown[2]); + } + + s1 = find("OBJECTID_INFORMATION"); + if (s1) { + VAL_UNKNOWN(objectid_information, unknown[0]); + VAL_UNKNOWN(objectid_information, unknown[1]); + VAL_UNKNOWN(objectid_information, unknown[2]); + VAL_UNKNOWN(objectid_information, unknown[3]); + VAL_UNKNOWN(objectid_information, unknown[4]); + VAL_UNKNOWN(objectid_information, unknown[5]); + } + + +#define STR_CHECK(sname, stype, field, flags) do { \ + s1 = find(sname); \ + if (s1) { \ + if (s1->stype.out.field.s && wire_bad_flags(&s1->stype.out.field, flags, cli->transport)) { \ + printf("(%d) incorrect string termination in %s/%s\n", \ + __LINE__, #stype, #field); \ + ret = false; \ + } \ + }} while (0) + + torture_comment(torture, "check for correct termination\n"); + + STR_CHECK("VOLUME", volume, volume_name, 0); + STR_CHECK("VOLUME_INFO", volume_info, volume_name, STR_UNICODE); + STR_CHECK("VOLUME_INFORMATION", volume_info, volume_name, STR_UNICODE); + STR_CHECK("ATTRIBUTE_INFO", attribute_info, fs_type, STR_UNICODE); + STR_CHECK("ATTRIBUTE_INFORMATION", attribute_info, fs_type, STR_UNICODE); + + return ret; +} diff --git a/source4/torture/raw/raw.c b/source4/torture/raw/raw.c new file mode 100644 index 0000000000..0a7fc3ebfd --- /dev/null +++ b/source4/torture/raw/raw.c @@ -0,0 +1,86 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Jelmer Vernooij 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/raw/libcliraw.h" +#include "torture/util.h" +#include "torture/smbtorture.h" +#include "torture/raw/proto.h" + +NTSTATUS torture_raw_init(void) +{ + struct torture_suite *suite = torture_suite_create( + talloc_autofree_context(), + "RAW"); + /* RAW smb tests */ + torture_suite_add_simple_test(suite, "BENCH-OPLOCK", torture_bench_oplock); + torture_suite_add_simple_test(suite, "PING-PONG", torture_ping_pong); + torture_suite_add_simple_test(suite, "BENCH-LOCK", torture_bench_lock); + torture_suite_add_simple_test(suite, "BENCH-OPEN", torture_bench_open); + torture_suite_add_simple_test(suite, "BENCH-LOOKUP", + torture_bench_lookup); + torture_suite_add_simple_test(suite, "BENCH-TCON", + torture_bench_treeconnect); + torture_suite_add_simple_test(suite, "OFFLINE", torture_test_offline); + torture_suite_add_1smb_test(suite, "QFSINFO", torture_raw_qfsinfo); + torture_suite_add_1smb_test(suite, "QFILEINFO", torture_raw_qfileinfo); + torture_suite_add_1smb_test(suite, "QFILEINFO-IPC", torture_raw_qfileinfo_pipe); + torture_suite_add_1smb_test(suite, "SFILEINFO", torture_raw_sfileinfo); + torture_suite_add_1smb_test(suite, "SFILEINFO-BUG", torture_raw_sfileinfo_bug); + torture_suite_add_1smb_test(suite, "SFILEINFO-RENAME", + torture_raw_sfileinfo_rename); + torture_suite_add_suite(suite, torture_raw_search(suite)); + torture_suite_add_1smb_test(suite, "CLOSE", torture_raw_close); + torture_suite_add_1smb_test(suite, "OPEN", torture_raw_open); + torture_suite_add_1smb_test(suite, "MKDIR", torture_raw_mkdir); + torture_suite_add_suite(suite, torture_raw_oplock(suite)); + torture_suite_add_1smb_test(suite, "HOLD-OPLOCK", torture_hold_oplock); + torture_suite_add_2smb_test(suite, "NOTIFY", torture_raw_notify); + torture_suite_add_1smb_test(suite, "MUX", torture_raw_mux); + torture_suite_add_1smb_test(suite, "IOCTL", torture_raw_ioctl); + torture_suite_add_1smb_test(suite, "CHKPATH", torture_raw_chkpath); + torture_suite_add_suite(suite, torture_raw_unlink(suite)); + torture_suite_add_suite(suite, torture_raw_read(suite)); + torture_suite_add_suite(suite, torture_raw_write(suite)); + torture_suite_add_suite(suite, torture_raw_lock(suite)); + torture_suite_add_1smb_test(suite, "CONTEXT", torture_raw_context); + torture_suite_add_suite(suite, torture_raw_rename(suite)); + torture_suite_add_1smb_test(suite, "SEEK", torture_raw_seek); + torture_suite_add_1smb_test(suite, "EAS", torture_raw_eas); + torture_suite_add_1smb_test(suite, "STREAMS", torture_raw_streams); + torture_suite_add_1smb_test(suite, "ACLS", torture_raw_acls); + torture_suite_add_1smb_test(suite, "COMPOSITE", torture_raw_composite); + torture_suite_add_simple_test(suite, "SAMBA3HIDE", torture_samba3_hide); + torture_suite_add_simple_test(suite, "SAMBA3CLOSEERR", torture_samba3_closeerr); + torture_suite_add_simple_test(suite, "SAMBA3ROOTDIRFID", + torture_samba3_rootdirfid); + torture_suite_add_simple_test(suite, "SAMBA3CHECKFSP", torture_samba3_checkfsp); + torture_suite_add_simple_test(suite, "SAMBA3BADPATH", torture_samba3_badpath); + torture_suite_add_simple_test(suite, "SAMBA3CASEINSENSITIVE", + torture_samba3_caseinsensitive); + torture_suite_add_simple_test(suite, "SAMBA3POSIXTIMEDLOCK", + torture_samba3_posixtimedlock); + torture_suite_add_simple_test(suite, "SCAN-EAMAX", torture_max_eas); + + suite->description = talloc_strdup(suite, "Tests for the raw SMB interface"); + + torture_register_suite(suite); + + return NT_STATUS_OK; +} diff --git a/source4/torture/raw/read.c b/source4/torture/raw/read.c new file mode 100644 index 0000000000..ada9b1f432 --- /dev/null +++ b/source4/torture/raw/read.c @@ -0,0 +1,954 @@ +/* + Unix SMB/CIFS implementation. + test suite for various read operations + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%s) Incorrect value %s=%ld - should be %ld\n", \ + __location__, #v, (long)v, (long)correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_BUFFER(buf, seed, len) do { \ + if (!check_buffer(buf, seed, len, __LINE__)) { \ + ret = false; \ + goto done; \ + }} while (0) + +#define BASEDIR "\\testread" + + +/* + setup a random buffer based on a seed +*/ +static void setup_buffer(uint8_t *buf, uint_t seed, int len) +{ + int i; + srandom(seed); + for (i=0;i<len;i++) buf[i] = random(); +} + +/* + check a random buffer based on a seed +*/ +static bool check_buffer(uint8_t *buf, uint_t seed, int len, int line) +{ + int i; + srandom(seed); + for (i=0;i<len;i++) { + uint8_t v = random(); + if (buf[i] != v) { + printf("Buffer incorrect at line %d! ofs=%d v1=0x%x v2=0x%x\n", + line, i, buf[i], v); + return false; + } + } + return true; +} + +/* + test read ops +*/ +static bool test_read(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_read io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + const char *test_data = "TEST DATA"; + uint_t seed = time(NULL); + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_READ_READ\n"); + io.generic.level = RAW_READ_READ; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying empty file read\n"); + io.read.in.file.fnum = fnum; + io.read.in.count = 1; + io.read.in.offset = 0; + io.read.in.remaining = 0; + io.read.out.data = buf; + status = smb_raw_read(cli->tree, &io); + + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.read.out.nread, 0); + + printf("Trying zero file read\n"); + io.read.in.count = 0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.read.out.nread, 0); + + printf("Trying bad fnum\n"); + io.read.in.file.fnum = fnum+1; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + io.read.in.file.fnum = fnum; + + smbcli_write(cli->tree, fnum, 0, test_data, 0, strlen(test_data)); + + printf("Trying small read\n"); + io.read.in.file.fnum = fnum; + io.read.in.offset = 0; + io.read.in.remaining = 0; + io.read.in.count = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.read.out.nread, strlen(test_data)); + if (memcmp(buf, test_data, strlen(test_data)) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data, buf); + goto done; + } + + printf("Trying short read\n"); + io.read.in.offset = 1; + io.read.in.count = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.read.out.nread, strlen(test_data)-1); + if (memcmp(buf, test_data+1, strlen(test_data)-1) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data+1, buf); + goto done; + } + + if (cli->transport->negotiate.capabilities & CAP_LARGE_FILES) { + printf("Trying max offset\n"); + io.read.in.offset = ~0; + io.read.in.count = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.read.out.nread, 0); + } + + setup_buffer(buf, seed, maxsize); + smbcli_write(cli->tree, fnum, 0, buf, 0, maxsize); + memset(buf, 0, maxsize); + + printf("Trying large read\n"); + io.read.in.offset = 0; + io.read.in.count = ~0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_BUFFER(buf, seed, io.read.out.nread); + + + printf("Trying locked region\n"); + cli->session->pid++; + if (NT_STATUS_IS_ERR(smbcli_lock(cli->tree, fnum, 103, 1, 0, WRITE_LOCK))) { + printf("Failed to lock file at %d\n", __LINE__); + ret = false; + goto done; + } + cli->session->pid--; + memset(buf, 0, maxsize); + io.read.in.offset = 0; + io.read.in.count = ~0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test lockread ops +*/ +static bool test_lockread(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_read io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + const char *test_data = "TEST DATA"; + uint_t seed = time(NULL); + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_READ_LOCKREAD\n"); + io.generic.level = RAW_READ_LOCKREAD; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying empty file read\n"); + io.lockread.in.file.fnum = fnum; + io.lockread.in.count = 1; + io.lockread.in.offset = 1; + io.lockread.in.remaining = 0; + io.lockread.out.data = buf; + status = smb_raw_read(cli->tree, &io); + + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lockread.out.nread, 0); + + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + printf("Trying zero file read\n"); + io.lockread.in.count = 0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_unlock(cli->tree, fnum, 1, 1); + + printf("Trying bad fnum\n"); + io.lockread.in.file.fnum = fnum+1; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + io.lockread.in.file.fnum = fnum; + + smbcli_write(cli->tree, fnum, 0, test_data, 0, strlen(test_data)); + + printf("Trying small read\n"); + io.lockread.in.file.fnum = fnum; + io.lockread.in.offset = 0; + io.lockread.in.remaining = 0; + io.lockread.in.count = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + smbcli_unlock(cli->tree, fnum, 1, 0); + + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lockread.out.nread, strlen(test_data)); + if (memcmp(buf, test_data, strlen(test_data)) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data, buf); + goto done; + } + + printf("Trying short read\n"); + io.lockread.in.offset = 1; + io.lockread.in.count = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + smbcli_unlock(cli->tree, fnum, 0, strlen(test_data)); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VALUE(io.lockread.out.nread, strlen(test_data)-1); + if (memcmp(buf, test_data+1, strlen(test_data)-1) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data+1, buf); + goto done; + } + + if (cli->transport->negotiate.capabilities & CAP_LARGE_FILES) { + printf("Trying max offset\n"); + io.lockread.in.offset = ~0; + io.lockread.in.count = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lockread.out.nread, 0); + } + + setup_buffer(buf, seed, maxsize); + smbcli_write(cli->tree, fnum, 0, buf, 0, maxsize); + memset(buf, 0, maxsize); + + printf("Trying large read\n"); + io.lockread.in.offset = 0; + io.lockread.in.count = ~0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + smbcli_unlock(cli->tree, fnum, 1, strlen(test_data)); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_BUFFER(buf, seed, io.lockread.out.nread); + smbcli_unlock(cli->tree, fnum, 0, 0xFFFF); + + + printf("Trying locked region\n"); + cli->session->pid++; + if (NT_STATUS_IS_ERR(smbcli_lock(cli->tree, fnum, 103, 1, 0, WRITE_LOCK))) { + printf("Failed to lock file at %d\n", __LINE__); + ret = false; + goto done; + } + cli->session->pid--; + memset(buf, 0, maxsize); + io.lockread.in.offset = 0; + io.lockread.in.count = ~0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + +done: + smbcli_close(cli->tree, fnum); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test readx ops +*/ +static bool test_readx(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_read io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + const char *test_data = "TEST DATA"; + uint_t seed = time(NULL); + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_READ_READX\n"); + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying empty file read\n"); + io.generic.level = RAW_READ_READX; + io.readx.in.file.fnum = fnum; + io.readx.in.mincnt = 1; + io.readx.in.maxcnt = 1; + io.readx.in.offset = 0; + io.readx.in.remaining = 0; + io.readx.in.read_for_execute = false; + io.readx.out.data = buf; + status = smb_raw_read(cli->tree, &io); + + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, 0); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + + printf("Trying zero file read\n"); + io.readx.in.mincnt = 0; + io.readx.in.maxcnt = 0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, 0); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + + printf("Trying bad fnum\n"); + io.readx.in.file.fnum = fnum+1; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + io.readx.in.file.fnum = fnum; + + smbcli_write(cli->tree, fnum, 0, test_data, 0, strlen(test_data)); + + printf("Trying small read\n"); + io.readx.in.file.fnum = fnum; + io.readx.in.offset = 0; + io.readx.in.remaining = 0; + io.readx.in.read_for_execute = false; + io.readx.in.mincnt = strlen(test_data); + io.readx.in.maxcnt = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, strlen(test_data)); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + if (memcmp(buf, test_data, strlen(test_data)) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data, buf); + goto done; + } + + printf("Trying short read\n"); + io.readx.in.offset = 1; + io.readx.in.mincnt = strlen(test_data); + io.readx.in.maxcnt = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, strlen(test_data)-1); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + if (memcmp(buf, test_data+1, strlen(test_data)-1) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data+1, buf); + goto done; + } + + if (cli->transport->negotiate.capabilities & CAP_LARGE_FILES) { + printf("Trying max offset\n"); + io.readx.in.offset = 0xffffffff; + io.readx.in.mincnt = strlen(test_data); + io.readx.in.maxcnt = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, 0); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + } + + printf("Trying mincnt past EOF\n"); + memset(buf, 0, maxsize); + io.readx.in.offset = 0; + io.readx.in.mincnt = 100; + io.readx.in.maxcnt = 110; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + CHECK_VALUE(io.readx.out.nread, strlen(test_data)); + if (memcmp(buf, test_data, strlen(test_data)) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data, buf); + goto done; + } + + + setup_buffer(buf, seed, maxsize); + smbcli_write(cli->tree, fnum, 0, buf, 0, maxsize); + memset(buf, 0, maxsize); + + printf("Trying large read\n"); + io.readx.in.offset = 0; + io.readx.in.mincnt = 0xFFFF; + io.readx.in.maxcnt = 0xFFFF; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + CHECK_VALUE(io.readx.out.nread, io.readx.in.maxcnt); + CHECK_BUFFER(buf, seed, io.readx.out.nread); + + printf("Trying extra large read\n"); + io.readx.in.offset = 0; + io.readx.in.mincnt = 100; + io.readx.in.maxcnt = 80000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + if (torture_setting_bool(tctx, "samba3", false)) { + printf("SAMBA3: large read extension\n"); + CHECK_VALUE(io.readx.out.nread, 80000); + } else { + CHECK_VALUE(io.readx.out.nread, 0); + } + CHECK_BUFFER(buf, seed, io.readx.out.nread); + + printf("Trying mincnt > maxcnt\n"); + memset(buf, 0, maxsize); + io.readx.in.offset = 0; + io.readx.in.mincnt = 30000; + io.readx.in.maxcnt = 20000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + CHECK_VALUE(io.readx.out.nread, io.readx.in.maxcnt); + CHECK_BUFFER(buf, seed, io.readx.out.nread); + + printf("Trying mincnt < maxcnt\n"); + memset(buf, 0, maxsize); + io.readx.in.offset = 0; + io.readx.in.mincnt = 20000; + io.readx.in.maxcnt = 30000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.remaining, 0xFFFF); + CHECK_VALUE(io.readx.out.compaction_mode, 0); + CHECK_VALUE(io.readx.out.nread, io.readx.in.maxcnt); + CHECK_BUFFER(buf, seed, io.readx.out.nread); + + if (cli->transport->negotiate.capabilities & CAP_LARGE_READX) { + printf("Trying large readx\n"); + io.readx.in.offset = 0; + io.readx.in.mincnt = 0; + io.readx.in.maxcnt = 0x10000 - 1; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, 0xFFFF); + + io.readx.in.maxcnt = 0x10000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + if (torture_setting_bool(tctx, "samba3", false)) { + printf("SAMBA3: large read extension\n"); + CHECK_VALUE(io.readx.out.nread, 0x10000); + } else { + CHECK_VALUE(io.readx.out.nread, 0); + } + + io.readx.in.maxcnt = 0x10001; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + if (torture_setting_bool(tctx, "samba3", false)) { + printf("SAMBA3: large read extension\n"); + CHECK_VALUE(io.readx.out.nread, 0x10001); + } else { + CHECK_VALUE(io.readx.out.nread, 0); + } + } + + printf("Trying locked region\n"); + cli->session->pid++; + if (NT_STATUS_IS_ERR(smbcli_lock(cli->tree, fnum, 103, 1, 0, WRITE_LOCK))) { + printf("Failed to lock file at %d\n", __LINE__); + ret = false; + goto done; + } + cli->session->pid--; + memset(buf, 0, maxsize); + io.readx.in.offset = 0; + io.readx.in.mincnt = 100; + io.readx.in.maxcnt = 200; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + if (!(cli->transport->negotiate.capabilities & CAP_LARGE_FILES)) { + printf("skipping large file tests - CAP_LARGE_FILES not set\n"); + goto done; + } + + printf("Trying large offset read\n"); + io.readx.in.offset = ((uint64_t)0x2) << 32; + io.readx.in.mincnt = 10; + io.readx.in.maxcnt = 10; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, 0); + + if (NT_STATUS_IS_ERR(smbcli_lock64(cli->tree, fnum, io.readx.in.offset, 1, 0, WRITE_LOCK))) { + printf("Failed to lock file at %d\n", __LINE__); + ret = false; + goto done; + } + + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readx.out.nread, 0); + +done: + smbcli_close(cli->tree, fnum); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test readbraw ops +*/ +static bool test_readbraw(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_read io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + const char *test_data = "TEST DATA"; + uint_t seed = time(NULL); + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_READ_READBRAW\n"); + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying empty file read\n"); + io.generic.level = RAW_READ_READBRAW; + io.readbraw.in.file.fnum = fnum; + io.readbraw.in.mincnt = 1; + io.readbraw.in.maxcnt = 1; + io.readbraw.in.offset = 0; + io.readbraw.in.timeout = 0; + io.readbraw.out.data = buf; + status = smb_raw_read(cli->tree, &io); + + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + + printf("Trying zero file read\n"); + io.readbraw.in.mincnt = 0; + io.readbraw.in.maxcnt = 0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + + printf("Trying bad fnum\n"); + io.readbraw.in.file.fnum = fnum+1; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + io.readbraw.in.file.fnum = fnum; + + smbcli_write(cli->tree, fnum, 0, test_data, 0, strlen(test_data)); + + printf("Trying small read\n"); + io.readbraw.in.file.fnum = fnum; + io.readbraw.in.offset = 0; + io.readbraw.in.mincnt = strlen(test_data); + io.readbraw.in.maxcnt = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, strlen(test_data)); + if (memcmp(buf, test_data, strlen(test_data)) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data, buf); + goto done; + } + + printf("Trying short read\n"); + io.readbraw.in.offset = 1; + io.readbraw.in.mincnt = strlen(test_data); + io.readbraw.in.maxcnt = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, strlen(test_data)-1); + if (memcmp(buf, test_data+1, strlen(test_data)-1) != 0) { + ret = false; + printf("incorrect data at %d!? (%s:%s)\n", __LINE__, test_data+1, buf); + goto done; + } + + if (cli->transport->negotiate.capabilities & CAP_LARGE_FILES) { + printf("Trying max offset\n"); + io.readbraw.in.offset = ~0; + io.readbraw.in.mincnt = strlen(test_data); + io.readbraw.in.maxcnt = strlen(test_data); + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + } + + setup_buffer(buf, seed, maxsize); + smbcli_write(cli->tree, fnum, 0, buf, 0, maxsize); + memset(buf, 0, maxsize); + + printf("Trying large read\n"); + io.readbraw.in.offset = 0; + io.readbraw.in.mincnt = ~0; + io.readbraw.in.maxcnt = ~0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0xFFFF); + CHECK_BUFFER(buf, seed, io.readbraw.out.nread); + + printf("Trying mincnt > maxcnt\n"); + memset(buf, 0, maxsize); + io.readbraw.in.offset = 0; + io.readbraw.in.mincnt = 30000; + io.readbraw.in.maxcnt = 20000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, io.readbraw.in.maxcnt); + CHECK_BUFFER(buf, seed, io.readbraw.out.nread); + + printf("Trying mincnt < maxcnt\n"); + memset(buf, 0, maxsize); + io.readbraw.in.offset = 0; + io.readbraw.in.mincnt = 20000; + io.readbraw.in.maxcnt = 30000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, io.readbraw.in.maxcnt); + CHECK_BUFFER(buf, seed, io.readbraw.out.nread); + + printf("Trying locked region\n"); + cli->session->pid++; + if (NT_STATUS_IS_ERR(smbcli_lock(cli->tree, fnum, 103, 1, 0, WRITE_LOCK))) { + printf("Failed to lock file at %d\n", __LINE__); + ret = false; + goto done; + } + cli->session->pid--; + memset(buf, 0, maxsize); + io.readbraw.in.offset = 0; + io.readbraw.in.mincnt = 100; + io.readbraw.in.maxcnt = 200; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + + printf("Trying locked region with timeout\n"); + memset(buf, 0, maxsize); + io.readbraw.in.offset = 0; + io.readbraw.in.mincnt = 100; + io.readbraw.in.maxcnt = 200; + io.readbraw.in.timeout = 10000; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + + if (cli->transport->negotiate.capabilities & CAP_LARGE_FILES) { + printf("Trying large offset read\n"); + io.readbraw.in.offset = ((uint64_t)0x2) << 32; + io.readbraw.in.mincnt = 10; + io.readbraw.in.maxcnt = 10; + io.readbraw.in.timeout = 0; + status = smb_raw_read(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.readbraw.out.nread, 0); + } + +done: + smbcli_close(cli->tree, fnum); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test read for execute +*/ +static bool test_read_for_execute(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_open op; + union smb_write wr; + union smb_read rd; + NTSTATUS status; + bool ret = true; + int fnum=0; + uint8_t *buf; + const int maxsize = 900; + const char *fname = BASEDIR "\\test.txt"; + const uint8_t data[] = "TEST DATA"; + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_READ_READX with read_for_execute\n"); + + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + op.ntcreatex.in.create_options = 0; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + wr.generic.level = RAW_WRITE_WRITEX; + wr.writex.in.file.fnum = fnum; + wr.writex.in.offset = 0; + wr.writex.in.wmode = 0; + wr.writex.in.remaining = 0; + wr.writex.in.count = ARRAY_SIZE(data); + wr.writex.in.data = data; + status = smb_raw_write(cli->tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(wr.writex.out.nwritten, ARRAY_SIZE(data)); + + status = smbcli_close(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("open file with SEC_FILE_EXECUTE\n"); + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_FILE_EXECUTE; + op.ntcreatex.in.create_options = 0; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + printf("read with FLAGS2_READ_PERMIT_EXECUTE\n"); + rd.generic.level = RAW_READ_READX; + rd.readx.in.file.fnum = fnum; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = maxsize; + rd.readx.in.offset = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = true; + rd.readx.out.data = buf; + status = smb_raw_read(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.readx.out.nread, ARRAY_SIZE(data)); + CHECK_VALUE(rd.readx.out.remaining, 0xFFFF); + CHECK_VALUE(rd.readx.out.compaction_mode, 0); + + printf("read without FLAGS2_READ_PERMIT_EXECUTE (should fail)\n"); + rd.generic.level = RAW_READ_READX; + rd.readx.in.file.fnum = fnum; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = maxsize; + rd.readx.in.offset = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = false; + rd.readx.out.data = buf; + status = smb_raw_read(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + status = smbcli_close(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("open file with SEC_FILE_READ_DATA\n"); + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_FILE_READ_DATA; + op.ntcreatex.in.create_options = 0; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + printf("read with FLAGS2_READ_PERMIT_EXECUTE\n"); + rd.generic.level = RAW_READ_READX; + rd.readx.in.file.fnum = fnum; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = maxsize; + rd.readx.in.offset = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = true; + rd.readx.out.data = buf; + status = smb_raw_read(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.readx.out.nread, ARRAY_SIZE(data)); + CHECK_VALUE(rd.readx.out.remaining, 0xFFFF); + CHECK_VALUE(rd.readx.out.compaction_mode, 0); + + printf("read without FLAGS2_READ_PERMIT_EXECUTE\n"); + rd.generic.level = RAW_READ_READX; + rd.readx.in.file.fnum = fnum; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = maxsize; + rd.readx.in.offset = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = false; + rd.readx.out.data = buf; + status = smb_raw_read(cli->tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.readx.out.nread, ARRAY_SIZE(data)); + CHECK_VALUE(rd.readx.out.remaining, 0xFFFF); + CHECK_VALUE(rd.readx.out.compaction_mode, 0); + +done: + smbcli_close(cli->tree, fnum); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + basic testing of read calls +*/ +struct torture_suite *torture_raw_read(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "READ"); + + torture_suite_add_1smb_test(suite, "read", test_read); + torture_suite_add_1smb_test(suite, "readx", test_readx); + torture_suite_add_1smb_test(suite, "lockread", test_lockread); + torture_suite_add_1smb_test(suite, "readbraw", test_readbraw); + torture_suite_add_1smb_test(suite, "read for execute", + test_read_for_execute); + + return suite; +} diff --git a/source4/torture/raw/rename.c b/source4/torture/raw/rename.c new file mode 100644 index 0000000000..9765c04101 --- /dev/null +++ b/source4/torture/raw/rename.c @@ -0,0 +1,455 @@ +/* + Unix SMB/CIFS implementation. + rename test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%s) Incorrect %s %d - should be %d\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +#define BASEDIR "\\testrename" + +/* + test SMBmv ops +*/ +static bool test_mv(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_rename io; + NTSTATUS status; + bool ret = true; + int fnum = -1; + const char *fname1 = BASEDIR "\\test1.txt"; + const char *fname2 = BASEDIR "\\test2.txt"; + const char *Fname1 = BASEDIR "\\Test1.txt"; + union smb_fileinfo finfo; + union smb_open op; + + printf("Testing SMBmv\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Trying simple rename\n"); + + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + op.ntcreatex.in.create_options = 0; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = fname1; + + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + io.generic.level = RAW_RENAME_RENAME; + io.rename.in.pattern1 = fname1; + io.rename.in.pattern2 = fname2; + io.rename.in.attrib = 0; + + printf("trying rename while first file open\n"); + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + smbcli_close(cli->tree, fnum); + + op.ntcreatex.in.access_mask = SEC_FILE_READ_DATA; + op.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + printf("trying rename while first file open with SHARE_ACCESS_DELETE\n"); + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.rename.in.pattern1 = fname2; + io.rename.in.pattern2 = fname1; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying case-changing rename\n"); + io.rename.in.pattern1 = fname1; + io.rename.in.pattern2 = Fname1; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.all_info.in.file.path = fname1; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + if (strcmp(finfo.all_info.out.fname.s, Fname1) != 0) { + printf("(%s) Incorrect filename [%s] after case-changing " + "rename, should be [%s]\n", __location__, + finfo.all_info.out.fname.s, Fname1); + } + + io.rename.in.pattern1 = fname1; + io.rename.in.pattern2 = fname2; + + printf("trying rename while not open\n"); + smb_raw_exit(cli->session); + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying self rename\n"); + io.rename.in.pattern1 = fname2; + io.rename.in.pattern2 = fname2; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.rename.in.pattern1 = fname1; + io.rename.in.pattern2 = fname1; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + + printf("trying wildcard rename\n"); + io.rename.in.pattern1 = BASEDIR "\\*.txt"; + io.rename.in.pattern2 = fname1; + + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("and again\n"); + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying extension change\n"); + io.rename.in.pattern1 = BASEDIR "\\*.txt"; + io.rename.in.pattern2 = BASEDIR "\\*.bak"; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + printf("Checking attrib handling\n"); + torture_set_file_attribute(cli->tree, BASEDIR "\\test1.bak", FILE_ATTRIBUTE_HIDDEN); + io.rename.in.pattern1 = BASEDIR "\\test1.bak"; + io.rename.in.pattern2 = BASEDIR "\\*.txt"; + io.rename.in.attrib = 0; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + io.rename.in.attrib = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + + +/* + test SMBntrename ops +*/ +static bool test_ntrename(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_rename io; + NTSTATUS status; + bool ret = true; + int fnum, i; + const char *fname1 = BASEDIR "\\test1.txt"; + const char *fname2 = BASEDIR "\\test2.txt"; + union smb_fileinfo finfo; + + printf("Testing SMBntrename\n"); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Trying simple rename\n"); + + fnum = create_complex_file(cli, tctx, fname1); + + io.generic.level = RAW_RENAME_NTRENAME; + io.ntrename.in.old_name = fname1; + io.ntrename.in.new_name = fname2; + io.ntrename.in.attrib = 0; + io.ntrename.in.cluster_size = 0; + io.ntrename.in.flags = RENAME_FLAG_RENAME; + + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + smb_raw_exit(cli->session); + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying self rename\n"); + io.ntrename.in.old_name = fname2; + io.ntrename.in.new_name = fname2; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.ntrename.in.old_name = fname1; + io.ntrename.in.new_name = fname1; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + printf("trying wildcard rename\n"); + io.ntrename.in.old_name = BASEDIR "\\*.txt"; + io.ntrename.in.new_name = fname1; + + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + + printf("Checking attrib handling\n"); + torture_set_file_attribute(cli->tree, fname2, FILE_ATTRIBUTE_HIDDEN); + io.ntrename.in.old_name = fname2; + io.ntrename.in.new_name = fname1; + io.ntrename.in.attrib = 0; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + io.ntrename.in.attrib = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_set_file_attribute(cli->tree, fname1, FILE_ATTRIBUTE_NORMAL); + + printf("Checking hard link\n"); + io.ntrename.in.old_name = fname1; + io.ntrename.in.new_name = fname2; + io.ntrename.in.attrib = 0; + io.ntrename.in.flags = RENAME_FLAG_HARD_LINK; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_set_file_attribute(cli->tree, fname1, FILE_ATTRIBUTE_SYSTEM); + + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.generic.in.file.path = fname2; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 2); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_SYSTEM); + + finfo.generic.in.file.path = fname1; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 2); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_SYSTEM); + + torture_set_file_attribute(cli->tree, fname1, FILE_ATTRIBUTE_NORMAL); + + smbcli_unlink(cli->tree, fname2); + + finfo.generic.in.file.path = fname1; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 1); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_NORMAL); + + printf("Checking copy\n"); + io.ntrename.in.old_name = fname1; + io.ntrename.in.new_name = fname2; + io.ntrename.in.attrib = 0; + io.ntrename.in.flags = RENAME_FLAG_COPY; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.generic.in.file.path = fname1; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 1); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_NORMAL); + + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.generic.in.file.path = fname2; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 1); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_NORMAL); + + torture_set_file_attribute(cli->tree, fname1, FILE_ATTRIBUTE_SYSTEM); + + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.generic.in.file.path = fname2; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 1); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_NORMAL); + + finfo.generic.in.file.path = fname1; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 1); + CHECK_VALUE(finfo.all_info.out.attrib, FILE_ATTRIBUTE_SYSTEM); + + torture_set_file_attribute(cli->tree, fname1, FILE_ATTRIBUTE_NORMAL); + + smbcli_unlink(cli->tree, fname2); + + finfo.generic.in.file.path = fname1; + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.nlink, 1); + + printf("Checking invalid flags\n"); + io.ntrename.in.old_name = fname1; + io.ntrename.in.new_name = fname2; + io.ntrename.in.attrib = 0; + io.ntrename.in.flags = 0; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + io.ntrename.in.flags = 300; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + io.ntrename.in.flags = 0x106; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + printf("Checking unknown field\n"); + io.ntrename.in.old_name = fname1; + io.ntrename.in.new_name = fname2; + io.ntrename.in.attrib = 0; + io.ntrename.in.flags = RENAME_FLAG_RENAME; + io.ntrename.in.cluster_size = 0xff; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying RENAME_FLAG_MOVE_CLUSTER_INFORMATION\n"); + + io.ntrename.in.old_name = fname2; + io.ntrename.in.new_name = fname1; + io.ntrename.in.attrib = 0; + io.ntrename.in.flags = RENAME_FLAG_MOVE_CLUSTER_INFORMATION; + io.ntrename.in.cluster_size = 1; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + io.ntrename.in.flags = RENAME_FLAG_COPY; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + +#if 0 + { + char buf[16384]; + fnum = smbcli_open(cli->tree, fname1, O_RDWR, DENY_NONE); + memset(buf, 1, sizeof(buf)); + smbcli_write(cli->tree, fnum, 0, buf, 0, sizeof(buf)); + smbcli_close(cli->tree, fnum); + + fnum = smbcli_open(cli->tree, fname2, O_RDWR, DENY_NONE); + memset(buf, 1, sizeof(buf)); + smbcli_write(cli->tree, fnum, 0, buf, 0, sizeof(buf)-1); + smbcli_close(cli->tree, fnum); + + torture_all_info(cli->tree, fname1); + torture_all_info(cli->tree, fname2); + } + + + io.ntrename.in.flags = RENAME_FLAG_MOVE_CLUSTER_INFORMATION; + status = smb_raw_rename(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + for (i=0;i<20000;i++) { + io.ntrename.in.cluster_size = i; + status = smb_raw_rename(cli->tree, &io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + printf("i=%d status=%s\n", i, nt_errstr(status)); + } + } +#endif + + printf("Checking other flags\n"); + + for (i=0;i<0xFFF;i++) { + if (i == RENAME_FLAG_RENAME || + i == RENAME_FLAG_HARD_LINK || + i == RENAME_FLAG_COPY) { + continue; + } + + io.ntrename.in.old_name = fname2; + io.ntrename.in.new_name = fname1; + io.ntrename.in.flags = i; + io.ntrename.in.attrib = 0; + io.ntrename.in.cluster_size = 0; + status = smb_raw_rename(cli->tree, &io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("flags=0x%x status=%s\n", i, nt_errstr(status)); + } + } + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +extern bool test_trans2rename(struct torture_context *tctx, struct smbcli_state *cli1, struct smbcli_state *cli2); +extern bool test_nttransrename(struct torture_context *tctx, struct smbcli_state *cli1); + +/* + basic testing of rename calls +*/ +struct torture_suite *torture_raw_rename(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "RENAME"); + + torture_suite_add_1smb_test(suite, "mv", test_mv); + /* test_trans2rename and test_nttransrename are actually in torture/raw/oplock.c to + use the handlers and macros there. */ + torture_suite_add_2smb_test(suite, "trans2rename", test_trans2rename); + torture_suite_add_1smb_test(suite, "nttransrename", test_nttransrename); + torture_suite_add_1smb_test(suite, "ntrename", test_ntrename); + + return suite; +} diff --git a/source4/torture/raw/samba3hide.c b/source4/torture/raw/samba3hide.c new file mode 100644 index 0000000000..1f1501045c --- /dev/null +++ b/source4/torture/raw/samba3hide.c @@ -0,0 +1,339 @@ +/* + Unix SMB/CIFS implementation. + Test samba3 hide unreadable/unwriteable + Copyright (C) Volker Lendecke 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +static void init_unixinfo_nochange(union smb_setfileinfo *info) +{ + ZERO_STRUCTP(info); + info->unix_basic.level = RAW_SFILEINFO_UNIX_BASIC; + info->unix_basic.in.mode = SMB_MODE_NO_CHANGE; + + info->unix_basic.in.end_of_file = SMB_SIZE_NO_CHANGE_HI; + info->unix_basic.in.end_of_file <<= 32; + info->unix_basic.in.end_of_file |= SMB_SIZE_NO_CHANGE_LO; + + info->unix_basic.in.num_bytes = SMB_SIZE_NO_CHANGE_HI; + info->unix_basic.in.num_bytes <<= 32; + info->unix_basic.in.num_bytes |= SMB_SIZE_NO_CHANGE_LO; + + info->unix_basic.in.status_change_time = SMB_TIME_NO_CHANGE_HI; + info->unix_basic.in.status_change_time <<= 32; + info->unix_basic.in.status_change_time |= SMB_TIME_NO_CHANGE_LO; + + info->unix_basic.in.access_time = SMB_TIME_NO_CHANGE_HI; + info->unix_basic.in.access_time <<= 32; + info->unix_basic.in.access_time |= SMB_TIME_NO_CHANGE_LO; + + info->unix_basic.in.change_time = SMB_TIME_NO_CHANGE_HI; + info->unix_basic.in.change_time <<= 32; + info->unix_basic.in.change_time |= SMB_TIME_NO_CHANGE_LO; + + info->unix_basic.in.uid = SMB_UID_NO_CHANGE; + info->unix_basic.in.gid = SMB_GID_NO_CHANGE; +} + +struct list_state { + const char *fname; + bool visible; +}; + +static void set_visible(struct clilist_file_info *i, const char *mask, + void *priv) +{ + struct list_state *state = (struct list_state *)priv; + + if (strcasecmp_m(state->fname, i->name) == 0) + state->visible = true; +} + +static bool is_visible(struct smbcli_tree *tree, const char *fname) +{ + struct list_state state; + + state.visible = false; + state.fname = fname; + + if (smbcli_list(tree, "*.*", 0, set_visible, &state) < 0) { + return false; + } + return state.visible; +} + +static bool is_readable(struct smbcli_tree *tree, const char *fname) +{ + int fnum; + fnum = smbcli_open(tree, fname, O_RDONLY, DENY_NONE); + if (fnum < 0) { + return false; + } + smbcli_close(tree, fnum); + return true; +} + +static bool is_writeable(TALLOC_CTX *mem_ctx, struct smbcli_tree *tree, + const char *fname) +{ + int fnum; + fnum = smbcli_open(tree, fname, O_WRONLY, DENY_NONE); + if (fnum < 0) { + return false; + } + smbcli_close(tree, fnum); + return true; +} + +/* + * This is not an exact method because there's a ton of reasons why a getatr + * might fail. But for our purposes it's sufficient. + */ + +static bool smbcli_file_exists(struct smbcli_tree *tree, const char *fname) +{ + return NT_STATUS_IS_OK(smbcli_getatr(tree, fname, NULL, NULL, NULL)); +} + +static NTSTATUS smbcli_chmod(struct smbcli_tree *tree, const char *fname, + uint64_t permissions) +{ + union smb_setfileinfo sfinfo; + init_unixinfo_nochange(&sfinfo); + sfinfo.unix_basic.in.file.path = fname; + sfinfo.unix_basic.in.permissions = permissions; + return smb_raw_setpathinfo(tree, &sfinfo); +} + +bool torture_samba3_hide(struct torture_context *torture) +{ + struct smbcli_state *cli; + const char *fname = "test.txt"; + int fnum; + NTSTATUS status; + struct smbcli_tree *hideunread; + struct smbcli_tree *hideunwrite; + + if (!torture_open_connection_share( + torture, &cli, torture, torture_setting_string(torture, "host", NULL), + torture_setting_string(torture, "share", NULL), torture->ev)) { + d_printf("torture_open_connection_share failed\n"); + return false; + } + + status = torture_second_tcon(torture, cli->session, "hideunread", + &hideunread); + if (!NT_STATUS_IS_OK(status)) { + d_printf("second_tcon(hideunread) failed: %s\n", + nt_errstr(status)); + return false; + } + + status = torture_second_tcon(torture, cli->session, "hideunwrite", + &hideunwrite); + if (!NT_STATUS_IS_OK(status)) { + d_printf("second_tcon(hideunwrite) failed: %s\n", + nt_errstr(status)); + return false; + } + + status = smbcli_unlink(cli->tree, fname); + if (NT_STATUS_EQUAL(status, NT_STATUS_CANNOT_DELETE)) { + smbcli_setatr(cli->tree, fname, 0, -1); + smbcli_unlink(cli->tree, fname); + } + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + d_printf("Failed to create %s - %s\n", fname, + smbcli_errstr(cli->tree)); + return false; + } + + smbcli_close(cli->tree, fnum); + + if (!smbcli_file_exists(cli->tree, fname)) { + d_printf("%s does not exist\n", fname); + return false; + } + + /* R/W file should be visible everywhere */ + + status = smbcli_chmod(cli->tree, fname, UNIX_R_USR|UNIX_W_USR); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smbcli_chmod failed: %s\n", nt_errstr(status)); + return false; + } + if (!is_writeable(torture, cli->tree, fname)) { + d_printf("File not writable\n"); + return false; + } + if (!is_readable(cli->tree, fname)) { + d_printf("File not readable\n"); + return false; + } + if (!is_visible(cli->tree, fname)) { + d_printf("r/w file not visible via normal share\n"); + return false; + } + if (!is_visible(hideunread, fname)) { + d_printf("r/w file not visible via hide unreadable\n"); + return false; + } + if (!is_visible(hideunwrite, fname)) { + d_printf("r/w file not visible via hide unwriteable\n"); + return false; + } + + /* R/O file should not be visible via hide unwriteable files */ + + status = smbcli_chmod(cli->tree, fname, UNIX_R_USR); + + if (!NT_STATUS_IS_OK(status)) { + d_printf("smbcli_chmod failed: %s\n", nt_errstr(status)); + return false; + } + if (is_writeable(torture, cli->tree, fname)) { + d_printf("r/o is writable\n"); + return false; + } + if (!is_readable(cli->tree, fname)) { + d_printf("r/o not readable\n"); + return false; + } + if (!is_visible(cli->tree, fname)) { + d_printf("r/o file not visible via normal share\n"); + return false; + } + if (!is_visible(hideunread, fname)) { + d_printf("r/o file not visible via hide unreadable\n"); + return false; + } + if (is_visible(hideunwrite, fname)) { + d_printf("r/o file visible via hide unwriteable\n"); + return false; + } + + /* inaccessible file should be only visible on normal share */ + + status = smbcli_chmod(cli->tree, fname, 0); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smbcli_chmod failed: %s\n", nt_errstr(status)); + return false; + } + if (is_writeable(torture, cli->tree, fname)) { + d_printf("inaccessible file is writable\n"); + return false; + } + if (is_readable(cli->tree, fname)) { + d_printf("inaccessible file is readable\n"); + return false; + } + if (!is_visible(cli->tree, fname)) { + d_printf("inaccessible file not visible via normal share\n"); + return false; + } + if (is_visible(hideunread, fname)) { + d_printf("inaccessible file visible via hide unreadable\n"); + return false; + } + if (is_visible(hideunwrite, fname)) { + d_printf("inaccessible file visible via hide unwriteable\n"); + return false; + } + + smbcli_chmod(cli->tree, fname, UNIX_R_USR|UNIX_W_USR); + smbcli_unlink(cli->tree, fname); + + return true; +} + +/* + * Try to force smb_close to return an error. The only way I can think of is + * to open a file with delete on close, chmod the parent dir to 000 and then + * close. smb_close should return NT_STATUS_ACCESS_DENIED. + */ + +bool torture_samba3_closeerr(struct torture_context *tctx) +{ + struct smbcli_state *cli = NULL; + bool result = false; + NTSTATUS status; + const char *dname = "closeerr.dir"; + const char *fname = "closeerr.dir\\closerr.txt"; + int fnum; + + if (!torture_open_connection(&cli, tctx, 0)) { + goto fail; + } + + smbcli_deltree(cli->tree, dname); + + torture_assert_ntstatus_ok( + tctx, smbcli_mkdir(cli->tree, dname), + talloc_asprintf(tctx, "smbcli_mdir failed: (%s)\n", + smbcli_errstr(cli->tree))); + + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, + DENY_NONE); + torture_assert(tctx, fnum != -1, + talloc_asprintf(tctx, "smbcli_open failed: %s\n", + smbcli_errstr(cli->tree))); + smbcli_close(cli->tree, fnum); + + fnum = smbcli_nt_create_full(cli->tree, fname, 0, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL, + NTCREATEX_SHARE_ACCESS_DELETE, + NTCREATEX_DISP_OPEN, 0, 0); + + torture_assert(tctx, fnum != -1, + talloc_asprintf(tctx, "smbcli_open failed: %s\n", + smbcli_errstr(cli->tree))); + + status = smbcli_nt_delete_on_close(cli->tree, fnum, true); + + torture_assert_ntstatus_ok(tctx, status, + "setting delete_on_close on file failed !"); + + status = smbcli_chmod(cli->tree, dname, 0); + + torture_assert_ntstatus_ok(tctx, status, + "smbcli_chmod on file failed !"); + + status = smbcli_close(cli->tree, fnum); + + smbcli_chmod(cli->tree, dname, UNIX_R_USR|UNIX_W_USR|UNIX_X_USR); + smbcli_deltree(cli->tree, dname); + + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_ACCESS_DENIED, + "smbcli_close"); + + result = true; + + fail: + if (cli) { + torture_close_connection(cli); + } + return result; +} diff --git a/source4/torture/raw/samba3misc.c b/source4/torture/raw/samba3misc.c new file mode 100644 index 0000000000..27b4d42dd8 --- /dev/null +++ b/source4/torture/raw/samba3misc.c @@ -0,0 +1,891 @@ +/* + Unix SMB/CIFS implementation. + Test some misc Samba3 code paths + Copyright (C) Volker Lendecke 2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "lib/events/events.h" +#include "param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + } \ +} while (0) + +bool torture_samba3_checkfsp(struct torture_context *torture) +{ + struct smbcli_state *cli; + const char *fname = "test.txt"; + const char *dirname = "testdir"; + int fnum; + NTSTATUS status; + bool ret = true; + TALLOC_CTX *mem_ctx; + ssize_t nread; + char buf[16]; + struct smbcli_tree *tree2; + + if ((mem_ctx = talloc_init("torture_samba3_checkfsp")) == NULL) { + d_printf("talloc_init failed\n"); + return false; + } + + if (!torture_open_connection_share( + torture, &cli, torture, torture_setting_string(torture, "host", NULL), + torture_setting_string(torture, "share", NULL), torture->ev)) { + d_printf("torture_open_connection_share failed\n"); + ret = false; + goto done; + } + + smbcli_deltree(cli->tree, dirname); + + status = torture_second_tcon(torture, cli->session, + torture_setting_string(torture, "share", NULL), + &tree2); + CHECK_STATUS(status, NT_STATUS_OK); + if (!NT_STATUS_IS_OK(status)) + goto done; + + /* Try a read on an invalid FID */ + + nread = smbcli_read(cli->tree, 4711, buf, 0, sizeof(buf)); + CHECK_STATUS(smbcli_nt_error(cli->tree), NT_STATUS_INVALID_HANDLE); + + /* Try a read on a directory handle */ + + status = smbcli_mkdir(cli->tree, dirname); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smbcli_mkdir failed: %s\n", nt_errstr(status)); + ret = false; + goto done; + } + + /* Open the directory */ + { + union smb_open io; + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.fname = dirname; + status = smb_raw_open(cli->tree, mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smb_open on the directory failed: %s\n", + nt_errstr(status)); + ret = false; + goto done; + } + fnum = io.ntcreatex.out.file.fnum; + } + + /* Try a read on the directory */ + + nread = smbcli_read(cli->tree, fnum, buf, 0, sizeof(buf)); + if (nread >= 0) { + d_printf("smbcli_read on a directory succeeded, expected " + "failure\n"); + ret = false; + } + + CHECK_STATUS(smbcli_nt_error(cli->tree), + NT_STATUS_INVALID_DEVICE_REQUEST); + + /* Same test on the second tcon */ + + nread = smbcli_read(tree2, fnum, buf, 0, sizeof(buf)); + if (nread >= 0) { + d_printf("smbcli_read on a directory succeeded, expected " + "failure\n"); + ret = false; + } + + CHECK_STATUS(smbcli_nt_error(tree2), NT_STATUS_INVALID_HANDLE); + + smbcli_close(cli->tree, fnum); + + /* Try a normal file read on a second tcon */ + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + d_printf("Failed to create %s - %s\n", fname, + smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + nread = smbcli_read(tree2, fnum, buf, 0, sizeof(buf)); + CHECK_STATUS(smbcli_nt_error(tree2), NT_STATUS_INVALID_HANDLE); + + smbcli_close(cli->tree, fnum); + + done: + smbcli_deltree(cli->tree, dirname); + torture_close_connection(cli); + talloc_free(mem_ctx); + + return ret; +} + +static NTSTATUS raw_smbcli_open(struct smbcli_tree *tree, const char *fname, int flags, int share_mode, int *fnum) +{ + union smb_open open_parms; + uint_t openfn=0; + uint_t accessmode=0; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + mem_ctx = talloc_init("raw_open"); + if (!mem_ctx) return NT_STATUS_NO_MEMORY; + + if (flags & O_CREAT) { + openfn |= OPENX_OPEN_FUNC_CREATE; + } + if (!(flags & O_EXCL)) { + if (flags & O_TRUNC) { + openfn |= OPENX_OPEN_FUNC_TRUNC; + } else { + openfn |= OPENX_OPEN_FUNC_OPEN; + } + } + + accessmode = (share_mode<<OPENX_MODE_DENY_SHIFT); + + if ((flags & O_ACCMODE) == O_RDWR) { + accessmode |= OPENX_MODE_ACCESS_RDWR; + } else if ((flags & O_ACCMODE) == O_WRONLY) { + accessmode |= OPENX_MODE_ACCESS_WRITE; + } else if ((flags & O_ACCMODE) == O_RDONLY) { + accessmode |= OPENX_MODE_ACCESS_READ; + } + +#if defined(O_SYNC) + if ((flags & O_SYNC) == O_SYNC) { + accessmode |= OPENX_MODE_WRITE_THRU; + } +#endif + + if (share_mode == DENY_FCB) { + accessmode = OPENX_MODE_ACCESS_FCB | OPENX_MODE_DENY_FCB; + } + + open_parms.openx.level = RAW_OPEN_OPENX; + open_parms.openx.in.flags = 0; + open_parms.openx.in.open_mode = accessmode; + open_parms.openx.in.search_attrs = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN; + open_parms.openx.in.file_attrs = 0; + open_parms.openx.in.write_time = 0; + open_parms.openx.in.open_func = openfn; + open_parms.openx.in.size = 0; + open_parms.openx.in.timeout = 0; + open_parms.openx.in.fname = fname; + + status = smb_raw_open(tree, mem_ctx, &open_parms); + talloc_free(mem_ctx); + + if (fnum && NT_STATUS_IS_OK(status)) { + *fnum = open_parms.openx.out.file.fnum; + } + + return status; +} + +static NTSTATUS raw_smbcli_t2open(struct smbcli_tree *tree, const char *fname, int flags, int share_mode, int *fnum) +{ + union smb_open io; + uint_t openfn=0; + uint_t accessmode=0; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + mem_ctx = talloc_init("raw_t2open"); + if (!mem_ctx) return NT_STATUS_NO_MEMORY; + + if (flags & O_CREAT) { + openfn |= OPENX_OPEN_FUNC_CREATE; + } + if (!(flags & O_EXCL)) { + if (flags & O_TRUNC) { + openfn |= OPENX_OPEN_FUNC_TRUNC; + } else { + openfn |= OPENX_OPEN_FUNC_OPEN; + } + } + + accessmode = (share_mode<<OPENX_MODE_DENY_SHIFT); + + if ((flags & O_ACCMODE) == O_RDWR) { + accessmode |= OPENX_MODE_ACCESS_RDWR; + } else if ((flags & O_ACCMODE) == O_WRONLY) { + accessmode |= OPENX_MODE_ACCESS_WRITE; + } else if ((flags & O_ACCMODE) == O_RDONLY) { + accessmode |= OPENX_MODE_ACCESS_READ; + } + +#if defined(O_SYNC) + if ((flags & O_SYNC) == O_SYNC) { + accessmode |= OPENX_MODE_WRITE_THRU; + } +#endif + + if (share_mode == DENY_FCB) { + accessmode = OPENX_MODE_ACCESS_FCB | OPENX_MODE_DENY_FCB; + } + + memset(&io, '\0', sizeof(io)); + io.t2open.level = RAW_OPEN_T2OPEN; + io.t2open.in.flags = 0; + io.t2open.in.open_mode = accessmode; + io.t2open.in.search_attrs = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN; + io.t2open.in.file_attrs = 0; + io.t2open.in.write_time = 0; + io.t2open.in.open_func = openfn; + io.t2open.in.size = 0; + io.t2open.in.timeout = 0; + io.t2open.in.fname = fname; + + io.t2open.in.num_eas = 1; + io.t2open.in.eas = talloc_array(mem_ctx, struct ea_struct, io.t2open.in.num_eas); + io.t2open.in.eas[0].flags = 0; + io.t2open.in.eas[0].name.s = ".CLASSINFO"; + io.t2open.in.eas[0].value = data_blob_talloc(mem_ctx, "first value", 11); + + status = smb_raw_open(tree, mem_ctx, &io); + talloc_free(mem_ctx); + + if (fnum && NT_STATUS_IS_OK(status)) { + *fnum = io.openx.out.file.fnum; + } + + return status; +} + +static NTSTATUS raw_smbcli_ntcreate(struct smbcli_tree *tree, const char *fname, int *fnum) +{ + union smb_open io; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + mem_ctx = talloc_init("raw_t2open"); + if (!mem_ctx) return NT_STATUS_NO_MEMORY; + + memset(&io, '\0', sizeof(io)); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = fname; + + status = smb_raw_open(tree, mem_ctx, &io); + talloc_free(mem_ctx); + + if (fnum && NT_STATUS_IS_OK(status)) { + *fnum = io.openx.out.file.fnum; + } + + return status; +} + + +bool torture_samba3_badpath(struct torture_context *torture) +{ + struct smbcli_state *cli_nt; + struct smbcli_state *cli_dos; + const char *fname = "test.txt"; + const char *fname1 = "test1.txt"; + const char *dirname = "testdir"; + char *fpath; + char *fpath1; + int fnum; + NTSTATUS status; + bool ret = true; + TALLOC_CTX *mem_ctx; + bool nt_status_support; + + if (!(mem_ctx = talloc_init("torture_samba3_badpath"))) { + d_printf("talloc_init failed\n"); + return false; + } + + nt_status_support = lp_nt_status_support(torture->lp_ctx); + + if (!lp_set_cmdline(torture->lp_ctx, "nt status support", "yes")) { + printf("Could not set 'nt status support = yes'\n"); + goto fail; + } + + if (!torture_open_connection(&cli_nt, torture, 0)) { + goto fail; + } + + if (!lp_set_cmdline(torture->lp_ctx, "nt status support", "no")) { + printf("Could not set 'nt status support = yes'\n"); + goto fail; + } + + if (!torture_open_connection(&cli_dos, torture, 1)) { + goto fail; + } + + if (!lp_set_cmdline(torture->lp_ctx, "nt status support", + nt_status_support ? "yes":"no")) { + printf("Could not reset 'nt status support = yes'"); + goto fail; + } + + smbcli_deltree(cli_nt->tree, dirname); + + status = smbcli_mkdir(cli_nt->tree, dirname); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smbcli_mkdir failed: %s\n", nt_errstr(status)); + ret = false; + goto done; + } + + status = smbcli_chkpath(cli_nt->tree, dirname); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_chkpath(cli_nt->tree, + talloc_asprintf(mem_ctx, "%s\\bla", dirname)); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + status = smbcli_chkpath(cli_dos->tree, + talloc_asprintf(mem_ctx, "%s\\bla", dirname)); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + status = smbcli_chkpath(cli_nt->tree, + talloc_asprintf(mem_ctx, "%s\\bla\\blub", + dirname)); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_NOT_FOUND); + status = smbcli_chkpath(cli_dos->tree, + talloc_asprintf(mem_ctx, "%s\\bla\\blub", + dirname)); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + if (!(fpath = talloc_asprintf(mem_ctx, "%s\\%s", dirname, fname))) { + goto fail; + } + fnum = smbcli_open(cli_nt->tree, fpath, O_RDWR | O_CREAT, DENY_NONE); + if (fnum == -1) { + d_printf("Could not create file %s: %s\n", fpath, + smbcli_errstr(cli_nt->tree)); + goto fail; + } + smbcli_close(cli_nt->tree, fnum); + + if (!(fpath1 = talloc_asprintf(mem_ctx, "%s\\%s", dirname, fname1))) { + goto fail; + } + fnum = smbcli_open(cli_nt->tree, fpath1, O_RDWR | O_CREAT, DENY_NONE); + if (fnum == -1) { + d_printf("Could not create file %s: %s\n", fpath1, + smbcli_errstr(cli_nt->tree)); + goto fail; + } + smbcli_close(cli_nt->tree, fnum); + + /* + * Do a whole bunch of error code checks on chkpath + */ + + status = smbcli_chkpath(cli_nt->tree, fpath); + CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY); + status = smbcli_chkpath(cli_dos->tree, fpath); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + status = smbcli_chkpath(cli_nt->tree, ".."); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + status = smbcli_chkpath(cli_dos->tree, ".."); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidpath)); + + status = smbcli_chkpath(cli_nt->tree, "."); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_chkpath(cli_dos->tree, "."); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + status = smbcli_chkpath(cli_nt->tree, "\t"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_chkpath(cli_dos->tree, "\t"); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + status = smbcli_chkpath(cli_nt->tree, "\t\\bla"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_chkpath(cli_dos->tree, "\t\\bla"); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + status = smbcli_chkpath(cli_nt->tree, "<"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_chkpath(cli_dos->tree, "<"); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + status = smbcli_chkpath(cli_nt->tree, "<\\bla"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_chkpath(cli_dos->tree, "<\\bla"); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRbadpath)); + + /* + * .... And the same gang against getatr. Note that the DOS error codes + * differ.... + */ + + status = smbcli_getatr(cli_nt->tree, fpath, NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OK); + status = smbcli_getatr(cli_dos->tree, fpath, NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_getatr(cli_nt->tree, "..", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + status = smbcli_getatr(cli_dos->tree, "..", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidpath)); + + status = smbcli_getatr(cli_nt->tree, ".", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_getatr(cli_dos->tree, ".", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = smbcli_getatr(cli_nt->tree, "\t", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_getatr(cli_dos->tree, "\t", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = smbcli_getatr(cli_nt->tree, "\t\\bla", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_getatr(cli_dos->tree, "\t\\bla", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = smbcli_getatr(cli_nt->tree, "<", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_getatr(cli_dos->tree, "<", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = smbcli_getatr(cli_nt->tree, "<\\bla", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = smbcli_getatr(cli_dos->tree, "<\\bla", NULL, NULL, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + /* Try the same set with openX. */ + + status = raw_smbcli_open(cli_nt->tree, "..", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + status = raw_smbcli_open(cli_dos->tree, "..", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidpath)); + + status = raw_smbcli_open(cli_nt->tree, ".", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = raw_smbcli_open(cli_dos->tree, ".", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = raw_smbcli_open(cli_nt->tree, "\t", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = raw_smbcli_open(cli_dos->tree, "\t", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = raw_smbcli_open(cli_nt->tree, "\t\\bla", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = raw_smbcli_open(cli_dos->tree, "\t\\bla", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = raw_smbcli_open(cli_nt->tree, "<", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = raw_smbcli_open(cli_dos->tree, "<", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + status = raw_smbcli_open(cli_nt->tree, "<\\bla", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + status = raw_smbcli_open(cli_dos->tree, "<\\bla", O_RDONLY, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS, ERRinvalidname)); + + /* Let's test EEXIST error code mapping. */ + status = raw_smbcli_open(cli_nt->tree, fpath, O_RDONLY | O_CREAT| O_EXCL, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + status = raw_smbcli_open(cli_dos->tree, fpath, O_RDONLY | O_CREAT| O_EXCL, DENY_NONE, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS,ERRfilexists)); + + status = raw_smbcli_t2open(cli_nt->tree, fpath, O_RDONLY | O_CREAT| O_EXCL, DENY_NONE, NULL); + if (!NT_STATUS_EQUAL(status, NT_STATUS_EAS_NOT_SUPPORTED) + || !torture_setting_bool(torture, "samba3", false)) { + /* Against samba3, treat EAS_NOT_SUPPORTED as acceptable */ + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + } + status = raw_smbcli_t2open(cli_dos->tree, fpath, O_RDONLY | O_CREAT| O_EXCL, DENY_NONE, NULL); + if (!NT_STATUS_EQUAL(status, NT_STATUS_DOS(ERRDOS,ERReasnotsupported)) + || !torture_setting_bool(torture, "samba3", false)) { + /* Against samba3, treat EAS_NOT_SUPPORTED as acceptable */ + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS,ERRfilexists)); + } + + status = raw_smbcli_ntcreate(cli_nt->tree, fpath, NULL); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + status = raw_smbcli_ntcreate(cli_dos->tree, fpath, NULL); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS,ERRfilexists)); + + /* Try the rename test. */ + { + union smb_rename io; + memset(&io, '\0', sizeof(io)); + io.rename.in.pattern1 = fpath1; + io.rename.in.pattern2 = fpath; + + /* Try with SMBmv rename. */ + status = smb_raw_rename(cli_nt->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + status = smb_raw_rename(cli_dos->tree, &io); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS,ERRrename)); + + /* Try with NT rename. */ + io.generic.level = RAW_RENAME_NTRENAME; + io.ntrename.in.old_name = fpath1; + io.ntrename.in.new_name = fpath; + io.ntrename.in.attrib = 0; + io.ntrename.in.cluster_size = 0; + io.ntrename.in.flags = RENAME_FLAG_RENAME; + + status = smb_raw_rename(cli_nt->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + status = smb_raw_rename(cli_dos->tree, &io); + CHECK_STATUS(status, NT_STATUS_DOS(ERRDOS,ERRrename)); + } + + goto done; + + fail: + ret = false; + + done: + if (cli_nt != NULL) { + smbcli_deltree(cli_nt->tree, dirname); + torture_close_connection(cli_nt); + } + if (cli_dos != NULL) { + torture_close_connection(cli_dos); + } + talloc_free(mem_ctx); + + return ret; +} + +static void count_fn(struct clilist_file_info *info, const char *name, + void *private_data) +{ + int *counter = (int *)private_data; + *counter += 1; +} + +bool torture_samba3_caseinsensitive(struct torture_context *torture) +{ + struct smbcli_state *cli; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + const char *dirname = "insensitive"; + const char *ucase_dirname = "InSeNsItIvE"; + const char *fname = "foo"; + char *fpath; + int fnum; + int counter = 0; + bool ret = true; + + if (!(mem_ctx = talloc_init("torture_samba3_caseinsensitive"))) { + d_printf("talloc_init failed\n"); + return false; + } + + if (!torture_open_connection(&cli, torture, 0)) { + goto done; + } + + smbcli_deltree(cli->tree, dirname); + + status = smbcli_mkdir(cli->tree, dirname); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smbcli_mkdir failed: %s\n", nt_errstr(status)); + goto done; + } + + if (!(fpath = talloc_asprintf(mem_ctx, "%s\\%s", dirname, fname))) { + goto done; + } + fnum = smbcli_open(cli->tree, fpath, O_RDWR | O_CREAT, DENY_NONE); + if (fnum == -1) { + d_printf("Could not create file %s: %s\n", fpath, + smbcli_errstr(cli->tree)); + goto done; + } + smbcli_close(cli->tree, fnum); + + smbcli_list(cli->tree, talloc_asprintf( + mem_ctx, "%s\\*", ucase_dirname), + FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_HIDDEN + |FILE_ATTRIBUTE_SYSTEM, + count_fn, (void *)&counter); + + if (counter == 3) { + ret = true; + } + else { + d_fprintf(stderr, "expected 3 entries, got %d\n", counter); + ret = false; + } + + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * Check that Samba3 correctly deals with conflicting posix byte range locks + * on an underlying file + */ + +bool torture_samba3_posixtimedlock(struct torture_context *tctx) +{ + struct smbcli_state *cli; + NTSTATUS status; + bool ret = true; + const char *dirname = "posixlock"; + const char *fname = "locked"; + const char *fpath; + const char *localdir; + const char *localname; + int fnum = -1; + + int fd = -1; + struct flock posix_lock; + + union smb_lock io; + struct smb_lock_entry lock_entry; + struct smbcli_request *req; + + if (!torture_open_connection(&cli, tctx, 0)) { + ret = false; + goto done; + } + + smbcli_deltree(cli->tree, dirname); + + status = smbcli_mkdir(cli->tree, dirname); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smbcli_mkdir failed: %s\n", + nt_errstr(status)); + ret = false; + goto done; + } + + if (!(fpath = talloc_asprintf(tctx, "%s\\%s", dirname, fname))) { + torture_warning(tctx, "talloc failed\n"); + ret = false; + goto done; + } + fnum = smbcli_open(cli->tree, fpath, O_RDWR | O_CREAT, DENY_NONE); + if (fnum == -1) { + torture_warning(tctx, "Could not create file %s: %s\n", fpath, + smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + if (!(localdir = torture_setting_string(tctx, "localdir", NULL))) { + torture_warning(tctx, "Need 'localdir' setting\n"); + ret = false; + goto done; + } + + if (!(localname = talloc_asprintf(tctx, "%s/%s/%s", localdir, dirname, + fname))) { + torture_warning(tctx, "talloc failed\n"); + ret = false; + goto done; + } + + /* + * Lock a byte range from posix + */ + + fd = open(localname, O_RDWR); + if (fd == -1) { + torture_warning(tctx, "open(%s) failed: %s\n", + localname, strerror(errno)); + goto done; + } + + posix_lock.l_type = F_WRLCK; + posix_lock.l_whence = SEEK_SET; + posix_lock.l_start = 0; + posix_lock.l_len = 1; + + if (fcntl(fd, F_SETLK, &posix_lock) == -1) { + torture_warning(tctx, "fcntl failed: %s\n", strerror(errno)); + ret = false; + goto done; + } + + /* + * Try a cifs brlock without timeout to see if posix locking = yes + */ + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + + lock_entry.count = 1; + lock_entry.offset = 0; + lock_entry.pid = cli->tree->session->pid; + + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.timeout = 0; + io.lockx.in.locks = &lock_entry; + io.lockx.in.file.fnum = fnum; + + status = smb_raw_lock(cli->tree, &io); + + ret = true; + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + if (!ret) { + goto done; + } + + /* + * Now fire off a timed brlock, unlock the posix lock and see if the + * timed lock gets through. + */ + + io.lockx.in.timeout = 5000; + + req = smb_raw_lock_send(cli->tree, &io); + if (req == NULL) { + torture_warning(tctx, "smb_raw_lock_send failed\n"); + ret = false; + goto done; + } + + /* + * Ship the async timed request to the server + */ + event_loop_once(req->transport->socket->event.ctx); + msleep(500); + + close(fd); + + status = smbcli_request_simple_recv(req); + + CHECK_STATUS(status, NT_STATUS_OK); + + done: + if (fnum != -1) { + smbcli_close(cli->tree, fnum); + } + if (fd != -1) { + close(fd); + } + smbcli_deltree(cli->tree, dirname); + return ret; +} + +bool torture_samba3_rootdirfid(struct torture_context *tctx) +{ + struct smbcli_state *cli; + NTSTATUS status; + uint16_t dnum; + union smb_open io; + const char *fname = "testfile"; + bool ret = false; + + if (!torture_open_connection(&cli, tctx, 0)) { + ret = false; + goto done; + } + + smbcli_unlink(cli->tree, fname); + + ZERO_STRUCT(io); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.flags = NTCREATEX_FLAGS_EXTENDED; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.access_mask = + SEC_STD_SYNCHRONIZE | SEC_FILE_EXECUTE; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = + NTCREATEX_SHARE_ACCESS_READ + | NTCREATEX_SHARE_ACCESS_READ; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.fname = "\\"; + status = smb_raw_open(cli->tree, tctx, &io); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smb_open on the directory failed: %s\n", + nt_errstr(status)); + ret = false; + goto done; + } + dnum = io.ntcreatex.out.file.fnum; + + io.ntcreatex.in.flags = + NTCREATEX_FLAGS_REQUEST_OPLOCK + | NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + io.ntcreatex.in.root_fid = dnum; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.fname = fname; + + status = smb_raw_open(cli->tree, tctx, &io); + if (!NT_STATUS_IS_OK(status)) { + d_printf("smb_open on the file %s failed: %s\n", + fname, nt_errstr(status)); + ret = false; + goto done; + } + + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + smbcli_close(cli->tree, dnum); + smbcli_unlink(cli->tree, fname); + + ret = true; + done: + return ret; +} + diff --git a/source4/torture/raw/search.c b/source4/torture/raw/search.c new file mode 100644 index 0000000000..2a6aef20b5 --- /dev/null +++ b/source4/torture/raw/search.c @@ -0,0 +1,1391 @@ +/* + Unix SMB/CIFS implementation. + RAW_SEARCH_* individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/filesys.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" + + +#define BASEDIR "\\testsearch" + +/* + callback function for single_search +*/ +static bool single_search_callback(void *private, const union smb_search_data *file) +{ + union smb_search_data *data = (union smb_search_data *)private; + + *data = *file; + + return true; +} + +/* + do a single file (non-wildcard) search +*/ +NTSTATUS torture_single_search(struct smbcli_state *cli, + TALLOC_CTX *tctx, + const char *pattern, + enum smb_search_level level, + enum smb_search_data_level data_level, + uint16_t attrib, + union smb_search_data *data) +{ + union smb_search_first io; + union smb_search_close c; + NTSTATUS status; + + switch (level) { + case RAW_SEARCH_SEARCH: + case RAW_SEARCH_FFIRST: + case RAW_SEARCH_FUNIQUE: + io.search_first.level = level; + io.search_first.data_level = RAW_SEARCH_DATA_SEARCH; + io.search_first.in.max_count = 1; + io.search_first.in.search_attrib = attrib; + io.search_first.in.pattern = pattern; + break; + + case RAW_SEARCH_TRANS2: + io.t2ffirst.level = RAW_SEARCH_TRANS2; + io.t2ffirst.data_level = data_level; + io.t2ffirst.in.search_attrib = attrib; + io.t2ffirst.in.max_count = 1; + io.t2ffirst.in.flags = FLAG_TRANS2_FIND_CLOSE; + io.t2ffirst.in.storage_type = 0; + io.t2ffirst.in.pattern = pattern; + break; + + case RAW_SEARCH_SMB2: + return NT_STATUS_INVALID_LEVEL; + } + + status = smb_raw_search_first(cli->tree, tctx, + &io, (void *)data, single_search_callback); + + if (NT_STATUS_IS_OK(status) && level == RAW_SEARCH_FFIRST) { + c.fclose.level = RAW_FINDCLOSE_FCLOSE; + c.fclose.in.max_count = 1; + c.fclose.in.search_attrib = 0; + c.fclose.in.id = data->search.id; + status = smb_raw_search_close(cli->tree, &c); + } + + return status; +} + + +static struct { + const char *name; + enum smb_search_level level; + enum smb_search_data_level data_level; + int name_offset; + int resume_key_offset; + uint32_t capability_mask; + NTSTATUS status; + union smb_search_data data; +} levels[] = { + {"FFIRST", + RAW_SEARCH_FFIRST, RAW_SEARCH_DATA_SEARCH, + offsetof(union smb_search_data, search.name), + -1, + }, + {"FUNIQUE", + RAW_SEARCH_FUNIQUE, RAW_SEARCH_DATA_SEARCH, + offsetof(union smb_search_data, search.name), + -1, + }, + {"SEARCH", + RAW_SEARCH_SEARCH, RAW_SEARCH_DATA_SEARCH, + offsetof(union smb_search_data, search.name), + -1, + }, + {"STANDARD", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_STANDARD, + offsetof(union smb_search_data, standard.name.s), + offsetof(union smb_search_data, standard.resume_key), + }, + {"EA_SIZE", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_EA_SIZE, + offsetof(union smb_search_data, ea_size.name.s), + offsetof(union smb_search_data, ea_size.resume_key), + }, + {"DIRECTORY_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_DIRECTORY_INFO, + offsetof(union smb_search_data, directory_info.name.s), + offsetof(union smb_search_data, directory_info.file_index), + }, + {"FULL_DIRECTORY_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, + offsetof(union smb_search_data, full_directory_info.name.s), + offsetof(union smb_search_data, full_directory_info.file_index), + }, + {"NAME_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_NAME_INFO, + offsetof(union smb_search_data, name_info.name.s), + offsetof(union smb_search_data, name_info.file_index), + }, + {"BOTH_DIRECTORY_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, + offsetof(union smb_search_data, both_directory_info.name.s), + offsetof(union smb_search_data, both_directory_info.file_index), + }, + {"ID_FULL_DIRECTORY_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, + offsetof(union smb_search_data, id_full_directory_info.name.s), + offsetof(union smb_search_data, id_full_directory_info.file_index), + }, + {"ID_BOTH_DIRECTORY_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, + offsetof(union smb_search_data, id_both_directory_info.name.s), + offsetof(union smb_search_data, id_both_directory_info.file_index), + }, + {"UNIX_INFO", + RAW_SEARCH_TRANS2, RAW_SEARCH_DATA_UNIX_INFO, + offsetof(union smb_search_data, unix_info.name), + offsetof(union smb_search_data, unix_info.file_index), + CAP_UNIX} +}; + + +/* + return level name +*/ +static const char *level_name(enum smb_search_level level, + enum smb_search_data_level data_level) +{ + int i; + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (level == levels[i].level && + data_level == levels[i].data_level) { + return levels[i].name; + } + } + return NULL; +} + +/* + extract the name from a smb_data structure and level +*/ +static const char *extract_name(void *data, enum smb_search_level level, + enum smb_search_data_level data_level) +{ + int i; + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (level == levels[i].level && + data_level == levels[i].data_level) { + return *(const char **)(levels[i].name_offset + (char *)data); + } + } + return NULL; +} + +/* + extract the name from a smb_data structure and level +*/ +static uint32_t extract_resume_key(void *data, enum smb_search_level level, + enum smb_search_data_level data_level) +{ + int i; + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (level == levels[i].level && + data_level == levels[i].data_level) { + return *(uint32_t *)(levels[i].resume_key_offset + (char *)data); + } + } + return 0; +} + +/* find a level in the table by name */ +static union smb_search_data *find(const char *name) +{ + int i; + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (NT_STATUS_IS_OK(levels[i].status) && + strcmp(levels[i].name, name) == 0) { + return &levels[i].data; + } + } + return NULL; +} + +/* + basic testing of all RAW_SEARCH_* calls using a single file +*/ +static bool test_one_file(struct torture_context *tctx, + struct smbcli_state *cli) +{ + bool ret = true; + int fnum; + const char *fname = "\\torture_search.txt"; + const char *fname2 = "\\torture_search-NOTEXIST.txt"; + NTSTATUS status; + int i; + union smb_fileinfo all_info, alt_info, name_info, internal_info; + union smb_search_data *s; + + fnum = create_complex_file(cli, tctx, fname); + if (fnum == -1) { + printf("ERROR: open of %s failed (%s)\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + /* call all the levels */ + for (i=0;i<ARRAY_SIZE(levels);i++) { + NTSTATUS expected_status; + uint32_t cap = cli->transport->negotiate.capabilities; + + torture_comment(tctx, "testing %s\n", levels[i].name); + + levels[i].status = torture_single_search(cli, tctx, fname, + levels[i].level, + levels[i].data_level, + 0, + &levels[i].data); + + /* see if this server claims to support this level */ + if ((cap & levels[i].capability_mask) != levels[i].capability_mask) { + printf("search level %s(%d) not supported by server\n", + levels[i].name, (int)levels[i].level); + continue; + } + + if (!NT_STATUS_IS_OK(levels[i].status)) { + printf("search level %s(%d) failed - %s\n", + levels[i].name, (int)levels[i].level, + nt_errstr(levels[i].status)); + ret = false; + continue; + } + + status = torture_single_search(cli, tctx, fname2, + levels[i].level, + levels[i].data_level, + 0, + &levels[i].data); + + expected_status = NT_STATUS_NO_SUCH_FILE; + if (levels[i].level == RAW_SEARCH_SEARCH || + levels[i].level == RAW_SEARCH_FFIRST || + levels[i].level == RAW_SEARCH_FUNIQUE) { + expected_status = STATUS_NO_MORE_FILES; + } + if (!NT_STATUS_EQUAL(status, expected_status)) { + printf("search level %s(%d) should fail with %s - %s\n", + levels[i].name, (int)levels[i].level, + nt_errstr(expected_status), + nt_errstr(status)); + ret = false; + } + } + + /* get the all_info file into to check against */ + all_info.generic.level = RAW_FILEINFO_ALL_INFO; + all_info.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, tctx, &all_info); + torture_assert_ntstatus_ok(tctx, status, "RAW_FILEINFO_ALL_INFO failed"); + + alt_info.generic.level = RAW_FILEINFO_ALT_NAME_INFO; + alt_info.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, tctx, &alt_info); + torture_assert_ntstatus_ok(tctx, status, "RAW_FILEINFO_ALT_NAME_INFO failed"); + + internal_info.generic.level = RAW_FILEINFO_INTERNAL_INFORMATION; + internal_info.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, tctx, &internal_info); + torture_assert_ntstatus_ok(tctx, status, "RAW_FILEINFO_INTERNAL_INFORMATION failed"); + + name_info.generic.level = RAW_FILEINFO_NAME_INFO; + name_info.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, tctx, &name_info); + torture_assert_ntstatus_ok(tctx, status, "RAW_FILEINFO_NAME_INFO failed"); + +#define CHECK_VAL(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if ((s->sname1.field1) != (v.sname2.out.field2)) { \ + printf("(%s) %s/%s [0x%x] != %s/%s [0x%x]\n", \ + __location__, \ + #sname1, #field1, (int)s->sname1.field1, \ + #sname2, #field2, (int)v.sname2.out.field2); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_TIME(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if (s->sname1.field1 != (~1 & nt_time_to_unix(v.sname2.out.field2))) { \ + printf("(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, timestring(tctx, s->sname1.field1), \ + #sname2, #field2, nt_time_string(tctx, v.sname2.out.field2)); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_NTTIME(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if (s->sname1.field1 != v.sname2.out.field2) { \ + printf("(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, nt_time_string(tctx, s->sname1.field1), \ + #sname2, #field2, nt_time_string(tctx, v.sname2.out.field2)); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_STR(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1 || strcmp(s->sname1.field1, v.sname2.out.field2.s)) { \ + printf("(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1, \ + #sname2, #field2, v.sname2.out.field2.s); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_WSTR(name, sname1, field1, v, sname2, field2, flags) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1.s || \ + strcmp(s->sname1.field1.s, v.sname2.out.field2.s) || \ + wire_bad_flags(&s->sname1.field1, flags, cli->transport)) { \ + printf("(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1.s, \ + #sname2, #field2, v.sname2.out.field2.s); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_NAME(name, sname1, field1, fname, flags) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1.s || \ + strcmp(s->sname1.field1.s, fname) || \ + wire_bad_flags(&s->sname1.field1, flags, cli->transport)) { \ + printf("(%s) %s/%s [%s] != %s\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1.s, \ + fname); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_UNIX_NAME(name, sname1, field1, fname, flags) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1 || \ + strcmp(s->sname1.field1, fname)) { \ + printf("(%s) %s/%s [%s] != %s\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1, \ + fname); \ + ret = false; \ + } \ + }} while (0) + + /* check that all the results are as expected */ + CHECK_VAL("SEARCH", search, attrib, all_info, all_info, attrib&0xFFF); + CHECK_VAL("STANDARD", standard, attrib, all_info, all_info, attrib&0xFFF); + CHECK_VAL("EA_SIZE", ea_size, attrib, all_info, all_info, attrib&0xFFF); + CHECK_VAL("DIRECTORY_INFO", directory_info, attrib, all_info, all_info, attrib); + CHECK_VAL("FULL_DIRECTORY_INFO", full_directory_info, attrib, all_info, all_info, attrib); + CHECK_VAL("BOTH_DIRECTORY_INFO", both_directory_info, attrib, all_info, all_info, attrib); + CHECK_VAL("ID_FULL_DIRECTORY_INFO", id_full_directory_info, attrib, all_info, all_info, attrib); + CHECK_VAL("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, attrib, all_info, all_info, attrib); + + CHECK_TIME("SEARCH", search, write_time, all_info, all_info, write_time); + CHECK_TIME("STANDARD", standard, write_time, all_info, all_info, write_time); + CHECK_TIME("EA_SIZE", ea_size, write_time, all_info, all_info, write_time); + CHECK_TIME("STANDARD", standard, create_time, all_info, all_info, create_time); + CHECK_TIME("EA_SIZE", ea_size, create_time, all_info, all_info, create_time); + CHECK_TIME("STANDARD", standard, access_time, all_info, all_info, access_time); + CHECK_TIME("EA_SIZE", ea_size, access_time, all_info, all_info, access_time); + + CHECK_NTTIME("DIRECTORY_INFO", directory_info, write_time, all_info, all_info, write_time); + CHECK_NTTIME("FULL_DIRECTORY_INFO", full_directory_info, write_time, all_info, all_info, write_time); + CHECK_NTTIME("BOTH_DIRECTORY_INFO", both_directory_info, write_time, all_info, all_info, write_time); + CHECK_NTTIME("ID_FULL_DIRECTORY_INFO", id_full_directory_info, write_time, all_info, all_info, write_time); + CHECK_NTTIME("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, write_time, all_info, all_info, write_time); + + CHECK_NTTIME("DIRECTORY_INFO", directory_info, create_time, all_info, all_info, create_time); + CHECK_NTTIME("FULL_DIRECTORY_INFO", full_directory_info, create_time, all_info, all_info, create_time); + CHECK_NTTIME("BOTH_DIRECTORY_INFO", both_directory_info, create_time, all_info, all_info, create_time); + CHECK_NTTIME("ID_FULL_DIRECTORY_INFO", id_full_directory_info, create_time, all_info, all_info, create_time); + CHECK_NTTIME("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, create_time, all_info, all_info, create_time); + + CHECK_NTTIME("DIRECTORY_INFO", directory_info, access_time, all_info, all_info, access_time); + CHECK_NTTIME("FULL_DIRECTORY_INFO", full_directory_info, access_time, all_info, all_info, access_time); + CHECK_NTTIME("BOTH_DIRECTORY_INFO", both_directory_info, access_time, all_info, all_info, access_time); + CHECK_NTTIME("ID_FULL_DIRECTORY_INFO", id_full_directory_info, access_time, all_info, all_info, access_time); + CHECK_NTTIME("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, access_time, all_info, all_info, access_time); + + CHECK_NTTIME("DIRECTORY_INFO", directory_info, change_time, all_info, all_info, change_time); + CHECK_NTTIME("FULL_DIRECTORY_INFO", full_directory_info, change_time, all_info, all_info, change_time); + CHECK_NTTIME("BOTH_DIRECTORY_INFO", both_directory_info, change_time, all_info, all_info, change_time); + CHECK_NTTIME("ID_FULL_DIRECTORY_INFO", id_full_directory_info, change_time, all_info, all_info, change_time); + CHECK_NTTIME("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, change_time, all_info, all_info, change_time); + + CHECK_VAL("SEARCH", search, size, all_info, all_info, size); + CHECK_VAL("STANDARD", standard, size, all_info, all_info, size); + CHECK_VAL("EA_SIZE", ea_size, size, all_info, all_info, size); + CHECK_VAL("DIRECTORY_INFO", directory_info, size, all_info, all_info, size); + CHECK_VAL("FULL_DIRECTORY_INFO", full_directory_info, size, all_info, all_info, size); + CHECK_VAL("BOTH_DIRECTORY_INFO", both_directory_info, size, all_info, all_info, size); + CHECK_VAL("ID_FULL_DIRECTORY_INFO", id_full_directory_info, size, all_info, all_info, size); + CHECK_VAL("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, size, all_info, all_info, size); + CHECK_VAL("UNIX_INFO", unix_info, size, all_info, all_info, size); + + CHECK_VAL("STANDARD", standard, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("EA_SIZE", ea_size, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("DIRECTORY_INFO", directory_info, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("FULL_DIRECTORY_INFO", full_directory_info, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("BOTH_DIRECTORY_INFO", both_directory_info, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("ID_FULL_DIRECTORY_INFO", id_full_directory_info, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, alloc_size, all_info, all_info, alloc_size); + CHECK_VAL("UNIX_INFO", unix_info, alloc_size, all_info, all_info, alloc_size); + + CHECK_VAL("EA_SIZE", ea_size, ea_size, all_info, all_info, ea_size); + CHECK_VAL("FULL_DIRECTORY_INFO", full_directory_info, ea_size, all_info, all_info, ea_size); + CHECK_VAL("BOTH_DIRECTORY_INFO", both_directory_info, ea_size, all_info, all_info, ea_size); + CHECK_VAL("ID_FULL_DIRECTORY_INFO", id_full_directory_info, ea_size, all_info, all_info, ea_size); + CHECK_VAL("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, ea_size, all_info, all_info, ea_size); + + CHECK_STR("SEARCH", search, name, alt_info, alt_name_info, fname); + CHECK_WSTR("BOTH_DIRECTORY_INFO", both_directory_info, short_name, alt_info, alt_name_info, fname, STR_UNICODE); + + CHECK_NAME("STANDARD", standard, name, fname+1, 0); + CHECK_NAME("EA_SIZE", ea_size, name, fname+1, 0); + CHECK_NAME("DIRECTORY_INFO", directory_info, name, fname+1, STR_TERMINATE_ASCII); + CHECK_NAME("FULL_DIRECTORY_INFO", full_directory_info, name, fname+1, STR_TERMINATE_ASCII); + CHECK_NAME("NAME_INFO", name_info, name, fname+1, STR_TERMINATE_ASCII); + CHECK_NAME("BOTH_DIRECTORY_INFO", both_directory_info, name, fname+1, STR_TERMINATE_ASCII); + CHECK_NAME("ID_FULL_DIRECTORY_INFO", id_full_directory_info, name, fname+1, STR_TERMINATE_ASCII); + CHECK_NAME("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, name, fname+1, STR_TERMINATE_ASCII); + CHECK_UNIX_NAME("UNIX_INFO", unix_info, name, fname+1, STR_TERMINATE_ASCII); + + CHECK_VAL("ID_FULL_DIRECTORY_INFO", id_full_directory_info, file_id, internal_info, internal_information, file_id); + CHECK_VAL("ID_BOTH_DIRECTORY_INFO", id_both_directory_info, file_id, internal_info, internal_information, file_id); + +done: + smb_raw_exit(cli->session); + smbcli_unlink(cli->tree, fname); + + return ret; +} + + +struct multiple_result { + TALLOC_CTX *tctx; + int count; + union smb_search_data *list; +}; + +/* + callback function for multiple_search +*/ +static bool multiple_search_callback(void *private, const union smb_search_data *file) +{ + struct multiple_result *data = (struct multiple_result *)private; + + + data->count++; + data->list = talloc_realloc(data->tctx, + data->list, + union smb_search_data, + data->count); + + data->list[data->count-1] = *file; + + return true; +} + +enum continue_type {CONT_FLAGS, CONT_NAME, CONT_RESUME_KEY}; + +/* + do a single file (non-wildcard) search +*/ +static NTSTATUS multiple_search(struct smbcli_state *cli, + TALLOC_CTX *tctx, + const char *pattern, + enum smb_search_data_level data_level, + enum continue_type cont_type, + void *data) +{ + union smb_search_first io; + union smb_search_next io2; + NTSTATUS status; + const int per_search = 100; + struct multiple_result *result = (struct multiple_result *)data; + + if (data_level == RAW_SEARCH_DATA_SEARCH) { + io.search_first.level = RAW_SEARCH_SEARCH; + io.search_first.data_level = RAW_SEARCH_DATA_SEARCH; + io.search_first.in.max_count = per_search; + io.search_first.in.search_attrib = 0; + io.search_first.in.pattern = pattern; + } else { + io.t2ffirst.level = RAW_SEARCH_TRANS2; + io.t2ffirst.data_level = data_level; + io.t2ffirst.in.search_attrib = 0; + io.t2ffirst.in.max_count = per_search; + io.t2ffirst.in.flags = FLAG_TRANS2_FIND_CLOSE_IF_END; + io.t2ffirst.in.storage_type = 0; + io.t2ffirst.in.pattern = pattern; + if (cont_type == CONT_RESUME_KEY) { + io.t2ffirst.in.flags |= FLAG_TRANS2_FIND_REQUIRE_RESUME | + FLAG_TRANS2_FIND_BACKUP_INTENT; + } + } + + status = smb_raw_search_first(cli->tree, tctx, + &io, data, multiple_search_callback); + + + while (NT_STATUS_IS_OK(status)) { + if (data_level == RAW_SEARCH_DATA_SEARCH) { + io2.search_next.level = RAW_SEARCH_SEARCH; + io2.search_next.data_level = RAW_SEARCH_DATA_SEARCH; + io2.search_next.in.max_count = per_search; + io2.search_next.in.search_attrib = 0; + io2.search_next.in.id = result->list[result->count-1].search.id; + } else { + io2.t2fnext.level = RAW_SEARCH_TRANS2; + io2.t2fnext.data_level = data_level; + io2.t2fnext.in.handle = io.t2ffirst.out.handle; + io2.t2fnext.in.max_count = per_search; + io2.t2fnext.in.resume_key = 0; + io2.t2fnext.in.flags = FLAG_TRANS2_FIND_CLOSE_IF_END; + io2.t2fnext.in.last_name = ""; + switch (cont_type) { + case CONT_RESUME_KEY: + io2.t2fnext.in.resume_key = extract_resume_key(&result->list[result->count-1], + io2.t2fnext.level, io2.t2fnext.data_level); + if (io2.t2fnext.in.resume_key == 0) { + printf("Server does not support resume by key for level %s\n", + level_name(io2.t2fnext.level, io2.t2fnext.data_level)); + return NT_STATUS_NOT_SUPPORTED; + } + io2.t2fnext.in.flags |= FLAG_TRANS2_FIND_REQUIRE_RESUME | + FLAG_TRANS2_FIND_BACKUP_INTENT; + break; + case CONT_NAME: + io2.t2fnext.in.last_name = extract_name(&result->list[result->count-1], + io2.t2fnext.level, io2.t2fnext.data_level); + break; + case CONT_FLAGS: + io2.t2fnext.in.flags |= FLAG_TRANS2_FIND_CONTINUE; + break; + } + } + + status = smb_raw_search_next(cli->tree, tctx, + &io2, data, multiple_search_callback); + if (!NT_STATUS_IS_OK(status)) { + break; + } + if (data_level == RAW_SEARCH_DATA_SEARCH) { + if (io2.search_next.out.count == 0) { + break; + } + } else if (io2.t2fnext.out.count == 0 || + io2.t2fnext.out.end_of_search) { + break; + } + } + + return status; +} + +#define CHECK_STATUS(status, correct) torture_assert_ntstatus_equal(tctx, status, correct, "incorrect status") + +#define CHECK_VALUE(v, correct) torture_assert_int_equal(tctx, (v), (correct), "incorrect value"); + +#define CHECK_STRING(v, correct) torture_assert_casestr_equal(tctx, v, correct, "incorrect value"); + + +static enum smb_search_data_level compare_data_level; + +static int search_compare(union smb_search_data *d1, union smb_search_data *d2) +{ + const char *s1, *s2; + enum smb_search_level level; + + if (compare_data_level == RAW_SEARCH_DATA_SEARCH) { + level = RAW_SEARCH_SEARCH; + } else { + level = RAW_SEARCH_TRANS2; + } + + s1 = extract_name(d1, level, compare_data_level); + s2 = extract_name(d2, level, compare_data_level); + return strcmp_safe(s1, s2); +} + + + +/* + basic testing of search calls using many files +*/ +static bool test_many_files(struct torture_context *tctx, + struct smbcli_state *cli) +{ + const int num_files = 700; + int i, fnum, t; + char *fname; + bool ret = true; + NTSTATUS status; + struct multiple_result result; + struct { + const char *name; + const char *cont_name; + enum smb_search_data_level data_level; + enum continue_type cont_type; + } search_types[] = { + {"SEARCH", "ID", RAW_SEARCH_DATA_SEARCH, CONT_RESUME_KEY}, + {"BOTH_DIRECTORY_INFO", "NAME", RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_NAME}, + {"BOTH_DIRECTORY_INFO", "FLAGS", RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_FLAGS}, + {"BOTH_DIRECTORY_INFO", "KEY", RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_RESUME_KEY}, + {"STANDARD", "FLAGS", RAW_SEARCH_DATA_STANDARD, CONT_FLAGS}, + {"STANDARD", "KEY", RAW_SEARCH_DATA_STANDARD, CONT_RESUME_KEY}, + {"STANDARD", "NAME", RAW_SEARCH_DATA_STANDARD, CONT_NAME}, + {"EA_SIZE", "FLAGS", RAW_SEARCH_DATA_EA_SIZE, CONT_FLAGS}, + {"EA_SIZE", "KEY", RAW_SEARCH_DATA_EA_SIZE, CONT_RESUME_KEY}, + {"EA_SIZE", "NAME", RAW_SEARCH_DATA_EA_SIZE, CONT_NAME}, + {"DIRECTORY_INFO", "FLAGS", RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_FLAGS}, + {"DIRECTORY_INFO", "KEY", RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_RESUME_KEY}, + {"DIRECTORY_INFO", "NAME", RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_NAME}, + {"FULL_DIRECTORY_INFO", "FLAGS", RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_FLAGS}, + {"FULL_DIRECTORY_INFO", "KEY", RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_RESUME_KEY}, + {"FULL_DIRECTORY_INFO", "NAME", RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_NAME}, + {"ID_FULL_DIRECTORY_INFO", "FLAGS", RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_FLAGS}, + {"ID_FULL_DIRECTORY_INFO", "KEY", RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_RESUME_KEY}, + {"ID_FULL_DIRECTORY_INFO", "NAME", RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_NAME}, + {"ID_BOTH_DIRECTORY_INFO", "NAME", RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_NAME}, + {"ID_BOTH_DIRECTORY_INFO", "FLAGS", RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_FLAGS}, + {"ID_BOTH_DIRECTORY_INFO", "KEY", RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_RESUME_KEY} + }; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + torture_comment(tctx, "Testing with %d files\n", num_files); + + for (i=0;i<num_files;i++) { + fname = talloc_asprintf(cli, BASEDIR "\\t%03d-%d.txt", i, i); + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, DENY_NONE); + torture_assert(tctx, fnum != -1, "Failed to create"); + talloc_free(fname); + smbcli_close(cli->tree, fnum); + } + + + for (t=0;t<ARRAY_SIZE(search_types);t++) { + ZERO_STRUCT(result); + + if ((search_types[t].cont_type == CONT_RESUME_KEY) && + (search_types[t].data_level != RAW_SEARCH_DATA_SEARCH) && + torture_setting_bool(tctx, "samba3", false)) { + torture_comment(tctx, + "SKIP: Continue %s via %s\n", + search_types[t].name, search_types[t].cont_name); + continue; + } + + result.tctx = talloc_new(tctx); + + torture_comment(tctx, + "Continue %s via %s\n", search_types[t].name, search_types[t].cont_name); + + status = multiple_search(cli, tctx, BASEDIR "\\*.*", + search_types[t].data_level, + search_types[t].cont_type, + &result); + + torture_assert_ntstatus_ok(tctx, status, "search failed"); + CHECK_VALUE(result.count, num_files); + + compare_data_level = search_types[t].data_level; + + qsort(result.list, result.count, sizeof(result.list[0]), + QSORT_CAST search_compare); + + for (i=0;i<result.count;i++) { + const char *s; + enum smb_search_level level; + if (compare_data_level == RAW_SEARCH_DATA_SEARCH) { + level = RAW_SEARCH_SEARCH; + } else { + level = RAW_SEARCH_TRANS2; + } + s = extract_name(&result.list[i], level, compare_data_level); + fname = talloc_asprintf(cli, "t%03d-%d.txt", i, i); + torture_assert_str_equal(tctx, fname, s, "Incorrect name"); + talloc_free(fname); + } + talloc_free(result.tctx); + } + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} + +/* + check a individual file result +*/ +static bool check_result(struct multiple_result *result, const char *name, bool exist, uint32_t attrib) +{ + int i; + for (i=0;i<result->count;i++) { + if (strcmp(name, result->list[i].both_directory_info.name.s) == 0) break; + } + if (i == result->count) { + if (exist) { + printf("failed: '%s' should exist with attribute %s\n", + name, attrib_string(result->list, attrib)); + return false; + } + return true; + } + + if (!exist) { + printf("failed: '%s' should NOT exist (has attribute %s)\n", + name, attrib_string(result->list, result->list[i].both_directory_info.attrib)); + return false; + } + + if ((result->list[i].both_directory_info.attrib&0xFFF) != attrib) { + printf("failed: '%s' should have attribute 0x%x (has 0x%x)\n", + name, + attrib, result->list[i].both_directory_info.attrib); + return false; + } + return true; +} + +/* + test what happens when the directory is modified during a search +*/ +static bool test_modify_search(struct torture_context *tctx, + struct smbcli_state *cli) +{ + const int num_files = 20; + int i, fnum; + char *fname; + bool ret = true; + NTSTATUS status; + struct multiple_result result; + union smb_search_first io; + union smb_search_next io2; + union smb_setfileinfo sfinfo; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Creating %d files\n", num_files); + + for (i=num_files-1;i>=0;i--) { + fname = talloc_asprintf(cli, BASEDIR "\\t%03d-%d.txt", i, i); + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + talloc_free(fname); + smbcli_close(cli->tree, fnum); + } + + printf("pulling the first file\n"); + ZERO_STRUCT(result); + result.tctx = talloc_new(tctx); + + io.t2ffirst.level = RAW_SEARCH_TRANS2; + io.t2ffirst.data_level = RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO; + io.t2ffirst.in.search_attrib = 0; + io.t2ffirst.in.max_count = 0; + io.t2ffirst.in.flags = 0; + io.t2ffirst.in.storage_type = 0; + io.t2ffirst.in.pattern = BASEDIR "\\*.*"; + + status = smb_raw_search_first(cli->tree, tctx, + &io, &result, multiple_search_callback); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(result.count, 1); + + printf("pulling the second file\n"); + io2.t2fnext.level = RAW_SEARCH_TRANS2; + io2.t2fnext.data_level = RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO; + io2.t2fnext.in.handle = io.t2ffirst.out.handle; + io2.t2fnext.in.max_count = 1; + io2.t2fnext.in.resume_key = 0; + io2.t2fnext.in.flags = 0; + io2.t2fnext.in.last_name = result.list[result.count-1].both_directory_info.name.s; + + status = smb_raw_search_next(cli->tree, tctx, + &io2, &result, multiple_search_callback); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(result.count, 2); + + result.count = 0; + + printf("Changing attributes and deleting\n"); + smbcli_open(cli->tree, BASEDIR "\\T003-03.txt.2", O_CREAT|O_RDWR, DENY_NONE); + smbcli_open(cli->tree, BASEDIR "\\T013-13.txt.2", O_CREAT|O_RDWR, DENY_NONE); + fnum = create_complex_file(cli, tctx, BASEDIR "\\T013-13.txt.3"); + smbcli_unlink(cli->tree, BASEDIR "\\T014-14.txt"); + torture_set_file_attribute(cli->tree, BASEDIR "\\T015-15.txt", FILE_ATTRIBUTE_HIDDEN); + torture_set_file_attribute(cli->tree, BASEDIR "\\T016-16.txt", FILE_ATTRIBUTE_NORMAL); + torture_set_file_attribute(cli->tree, BASEDIR "\\T017-17.txt", FILE_ATTRIBUTE_SYSTEM); + torture_set_file_attribute(cli->tree, BASEDIR "\\T018-18.txt", 0); + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.fnum = fnum; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + io2.t2fnext.level = RAW_SEARCH_TRANS2; + io2.t2fnext.data_level = RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO; + io2.t2fnext.in.handle = io.t2ffirst.out.handle; + io2.t2fnext.in.max_count = num_files + 3; + io2.t2fnext.in.resume_key = 0; + io2.t2fnext.in.flags = 0; + io2.t2fnext.in.last_name = "."; + + status = smb_raw_search_next(cli->tree, tctx, + &io2, &result, multiple_search_callback); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(result.count, 20); + + ret &= check_result(&result, "t009-9.txt", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(&result, "t014-14.txt", false, 0); + ret &= check_result(&result, "t015-15.txt", false, 0); + ret &= check_result(&result, "t016-16.txt", true, FILE_ATTRIBUTE_NORMAL); + ret &= check_result(&result, "t017-17.txt", false, 0); + ret &= check_result(&result, "t018-18.txt", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(&result, "t019-19.txt", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(&result, "T013-13.txt.2", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(&result, "T003-3.txt.2", false, 0); + ret &= check_result(&result, "T013-13.txt.3", true, FILE_ATTRIBUTE_ARCHIVE); + + if (!ret) { + for (i=0;i<result.count;i++) { + printf("%s %s (0x%x)\n", + result.list[i].both_directory_info.name.s, + attrib_string(tctx, result.list[i].both_directory_info.attrib), + result.list[i].both_directory_info.attrib); + } + } + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} + + +/* + testing if directories always come back sorted +*/ +static bool test_sorted(struct torture_context *tctx, struct smbcli_state *cli) +{ + const int num_files = 700; + int i, fnum; + char *fname; + bool ret = true; + NTSTATUS status; + struct multiple_result result; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Creating %d files\n", num_files); + + for (i=0;i<num_files;i++) { + fname = talloc_asprintf(cli, BASEDIR "\\%s.txt", generate_random_str_list(tctx, 10, "abcdefgh")); + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + talloc_free(fname); + smbcli_close(cli->tree, fnum); + } + + + ZERO_STRUCT(result); + result.tctx = tctx; + + status = multiple_search(cli, tctx, BASEDIR "\\*.*", + RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, + CONT_NAME, &result); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(result.count, num_files); + + for (i=0;i<num_files-1;i++) { + const char *name1, *name2; + name1 = result.list[i].both_directory_info.name.s; + name2 = result.list[i+1].both_directory_info.name.s; + if (strcasecmp_m(name1, name2) > 0) { + printf("non-alphabetical order at entry %d '%s' '%s'\n", + i, name1, name2); + printf("Server does not produce sorted directory listings (not an error)\n"); + goto done; + } + } + + talloc_free(result.list); + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} + + + +/* + basic testing of many old style search calls using separate dirs +*/ +static bool test_many_dirs(struct torture_context *tctx, + struct smbcli_state *cli) +{ + const int num_dirs = 20; + int i, fnum, n; + char *fname, *dname; + bool ret = true; + NTSTATUS status; + union smb_search_data *file, *file2, *file3; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Creating %d dirs\n", num_dirs); + + for (i=0;i<num_dirs;i++) { + dname = talloc_asprintf(cli, BASEDIR "\\d%d", i); + status = smbcli_mkdir(cli->tree, dname); + if (!NT_STATUS_IS_OK(status)) { + printf("(%s) Failed to create %s - %s\n", + __location__, dname, nt_errstr(status)); + ret = false; + goto done; + } + + for (n=0;n<3;n++) { + fname = talloc_asprintf(cli, BASEDIR "\\d%d\\f%d-%d.txt", i, i, n); + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, DENY_NONE); + if (fnum == -1) { + printf("(%s) Failed to create %s - %s\n", + __location__, fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + talloc_free(fname); + smbcli_close(cli->tree, fnum); + } + + talloc_free(dname); + } + + file = talloc_zero_array(tctx, union smb_search_data, num_dirs); + file2 = talloc_zero_array(tctx, union smb_search_data, num_dirs); + file3 = talloc_zero_array(tctx, union smb_search_data, num_dirs); + + printf("Search first on %d dirs\n", num_dirs); + + for (i=0;i<num_dirs;i++) { + union smb_search_first io; + io.search_first.level = RAW_SEARCH_SEARCH; + io.search_first.data_level = RAW_SEARCH_DATA_SEARCH; + io.search_first.in.max_count = 1; + io.search_first.in.search_attrib = 0; + io.search_first.in.pattern = talloc_asprintf(tctx, BASEDIR "\\d%d\\*.txt", i); + fname = talloc_asprintf(tctx, "f%d-", i); + + io.search_first.out.count = 0; + + status = smb_raw_search_first(cli->tree, tctx, + &io, (void *)&file[i], single_search_callback); + if (io.search_first.out.count != 1) { + printf("(%s) search first gave %d entries for dir %d - %s\n", + __location__, io.search_first.out.count, i, nt_errstr(status)); + ret = false; + goto done; + } + CHECK_STATUS(status, NT_STATUS_OK); + if (strncasecmp(file[i].search.name, fname, strlen(fname)) != 0) { + printf("(%s) incorrect name '%s' expected '%s'[12].txt\n", + __location__, file[i].search.name, fname); + ret = false; + goto done; + } + + talloc_free(fname); + } + + printf("Search next on %d dirs\n", num_dirs); + + for (i=0;i<num_dirs;i++) { + union smb_search_next io2; + + io2.search_next.level = RAW_SEARCH_SEARCH; + io2.search_next.data_level = RAW_SEARCH_DATA_SEARCH; + io2.search_next.in.max_count = 1; + io2.search_next.in.search_attrib = 0; + io2.search_next.in.id = file[i].search.id; + fname = talloc_asprintf(tctx, "f%d-", i); + + io2.search_next.out.count = 0; + + status = smb_raw_search_next(cli->tree, tctx, + &io2, (void *)&file2[i], single_search_callback); + if (io2.search_next.out.count != 1) { + printf("(%s) search next gave %d entries for dir %d - %s\n", + __location__, io2.search_next.out.count, i, nt_errstr(status)); + ret = false; + goto done; + } + CHECK_STATUS(status, NT_STATUS_OK); + if (strncasecmp(file2[i].search.name, fname, strlen(fname)) != 0) { + printf("(%s) incorrect name '%s' expected '%s'[12].txt\n", + __location__, file2[i].search.name, fname); + ret = false; + goto done; + } + + talloc_free(fname); + } + + + printf("Search next (rewind) on %d dirs\n", num_dirs); + + for (i=0;i<num_dirs;i++) { + union smb_search_next io2; + + io2.search_next.level = RAW_SEARCH_SEARCH; + io2.search_next.data_level = RAW_SEARCH_DATA_SEARCH; + io2.search_next.in.max_count = 1; + io2.search_next.in.search_attrib = 0; + io2.search_next.in.id = file[i].search.id; + fname = talloc_asprintf(tctx, "f%d-", i); + io2.search_next.out.count = 0; + + status = smb_raw_search_next(cli->tree, tctx, + &io2, (void *)&file3[i], single_search_callback); + if (io2.search_next.out.count != 1) { + printf("(%s) search next gave %d entries for dir %d - %s\n", + __location__, io2.search_next.out.count, i, nt_errstr(status)); + ret = false; + goto done; + } + CHECK_STATUS(status, NT_STATUS_OK); + + if (strncasecmp(file3[i].search.name, file2[i].search.name, 3) != 0) { + printf("(%s) incorrect name '%s' on rewind at dir %d\n", + __location__, file2[i].search.name, i); + ret = false; + goto done; + } + + if (strcmp(file3[i].search.name, file2[i].search.name) != 0) { + printf("(%s) server did not rewind - got '%s' expected '%s'\n", + __location__, file3[i].search.name, file2[i].search.name); + ret = false; + goto done; + } + + talloc_free(fname); + } + + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} + + +/* + testing of OS/2 style delete +*/ +static bool test_os2_delete(struct torture_context *tctx, + struct smbcli_state *cli) +{ + const int num_files = 700; + const int delete_count = 4; + int total_deleted = 0; + int i, fnum; + char *fname; + bool ret = true; + NTSTATUS status; + union smb_search_first io; + union smb_search_next io2; + struct multiple_result result; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing OS/2 style delete on %d files\n", num_files); + + for (i=0;i<num_files;i++) { + fname = talloc_asprintf(cli, BASEDIR "\\file%u.txt", i); + fnum = smbcli_open(cli->tree, fname, O_CREAT|O_RDWR, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + talloc_free(fname); + smbcli_close(cli->tree, fnum); + } + + + ZERO_STRUCT(result); + result.tctx = tctx; + + io.t2ffirst.level = RAW_SEARCH_TRANS2; + io.t2ffirst.data_level = RAW_SEARCH_DATA_EA_SIZE; + io.t2ffirst.in.search_attrib = 0; + io.t2ffirst.in.max_count = 100; + io.t2ffirst.in.flags = FLAG_TRANS2_FIND_REQUIRE_RESUME; + io.t2ffirst.in.storage_type = 0; + io.t2ffirst.in.pattern = BASEDIR "\\*"; + + status = smb_raw_search_first(cli->tree, tctx, + &io, &result, multiple_search_callback); + CHECK_STATUS(status, NT_STATUS_OK); + + for (i=0;i<MIN(result.count, delete_count);i++) { + fname = talloc_asprintf(cli, BASEDIR "\\%s", result.list[i].ea_size.name.s); + status = smbcli_unlink(cli->tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + total_deleted++; + talloc_free(fname); + } + + io2.t2fnext.level = RAW_SEARCH_TRANS2; + io2.t2fnext.data_level = RAW_SEARCH_DATA_EA_SIZE; + io2.t2fnext.in.handle = io.t2ffirst.out.handle; + io2.t2fnext.in.max_count = 100; + io2.t2fnext.in.resume_key = result.list[i-1].ea_size.resume_key; + io2.t2fnext.in.flags = FLAG_TRANS2_FIND_REQUIRE_RESUME; + io2.t2fnext.in.last_name = result.list[i-1].ea_size.name.s; + + do { + ZERO_STRUCT(result); + result.tctx = tctx; + + status = smb_raw_search_next(cli->tree, tctx, + &io2, &result, multiple_search_callback); + if (!NT_STATUS_IS_OK(status)) { + break; + } + + for (i=0;i<MIN(result.count, delete_count);i++) { + fname = talloc_asprintf(cli, BASEDIR "\\%s", result.list[i].ea_size.name.s); + status = smbcli_unlink(cli->tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + total_deleted++; + talloc_free(fname); + } + + if (i>0) { + io2.t2fnext.in.resume_key = result.list[i-1].ea_size.resume_key; + io2.t2fnext.in.last_name = result.list[i-1].ea_size.name.s; + } + } while (NT_STATUS_IS_OK(status) && result.count != 0); + + CHECK_STATUS(status, NT_STATUS_OK); + + if (total_deleted != num_files) { + printf("error: deleted %d - expected to delete %d\n", + total_deleted, num_files); + ret = false; + } + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} + + +static int ealist_cmp(union smb_search_data *r1, union smb_search_data *r2) +{ + return strcmp(r1->ea_list.name.s, r2->ea_list.name.s); +} + +/* + testing of the rather strange ea_list level +*/ +static bool test_ea_list(struct torture_context *tctx, + struct smbcli_state *cli) +{ + int fnum; + bool ret = true; + NTSTATUS status; + union smb_search_first io; + union smb_search_next nxt; + struct multiple_result result; + union smb_setfileinfo setfile; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_SEARCH_EA_LIST level\n"); + + fnum = smbcli_open(cli->tree, BASEDIR "\\file1.txt", O_CREAT|O_RDWR, DENY_NONE); + smbcli_close(cli->tree, fnum); + + fnum = smbcli_open(cli->tree, BASEDIR "\\file2.txt", O_CREAT|O_RDWR, DENY_NONE); + smbcli_close(cli->tree, fnum); + + fnum = smbcli_open(cli->tree, BASEDIR "\\file3.txt", O_CREAT|O_RDWR, DENY_NONE); + smbcli_close(cli->tree, fnum); + + setfile.generic.level = RAW_SFILEINFO_EA_SET; + setfile.generic.in.file.path = BASEDIR "\\file2.txt"; + setfile.ea_set.in.num_eas = 2; + setfile.ea_set.in.eas = talloc_array(tctx, struct ea_struct, 2); + setfile.ea_set.in.eas[0].flags = 0; + setfile.ea_set.in.eas[0].name.s = "EA ONE"; + setfile.ea_set.in.eas[0].value = data_blob_string_const("VALUE 1"); + setfile.ea_set.in.eas[1].flags = 0; + setfile.ea_set.in.eas[1].name.s = "SECOND EA"; + setfile.ea_set.in.eas[1].value = data_blob_string_const("Value Two"); + + status = smb_raw_setpathinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + setfile.generic.in.file.path = BASEDIR "\\file3.txt"; + status = smb_raw_setpathinfo(cli->tree, &setfile); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(result); + result.tctx = tctx; + + io.t2ffirst.level = RAW_SEARCH_TRANS2; + io.t2ffirst.data_level = RAW_SEARCH_DATA_EA_LIST; + io.t2ffirst.in.search_attrib = 0; + io.t2ffirst.in.max_count = 2; + io.t2ffirst.in.flags = FLAG_TRANS2_FIND_REQUIRE_RESUME; + io.t2ffirst.in.storage_type = 0; + io.t2ffirst.in.pattern = BASEDIR "\\*"; + io.t2ffirst.in.num_names = 2; + io.t2ffirst.in.ea_names = talloc_array(tctx, struct ea_name, 2); + io.t2ffirst.in.ea_names[0].name.s = "SECOND EA"; + io.t2ffirst.in.ea_names[1].name.s = "THIRD EA"; + + status = smb_raw_search_first(cli->tree, tctx, + &io, &result, multiple_search_callback); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(result.count, 2); + + nxt.t2fnext.level = RAW_SEARCH_TRANS2; + nxt.t2fnext.data_level = RAW_SEARCH_DATA_EA_LIST; + nxt.t2fnext.in.handle = io.t2ffirst.out.handle; + nxt.t2fnext.in.max_count = 2; + nxt.t2fnext.in.resume_key = result.list[1].ea_list.resume_key; + nxt.t2fnext.in.flags = FLAG_TRANS2_FIND_REQUIRE_RESUME | FLAG_TRANS2_FIND_CONTINUE; + nxt.t2fnext.in.last_name = result.list[1].ea_list.name.s; + nxt.t2fnext.in.num_names = 2; + nxt.t2fnext.in.ea_names = talloc_array(tctx, struct ea_name, 2); + nxt.t2fnext.in.ea_names[0].name.s = "SECOND EA"; + nxt.t2fnext.in.ea_names[1].name.s = "THIRD EA"; + + status = smb_raw_search_next(cli->tree, tctx, + &nxt, &result, multiple_search_callback); + CHECK_STATUS(status, NT_STATUS_OK); + + /* we have to sort the result as different servers can return directories + in different orders */ + qsort(result.list, result.count, sizeof(result.list[0]), + (comparison_fn_t)ealist_cmp); + + CHECK_VALUE(result.count, 3); + CHECK_VALUE(result.list[0].ea_list.eas.num_eas, 2); + CHECK_STRING(result.list[0].ea_list.name.s, "file1.txt"); + CHECK_STRING(result.list[0].ea_list.eas.eas[0].name.s, "SECOND EA"); + CHECK_VALUE(result.list[0].ea_list.eas.eas[0].value.length, 0); + CHECK_STRING(result.list[0].ea_list.eas.eas[1].name.s, "THIRD EA"); + CHECK_VALUE(result.list[0].ea_list.eas.eas[1].value.length, 0); + + CHECK_STRING(result.list[1].ea_list.name.s, "file2.txt"); + CHECK_STRING(result.list[1].ea_list.eas.eas[0].name.s, "SECOND EA"); + CHECK_VALUE(result.list[1].ea_list.eas.eas[0].value.length, 9); + CHECK_STRING((const char *)result.list[1].ea_list.eas.eas[0].value.data, "Value Two"); + CHECK_STRING(result.list[1].ea_list.eas.eas[1].name.s, "THIRD EA"); + CHECK_VALUE(result.list[1].ea_list.eas.eas[1].value.length, 0); + + CHECK_STRING(result.list[2].ea_list.name.s, "file3.txt"); + CHECK_STRING(result.list[2].ea_list.eas.eas[0].name.s, "SECOND EA"); + CHECK_VALUE(result.list[2].ea_list.eas.eas[0].value.length, 9); + CHECK_STRING((const char *)result.list[2].ea_list.eas.eas[0].value.data, "Value Two"); + CHECK_STRING(result.list[2].ea_list.eas.eas[1].name.s, "THIRD EA"); + CHECK_VALUE(result.list[2].ea_list.eas.eas[1].value.length, 0); + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} + + + +/* + basic testing of all RAW_SEARCH_* calls using a single file +*/ +struct torture_suite *torture_raw_search(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "SEARCH"); + + torture_suite_add_1smb_test(suite, "one file search", test_one_file); + torture_suite_add_1smb_test(suite, "many files", test_many_files); + torture_suite_add_1smb_test(suite, "sorted", test_sorted); + torture_suite_add_1smb_test(suite, "modify search", test_modify_search); + torture_suite_add_1smb_test(suite, "many dirs", test_many_dirs); + torture_suite_add_1smb_test(suite, "os2 delete", test_os2_delete); + torture_suite_add_1smb_test(suite, "ea list", test_ea_list); + + return suite; +} diff --git a/source4/torture/raw/seek.c b/source4/torture/raw/seek.c new file mode 100644 index 0000000000..3ba022feef --- /dev/null +++ b/source4/torture/raw/seek.c @@ -0,0 +1,243 @@ +/* + Unix SMB/CIFS implementation. + seek test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/filesys.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%d) Incorrect status %s - should be %s\n", \ + __LINE__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%d) Incorrect value %s=%d - should be %d\n", \ + __LINE__, #v, (int)v, (int)correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define BASEDIR "\\testseek" + +/* + test seek ops +*/ +static bool test_seek(struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + union smb_seek io; + union smb_fileinfo finfo; + union smb_setfileinfo sfinfo; + NTSTATUS status; + bool ret = true; + int fnum, fnum2; + const char *fname = BASEDIR "\\test.txt"; + uint8_t c[2]; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT|O_TRUNC, DENY_NONE); + if (fnum == -1) { + printf("Failed to open test.txt - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum; + + printf("Trying bad handle\n"); + io.lseek.in.file.fnum = fnum+1; + io.lseek.in.mode = SEEK_MODE_START; + io.lseek.in.offset = 0; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("Trying simple seek\n"); + io.lseek.in.file.fnum = fnum; + io.lseek.in.mode = SEEK_MODE_START; + io.lseek.in.offset = 17; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, 17); + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 0); + + printf("Trying relative seek\n"); + io.lseek.in.file.fnum = fnum; + io.lseek.in.mode = SEEK_MODE_CURRENT; + io.lseek.in.offset = -3; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, 14); + + printf("Trying end seek\n"); + io.lseek.in.file.fnum = fnum; + io.lseek.in.mode = SEEK_MODE_END; + io.lseek.in.offset = 0; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + finfo.generic.level = RAW_FILEINFO_ALL_INFO; + finfo.all_info.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, finfo.all_info.out.size); + + printf("Trying max seek\n"); + io.lseek.in.file.fnum = fnum; + io.lseek.in.mode = SEEK_MODE_START; + io.lseek.in.offset = -1; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, 0xffffffff); + + printf("Testing position information change\n"); + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 0); + + printf("Trying max overflow\n"); + io.lseek.in.file.fnum = fnum; + io.lseek.in.mode = SEEK_MODE_CURRENT; + io.lseek.in.offset = 1000; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, 999); + + printf("Testing position information change\n"); + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 0); + + printf("trying write to update offset\n"); + ZERO_STRUCT(c); + if (smbcli_write(cli->tree, fnum, 0, c, 0, 2) != 2) { + printf("Write failed - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Testing position information change\n"); + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 0); + + io.lseek.in.file.fnum = fnum; + io.lseek.in.mode = SEEK_MODE_CURRENT; + io.lseek.in.offset = 0; + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, 2); + + if (smbcli_read(cli->tree, fnum, c, 0, 1) != 1) { + printf("Read failed - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Testing position information change\n"); + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 1); + + status = smb_raw_seek(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.lseek.out.offset, 1); + + printf("Testing position information\n"); + fnum2 = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE); + if (fnum2 == -1) { + printf("2nd open failed - %s\n", smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION; + sfinfo.position_information.in.file.fnum = fnum2; + sfinfo.position_information.in.position = 25; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum2; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 25); + + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 1); + + printf("position_information via paths\n"); + + sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION; + sfinfo.position_information.in.file.path = fname; + sfinfo.position_information.in.position = 32; + status = smb_raw_setpathinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.fnum = fnum2; + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 25); + + finfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + finfo.position_information.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.position_information.out.position, 0); + + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + basic testing of seek calls +*/ +bool torture_raw_seek(struct torture_context *torture, struct smbcli_state *cli) +{ + bool ret = true; + + ret &= test_seek(cli, torture); + + return ret; +} diff --git a/source4/torture/raw/setfileinfo.c b/source4/torture/raw/setfileinfo.c new file mode 100644 index 0000000000..10eaa6710d --- /dev/null +++ b/source4/torture/raw/setfileinfo.c @@ -0,0 +1,703 @@ +/* + Unix SMB/CIFS implementation. + RAW_SFILEINFO_* individual test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/time.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "torture/raw/proto.h" + +#define BASEDIR "\\testsfileinfo" + +/* basic testing of all RAW_SFILEINFO_* calls + for each call we test that it succeeds, and where possible test + for consistency between the calls. +*/ +bool torture_raw_sfileinfo(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + int fnum = -1; + char *fnum_fname; + char *fnum_fname_new; + char *path_fname; + char *path_fname_new; + union smb_fileinfo finfo1, finfo2; + union smb_setfileinfo sfinfo; + NTSTATUS status, status2; + const char *call_name; + time_t basetime = (time(NULL) - 86400) & ~1; + bool check_fnum; + int n = time(NULL) % 100; + + asprintf(&path_fname, BASEDIR "\\fname_test_%d.txt", n); + asprintf(&path_fname_new, BASEDIR "\\fname_test_new_%d.txt", n); + asprintf(&fnum_fname, BASEDIR "\\fnum_test_%d.txt", n); + asprintf(&fnum_fname_new, BASEDIR "\\fnum_test_new_%d.txt", n); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + +#define RECREATE_FILE(fname) do { \ + if (fnum != -1) smbcli_close(cli->tree, fnum); \ + fnum = create_complex_file(cli, torture, fname); \ + if (fnum == -1) { \ + printf("(%s) ERROR: open of %s failed (%s)\n", \ + __location__, fname, smbcli_errstr(cli->tree)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define RECREATE_BOTH do { \ + RECREATE_FILE(path_fname); \ + smbcli_close(cli->tree, fnum); \ + RECREATE_FILE(fnum_fname); \ + } while (0) + + RECREATE_BOTH; + +#define CHECK_CALL_FNUM(call, rightstatus) do { \ + check_fnum = true; \ + call_name = #call; \ + sfinfo.generic.level = RAW_SFILEINFO_ ## call; \ + sfinfo.generic.in.file.fnum = fnum; \ + status = smb_raw_setfileinfo(cli->tree, &sfinfo); \ + if (!NT_STATUS_EQUAL(status, rightstatus)) { \ + printf("(%s) %s - %s (should be %s)\n", __location__, #call, \ + nt_errstr(status), nt_errstr(rightstatus)); \ + ret = false; \ + } \ + finfo1.generic.level = RAW_FILEINFO_ALL_INFO; \ + finfo1.generic.in.file.fnum = fnum; \ + status2 = smb_raw_fileinfo(cli->tree, torture, &finfo1); \ + if (!NT_STATUS_IS_OK(status2)) { \ + printf("(%s) %s pathinfo - %s\n", __location__, #call, nt_errstr(status)); \ + ret = false; \ + }} while (0) + +#define CHECK_CALL_PATH(call, rightstatus) do { \ + check_fnum = false; \ + call_name = #call; \ + sfinfo.generic.level = RAW_SFILEINFO_ ## call; \ + sfinfo.generic.in.file.path = path_fname; \ + status = smb_raw_setpathinfo(cli->tree, &sfinfo); \ + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { \ + sfinfo.generic.in.file.path = path_fname_new; \ + status = smb_raw_setpathinfo(cli->tree, &sfinfo); \ + } \ + if (!NT_STATUS_EQUAL(status, rightstatus)) { \ + printf("(%s) %s - %s (should be %s)\n", __location__, #call, \ + nt_errstr(status), nt_errstr(rightstatus)); \ + ret = false; \ + } \ + finfo1.generic.level = RAW_FILEINFO_ALL_INFO; \ + finfo1.generic.in.file.path = path_fname; \ + status2 = smb_raw_pathinfo(cli->tree, torture, &finfo1); \ + if (NT_STATUS_EQUAL(status2, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { \ + finfo1.generic.in.file.path = path_fname_new; \ + status2 = smb_raw_pathinfo(cli->tree, torture, &finfo1); \ + } \ + if (!NT_STATUS_IS_OK(status2)) { \ + printf("(%s) %s pathinfo - %s\n", __location__, #call, nt_errstr(status2)); \ + ret = false; \ + }} while (0) + +#define CHECK1(call) \ + do { if (NT_STATUS_IS_OK(status)) { \ + finfo2.generic.level = RAW_FILEINFO_ ## call; \ + if (check_fnum) { \ + finfo2.generic.in.file.fnum = fnum; \ + status2 = smb_raw_fileinfo(cli->tree, torture, &finfo2); \ + } else { \ + finfo2.generic.in.file.path = path_fname; \ + status2 = smb_raw_pathinfo(cli->tree, torture, &finfo2); \ + if (NT_STATUS_EQUAL(status2, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { \ + finfo2.generic.in.file.path = path_fname_new; \ + status2 = smb_raw_pathinfo(cli->tree, torture, &finfo2); \ + } \ + } \ + if (!NT_STATUS_IS_OK(status2)) { \ + printf("%s - %s\n", #call, nt_errstr(status2)); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_VALUE(call, stype, field, value) do { \ + CHECK1(call); \ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(status2) && finfo2.stype.out.field != value) { \ + printf("(%s) %s - %s/%s should be 0x%x - 0x%x\n", __location__, \ + call_name, #stype, #field, \ + (uint_t)value, (uint_t)finfo2.stype.out.field); \ + dump_all_info(torture, &finfo1); \ + ret = false; \ + }} while (0) + +#define CHECK_TIME(call, stype, field, value) do { \ + CHECK1(call); \ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(status2) && nt_time_to_unix(finfo2.stype.out.field) != value) { \ + printf("(%s) %s - %s/%s should be 0x%x - 0x%x\n", __location__, \ + call_name, #stype, #field, \ + (uint_t)value, \ + (uint_t)nt_time_to_unix(finfo2.stype.out.field)); \ + printf("\t%s", timestring(torture, value)); \ + printf("\t%s\n", nt_time_string(torture, finfo2.stype.out.field)); \ + dump_all_info(torture, &finfo1); \ + ret = false; \ + }} while (0) + +#define CHECK_STR(call, stype, field, value) do { \ + CHECK1(call); \ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(status2) && strcmp(finfo2.stype.out.field, value) != 0) { \ + printf("(%s) %s - %s/%s should be '%s' - '%s'\n", __location__, \ + call_name, #stype, #field, \ + value, \ + finfo2.stype.out.field); \ + dump_all_info(torture, &finfo1); \ + ret = false; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + + printf("test setattr\n"); + sfinfo.setattr.in.attrib = FILE_ATTRIBUTE_READONLY; + sfinfo.setattr.in.write_time = basetime; + CHECK_CALL_PATH(SETATTR, NT_STATUS_OK); + CHECK_VALUE (ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_READONLY); + CHECK_TIME (ALL_INFO, all_info, write_time, basetime); + + printf("setting to NORMAL doesn't do anything\n"); + sfinfo.setattr.in.attrib = FILE_ATTRIBUTE_NORMAL; + sfinfo.setattr.in.write_time = 0; + CHECK_CALL_PATH(SETATTR, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_READONLY); + CHECK_TIME (ALL_INFO, all_info, write_time, basetime); + + printf("a zero write_time means don't change\n"); + sfinfo.setattr.in.attrib = 0; + sfinfo.setattr.in.write_time = 0; + CHECK_CALL_PATH(SETATTR, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_NORMAL); + CHECK_TIME (ALL_INFO, all_info, write_time, basetime); + + printf("test setattre\n"); + sfinfo.setattre.in.create_time = basetime + 20; + sfinfo.setattre.in.access_time = basetime + 30; + sfinfo.setattre.in.write_time = basetime + 40; + CHECK_CALL_FNUM(SETATTRE, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 20); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 30); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 40); + + sfinfo.setattre.in.create_time = 0; + sfinfo.setattre.in.access_time = 0; + sfinfo.setattre.in.write_time = 0; + CHECK_CALL_FNUM(SETATTRE, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 20); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 30); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 40); + + printf("test standard level\n"); + sfinfo.standard.in.create_time = basetime + 100; + sfinfo.standard.in.access_time = basetime + 200; + sfinfo.standard.in.write_time = basetime + 300; + CHECK_CALL_FNUM(STANDARD, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + + printf("test basic_info level\n"); + basetime += 86400; + unix_to_nt_time(&sfinfo.basic_info.in.create_time, basetime + 100); + unix_to_nt_time(&sfinfo.basic_info.in.access_time, basetime + 200); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, basetime + 300); + unix_to_nt_time(&sfinfo.basic_info.in.change_time, basetime + 400); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_READONLY; + CHECK_CALL_FNUM(BASIC_INFO, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + CHECK_TIME(ALL_INFO, all_info, change_time, basetime + 400); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_READONLY); + + printf("a zero time means don't change\n"); + unix_to_nt_time(&sfinfo.basic_info.in.create_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.access_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.change_time, 0); + sfinfo.basic_info.in.attrib = 0; + CHECK_CALL_FNUM(BASIC_INFO, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + CHECK_TIME(ALL_INFO, all_info, change_time, basetime + 400); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_READONLY); + + printf("test basic_information level\n"); + basetime += 86400; + unix_to_nt_time(&sfinfo.basic_info.in.create_time, basetime + 100); + unix_to_nt_time(&sfinfo.basic_info.in.access_time, basetime + 200); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, basetime + 300); + unix_to_nt_time(&sfinfo.basic_info.in.change_time, basetime + 400); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + CHECK_CALL_FNUM(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + CHECK_TIME(ALL_INFO, all_info, change_time, basetime + 400); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_NORMAL); + + CHECK_CALL_PATH(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + CHECK_TIME(ALL_INFO, all_info, change_time, basetime + 400); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_NORMAL); + + printf("a zero time means don't change\n"); + unix_to_nt_time(&sfinfo.basic_info.in.create_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.access_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.change_time, 0); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + CHECK_CALL_FNUM(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + CHECK_TIME(ALL_INFO, all_info, change_time, basetime + 400); + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_NORMAL); + + CHECK_CALL_PATH(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_TIME(ALL_INFO, all_info, create_time, basetime + 100); + CHECK_TIME(ALL_INFO, all_info, access_time, basetime + 200); + CHECK_TIME(ALL_INFO, all_info, write_time, basetime + 300); + + /* interesting - w2k3 leaves change_time as current time for 0 change time + in setpathinfo + CHECK_TIME(ALL_INFO, all_info, change_time, basetime + 400); + */ + CHECK_VALUE(ALL_INFO, all_info, attrib, FILE_ATTRIBUTE_NORMAL); + + printf("test disposition_info level\n"); + sfinfo.disposition_info.in.delete_on_close = 1; + CHECK_CALL_FNUM(DISPOSITION_INFO, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, delete_pending, 1); + CHECK_VALUE(ALL_INFO, all_info, nlink, 0); + + sfinfo.disposition_info.in.delete_on_close = 0; + CHECK_CALL_FNUM(DISPOSITION_INFO, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, delete_pending, 0); + CHECK_VALUE(ALL_INFO, all_info, nlink, 1); + + printf("test disposition_information level\n"); + sfinfo.disposition_info.in.delete_on_close = 1; + CHECK_CALL_FNUM(DISPOSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, delete_pending, 1); + CHECK_VALUE(ALL_INFO, all_info, nlink, 0); + + /* this would delete the file! */ + /* + CHECK_CALL_PATH(DISPOSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, delete_pending, 1); + CHECK_VALUE(ALL_INFO, all_info, nlink, 0); + */ + + sfinfo.disposition_info.in.delete_on_close = 0; + CHECK_CALL_FNUM(DISPOSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, delete_pending, 0); + CHECK_VALUE(ALL_INFO, all_info, nlink, 1); + + CHECK_CALL_PATH(DISPOSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, delete_pending, 0); + CHECK_VALUE(ALL_INFO, all_info, nlink, 1); + + printf("test allocation_info level\n"); + sfinfo.allocation_info.in.alloc_size = 0; + CHECK_CALL_FNUM(ALLOCATION_INFO, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 0); + CHECK_VALUE(ALL_INFO, all_info, alloc_size, 0); + + sfinfo.allocation_info.in.alloc_size = 4096; + CHECK_CALL_FNUM(ALLOCATION_INFO, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, alloc_size, 4096); + CHECK_VALUE(ALL_INFO, all_info, size, 0); + + RECREATE_BOTH; + sfinfo.allocation_info.in.alloc_size = 0; + CHECK_CALL_FNUM(ALLOCATION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 0); + CHECK_VALUE(ALL_INFO, all_info, alloc_size, 0); + + CHECK_CALL_PATH(ALLOCATION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 0); + CHECK_VALUE(ALL_INFO, all_info, alloc_size, 0); + + sfinfo.allocation_info.in.alloc_size = 4096; + CHECK_CALL_FNUM(ALLOCATION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, alloc_size, 4096); + CHECK_VALUE(ALL_INFO, all_info, size, 0); + + /* setting the allocation size up via setpathinfo seems + to be broken in w2k3 */ + CHECK_CALL_PATH(ALLOCATION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, alloc_size, 0); + CHECK_VALUE(ALL_INFO, all_info, size, 0); + + printf("test end_of_file_info level\n"); + sfinfo.end_of_file_info.in.size = 37; + CHECK_CALL_FNUM(END_OF_FILE_INFO, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 37); + + sfinfo.end_of_file_info.in.size = 7; + CHECK_CALL_FNUM(END_OF_FILE_INFO, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 7); + + sfinfo.end_of_file_info.in.size = 37; + CHECK_CALL_FNUM(END_OF_FILE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 37); + + CHECK_CALL_PATH(END_OF_FILE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 37); + + sfinfo.end_of_file_info.in.size = 7; + CHECK_CALL_FNUM(END_OF_FILE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 7); + + CHECK_CALL_PATH(END_OF_FILE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(ALL_INFO, all_info, size, 7); + + printf("test position_information level\n"); + sfinfo.position_information.in.position = 123456; + CHECK_CALL_FNUM(POSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(POSITION_INFORMATION, position_information, position, 123456); + + CHECK_CALL_PATH(POSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(POSITION_INFORMATION, position_information, position, 0); + + printf("test mode_information level\n"); + sfinfo.mode_information.in.mode = 2; + CHECK_CALL_FNUM(MODE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(MODE_INFORMATION, mode_information, mode, 2); + + CHECK_CALL_PATH(MODE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(MODE_INFORMATION, mode_information, mode, 0); + + sfinfo.mode_information.in.mode = 1; + CHECK_CALL_FNUM(MODE_INFORMATION, NT_STATUS_INVALID_PARAMETER); + CHECK_CALL_PATH(MODE_INFORMATION, NT_STATUS_INVALID_PARAMETER); + + sfinfo.mode_information.in.mode = 0; + CHECK_CALL_FNUM(MODE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(MODE_INFORMATION, mode_information, mode, 0); + + CHECK_CALL_PATH(MODE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(MODE_INFORMATION, mode_information, mode, 0); + +#if 0 + printf("test unix_basic level\n"); + CHECK_CALL_FNUM(UNIX_BASIC, NT_STATUS_OK); + CHECK_CALL_PATH(UNIX_BASIC, NT_STATUS_OK); + + printf("test unix_link level\n"); + CHECK_CALL_FNUM(UNIX_LINK, NT_STATUS_OK); + CHECK_CALL_PATH(UNIX_LINK, NT_STATUS_OK); +#endif + +done: + smb_raw_exit(cli->session); + smbcli_close(cli->tree, fnum); + if (NT_STATUS_IS_ERR(smbcli_unlink(cli->tree, fnum_fname))) { + printf("Failed to delete %s - %s\n", fnum_fname, smbcli_errstr(cli->tree)); + } + if (NT_STATUS_IS_ERR(smbcli_unlink(cli->tree, path_fname))) { + printf("Failed to delete %s - %s\n", path_fname, smbcli_errstr(cli->tree)); + } + + return ret; +} + +/* + * basic testing of all RAW_SFILEINFO_RENAME call + */ +bool torture_raw_sfileinfo_rename(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + int fnum_saved, d_fnum, fnum2, fnum = -1; + char *fnum_fname; + char *fnum_fname_new; + char *path_fname; + char *path_fname_new; + char *path_dname; + char *path_dname_new; + char *saved_name; + char *saved_name_new; + union smb_fileinfo finfo1, finfo2; + union smb_setfileinfo sfinfo; + NTSTATUS status, status2; + const char *call_name; + bool check_fnum; + int n = time(NULL) % 100; + + asprintf(&path_fname, BASEDIR "\\fname_test_%d.txt", n); + asprintf(&path_fname_new, BASEDIR "\\fname_test_new_%d.txt", n); + asprintf(&fnum_fname, BASEDIR "\\fnum_test_%d.txt", n); + asprintf(&fnum_fname_new, BASEDIR "\\fnum_test_new_%d.txt", n); + asprintf(&path_dname, BASEDIR "\\dname_test_%d", n); + asprintf(&path_dname_new, BASEDIR "\\dname_test_new_%d", n); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + RECREATE_BOTH; + + ZERO_STRUCT(sfinfo); + + smbcli_close(cli->tree, create_complex_file(cli, torture, fnum_fname_new)); + smbcli_close(cli->tree, create_complex_file(cli, torture, path_fname_new)); + + sfinfo.rename_information.in.overwrite = 0; + sfinfo.rename_information.in.root_fid = 0; + sfinfo.rename_information.in.new_name = fnum_fname_new+strlen(BASEDIR)+1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OBJECT_NAME_COLLISION); + + sfinfo.rename_information.in.new_name = path_fname_new+strlen(BASEDIR)+1; + CHECK_CALL_PATH(RENAME_INFORMATION, NT_STATUS_OBJECT_NAME_COLLISION); + + sfinfo.rename_information.in.new_name = fnum_fname_new; + sfinfo.rename_information.in.overwrite = 1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_NOT_SUPPORTED); + + sfinfo.rename_information.in.new_name = fnum_fname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname_new); + + printf("Trying rename with dest file open\n"); + fnum2 = create_complex_file(cli, torture, fnum_fname); + sfinfo.rename_information.in.new_name = fnum_fname+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_ACCESS_DENIED); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname_new); + + fnum_saved = fnum; + fnum = fnum2; + sfinfo.disposition_info.in.delete_on_close = 1; + CHECK_CALL_FNUM(DISPOSITION_INFO, NT_STATUS_OK); + fnum = fnum_saved; + + printf("Trying rename with dest file open and delete_on_close\n"); + sfinfo.rename_information.in.new_name = fnum_fname+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_ACCESS_DENIED); + + smbcli_close(cli->tree, fnum2); + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname); + + printf("Trying rename with source file open twice\n"); + sfinfo.rename_information.in.new_name = fnum_fname+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname); + + fnum2 = create_complex_file(cli, torture, fnum_fname); + sfinfo.rename_information.in.new_name = fnum_fname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 0; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname_new); + smbcli_close(cli->tree, fnum2); + + sfinfo.rename_information.in.new_name = fnum_fname+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 0; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname); + + sfinfo.rename_information.in.new_name = path_fname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 1; + CHECK_CALL_PATH(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_fname_new); + + sfinfo.rename_information.in.new_name = fnum_fname+strlen(BASEDIR)+1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname); + + sfinfo.rename_information.in.new_name = path_fname+strlen(BASEDIR)+1; + CHECK_CALL_PATH(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_fname); + + printf("Trying rename with a root fid\n"); + status = create_directory_handle(cli->tree, BASEDIR, &d_fnum); + CHECK_STATUS(status, NT_STATUS_OK); + sfinfo.rename_information.in.new_name = fnum_fname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.root_fid = d_fnum; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_INVALID_PARAMETER); + CHECK_STR(NAME_INFO, name_info, fname.s, fnum_fname); + smbcli_close(cli->tree, d_fnum); + + printf("Trying rename directory\n"); + if (!torture_setup_dir(cli, path_dname)) { + ret = false; + goto done; + } + saved_name = path_fname; + saved_name_new = path_fname_new; + path_fname = path_dname; + path_fname_new = path_dname_new; + sfinfo.rename_information.in.new_name = path_dname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 0; + sfinfo.rename_information.in.root_fid = 0; + CHECK_CALL_PATH(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_dname_new); + path_fname = saved_name; + path_fname_new = saved_name_new; + + if (torture_setting_bool(torture, "samba3", false)) { + printf("SKIP: Trying rename directory with a handle\n"); + printf("SKIP: Trying rename by path while a handle is open\n"); + printf("SKIP: Trying rename directory by path while a handle is open\n"); + goto done; + } + + printf("Trying rename directory with a handle\n"); + status = create_directory_handle(cli->tree, path_dname_new, &d_fnum); + fnum_saved = fnum; + fnum = d_fnum; + saved_name = fnum_fname; + saved_name_new = fnum_fname_new; + fnum_fname = path_dname; + fnum_fname_new = path_dname_new; + sfinfo.rename_information.in.new_name = path_dname+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 0; + sfinfo.rename_information.in.root_fid = 0; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_dname); + smbcli_close(cli->tree, d_fnum); + fnum = fnum_saved; + fnum_fname = saved_name; + fnum_fname_new = saved_name_new; + + printf("Trying rename by path while a handle is open\n"); + fnum_saved = fnum; + fnum = create_complex_file(cli, torture, path_fname); + sfinfo.rename_information.in.new_name = path_fname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 0; + sfinfo.rename_information.in.root_fid = 0; + CHECK_CALL_PATH(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_fname_new); + /* check that the handle returns the same name */ + check_fnum = true; + CHECK_STR(NAME_INFO, name_info, fname.s, path_fname_new); + /* rename it back on the handle */ + sfinfo.rename_information.in.new_name = path_fname+strlen(BASEDIR)+1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_fname); + check_fnum = false; + CHECK_STR(NAME_INFO, name_info, fname.s, path_fname); + smbcli_close(cli->tree, fnum); + fnum = fnum_saved; + + printf("Trying rename directory by path while a handle is open\n"); + status = create_directory_handle(cli->tree, path_dname, &d_fnum); + fnum_saved = fnum; + fnum = d_fnum; + saved_name = path_fname; + saved_name_new = path_fname_new; + path_fname = path_dname; + path_fname_new = path_dname_new; + sfinfo.rename_information.in.new_name = path_dname_new+strlen(BASEDIR)+1; + sfinfo.rename_information.in.overwrite = 0; + sfinfo.rename_information.in.root_fid = 0; + CHECK_CALL_PATH(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_dname_new); + path_fname = saved_name; + path_fname_new = saved_name_new; + saved_name = fnum_fname; + saved_name_new = fnum_fname_new; + fnum_fname = path_dname; + fnum_fname_new = path_dname_new; + /* check that the handle returns the same name */ + check_fnum = true; + CHECK_STR(NAME_INFO, name_info, fname.s, path_dname_new); + /* rename it back on the handle */ + sfinfo.rename_information.in.new_name = path_dname+strlen(BASEDIR)+1; + CHECK_CALL_FNUM(RENAME_INFORMATION, NT_STATUS_OK); + CHECK_STR(NAME_INFO, name_info, fname.s, path_dname); + fnum_fname = saved_name; + fnum_fname_new = saved_name_new; + saved_name = path_fname; + saved_name_new = path_fname_new; + path_fname = path_dname; + path_fname_new = path_dname_new; + check_fnum = false; + CHECK_STR(NAME_INFO, name_info, fname.s, path_dname); + smbcli_close(cli->tree, d_fnum); + fnum = fnum_saved; + path_fname = saved_name; + path_fname_new = saved_name_new; + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + look for the w2k3 setpathinfo STANDARD bug +*/ +bool torture_raw_sfileinfo_bug(struct torture_context *torture, + struct smbcli_state *cli) +{ + const char *fname = "\\bug3.txt"; + union smb_setfileinfo sfinfo; + NTSTATUS status; + int fnum; + + if (!torture_setting_bool(torture, "dangerous", false)) + torture_skip(torture, + "torture_raw_sfileinfo_bug disabled - enable dangerous tests to use\n"); + + fnum = create_complex_file(cli, torture, fname); + smbcli_close(cli->tree, fnum); + + sfinfo.generic.level = RAW_SFILEINFO_STANDARD; + sfinfo.generic.in.file.path = fname; + + sfinfo.standard.in.create_time = 0; + sfinfo.standard.in.access_time = 0; + sfinfo.standard.in.write_time = 0; + + status = smb_raw_setpathinfo(cli->tree, &sfinfo); + printf("%s - %s\n", fname, nt_errstr(status)); + + printf("now try and delete %s\n", fname); + + return true; +} diff --git a/source4/torture/raw/streams.c b/source4/torture/raw/streams.c new file mode 100644 index 0000000000..8b2d327653 --- /dev/null +++ b/source4/torture/raw/streams.c @@ -0,0 +1,649 @@ +/* + Unix SMB/CIFS implementation. + + test alternate data streams + + Copyright (C) Andrew Tridgell 2004 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define BASEDIR "\\teststreams" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%s) Incorrect value %s=%d - should be %d\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +/* + check that a stream has the right contents +*/ +static bool check_stream(struct smbcli_state *cli, const char *location, + TALLOC_CTX *mem_ctx, + const char *fname, const char *sname, + const char *value) +{ + int fnum; + const char *full_name; + uint8_t *buf; + ssize_t ret; + + full_name = talloc_asprintf(mem_ctx, "%s:%s", fname, sname); + + fnum = smbcli_open(cli->tree, full_name, O_RDONLY, DENY_NONE); + + if (value == NULL) { + if (fnum != -1) { + printf("(%s) should have failed stream open of %s\n", + location, full_name); + return false; + } + return true; + } + + if (fnum == -1) { + printf("(%s) Failed to open stream '%s' - %s\n", + location, full_name, smbcli_errstr(cli->tree)); + return false; + } + + buf = talloc_array(mem_ctx, uint8_t, strlen(value)+11); + + ret = smbcli_read(cli->tree, fnum, buf, 0, strlen(value)+11); + if (ret != strlen(value)) { + printf("(%s) Failed to read %lu bytes from stream '%s' - got %d\n", + location, (long)strlen(value), full_name, (int)ret); + return false; + } + + if (memcmp(buf, value, strlen(value)) != 0) { + printf("(%s) Bad data in stream\n", location); + return false; + } + + smbcli_close(cli->tree, fnum); + return true; +} + +static int qsort_string(const void *v1, const void *v2) +{ + char * const *s1 = v1; + char * const *s2 = v2; + return strcmp(*s1, *s2); +} + +static int qsort_stream(const void *v1, const void *v2) +{ + const struct stream_struct * s1 = v1; + const struct stream_struct * s2 = v2; + return strcmp(s1->stream_name.s, s2->stream_name.s); +} + +static bool check_stream_list(struct smbcli_state *cli, const char *fname, + int num_exp, const char **exp) +{ + union smb_fileinfo finfo; + NTSTATUS status; + int i; + TALLOC_CTX *tmp_ctx = talloc_new(cli); + char **exp_sort; + struct stream_struct *stream_sort; + bool ret = false; + + finfo.generic.level = RAW_FILEINFO_STREAM_INFO; + finfo.generic.in.file.path = fname; + + status = smb_raw_pathinfo(cli->tree, tmp_ctx, &finfo); + if (!NT_STATUS_IS_OK(status)) { + d_fprintf(stderr, "(%s) smb_raw_pathinfo failed: %s\n", + __location__, nt_errstr(status)); + goto fail; + } + + if (finfo.stream_info.out.num_streams != num_exp) { + d_fprintf(stderr, "(%s) expected %d streams, got %d\n", + __location__, num_exp, + finfo.stream_info.out.num_streams); + goto fail; + } + + if (num_exp == 0) { + ret = true; + goto fail; + } + + exp_sort = talloc_memdup(tmp_ctx, exp, num_exp * sizeof(*exp)); + + if (exp_sort == NULL) { + goto fail; + } + + qsort(exp_sort, num_exp, sizeof(*exp_sort), qsort_string); + + stream_sort = talloc_memdup(tmp_ctx, finfo.stream_info.out.streams, + finfo.stream_info.out.num_streams * + sizeof(*stream_sort)); + + if (stream_sort == NULL) { + goto fail; + } + + qsort(stream_sort, finfo.stream_info.out.num_streams, + sizeof(*stream_sort), qsort_stream); + + for (i=0; i<num_exp; i++) { + if (strcmp(exp_sort[i], stream_sort[i].stream_name.s) != 0) { + d_fprintf(stderr, "(%s) expected stream name %s, got " + "%s\n", __location__, exp_sort[i], + stream_sort[i].stream_name.s); + goto fail; + } + } + + ret = true; + fail: + talloc_free(tmp_ctx); + return ret; +} + +/* + test bahavior of streams on directories +*/ +static bool test_stream_dir(struct torture_context *tctx, + struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\stream.txt"; + const char *sname1; + bool ret = true; + const char *basedir_data; + + basedir_data = talloc_asprintf(mem_ctx, "%s::$DATA", BASEDIR); + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + + printf("(%s) opening non-existant directory stream\n", __location__); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = sname1; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY); + + printf("(%s) opening basedir stream\n", __location__); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_DIRECTORY; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = basedir_data; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY); + + printf("(%s) opening basedir ::$DATA stream\n", __location__); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0x10; + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = 0; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = basedir_data; + status = smb_raw_open(cli->tree, mem_ctx, &io); + if (torture_setting_bool(tctx, "samba3", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_FILE_IS_A_DIRECTORY); + } + + printf("(%s) list the streams on the basedir\n", __location__); + ret &= check_stream_list(cli, BASEDIR, 0, NULL); +done: + return ret; +} + +/* + test basic behavior of streams on directories +*/ +static bool test_stream_io(struct torture_context *tctx, + struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\stream.txt"; + const char *sname1, *sname2; + bool ret = true; + int fnum = -1; + ssize_t retsize; + + const char *one[] = { "::$DATA" }; + const char *two[] = { "::$DATA", ":Second Stream:$DATA" }; + const char *three[] = { "::$DATA", ":Stream One:$DATA", + ":Second Stream:$DATA" }; + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, "Second Stream"); + + printf("(%s) creating a stream on a non-existant file\n", __location__); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = sname1; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One", NULL); + + printf("(%s) check that open of base file is allowed\n", __location__); + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.fname = fname; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + + printf("(%s) writing to stream\n", __location__); + retsize = smbcli_write(cli->tree, fnum, 0, "test data", 0, 9); + CHECK_VALUE(retsize, 9); + + smbcli_close(cli->tree, fnum); + + ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One", "test data"); + + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.fname = sname1; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("(%s) modifying stream\n", __location__); + retsize = smbcli_write(cli->tree, fnum, 0, "MORE DATA ", 5, 10); + CHECK_VALUE(retsize, 10); + + smbcli_close(cli->tree, fnum); + + ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One:$FOO", NULL); + + printf("(%s) creating a stream2 on a existing file\n", __location__); + io.ntcreatex.in.fname = sname2; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + printf("(%s) modifying stream\n", __location__); + retsize = smbcli_write(cli->tree, fnum, 0, "SECOND STREAM", 0, 13); + CHECK_VALUE(retsize, 13); + + smbcli_close(cli->tree, fnum); + + ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One", "test MORE DATA "); + ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One:$DATA", "test MORE DATA "); + ret &= check_stream(cli, __location__, mem_ctx, fname, "Stream One:", NULL); + ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream", "SECOND STREAM"); + if (!torture_setting_bool(tctx, "samba4", false)) { + ret &= check_stream(cli, __location__, mem_ctx, fname, + "SECOND STREAM:$DATA", "SECOND STREAM"); + } + ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream:$DATA", "SECOND STREAM"); + ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream:", NULL); + ret &= check_stream(cli, __location__, mem_ctx, fname, "Second Stream:$FOO", NULL); + + check_stream_list(cli, fname, 3, three); + + printf("(%s) deleting stream\n", __location__); + status = smbcli_unlink(cli->tree, sname1); + CHECK_STATUS(status, NT_STATUS_OK); + + check_stream_list(cli, fname, 2, two); + + printf("(%s) delete a stream via delete-on-close\n", __location__); + io.ntcreatex.in.fname = sname2; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + smbcli_close(cli->tree, fnum); + status = smbcli_unlink(cli->tree, sname2); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + check_stream_list(cli, fname, 1, one); + + if (!torture_setting_bool(tctx, "samba4", false)) { + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.fname = sname1; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + io.ntcreatex.in.fname = sname2; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, io.ntcreatex.out.file.fnum); + } + + printf("(%s) deleting file\n", __location__); + status = smbcli_unlink(cli->tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* + test stream sharemodes +*/ +static bool test_stream_sharemodes(struct torture_context *tctx, + struct smbcli_state *cli, + TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\stream.txt"; + const char *sname1, *sname2; + bool ret = true; + int fnum1 = -1; + int fnum2 = -1; + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, "Second Stream"); + + printf("(%s) testing stream share mode conflicts\n", __location__); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_WRITE_DATA; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = sname1; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum1 = io.ntcreatex.out.file.fnum; + + /* + * A different stream does not give a sharing violation + */ + + io.ntcreatex.in.fname = sname2; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = io.ntcreatex.out.file.fnum; + + /* + * ... whereas the same stream does with unchanged access/share_access + * flags + */ + + io.ntcreatex.in.fname = sname1; + io.ntcreatex.in.open_disposition = 0; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.ntcreatex.in.fname = sname2; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + +done: + if (fnum1 != -1) smbcli_close(cli->tree, fnum1); + if (fnum2 != -1) smbcli_close(cli->tree, fnum2); + status = smbcli_unlink(cli->tree, fname); + return ret; +} + +/* + * Test FILE_SHARE_DELETE on streams + * + * A stream opened with !FILE_SHARE_DELETE prevents the main file to be opened + * with SEC_STD_DELETE. + * + * The main file opened with !FILE_SHARE_DELETE does *not* prevent a stream to + * be opened with SEC_STD_DELETE. + * + * A stream held open with FILE_SHARE_DELETE allows the file to be + * deleted. After the main file is deleted, access to the open file descriptor + * still works, but all name-based access to both the main file as well as the + * stream is denied with DELETE ending. + * + * This means, an open of the main file with SEC_STD_DELETE should walk all + * streams and also open them with SEC_STD_DELETE. If any of these opens gives + * SHARING_VIOLATION, the main open fails. + * + * Closing the main file after delete_on_close has been set does not really + * unlink it but leaves the corresponding share mode entry with + * delete_on_close being set around until all streams are closed. + * + * Opening a stream must also look at the main file's share mode entry, look + * at the delete_on_close bit and potentially return DELETE_PENDING. + */ + +static bool test_stream_delete(struct torture_context *tctx, + struct smbcli_state *cli, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + union smb_open io; + const char *fname = BASEDIR "\\stream.txt"; + const char *sname1; + bool ret = true; + int fnum = -1; + uint8_t buf[9]; + ssize_t retsize; + union smb_fileinfo finfo; + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + + printf("(%s) opening non-existant file stream\n", __location__); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.root_fid = 0; + io.ntcreatex.in.flags = 0; + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA; + io.ntcreatex.in.create_options = 0; + io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + io.ntcreatex.in.share_access = 0; + io.ntcreatex.in.alloc_size = 0; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.ntcreatex.in.security_flags = 0; + io.ntcreatex.in.fname = sname1; + + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + retsize = smbcli_write(cli->tree, fnum, 0, "test data", 0, 9); + CHECK_VALUE(retsize, 9); + + /* + * One stream opened without FILE_SHARE_DELETE prevents the main file + * to be deleted or even opened with DELETE access + */ + + status = smbcli_unlink(cli->tree, fname); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.fname = fname; + io.ntcreatex.in.access_mask = SEC_STD_DELETE; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + smbcli_close(cli->tree, fnum); + + /* + * ... but unlink works if a stream is opened with FILE_SHARE_DELETE + */ + + io.ntcreatex.in.fname = sname1; + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + status = smbcli_unlink(cli->tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * file access still works on the stream while the main file is closed + */ + + retsize = smbcli_read(cli->tree, fnum, buf, 0, 9); + CHECK_VALUE(retsize, 9); + + finfo.generic.level = RAW_FILEINFO_STANDARD; + finfo.generic.in.file.path = fname; + + /* + * name-based access to both the main file and the stream does not + * work anymore but gives DELETE_PENDING + */ + + status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_DELETE_PENDING); + + if (!torture_setting_bool(tctx, "samba3", false)) { + + /* + * S3 doesn't do this yet + */ + + finfo.generic.in.file.path = sname1; + status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_DELETE_PENDING); + } + + /* + * fd-based qfileinfo on the stream still works, the stream does not + * have the delete-on-close bit set. This could mean that open on the + * stream first opens the main file + */ + + finfo.all_info.level = RAW_FILEINFO_ALL_INFO; + finfo.all_info.in.file.fnum = fnum; + + status = smb_raw_fileinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(finfo.all_info.out.delete_pending, 0); + + smbcli_close(cli->tree, fnum); + + /* + * After closing the stream the file is really gone. + */ + + finfo.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + io.ntcreatex.in.access_mask = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA + |SEC_STD_DELETE; + io.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_CREATE; + status = smb_raw_open(cli->tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = io.ntcreatex.out.file.fnum; + + finfo.generic.in.file.path = fname; + status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + status = smb_raw_pathinfo(cli->tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); +done: + smbcli_close(cli->tree, fnum); + return ret; +} + +/* + basic testing of streams calls +*/ +bool torture_raw_streams(struct torture_context *torture, + struct smbcli_state *cli) +{ + bool ret = true; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + ret &= test_stream_dir(torture, cli, torture); + smb_raw_exit(cli->session); + ret &= test_stream_io(torture, cli, torture); + smb_raw_exit(cli->session); + ret &= test_stream_sharemodes(torture, cli, torture); + smb_raw_exit(cli->session); + if (!torture_setting_bool(torture, "samba4", false)) { + ret &= test_stream_delete(torture, cli, torture); + } + + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + + return ret; +} diff --git a/source4/torture/raw/tconrate.c b/source4/torture/raw/tconrate.c new file mode 100644 index 0000000000..6f0ba0d617 --- /dev/null +++ b/source4/torture/raw/tconrate.c @@ -0,0 +1,201 @@ +/* + SMB tree connection rate test + + Copyright (C) 2006-2007 James Peach + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/libcli.h" +#include "libcli/resolve/resolve.h" +#include "torture/smbtorture.h" +#include "lib/cmdline/popt_common.h" +#include "param/param.h" + +#include "system/filesys.h" +#include "system/shmem.h" + +#define TIME_LIMIT_SECS 30 +#define usec_to_sec(s) ((s) / 1000000) +#define sec_to_usec(s) ((s) * 1000000) + +/* Map a shared memory buffer of at least nelem counters. */ +static void * map_count_buffer(unsigned nelem, size_t elemsz) +{ + void * buf; + size_t bufsz; + size_t pagesz = getpagesize(); + + bufsz = nelem * elemsz; + bufsz = (bufsz + pagesz) % pagesz; /* round up to pagesz */ + +#ifdef MAP_ANON + /* BSD */ + buf = mmap(NULL, bufsz, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, + -1 /* fd */, 0 /* offset */); +#else + buf = mmap(NULL, bufsz, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, + open("/dev/zero", O_RDWR), 0 /* offset */); +#endif + + if (buf == MAP_FAILED) { + printf("failed to map count buffer: %s\n", + strerror(errno)); + return NULL; + } + + return buf; + +} + +static int fork_tcon_client(struct torture_context *tctx, + int *tcon_count, unsigned tcon_timelimit, + const char *host, const char *share) +{ + pid_t child; + struct smbcli_state *cli; + struct timeval end; + struct timeval now; + struct smbcli_options options; + + lp_smbcli_options(tctx->lp_ctx, &options); + + child = fork(); + if (child == -1) { + printf("failed to fork child: %s\n,", strerror(errno)); + return -1; + } else if (child != 0) { + /* Parent, just return. */ + return 0; + } + + /* Child. Just make as many connections as possible within the + * time limit. Don't bother synchronising the child start times + * because it's probably not work the effort, and a bit of startup + * jitter is probably a more realistic test. + */ + + + end = timeval_current(); + now = timeval_current(); + end.tv_sec += tcon_timelimit; + *tcon_count = 0; + + while (timeval_compare(&now, &end) == -1) { + NTSTATUS status; + + status = smbcli_full_connection(NULL, &cli, + host, lp_smb_ports(tctx->lp_ctx), share, + NULL, cmdline_credentials, + lp_resolve_context(tctx->lp_ctx), + tctx->ev, &options); + + if (!NT_STATUS_IS_OK(status)) { + printf("failed to connect to //%s/%s: %s\n", + host, share, nt_errstr(status)); + goto done; + } + + smbcli_tdis(cli); + + *tcon_count = *tcon_count + 1; + now = timeval_current(); + } + +done: + exit(0); +} + +static bool children_remain(void) +{ + /* Reap as many children as possible. */ + for (;;) { + pid_t ret = waitpid(-1, NULL, WNOHANG); + if (ret == 0) { + /* no children ready */ + return true; + } + if (ret == -1) { + /* no children left. maybe */ + return errno == ECHILD ? false : true; + } + } + + /* notreached */ + return false; +} + +static double rate_convert_secs(unsigned count, + const struct timeval *start, const struct timeval *end) +{ + return (double)count / + usec_to_sec((double)usec_time_diff(end, start)); +} + +/* Test the rate at which the server will accept connections. */ +bool torture_bench_treeconnect(struct torture_context *tctx) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + + int timelimit = torture_setting_int(tctx, "timelimit", + TIME_LIMIT_SECS); + int nprocs = torture_setting_int(tctx, "nprocs", 4); + + int *curr_counts = map_count_buffer(nprocs, sizeof(int)); + int *last_counts = talloc_array(NULL, int, nprocs); + + struct timeval now, last, start; + int i, delta; + + torture_assert(tctx, nprocs > 0, "bad proc count"); + torture_assert(tctx, timelimit > 0, "bad timelimit"); + torture_assert(tctx, curr_counts, "allocation failure"); + torture_assert(tctx, last_counts, "allocation failure"); + + start = last = timeval_current(); + for (i = 0; i < nprocs; ++i) { + fork_tcon_client(tctx, &curr_counts[i], timelimit, host, share); + } + + while (children_remain()) { + + sleep(1); + now = timeval_current(); + + for (i = 0, delta = 0; i < nprocs; ++i) { + delta += curr_counts[i] - last_counts[i]; + } + + printf("%u connections/sec\n", + (unsigned)rate_convert_secs(delta, &last, &now)); + + memcpy(last_counts, curr_counts, nprocs * sizeof(int)); + last = timeval_current(); + } + + now = timeval_current(); + + for (i = 0, delta = 0; i < nprocs; ++i) { + delta += curr_counts[i]; + } + + printf("TOTAL: %u connections/sec over %u secs\n", + (unsigned)rate_convert_secs(delta, &start, &now), + timelimit); + return true; +} + +/* vim: set sts=8 sw=8 : */ diff --git a/source4/torture/raw/unlink.c b/source4/torture/raw/unlink.c new file mode 100644 index 0000000000..ddddeba41b --- /dev/null +++ b/source4/torture/raw/unlink.c @@ -0,0 +1,450 @@ +/* + Unix SMB/CIFS implementation. + unlink test suite + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/filesys.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define BASEDIR "\\testunlink" + +/* + test unlink ops +*/ +static bool test_unlink(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_unlink io; + NTSTATUS status; + bool ret = true; + const char *fname = BASEDIR "\\test.txt"; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Trying non-existant file\n"); + io.unlink.in.pattern = fname; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + smbcli_close(cli->tree, smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE)); + + io.unlink.in.pattern = fname; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Trying a hidden file\n"); + smbcli_close(cli->tree, smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE)); + torture_set_file_attribute(cli->tree, fname, FILE_ATTRIBUTE_HIDDEN); + + io.unlink.in.pattern = fname; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + io.unlink.in.pattern = fname; + io.unlink.in.attrib = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.unlink.in.pattern = fname; + io.unlink.in.attrib = FILE_ATTRIBUTE_HIDDEN; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + printf("Trying a directory\n"); + io.unlink.in.pattern = BASEDIR; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_IS_A_DIRECTORY); + + io.unlink.in.pattern = BASEDIR; + io.unlink.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_IS_A_DIRECTORY); + + printf("Trying a bad path\n"); + io.unlink.in.pattern = ".."; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + + io.unlink.in.pattern = "\\.."; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + + io.unlink.in.pattern = BASEDIR "\\..\\.."; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD); + + io.unlink.in.pattern = BASEDIR "\\.."; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_IS_A_DIRECTORY); + + printf("Trying wildcards\n"); + smbcli_close(cli->tree, smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE)); + io.unlink.in.pattern = BASEDIR "\\t*.t"; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + io.unlink.in.pattern = BASEDIR "\\z*"; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + io.unlink.in.pattern = BASEDIR "\\z*"; + io.unlink.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + status = smb_raw_unlink(cli->tree, &io); + + if (torture_setting_bool(tctx, "samba3", false)) { + /* + * In Samba3 we gave up upon getting the error codes in + * wildcard unlink correct. Trying gentest showed that this is + * irregular beyond our capabilities. So for + * FILE_ATTRIBUTE_DIRECTORY we always return NAME_INVALID. + * Tried by jra and vl. If others feel like solving this + * puzzle, please tell us :-) + */ + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + } + else { + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + } + + io.unlink.in.pattern = BASEDIR "\\*"; + io.unlink.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + + io.unlink.in.pattern = BASEDIR "\\?"; + io.unlink.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + + io.unlink.in.pattern = BASEDIR "\\t*"; + io.unlink.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + status = smb_raw_unlink(cli->tree, &io); + if (torture_setting_bool(tctx, "samba3", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + } + else { + CHECK_STATUS(status, NT_STATUS_OK); + } + + smbcli_close(cli->tree, smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE)); + + io.unlink.in.pattern = BASEDIR "\\*.dat"; + io.unlink.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + status = smb_raw_unlink(cli->tree, &io); + if (torture_setting_bool(tctx, "samba3", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + } + else { + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + } + + io.unlink.in.pattern = BASEDIR "\\*.tx?"; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + if (torture_setting_bool(tctx, "samba3", false)) { + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + } + else { + CHECK_STATUS(status, NT_STATUS_OK); + } + + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_NO_SUCH_FILE); + + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test delete on close +*/ +static bool test_delete_on_close(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_open op; + union smb_unlink io; + struct smb_rmdir dio; + NTSTATUS status; + bool ret = true; + int fnum, fnum2; + const char *fname = BASEDIR "\\test.txt"; + const char *dname = BASEDIR "\\test.dir"; + const char *inside = BASEDIR "\\test.dir\\test.txt"; + union smb_setfileinfo sfinfo; + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + dio.in.path = dname; + + io.unlink.in.pattern = fname; + io.unlink.in.attrib = 0; + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + printf("Testing with delete_on_close 0\n"); + fnum = create_complex_file(cli, tctx, fname); + + sfinfo.disposition_info.level = RAW_SFILEINFO_DISPOSITION_INFO; + sfinfo.disposition_info.in.file.fnum = fnum; + sfinfo.disposition_info.in.delete_on_close = 0; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Testing with delete_on_close 1\n"); + fnum = create_complex_file(cli, tctx, fname); + sfinfo.disposition_info.in.file.fnum = fnum; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + status = smb_raw_unlink(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + + printf("Testing with directory and delete_on_close 0\n"); + status = create_directory_handle(cli->tree, dname, &fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + sfinfo.disposition_info.level = RAW_SFILEINFO_DISPOSITION_INFO; + sfinfo.disposition_info.in.file.fnum = fnum; + sfinfo.disposition_info.in.delete_on_close = 0; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_OK); + + printf("Testing with directory delete_on_close 1\n"); + status = create_directory_handle(cli->tree, dname, &fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + sfinfo.disposition_info.in.file.fnum = fnum; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + + if (!torture_setting_bool(tctx, "samba3", false)) { + + /* + * Known deficiency, also skipped in base-delete. + */ + + printf("Testing with non-empty directory delete_on_close\n"); + status = create_directory_handle(cli->tree, dname, &fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + fnum2 = create_complex_file(cli, tctx, inside); + + sfinfo.disposition_info.in.file.fnum = fnum; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_DIRECTORY_NOT_EMPTY); + + sfinfo.disposition_info.in.file.fnum = fnum2; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + sfinfo.disposition_info.in.file.fnum = fnum; + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_DIRECTORY_NOT_EMPTY); + + smbcli_close(cli->tree, fnum2); + + status = smb_raw_setfileinfo(cli->tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } + + printf("Testing open dir with delete_on_close\n"); + status = create_directory_handle(cli->tree, dname, &fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + fnum2 = create_complex_file(cli, tctx, inside); + smbcli_close(cli->tree, fnum2); + + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + op.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY |NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = dname; + + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + smbcli_close(cli->tree, fnum); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_DIRECTORY_NOT_EMPTY); + + smbcli_deltree(cli->tree, dname); + + printf("Testing double open dir with second delete_on_close\n"); + status = create_directory_handle(cli->tree, dname, &fnum); + CHECK_STATUS(status, NT_STATUS_OK); + smbcli_close(cli->tree, fnum); + + fnum2 = create_complex_file(cli, tctx, inside); + smbcli_close(cli->tree, fnum2); + + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + op.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY |NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = dname; + + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = op.ntcreatex.out.file.fnum; + + smbcli_close(cli->tree, fnum2); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_DIRECTORY_NOT_EMPTY); + + smbcli_deltree(cli->tree, dname); + + printf("Testing pre-existing open dir with second delete_on_close\n"); + status = create_directory_handle(cli->tree, dname, &fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + smbcli_close(cli->tree, fnum); + + fnum = create_complex_file(cli, tctx, inside); + smbcli_close(cli->tree, fnum); + + /* we have a dir with a file in it, no handles open */ + + op.generic.level = RAW_OPEN_NTCREATEX; + op.ntcreatex.in.root_fid = 0; + op.ntcreatex.in.flags = 0; + op.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL; + op.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY |NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + op.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL; + op.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; + op.ntcreatex.in.alloc_size = 0; + op.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + op.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS; + op.ntcreatex.in.security_flags = 0; + op.ntcreatex.in.fname = dname; + + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum = op.ntcreatex.out.file.fnum; + + /* open without delete on close */ + op.ntcreatex.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + status = smb_raw_open(cli->tree, tctx, &op); + CHECK_STATUS(status, NT_STATUS_OK); + fnum2 = op.ntcreatex.out.file.fnum; + + /* close 2nd file handle */ + smbcli_close(cli->tree, fnum2); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_DIRECTORY_NOT_EMPTY); + + + smbcli_close(cli->tree, fnum); + + status = smb_raw_rmdir(cli->tree, &dio); + CHECK_STATUS(status, NT_STATUS_DIRECTORY_NOT_EMPTY); + +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + basic testing of unlink calls +*/ +struct torture_suite *torture_raw_unlink(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "UNLINK"); + + torture_suite_add_1smb_test(suite, "unlink", test_unlink); + torture_suite_add_1smb_test(suite, "delete_on_close", test_delete_on_close); + + return suite; +} diff --git a/source4/torture/raw/write.c b/source4/torture/raw/write.c new file mode 100644 index 0000000000..5d3628ca86 --- /dev/null +++ b/source4/torture/raw/write.c @@ -0,0 +1,724 @@ +/* + Unix SMB/CIFS implementation. + test suite for various write operations + + Copyright (C) Andrew Tridgell 2003 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/libcli.h" +#include "torture/util.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + printf("(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + printf("(%s) Incorrect value %s=%d - should be %d\n", \ + __location__, #v, v, correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_BUFFER(buf, seed, len) do { \ + if (!check_buffer(buf, seed, len, __location__)) { \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_ALL_INFO(v, field) do { \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFO; \ + finfo.all_info.in.file.path = fname; \ + status = smb_raw_pathinfo(cli->tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + if ((v) != finfo.all_info.out.field) { \ + printf("(%s) wrong value for field %s %.0f - %.0f\n", \ + __location__, #field, (double)v, (double)finfo.all_info.out.field); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + + +#define BASEDIR "\\testwrite" + + +/* + setup a random buffer based on a seed +*/ +static void setup_buffer(uint8_t *buf, uint_t seed, int len) +{ + int i; + srandom(seed); + for (i=0;i<len;i++) buf[i] = random(); +} + +/* + check a random buffer based on a seed +*/ +static bool check_buffer(uint8_t *buf, uint_t seed, int len, const char *location) +{ + int i; + srandom(seed); + for (i=0;i<len;i++) { + uint8_t v = random(); + if (buf[i] != v) { + printf("Buffer incorrect at %s! ofs=%d buf=0x%x correct=0x%x\n", + location, i, buf[i], v); + return false; + } + } + return true; +} + +/* + test write ops +*/ +static bool test_write(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_write io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + uint_t seed = time(NULL); + union smb_fileinfo finfo; + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_WRITE_WRITE\n"); + io.generic.level = RAW_WRITE_WRITE; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying zero write\n"); + io.write.in.file.fnum = fnum; + io.write.in.count = 0; + io.write.in.offset = 0; + io.write.in.remaining = 0; + io.write.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.write.out.nwritten, 0); + + setup_buffer(buf, seed, maxsize); + + printf("Trying small write\n"); + io.write.in.count = 9; + io.write.in.offset = 4; + io.write.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.write.out.nwritten, io.write.in.count); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 13) != 13) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf+4, seed, 9); + CHECK_VALUE(IVAL(buf,0), 0); + + setup_buffer(buf, seed, maxsize); + + printf("Trying large write\n"); + io.write.in.count = 4000; + io.write.in.offset = 0; + io.write.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.write.out.nwritten, 4000); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + + printf("Trying bad fnum\n"); + io.write.in.file.fnum = fnum+1; + io.write.in.count = 4000; + io.write.in.offset = 0; + io.write.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("Setting file as sparse\n"); + status = torture_set_sparse(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!(cli->transport->negotiate.capabilities & CAP_LARGE_FILES)) { + printf("skipping large file tests - CAP_LARGE_FILES not set\n"); + goto done; + } + + if (!(cli->transport->negotiate.capabilities & CAP_LARGE_FILES)) { + printf("skipping large file tests - CAP_LARGE_FILES not set\n"); + goto done; + } + + printf("Trying 2^32 offset\n"); + setup_buffer(buf, seed, maxsize); + io.write.in.file.fnum = fnum; + io.write.in.count = 4000; + io.write.in.offset = 0xFFFFFFFF - 2000; + io.write.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.write.out.nwritten, 4000); + CHECK_ALL_INFO(io.write.in.count + (uint64_t)io.write.in.offset, size); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, io.write.in.offset, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test writex ops +*/ +static bool test_writex(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_write io; + NTSTATUS status; + bool ret = true; + int fnum, i; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + uint_t seed = time(NULL); + union smb_fileinfo finfo; + int max_bits=63; + + if (!torture_setting_bool(tctx, "dangerous", false)) { + max_bits=33; + torture_comment(tctx, "dangerous not set - limiting range of test to 2^%d\n", max_bits); + } + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_WRITE_WRITEX\n"); + io.generic.level = RAW_WRITE_WRITEX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying zero write\n"); + io.writex.in.file.fnum = fnum; + io.writex.in.offset = 0; + io.writex.in.wmode = 0; + io.writex.in.remaining = 0; + io.writex.in.count = 0; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, 0); + + setup_buffer(buf, seed, maxsize); + + printf("Trying small write\n"); + io.writex.in.count = 9; + io.writex.in.offset = 4; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, io.writex.in.count); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 13) != 13) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf+4, seed, 9); + CHECK_VALUE(IVAL(buf,0), 0); + + setup_buffer(buf, seed, maxsize); + + printf("Trying large write\n"); + io.writex.in.count = 4000; + io.writex.in.offset = 0; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, 4000); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + + printf("Trying bad fnum\n"); + io.writex.in.file.fnum = fnum+1; + io.writex.in.count = 4000; + io.writex.in.offset = 0; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("Testing wmode\n"); + io.writex.in.file.fnum = fnum; + io.writex.in.count = 1; + io.writex.in.offset = 0; + io.writex.in.wmode = 1; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, io.writex.in.count); + + io.writex.in.wmode = 2; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, io.writex.in.count); + + + printf("Trying locked region\n"); + cli->session->pid++; + if (NT_STATUS_IS_ERR(smbcli_lock(cli->tree, fnum, 3, 1, 0, WRITE_LOCK))) { + printf("Failed to lock file at %s\n", __location__); + ret = false; + goto done; + } + cli->session->pid--; + io.writex.in.wmode = 0; + io.writex.in.count = 4; + io.writex.in.offset = 0; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + printf("Setting file as sparse\n"); + status = torture_set_sparse(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!(cli->transport->negotiate.capabilities & CAP_LARGE_FILES)) { + printf("skipping large file tests - CAP_LARGE_FILES not set\n"); + goto done; + } + + printf("Trying 2^32 offset\n"); + setup_buffer(buf, seed, maxsize); + io.writex.in.file.fnum = fnum; + io.writex.in.count = 4000; + io.writex.in.offset = 0xFFFFFFFF - 2000; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, 4000); + CHECK_ALL_INFO(io.writex.in.count + (uint64_t)io.writex.in.offset, size); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, io.writex.in.offset, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + + for (i=33;i<max_bits;i++) { + printf("Trying 2^%d offset\n", i); + setup_buffer(buf, seed+1, maxsize); + io.writex.in.file.fnum = fnum; + io.writex.in.count = 4000; + io.writex.in.offset = ((uint64_t)1) << i; + io.writex.in.data = buf; + status = smb_raw_write(cli->tree, &io); + if (i>33 && + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + break; + } + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writex.out.nwritten, 4000); + CHECK_ALL_INFO(io.writex.in.count + (uint64_t)io.writex.in.offset, size); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, io.writex.in.offset, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed+1, 4000); + } + printf("limit is 2^%d\n", i); + + setup_buffer(buf, seed, maxsize); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test write unlock ops +*/ +static bool test_writeunlock(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_write io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + uint_t seed = time(NULL); + union smb_fileinfo finfo; + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_WRITE_WRITEUNLOCK\n"); + io.generic.level = RAW_WRITE_WRITEUNLOCK; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying zero write\n"); + io.writeunlock.in.file.fnum = fnum; + io.writeunlock.in.count = 0; + io.writeunlock.in.offset = 0; + io.writeunlock.in.remaining = 0; + io.writeunlock.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeunlock.out.nwritten, io.writeunlock.in.count); + + setup_buffer(buf, seed, maxsize); + + printf("Trying small write\n"); + io.writeunlock.in.count = 9; + io.writeunlock.in.offset = 4; + io.writeunlock.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + if (smbcli_read(cli->tree, fnum, buf, 0, 13) != 13) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf+4, seed, 9); + CHECK_VALUE(IVAL(buf,0), 0); + + setup_buffer(buf, seed, maxsize); + smbcli_lock(cli->tree, fnum, io.writeunlock.in.offset, io.writeunlock.in.count, + 0, WRITE_LOCK); + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeunlock.out.nwritten, io.writeunlock.in.count); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 13) != 13) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf+4, seed, 9); + CHECK_VALUE(IVAL(buf,0), 0); + + setup_buffer(buf, seed, maxsize); + + printf("Trying large write\n"); + io.writeunlock.in.count = 4000; + io.writeunlock.in.offset = 0; + io.writeunlock.in.data = buf; + smbcli_lock(cli->tree, fnum, io.writeunlock.in.offset, io.writeunlock.in.count, + 0, WRITE_LOCK); + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeunlock.out.nwritten, 4000); + + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + + printf("Trying bad fnum\n"); + io.writeunlock.in.file.fnum = fnum+1; + io.writeunlock.in.count = 4000; + io.writeunlock.in.offset = 0; + io.writeunlock.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("Setting file as sparse\n"); + status = torture_set_sparse(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!(cli->transport->negotiate.capabilities & CAP_LARGE_FILES)) { + printf("skipping large file tests - CAP_LARGE_FILES not set\n"); + goto done; + } + + printf("Trying 2^32 offset\n"); + setup_buffer(buf, seed, maxsize); + io.writeunlock.in.file.fnum = fnum; + io.writeunlock.in.count = 4000; + io.writeunlock.in.offset = 0xFFFFFFFF - 2000; + io.writeunlock.in.data = buf; + smbcli_lock(cli->tree, fnum, io.writeunlock.in.offset, io.writeunlock.in.count, + 0, WRITE_LOCK); + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeunlock.out.nwritten, 4000); + CHECK_ALL_INFO(io.writeunlock.in.count + (uint64_t)io.writeunlock.in.offset, size); + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, io.writeunlock.in.offset, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + + +/* + test write close ops +*/ +static bool test_writeclose(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_write io; + NTSTATUS status; + bool ret = true; + int fnum; + uint8_t *buf; + const int maxsize = 90000; + const char *fname = BASEDIR "\\test.txt"; + uint_t seed = time(NULL); + union smb_fileinfo finfo; + + buf = talloc_zero_array(tctx, uint8_t, maxsize); + + if (!torture_setup_dir(cli, BASEDIR)) { + return false; + } + + printf("Testing RAW_WRITE_WRITECLOSE\n"); + io.generic.level = RAW_WRITE_WRITECLOSE; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + if (fnum == -1) { + printf("Failed to create %s - %s\n", fname, smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + printf("Trying zero write\n"); + io.writeclose.in.file.fnum = fnum; + io.writeclose.in.count = 0; + io.writeclose.in.offset = 0; + io.writeclose.in.mtime = 0; + io.writeclose.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeclose.out.nwritten, io.writeclose.in.count); + + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeclose.out.nwritten, io.writeclose.in.count); + + setup_buffer(buf, seed, maxsize); + + printf("Trying small write\n"); + io.writeclose.in.count = 9; + io.writeclose.in.offset = 4; + io.writeclose.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + fnum = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE); + io.writeclose.in.file.fnum = fnum; + + if (smbcli_read(cli->tree, fnum, buf, 0, 13) != 13) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf+4, seed, 9); + CHECK_VALUE(IVAL(buf,0), 0); + + setup_buffer(buf, seed, maxsize); + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeclose.out.nwritten, io.writeclose.in.count); + + fnum = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE); + io.writeclose.in.file.fnum = fnum; + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 13) != 13) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf+4, seed, 9); + CHECK_VALUE(IVAL(buf,0), 0); + + setup_buffer(buf, seed, maxsize); + + printf("Trying large write\n"); + io.writeclose.in.count = 4000; + io.writeclose.in.offset = 0; + io.writeclose.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeclose.out.nwritten, 4000); + + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + fnum = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE); + io.writeclose.in.file.fnum = fnum; + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, 0, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + + printf("Trying bad fnum\n"); + io.writeclose.in.file.fnum = fnum+1; + io.writeclose.in.count = 4000; + io.writeclose.in.offset = 0; + io.writeclose.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_HANDLE); + + printf("Setting file as sparse\n"); + status = torture_set_sparse(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!(cli->transport->negotiate.capabilities & CAP_LARGE_FILES)) { + printf("skipping large file tests - CAP_LARGE_FILES not set\n"); + goto done; + } + + printf("Trying 2^32 offset\n"); + setup_buffer(buf, seed, maxsize); + io.writeclose.in.file.fnum = fnum; + io.writeclose.in.count = 4000; + io.writeclose.in.offset = 0xFFFFFFFF - 2000; + io.writeclose.in.data = buf; + status = smb_raw_write(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(io.writeclose.out.nwritten, 4000); + CHECK_ALL_INFO(io.writeclose.in.count + (uint64_t)io.writeclose.in.offset, size); + + fnum = smbcli_open(cli->tree, fname, O_RDWR, DENY_NONE); + io.writeclose.in.file.fnum = fnum; + + memset(buf, 0, maxsize); + if (smbcli_read(cli->tree, fnum, buf, io.writeclose.in.offset, 4000) != 4000) { + printf("read failed at %s\n", __location__); + ret = false; + goto done; + } + CHECK_BUFFER(buf, seed, 4000); + +done: + smbcli_close(cli->tree, fnum); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + basic testing of write calls +*/ +struct torture_suite *torture_raw_write(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "WRITE"); + + torture_suite_add_1smb_test(suite, "write", test_write); + torture_suite_add_1smb_test(suite, "write unlock", test_writeunlock); + torture_suite_add_1smb_test(suite, "write close", test_writeclose); + torture_suite_add_1smb_test(suite, "writex", test_writex); + + return suite; +} |