diff options
Diffstat (limited to 'source4/torture/raw/lock.c')
-rw-r--r-- | source4/torture/raw/lock.c | 3586 |
1 files changed, 3586 insertions, 0 deletions
diff --git a/source4/torture/raw/lock.c b/source4/torture/raw/lock.c new file mode 100644 index 0000000..5c9e1be --- /dev/null +++ b/source4/torture/raw/lock.c @@ -0,0 +1,3586 @@ +/* + 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/cmdline.h" +#include "param/param.h" +#include "torture/raw/proto.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_STATUS_CONT(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + }} while (0) + +#define CHECK_STATUS_OR(status, correct1, correct2) do { \ + if ((!NT_STATUS_EQUAL(status, correct1)) && \ + (!NT_STATUS_EQUAL(status, correct2))) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s or %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct1), \ + nt_errstr(correct2)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_STATUS_OR_CONT(status, correct1, correct2) do { \ + if ((!NT_STATUS_EQUAL(status, correct1)) && \ + (!NT_STATUS_EQUAL(status, correct2))) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s or %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct1), \ + nt_errstr(correct2)); \ + ret = false; \ + }} while (0) +#define BASEDIR "\\testlock" + +#define TARGET_IS_W2K8(_tctx) (torture_setting_bool(_tctx, "w2k8", false)) +#define TARGET_IS_WIN7(_tctx) (torture_setting_bool(_tctx, "win7", false)) +#define TARGET_IS_WINDOWS(_tctx) \ + ((torture_setting_bool(_tctx, "w2k3", false)) || \ + (torture_setting_bool(_tctx, "w2k8", false)) || \ + (torture_setting_bool(_tctx, "win7", false))) +#define TARGET_IS_SAMBA3(_tctx) (torture_setting_bool(_tctx, "samba3", false)) +#define TARGET_IS_SAMBA4(_tctx) (torture_setting_bool(_tctx, "samba4", false)) + +#define TARGET_SUPPORTS_INVALID_LOCK_RANGE(_tctx) \ + (torture_setting_bool(_tctx, "invalid_lock_range_support", true)) +#define TARGET_SUPPORTS_SMBEXIT(_tctx) \ + (torture_setting_bool(_tctx, "smbexit_pdu_support", true)) +#define TARGET_SUPPORTS_SMBLOCK(_tctx) \ + (torture_setting_bool(_tctx, "smblock_pdu_support", true)) +#define TARGET_SUPPORTS_OPENX_DENY_DOS(_tctx) \ + (torture_setting_bool(_tctx, "openx_deny_dos_support", true)) +#define TARGET_RETURNS_RANGE_NOT_LOCKED(_tctx) \ + (torture_setting_bool(_tctx, "range_not_locked_on_file_close", true)) +/* + 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 (!TARGET_SUPPORTS_SMBLOCK(tctx)) + torture_skip(tctx, "Target does not support the SMBlock PDU"); + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing RAW_LOCK_LOCK\n"); + io.generic.level = RAW_LOCK_LOCK; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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_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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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"; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing RAW_LOCK_LOCKX\n"); + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + 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); + + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + + torture_comment(tctx, "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); + if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(tctx)) + CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); + else + 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; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "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); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + if (smbcli_write(cli->tree, fnum, 0, &c, 0, 1) != 1) { + torture_result(tctx, TORTURE_FAIL, + "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) { + torture_result(tctx, TORTURE_FAIL, + "Failed to read 1 byte - %s\n", + smbcli_errstr(cli->tree)); + ret = false; + goto done; + } + + cli->session->pid = 2; + + if (smbcli_read(cli->tree, fnum, &c, 0, 1) == 1) { + torture_result(tctx, TORTURE_FAIL, + "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) { + torture_result(tctx, TORTURE_FAIL, + "High pid is used on this server!\n"); + ret = false; + } else { + torture_warning(tctx, "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, *req2; + struct smbcli_session_options options; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + lpcfg_smbcli_session_options(tctx->lp_ctx, &options); + + torture_comment(tctx, "Testing LOCKING_ANDX_CANCEL_LOCK\n"); + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + 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; + lock[1].pid = cli->session->pid; + lock[1].offset = 110; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + t = time_mono(NULL); + + torture_comment(tctx, "Testing cancel by CANCEL_LOCK\n"); + + /* setup a timed lock */ + io.lockx.in.timeout = 10000; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + /* 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); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "lock cancel was not immediate (%s)\n", __location__)); + + /* MS-CIFS (2.2.4.32.1) states that a cancel is honored if and only + * if the lock vector contains one entry. When given multiple cancel + * requests in a single PDU we expect the server to return an + * error. Samba4 handles this correctly. Windows servers seem to + * accept the request but only cancel the first lock. Samba3 + * now does what Windows does (JRA). + */ + torture_comment(tctx, "Testing multiple cancel\n"); + + /* acquire second lock */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[1]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* setup 2 timed locks */ + t = time_mono(NULL); + io.lockx.in.timeout = 10000; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock[0]; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + io.lockx.in.locks = &lock[1]; + req2 = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req2 != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + /* try to cancel both locks in the same packet */ + io.lockx.in.timeout = 0; + io.lockx.in.lock_cnt = 2; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK | LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = lock; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_warning(tctx, "Target server accepted a lock cancel " + "request with multiple locks. This violates " + "MS-CIFS 2.2.4.32.1.\n"); + + /* receive the failed lock requests */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "first lock was not cancelled immediately (%s)\n", + __location__)); + + /* send cancel to second lock */ + io.lockx.in.timeout = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK | + LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[1]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req2); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "second lock was not cancelled immediately (%s)\n", + __location__)); + + /* cleanup the second lock */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[1]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* If a lock request contained multiple ranges and we are cancelling + * one while it's still pending, what happens? */ + torture_comment(tctx, "Testing cancel 1/2 lock request\n"); + + /* Send request with two ranges */ + io.lockx.in.timeout = -1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 2; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = lock; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup pending lock (%s)\n", __location__)); + + /* Try to cancel the first lock range */ + io.lockx.in.timeout = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK | LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Locking request should've failed and second range should be + * unlocked */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[1]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Cleanup both locks */ + io.lockx.in.ulock_cnt = 2; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = lock; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing cancel 2/2 lock request\n"); + + /* Lock second range so it contends */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[1]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Send request with two ranges */ + io.lockx.in.timeout = -1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 2; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = lock; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup pending lock (%s)\n", __location__)); + + /* Try to cancel the second lock range */ + io.lockx.in.timeout = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_CANCEL_LOCK | LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[1]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Locking request should've failed and first range should be + * unlocked */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Cleanup both locks */ + io.lockx.in.ulock_cnt = 2; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io.lockx.in.locks = lock; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "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; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.timeout = 5000; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + 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_mono(NULL); + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "lock cancel by unlock was not immediate (%s) - took %d secs\n", + __location__, (int)(time_mono(NULL)-t))); + + torture_comment(tctx, "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_LOCK_NOT_GRANTED); + + { + /* + * Make the test block on the second lock + * request. This is to regression-test 64c0367. + */ + uint64_t tmp = lock[1].offset; + lock[1].offset = lock[0].offset; + lock[0].offset = tmp; + } + + t = time_mono(NULL); + io.lockx.in.timeout = 10000; + io.lockx.in.lock_cnt = 2; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + status = smbcli_close(cli->tree, fnum); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + if (TARGET_RETURNS_RANGE_NOT_LOCKED(tctx)) + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + else + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "lock cancel by close was not immediate (%s)\n", __location__)); + + { + /* + * Undo the change for 64c0367 + */ + uint64_t tmp = lock[1].offset; + lock[1].offset = lock[0].offset; + lock[0].offset = tmp; + } + + torture_comment(tctx, "create a new sessions\n"); + session = smbcli_session_init(cli->transport, tctx, false, options); + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; + setup.in.workgroup = lpcfg_workgroup(tctx->lp_ctx); + setup.in.credentials = samba_cmdline_get_creds(); + setup.in.gensec_settings = lpcfg_gensec_settings(tctx, tctx->lp_ctx); + status = smb_composite_sesssetup(session, &setup); + CHECK_STATUS(status, NT_STATUS_OK); + session->vuid = setup.out.vuid; + + torture_comment(tctx, "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 = TCONX_FLAG_EXTENDED_RESPONSE; + 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; + + torture_comment(tctx, "Testing cancel by exit\n"); + if (TARGET_SUPPORTS_SMBEXIT(tctx)) { + fname = BASEDIR "\\test_exit.txt"; + fnum = smbcli_open(tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to reopen %s - %s\n", + fname, smbcli_errstr(tree))); + + 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_mono(NULL); + req = smb_raw_lock_send(tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", + __location__)); + + status = smb_raw_exit(session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + if (TARGET_RETURNS_RANGE_NOT_LOCKED(tctx)) + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + else + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "lock cancel by exit was not immediate (%s)\n", + __location__)); + } + else { + torture_comment(tctx, + " skipping test, SMBExit not supported\n"); + } + + torture_comment(tctx, "Testing cancel by ulogoff\n"); + fname = BASEDIR "\\test_ulogoff.txt"; + fnum = smbcli_open(tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to reopen %s - %s\n", + fname, smbcli_errstr(tree))); + + 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_mono(NULL); + req = smb_raw_lock_send(tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + status = smb_raw_ulogoff(session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + if (TARGET_RETURNS_RANGE_NOT_LOCKED(tctx)) { + if (NT_STATUS_EQUAL(NT_STATUS_FILE_LOCK_CONFLICT, status)) { + torture_result(tctx, TORTURE_FAIL, + "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); + } else { + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + } + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "lock cancel by ulogoff was not immediate (%s)\n", __location__)); + + torture_comment(tctx, "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); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to reopen %s - %s\n", + fname, smbcli_errstr(tree))); + + 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_mono(NULL); + req = smb_raw_lock_send(tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + status = smb_tree_disconnect(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smbcli_request_simple_recv(req); + if (TARGET_RETURNS_RANGE_NOT_LOCKED(tctx)) + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + else + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "lock cancel by tdis was not immediate (%s)\n", __location__)); + +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; + int delay; + uint16_t deny_mode = 0; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing LOCK_NOT_GRANTED vs. FILE_LOCK_CONFLICT\n"); + + torture_comment(tctx, "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, not + * per file handle + */ + if (TARGET_SUPPORTS_OPENX_DENY_DOS(tctx)) + deny_mode = OPENX_MODE_DENY_DOS; + else + deny_mode = OPENX_MODE_DENY_NONE; + + 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 | deny_mode; + 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 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); + t = 1; + torture_comment(tctx, "Testing with timeout > 0 (=%d)\n", + t); + fname = BASEDIR "\\test1.txt"; + goto next_run; + } + + t = 4000; + torture_comment(tctx, "Testing special cases with timeout > 0 (=%d)\n", + t); + + /* + * 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); + torture_comment(tctx, "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); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to reopen %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + 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_mono(NULL); + io.lockx.in.timeout = t; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + 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); + + delay = t / 1000; + if (TARGET_IS_W2K8(tctx) || TARGET_IS_WIN7(tctx)) { + delay /= 2; + } + + torture_assert(tctx,!(time_mono(NULL) < start+delay), talloc_asprintf(tctx, + "lock comes back to early timeout[%d] delay[%d]" + "(%s)\n", t, delay, __location__)); + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + smbcli_close(cli->tree, fnum); + fname = BASEDIR "\\test3.txt"; + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to reopen %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + 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_mono(NULL); + io.lockx.in.timeout = t; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + 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); + + delay = t / 1000; + if (TARGET_IS_W2K8(tctx) || TARGET_IS_WIN7(tctx)) { + delay /= 2; + } + + torture_assert(tctx,!(time_mono(NULL) < start+delay), talloc_asprintf(tctx, + "lock comes back to early timeout[%d] delay[%d]" + "(%s)\n", t, delay, __location__)); + + lock[0].offset = 100; + lock[0].count = 10; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + smbcli_close(cli->tree, fnum); + fname = BASEDIR "\\test4.txt"; + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to reopen %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + 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_mono(NULL); + io.lockx.in.timeout = t; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed lock (%s)\n", __location__)); + + 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); + + delay = t / 1000; + if (TARGET_IS_W2K8(tctx) || TARGET_IS_WIN7(tctx)) { + delay /= 2; + } + + torture_assert(tctx,!(time_mono(NULL) < start+delay), talloc_asprintf(tctx, + "lock comes back to early timeout[%d] delay[%d]" + "(%s)\n", t, delay, __location__)); + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + +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"; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing LOCKING_ANDX_CHANGE_LOCKTYPE\n"); + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + 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) { + torture_result(tctx, TORTURE_FAIL, + "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) { + torture_result(tctx, TORTURE_FAIL, + "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; +} + +struct double_lock_test { + struct smb_lock_entry lock1; + struct smb_lock_entry lock2; + NTSTATUS exp_status; +}; + +/** + * Tests zero byte locks. + */ +static struct double_lock_test zero_byte_tests[] = { + /* {pid, offset, count}, {pid, offset, count}, status */ + + /** First, takes a zero byte lock at offset 10. Then: + * - Taking 0 byte lock at 10 should succeed. + * - Taking 1 byte locks at 9,10,11 should succeed. + * - Taking 2 byte lock at 9 should fail. + * - Taking 2 byte lock at 10 should succeed. + * - Taking 3 byte lock at 9 should fail. + */ + {{1000, 10, 0}, {1001, 10, 0}, NT_STATUS_OK}, + {{1000, 10, 0}, {1001, 9, 1}, NT_STATUS_OK}, + {{1000, 10, 0}, {1001, 10, 1}, NT_STATUS_OK}, + {{1000, 10, 0}, {1001, 11, 1}, NT_STATUS_OK}, + {{1000, 10, 0}, {1001, 9, 2}, NT_STATUS_LOCK_NOT_GRANTED}, + {{1000, 10, 0}, {1001, 10, 2}, NT_STATUS_OK}, + {{1000, 10, 0}, {1001, 9, 3}, NT_STATUS_LOCK_NOT_GRANTED}, + + /** Same, but opposite order. */ + {{1001, 10, 0}, {1000, 10, 0}, NT_STATUS_OK}, + {{1001, 9, 1}, {1000, 10, 0}, NT_STATUS_OK}, + {{1001, 10, 1}, {1000, 10, 0}, NT_STATUS_OK}, + {{1001, 11, 1}, {1000, 10, 0}, NT_STATUS_OK}, + {{1001, 9, 2}, {1000, 10, 0}, NT_STATUS_LOCK_NOT_GRANTED}, + {{1001, 10, 2}, {1000, 10, 0}, NT_STATUS_OK}, + {{1001, 9, 3}, {1000, 10, 0}, NT_STATUS_LOCK_NOT_GRANTED}, + + /** Zero zero case. */ + {{1000, 0, 0}, {1001, 0, 0}, NT_STATUS_OK}, +}; + +static bool test_zerobytelocks(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_lock io; + NTSTATUS status; + bool ret = true; + int fnum, i; + const char *fname = BASEDIR "\\zero.txt"; + + torture_comment(tctx, "Testing zero length byte range locks:\n"); + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + io.generic.level = RAW_LOCK_LOCKX; + + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* Setup initial parameters */ + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.file.fnum = fnum; + io.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; /* Exclusive */ + io.lockx.in.timeout = 0; + + /* Try every combination of locks in zero_byte_tests. The first lock is + * assumed to succeed. The second lock may contend, depending on the + * expected status. */ + for (i = 0; + i < ARRAY_SIZE(zero_byte_tests); + i++) { + torture_comment(tctx, " ... {%d, %llu, %llu} + {%d, %llu, %llu} = %s\n", + zero_byte_tests[i].lock1.pid, + (unsigned long long) zero_byte_tests[i].lock1.offset, + (unsigned long long) zero_byte_tests[i].lock1.count, + zero_byte_tests[i].lock2.pid, + (unsigned long long) zero_byte_tests[i].lock2.offset, + (unsigned long long) zero_byte_tests[i].lock2.count, + nt_errstr(zero_byte_tests[i].exp_status)); + + /* Lock both locks. */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + + io.lockx.in.locks = discard_const_p(struct smb_lock_entry, + &zero_byte_tests[i].lock1); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.locks = discard_const_p(struct smb_lock_entry, + &zero_byte_tests[i].lock2); + status = smb_raw_lock(cli->tree, &io); + + if (NT_STATUS_EQUAL(zero_byte_tests[i].exp_status, + NT_STATUS_LOCK_NOT_GRANTED)) { + /* Allow either of the failure messages and keep going + * if we see the wrong status. */ + CHECK_STATUS_OR_CONT(status, + NT_STATUS_LOCK_NOT_GRANTED, + NT_STATUS_FILE_LOCK_CONFLICT); + + } else { + CHECK_STATUS_CONT(status, + zero_byte_tests[i].exp_status); + } + + /* Unlock both locks. */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + + if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + } + + io.lockx.in.locks = discard_const_p(struct smb_lock_entry, + &zero_byte_tests[i].lock1); + 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; +} + +static bool test_unlock(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_lock io; + NTSTATUS status; + bool ret = true; + int fnum1, fnum2; + const char *fname = BASEDIR "\\unlock.txt"; + struct smb_lock_entry lock1; + struct smb_lock_entry lock2; + + torture_comment(tctx, "Testing LOCKX unlock:\n"); + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum1 != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + fnum2 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum2 != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* Setup initial parameters */ + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.timeout = 0; + + lock1.pid = cli->session->pid; + lock1.offset = 0; + lock1.count = 10; + lock2.pid = cli->session->pid - 1; + lock2.offset = 0; + lock2.count = 10; + + /** + * Take exclusive lock, then unlock it with a shared-unlock call. + */ + torture_comment(tctx, " taking exclusive lock.\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = 0; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, " unlock the exclusive with a shared unlock call.\n"); + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, " try shared lock on pid2/fnum2, testing the unlock.\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + io.lockx.in.file.fnum = fnum2; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /** + * Unlock a shared lock with an exclusive-unlock call. + */ + torture_comment(tctx, " unlock new shared lock with exclusive unlock call.\n"); + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = 0; + io.lockx.in.file.fnum = fnum2; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, " try exclusive lock on pid1, testing the unlock.\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = 0; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + 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); + + /** + * Test unlocking of 0-byte locks. + */ + + torture_comment(tctx, " lock shared and exclusive 0-byte locks, testing that Windows " + "always unlocks the exclusive first.\n"); + lock1.pid = cli->session->pid; + lock1.offset = 10; + lock1.count = 0; + lock2.pid = cli->session->pid; + lock2.offset = 5; + lock2.count = 10; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + + /* lock 0-byte shared + * Note: Order of the shared/exclusive locks doesn't matter. */ + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* lock 0-byte exclusive */ + io.lockx.in.mode = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* test contention */ + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + io.lockx.in.locks = &lock2; + io.lockx.in.file.fnum = fnum2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS_OR(status, NT_STATUS_LOCK_NOT_GRANTED, + NT_STATUS_FILE_LOCK_CONFLICT); + + /* unlock */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* test - can we take a shared lock? */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + io.lockx.in.file.fnum = fnum2; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + + /* XXX Samba 3 will fail this test. This is temporary(because this isn't + * new to Win7, it succeeds in WinXP too), until I can come to a + * resolution as to whether Samba should support this or not. There is + * code to preference unlocking exclusive locks before shared locks, + * but its wrapped with "#ifdef ZERO_ZERO". -zkirsch */ + if (TARGET_IS_SAMBA3(tctx)) { + CHECK_STATUS_OR(status, NT_STATUS_LOCK_NOT_GRANTED, + NT_STATUS_FILE_LOCK_CONFLICT); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + } + + /* cleanup */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + status = smb_raw_lock(cli->tree, &io); + + /* XXX Same as above. */ + if (TARGET_IS_SAMBA3(tctx)) { + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + } + + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum1); + smbcli_close(cli->tree, fnum2); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +static bool test_multiple_unlock(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_lock io; + NTSTATUS status; + bool ret = true; + int fnum1; + const char *fname = BASEDIR "\\unlock_multiple.txt"; + struct smb_lock_entry lock1; + struct smb_lock_entry lock2; + struct smb_lock_entry locks[2]; + + torture_comment(tctx, "Testing LOCKX multiple unlock:\n"); + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum1 != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* Setup initial parameters */ + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.timeout = 0; + + lock1.pid = cli->session->pid; + lock1.offset = 0; + lock1.count = 10; + lock2.pid = cli->session->pid; + lock2.offset = 10; + lock2.count = 10; + + locks[0] = lock1; + locks[1] = lock2; + + io.lockx.in.file.fnum = fnum1; + io.lockx.in.mode = 0; /* exclusive */ + + /** Test1: Take second lock, but not first. */ + torture_comment(tctx, " unlock 2 locks, first one not locked. Expect no locks " + "unlocked. \n"); + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try to unlock both locks. */ + io.lockx.in.ulock_cnt = 2; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = locks; + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* Second lock should not be unlocked. */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /** Test2: Take first lock, but not second. */ + torture_comment(tctx, " unlock 2 locks, second one not locked. Expect first lock " + "unlocked.\n"); + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try to unlock both locks. */ + io.lockx.in.ulock_cnt = 2; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = locks; + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* First lock should be unlocked. */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test3: Request 2 locks, second will contend. What happens to the + * first? */ + torture_comment(tctx, " request 2 locks, second one will contend. " + "Expect both to fail.\n"); + + /* Lock the second range */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock2; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Request both locks */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 2; + io.lockx.in.locks = locks; + + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* First lock should be unlocked. */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + io.lockx.in.ulock_cnt = 2; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = locks; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test4: Request unlock and lock. The lock contends, is the unlock + * then re-locked? */ + torture_comment(tctx, " request unlock and lock, second one will " + "contend. Expect the unlock to succeed.\n"); + + /* Lock both ranges */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 2; + io.lockx.in.locks = locks; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Attempt to unlock the first range and lock the second */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = locks; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* The first lock should've been unlocked */ + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + io.lockx.in.ulock_cnt = 2; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = locks; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum1); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/** + * torture_locktest5 covers stacking pretty well, but its missing two tests: + * - stacking an exclusive on top of shared fails + * - stacking two exclusives fail + */ +static bool test_stacking(struct torture_context *tctx, struct smbcli_state *cli) +{ + union smb_lock io; + NTSTATUS status; + bool ret = true; + int fnum1; + const char *fname = BASEDIR "\\stacking.txt"; + struct smb_lock_entry lock1; + + torture_comment(tctx, "Testing stacking:\n"); + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + io.generic.level = RAW_LOCK_LOCKX; + + fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum1 != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* Setup initial parameters */ + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.timeout = 0; + + lock1.pid = cli->session->pid; + lock1.offset = 0; + lock1.count = 10; + + /** + * Try to take a shared lock, then stack an exclusive. + */ + torture_comment(tctx, " stacking an exclusive on top of a shared lock fails.\n"); + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS_OR(status, NT_STATUS_LOCK_NOT_GRANTED, + NT_STATUS_FILE_LOCK_CONFLICT); + + /* cleanup */ + 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); + + /** + * Prove that two exclusive locks do not stack. + */ + torture_comment(tctx, " two exclusive locks do not stack.\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS_OR(status, NT_STATUS_LOCK_NOT_GRANTED, + NT_STATUS_FILE_LOCK_CONFLICT); + + /* cleanup */ + 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); + +done: + smbcli_close(cli->tree, fnum1); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/** + * Test how 0-byte read requests contend with byte range locks + */ +static bool test_zerobyteread(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + union smb_read rd; + NTSTATUS status; + bool ret = true; + int fnum1, fnum2; + const char *fname = BASEDIR "\\zerobyteread.txt"; + struct smb_lock_entry lock1; + uint8_t c = 1; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + io.generic.level = RAW_LOCK_LOCKX; + + fnum1 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum1 != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + fnum2 = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum2 != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* Setup initial parameters */ + io.lockx.level = RAW_LOCK_LOCKX; + io.lockx.in.timeout = 0; + + lock1.pid = cli->session->pid; + lock1.offset = 0; + lock1.count = 10; + + ZERO_STRUCT(rd); + rd.readx.level = RAW_READ_READX; + + torture_comment(tctx, "Testing zero byte read on lock range:\n"); + + /* Take an exclusive lock */ + torture_comment(tctx, " taking exclusive lock.\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try a zero byte read */ + torture_comment(tctx, " reading 0 bytes.\n"); + rd.readx.in.file.fnum = fnum2; + rd.readx.in.offset = 5; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = false; + rd.readx.out.data = &c; + status = smb_raw_read(cli->tree, &rd); + torture_assert_int_equal_goto(tctx, rd.readx.out.nread, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Unlock lock */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing zero byte read on zero byte lock " + "range:\n"); + + /* Take an exclusive lock */ + torture_comment(tctx, " taking exclusive 0-byte lock.\n"); + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + lock1.offset = 5; + lock1.count = 0; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try a zero byte read before the lock */ + torture_comment(tctx, " reading 0 bytes before the lock.\n"); + rd.readx.in.file.fnum = fnum2; + rd.readx.in.offset = 4; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = false; + rd.readx.out.data = &c; + status = smb_raw_read(cli->tree, &rd); + torture_assert_int_equal_goto(tctx, rd.readx.out.nread, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try a zero byte read on the lock */ + torture_comment(tctx, " reading 0 bytes on the lock.\n"); + rd.readx.in.file.fnum = fnum2; + rd.readx.in.offset = 5; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = false; + rd.readx.out.data = &c; + status = smb_raw_read(cli->tree, &rd); + torture_assert_int_equal_goto(tctx, rd.readx.out.nread, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try a zero byte read after the lock */ + torture_comment(tctx, " reading 0 bytes after the lock.\n"); + rd.readx.in.file.fnum = fnum2; + rd.readx.in.offset = 6; + rd.readx.in.mincnt = 0; + rd.readx.in.maxcnt = 0; + rd.readx.in.remaining = 0; + rd.readx.in.read_for_execute = false; + rd.readx.out.data = &c; + status = smb_raw_read(cli->tree, &rd); + torture_assert_int_equal_goto(tctx, rd.readx.out.nread, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Unlock lock */ + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + io.lockx.in.file.fnum = fnum1; + io.lockx.in.locks = &lock1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smbcli_close(cli->tree, fnum1); + smbcli_close(cli->tree, fnum2); + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} +/* + test multi Locking&X operation +*/ +static bool test_multilock(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; + const char *fname = BASEDIR "\\multilock_test.txt"; + time_t t; + struct smbcli_request *req; + struct smbcli_session_options options; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + lpcfg_smbcli_session_options(tctx->lp_ctx, &options); + + torture_comment(tctx, "Testing LOCKING_ANDX multi-lock\n"); + io.generic.level = RAW_LOCK_LOCKX; + + /* Create the test file. */ + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* + * Lock regions 100->109, 120->129 as + * two separate write locks in one request. + */ + 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 = 2; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + lock[1].pid = cli->session->pid; + lock[1].offset = 120; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Now request the same locks on a different + * context as blocking locks with infinite timeout. + */ + + io.lockx.in.timeout = 20000; + lock[0].pid = cli->session->pid+1; + lock[1].pid = cli->session->pid+1; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* Unlock lock[0] */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[0]; + lock[0].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Start the clock. */ + t = time_mono(NULL); + + /* Unlock lock[1] */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[1]; + lock[1].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* receive the successful blocked lock requests */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Fail if this took more than 2 seconds. */ + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "Blocking locks were not granted immediately (%s)\n", + __location__)); +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test multi2 Locking&X operation + This test is designed to show that + lock precedence on the server is based + on the order received, not on the ability + to grant. For example: + + A blocked lock request containing 2 locks + will be satified before a subsequent blocked + lock request over one of the same regions, + even if that region is then unlocked. E.g. + + (a) lock 100->109, 120->129 (granted) + (b) lock 100->109, 120-129 (blocks) + (c) lock 100->109 (blocks) + (d) unlock 100->109 + + lock (c) will not be granted as lock (b) + will take precedence. +*/ +static bool test_multilock2(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; + const char *fname = BASEDIR "\\multilock2_test.txt"; + time_t t; + struct smbcli_request *req; + struct smbcli_request *req2; + struct smbcli_session_options options; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), "Failed to setup up test directory: " BASEDIR); + + lpcfg_smbcli_session_options(tctx->lp_ctx, &options); + + torture_comment(tctx, "Testing LOCKING_ANDX multi-lock 2\n"); + io.generic.level = RAW_LOCK_LOCKX; + + /* Create the test file. */ + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* + * Lock regions 100->109, 120->129 as + * two separate write locks in one request. + */ + 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 = 2; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + lock[1].pid = cli->session->pid; + lock[1].offset = 120; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Now request the same locks on a different + * context as blocking locks. + */ + + io.lockx.in.timeout = 20000; + lock[0].pid = cli->session->pid+1; + lock[1].pid = cli->session->pid+1; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * Request the first lock again on a separate context. + * Wait 2 seconds. This should time out (the previous + * multi-lock request should take precedence). + */ + + io.lockx.in.timeout = 2000; + lock[0].pid = cli->session->pid+2; + io.lockx.in.lock_cnt = 1; + req2 = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req2 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* Unlock lock[0] */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[0]; + lock[0].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Did the second lock complete (should time out) ? */ + status = smbcli_request_simple_recv(req2); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + + /* Start the clock. */ + t = time_mono(NULL); + + /* Unlock lock[1] */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[1]; + lock[1].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* receive the successful blocked lock requests */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Fail if this took more than 2 seconds. */ + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "Blocking locks were not granted immediately (%s)\n", + __location__)); +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test multi3 Locking&X operation + This test is designed to show that + lock precedence on the server is based + on the order received, not on the ability + to grant. + + Compared to test_multilock2() (above) + this test demonstrates that completely + unrelated ranges work independently. + + For example: + + A blocked lock request containing 2 locks + will be satified before a subsequent blocked + lock request over one of the same regions, + even if that region is then unlocked. But + a lock of a different region goes through. E.g. + + All locks are LOCKING_ANDX_EXCLUSIVE_LOCK (rw). + + (a) lock 100->109, 120->129 (granted) + (b) lock 100->109, 120->129 (blocks, timeout=20s) + (c) lock 100->109 (blocks, timeout=2s) + (d) lock 110->119 (granted) + (e) lock 110->119 (blocks, timeout=20s) + (f) unlock 100->109 (a) + (g) lock 100->109 (not granted, blocked by (b)) + (h) lock 100->109 (not granted, blocked by itself (b)) + (i) lock (c) will not be granted(conflict, times out) + as lock (b) will take precedence. + (j) unlock 110-119 (d) + (k) lock (e) completes and is not blocked by (a) nor (b) + (l) lock 100->109 (not granted(conflict), blocked by (b)) + (m) lock 100->109 (not granted(conflict), blocked by itself (b)) + (n) unlock 120-129 (a) + (o) lock (b) completes +*/ +static bool test_multilock3(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[2]; + union smb_lock io3; + struct smb_lock_entry lock3[1]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\multilock3_test.txt"; + time_t t; + struct smbcli_request *req = NULL; + struct smbcli_request *req2 = NULL; + struct smbcli_request *req4 = NULL; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), + "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing LOCKING_ANDX multi-lock 3\n"); + io.generic.level = RAW_LOCK_LOCKX; + + /* Create the test file. */ + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* + * a) + * Lock regions 100->109, 120->129 as + * two separate write locks in one request. + */ + 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 = 2; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + lock[1].pid = cli->session->pid; + lock[1].offset = 120; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * b) + * Now request the same locks on a different + * context as blocking locks. + */ + io.lockx.in.timeout = 20000; + lock[0].pid = cli->session->pid+1; + lock[1].pid = cli->session->pid+1; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * c) + * Request the first lock again on a separate context. + * Wait 2 seconds. This should time out (the previous + * multi-lock request should take precedence). + */ + io.lockx.in.timeout = 2000; + lock[0].pid = cli->session->pid+2; + io.lockx.in.lock_cnt = 1; + req2 = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req2 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * d) + * Lock regions 110->119 + */ + io3.lockx.level = RAW_LOCK_LOCKX; + io3.lockx.in.file.fnum = fnum; + io3.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 0; + io3.lockx.in.lock_cnt = 1; + io3.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock3[0].pid = cli->session->pid+3; + lock3[0].offset = 110; + lock3[0].count = 10; + io3.lockx.in.locks = &lock3[0]; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * e) + * try 110-119 again + */ + io3.lockx.in.timeout = 20000; + lock3[0].pid = cli->session->pid+4; + req4 = smb_raw_lock_send(cli->tree, &io3); + torture_assert(tctx,(req4 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * f) + * Unlock (a) lock[0] 100-109 + */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[0]; + lock[0].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * g) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+5; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* + * h) + * try to lock lock[0] 100-109 again with + * the pid that's still waiting + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req2->state <= SMBCLI_REQUEST_RECV, + "req2 should still wait"); + + /* + * i) + * Did the second lock complete (should time out) ? + */ + status = smbcli_request_simple_recv(req2); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + torture_assert(tctx, req4->state <= SMBCLI_REQUEST_RECV, + "req4 should still wait"); + + /* + * j) + * Unlock (d) lock[0] 110-119 + */ + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 1; + io3.lockx.in.lock_cnt = 0; + lock3[0].pid = cli->session->pid+3; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * k) + * receive the successful blocked lock request (e) + * on 110-119 while the 100-109/120-129 is still waiting. + */ + status = smbcli_request_simple_recv(req4); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * l) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+6; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + + /* + * m) + * try to lock lock[0] 100-109 again with + * the pid that's still waiting + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + + /* Start the clock. */ + t = time_mono(NULL); + + /* + * n) + * Unlock lock[1] 120-129 */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[1]; + lock[1].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * o) + * receive the successful blocked lock request (b) + */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Fail if this took more than 2 seconds. */ + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "Blocking locks were not granted immediately (%s)\n", + __location__)); +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test multi4 Locking&X operation + This test is designed to show that + lock precedence on the server is based + on the order received, not on the ability + to grant. + + Compared to test_multilock3() (above) + this test demonstrates that pending read-only/shared + locks doesn't block shared locks others. + + The outstanding requests build an implicit + database that's checked before checking + the already granted locks in the real database. + + For example: + + A blocked read-lock request containing 2 locks + will be still be blocked, while one region + is still write-locked. While it doesn't block + other read-lock requests for the other region. E.g. + + (a) lock(rw) 100->109, 120->129 (granted) + (b) lock(ro) 100->109, 120->129 (blocks, timeout=20s) + (c) lock(ro) 100->109 (blocks, timeout=MAX) + (d) lock(rw) 110->119 (granted) + (e) lock(rw) 110->119 (blocks, timeout=20s) + (f) unlock 100->109 (a) + (g) lock(ro) (c) completes and is not blocked by (a) nor (b) + (h) lock(rw) 100->109 (not granted, blocked by (c)) + (i) lock(rw) 100->109 (pid (b)) (not granted(conflict), blocked by (c)) + (j) unlock 110-119 + (k) lock (e) completes and is not blocked by (a) nor (b) + (l) lock 100->109 (not granted(conflict), blocked by (b)) + (m) lock 100->109 (pid (b)) (not granted(conflict), blocked by itself (b)) + (n) unlock 120-129 (a) + (o) lock (b) completes +*/ +static bool test_multilock4(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[2]; + union smb_lock io3; + struct smb_lock_entry lock3[1]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\multilock4_test.txt"; + time_t t; + struct smbcli_request *req = NULL; + struct smbcli_request *req2 = NULL; + struct smbcli_request *req4 = NULL; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), + "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing LOCKING_ANDX multi-lock 4\n"); + io.generic.level = RAW_LOCK_LOCKX; + + /* Create the test file. */ + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* + * a) + * Lock regions 100->109, 120->129 as + * two separate write locks in one request. + */ + 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 = 2; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + lock[1].pid = cli->session->pid; + lock[1].offset = 120; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * b) + * Now request the same locks on a different + * context as blocking locks. But readonly. + */ + io.lockx.in.timeout = 20000; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + lock[0].pid = cli->session->pid+1; + lock[1].pid = cli->session->pid+1; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * c) + * Request the first lock again on a separate context. + * Wait forever. The previous multi-lock request (b) + * should take precedence. Also readonly. + */ + io.lockx.in.timeout = UINT32_MAX; + lock[0].pid = cli->session->pid+2; + io.lockx.in.lock_cnt = 1; + req2 = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req2 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * d) + * Lock regions 110->119 + */ + io3.lockx.level = RAW_LOCK_LOCKX; + io3.lockx.in.file.fnum = fnum; + io3.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 0; + io3.lockx.in.lock_cnt = 1; + io3.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock3[0].pid = cli->session->pid+3; + lock3[0].offset = 110; + lock3[0].count = 10; + io3.lockx.in.locks = &lock3[0]; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * e) + * try 110-119 again + */ + io3.lockx.in.timeout = 20000; + lock3[0].pid = cli->session->pid+4; + req4 = smb_raw_lock_send(cli->tree, &io3); + torture_assert(tctx,(req4 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * f) + * Unlock (a) lock[0] 100-109 + */ + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[0]; + lock[0].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * g) + * receive the successful blocked lock request (c) + * on 110-119 while (b) 100-109/120-129 is still waiting. + */ + status = smbcli_request_simple_recv(req2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * h) + * try to lock lock[0] 100-109 again + * (read/write) + */ + lock[0].pid = cli->session->pid+5; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* + * i) + * try to lock lock[0] 100-109 again with the pid (b) + * that's still waiting. + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + torture_assert(tctx, req4->state <= SMBCLI_REQUEST_RECV, + "req4 should still wait"); + + /* + * j) + * Unlock (d) lock[0] 110-119 + */ + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 1; + io3.lockx.in.lock_cnt = 0; + lock3[0].pid = cli->session->pid+3; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * k) + * receive the successful blocked + * lock request (e) on 110-119. + */ + status = smbcli_request_simple_recv(req4); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * l) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+6; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* + * m) + * try to lock lock[0] 100-109 again with the pid (b) + * that's still waiting + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + + /* Start the clock. */ + t = time_mono(NULL); + + /* + * n) + * Unlock (a) lock[1] 120-129 + */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[1]; + lock[1].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * o) + * receive the successful blocked lock request (b) + */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Fail if this took more than 2 seconds. */ + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "Blocking locks were not granted immediately (%s)\n", + __location__)); +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test multi5 Locking&X operation + This test is designed to show that + lock precedence on the server is based + on the order received, not on the ability + to grant. + + Compared to test_multilock3() (above) + this test demonstrates that the initial + lock request that block the following + exclusive locks can be a shared lock. + + For example: + + All locks except (a) are LOCKING_ANDX_EXCLUSIVE_LOCK (rw). + + (a) lock(ro) 100->109, 120->129 (granted) + (b) lock 100->109, 120->129 (blocks, timeout=20s) + (c) lock 100->109 (blocks, timeout=2s) + (d) lock 110->119 (granted) + (e) lock 110->119 (blocks, timeout=20s) + (f) unlock 100->109 (a) + (g) lock 100->109 (not granted, blocked by (b)) + (h) lock 100->109 (not granted, blocked by itself (b)) + (i) lock (c) will not be granted(conflict, times out) + as lock (b) will take precedence. + (j) unlock 110-119 (d) + (k) lock (e) completes and is not blocked by (a) nor (b) + (l) lock 100->109 (not granted(conflict), blocked by (b)) + (m) lock 100->109 (not granted(conflict), blocked by itself (b)) + (n) unlock 120-129 (a) + (o) lock (b) completes +*/ +static bool test_multilock5(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[2]; + union smb_lock io3; + struct smb_lock_entry lock3[1]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\multilock5_test.txt"; + time_t t; + struct smbcli_request *req = NULL; + struct smbcli_request *req2 = NULL; + struct smbcli_request *req4 = NULL; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), + "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing LOCKING_ANDX multi-lock 5\n"); + io.generic.level = RAW_LOCK_LOCKX; + + /* Create the test file. */ + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* + * a) + * Lock regions 100->109, 120->129 as + * two separate write locks in one request. + * (read only) + */ + 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 = 2; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + lock[1].pid = cli->session->pid; + lock[1].offset = 120; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * b) + * Now request the same locks on a different + * context as blocking locks. + * (read write) + */ + io.lockx.in.timeout = 20000; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid+1; + lock[1].pid = cli->session->pid+1; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * c) + * Request the first lock again on a separate context. + * Wait 2 seconds. This should time out (the previous + * multi-lock request should take precedence). + * (read write) + */ + io.lockx.in.timeout = 2000; + lock[0].pid = cli->session->pid+2; + io.lockx.in.lock_cnt = 1; + req2 = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req2 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * d) + * Lock regions 110->119 + */ + io3.lockx.level = RAW_LOCK_LOCKX; + io3.lockx.in.file.fnum = fnum; + io3.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 0; + io3.lockx.in.lock_cnt = 1; + io3.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock3[0].pid = cli->session->pid+3; + lock3[0].offset = 110; + lock3[0].count = 10; + io3.lockx.in.locks = &lock3[0]; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * e) + * try 110-119 again + */ + io3.lockx.in.timeout = 20000; + lock3[0].pid = cli->session->pid+4; + req4 = smb_raw_lock_send(cli->tree, &io3); + torture_assert(tctx,(req4 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * f) + * Unlock (a) lock[0] 100-109 + * + * Note we send LOCKING_ANDX_EXCLUSIVE_LOCK + * while the lock used LOCKING_ANDX_SHARED_LOCK + * to check if that also works. + */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[0]; + lock[0].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * g) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+5; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* + * h) + * try to lock lock[0] 100-109 again with the pid (b) + * that's still waiting. + * (read write) + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req2->state <= SMBCLI_REQUEST_RECV, + "req2 should still wait"); + + /* + * i) + * Did the second lock complete (should time out) ? + */ + status = smbcli_request_simple_recv(req2); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + torture_assert(tctx, req4->state <= SMBCLI_REQUEST_RECV, + "req4 should still wait"); + + /* + * j) + * Unlock (d) lock[0] 110-119 + */ + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 1; + io3.lockx.in.lock_cnt = 0; + lock3[0].pid = cli->session->pid+3; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * k) + * receive the successful blocked lock requests + * on 110-119 while the 100-109/120-129 is still waiting. + */ + status = smbcli_request_simple_recv(req4); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * l) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+6; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* + * m) + * try to lock lock[0] 100-109 again with the pid (b) + * that's still waiting + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + + /* Start the clock. */ + t = time_mono(NULL); + + /* + * n) + * Unlock (a) lock[1] 120-129 + */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[1]; + lock[1].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * o) + * receive the successful blocked lock request (b) + */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Fail if this took more than 2 seconds. */ + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "Blocking locks were not granted immediately (%s)\n", + __location__)); +done: + smb_raw_exit(cli->session); + smbcli_deltree(cli->tree, BASEDIR); + return ret; +} + +/* + test multi6 Locking&X operation + This test is designed to show that + lock precedence on the server is based + on the order received, not on the ability + to grant. + + Compared to test_multilock4() (above) + this test demonstrates the behavior if + only just the first blocking lock + being a shared lock. + + For example: + + All locks except (b) are LOCKING_ANDX_EXCLUSIVE_LOCK (rw). + + (a) lock 100->109, 120->129 (granted) + (b) lock(ro) 100->109, 120->129 (blocks, timeout=20s) + (c) lock 100->109 (blocks, timeout=2s) + (d) lock 110->119 (granted) + (e) lock 110->119 (blocks, timeout=20s) + (f) unlock 100->109 (a) + (g) lock 100->109 (not granted, blocked by (b)) + (h) lock 100->109 (not granted, blocked by itself (b)) + (i) lock (c) will not be granted(conflict, times out) + as lock (b) will take precedence. + (j) unlock 110-119 (d) + (k) lock (e) completes and is not blocked by (a) nor (b) + (l) lock 100->109 (not granted(conflict), blocked by (b)) + (m) lock 100->109 (not granted(conflict), blocked by itself (b)) + (n) unlock 120-129 (a) + (o) lock (b) completes +*/ +static bool test_multilock6(struct torture_context *tctx, + struct smbcli_state *cli) +{ + union smb_lock io; + struct smb_lock_entry lock[2]; + union smb_lock io3; + struct smb_lock_entry lock3[1]; + NTSTATUS status; + bool ret = true; + int fnum; + const char *fname = BASEDIR "\\multilock6_test.txt"; + time_t t; + struct smbcli_request *req = NULL; + struct smbcli_request *req2 = NULL; + struct smbcli_request *req4 = NULL; + + torture_assert(tctx, torture_setup_dir(cli, BASEDIR), + "Failed to setup up test directory: " BASEDIR); + + torture_comment(tctx, "Testing LOCKING_ANDX multi-lock 6\n"); + io.generic.level = RAW_LOCK_LOCKX; + + /* Create the test file. */ + fnum = smbcli_open(cli->tree, fname, O_RDWR|O_CREAT, DENY_NONE); + torture_assert(tctx,(fnum != -1), talloc_asprintf(tctx, + "Failed to create %s - %s\n", + fname, smbcli_errstr(cli->tree))); + + /* + * a) + * Lock regions 100->109, 120->129 as + * two separate write locks in one request. + */ + 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 = 2; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid; + lock[0].offset = 100; + lock[0].count = 10; + lock[1].pid = cli->session->pid; + lock[1].offset = 120; + lock[1].count = 10; + io.lockx.in.locks = &lock[0]; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * b) + * Now request the same locks on a different + * context as blocking locks. + * (read only) + */ + io.lockx.in.timeout = 20000; + io.lockx.in.mode = LOCKING_ANDX_SHARED_LOCK; + lock[0].pid = cli->session->pid+1; + lock[1].pid = cli->session->pid+1; + req = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * c) + * Request the first lock again on a separate context. + * Wait 2 seconds. This should time out (the previous + * multi-lock request should take precedence). + */ + io.lockx.in.timeout = 2000; + io.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock[0].pid = cli->session->pid+2; + io.lockx.in.lock_cnt = 1; + req2 = smb_raw_lock_send(cli->tree, &io); + torture_assert(tctx,(req2 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * d) + * Lock regions 110->119 + */ + io3.lockx.level = RAW_LOCK_LOCKX; + io3.lockx.in.file.fnum = fnum; + io3.lockx.in.mode = LOCKING_ANDX_LARGE_FILES; + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 0; + io3.lockx.in.lock_cnt = 1; + io3.lockx.in.mode = LOCKING_ANDX_EXCLUSIVE_LOCK; + lock3[0].pid = cli->session->pid+3; + lock3[0].offset = 110; + lock3[0].count = 10; + io3.lockx.in.locks = &lock3[0]; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * e) + * try 110-119 again + */ + io3.lockx.in.timeout = 20000; + lock3[0].pid = cli->session->pid+4; + req4 = smb_raw_lock_send(cli->tree, &io3); + torture_assert(tctx,(req4 != NULL), talloc_asprintf(tctx, + "Failed to setup timed locks (%s)\n", __location__)); + + /* + * f) + * Unlock (a) lock[0] 100-109 + */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[0]; + lock[0].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * g) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+5; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* + * h) + * try to lock lock[0] 100-109 again with the pid (b) + * that's still waiting + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req2->state <= SMBCLI_REQUEST_RECV, + "req2 should still wait"); + + /* + * i) + * Did the second lock (c) complete (should time out) ? + */ + status = smbcli_request_simple_recv(req2); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + torture_assert(tctx, req4->state <= SMBCLI_REQUEST_RECV, + "req4 should still wait"); + + /* + * j) + * Unlock (d) lock[0] 110-119 + */ + io3.lockx.in.timeout = 0; + io3.lockx.in.ulock_cnt = 1; + io3.lockx.in.lock_cnt = 0; + lock3[0].pid = cli->session->pid+3; + status = smb_raw_lock(cli->tree, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * k) + * receive the successful blocked lock request (e) + * on 110-119 while (b) 100-109/120-129 is still waiting. + */ + status = smbcli_request_simple_recv(req4); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * l) + * try to lock lock[0] 100-109 again + */ + lock[0].pid = cli->session->pid+6; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + /* + * m) + * try to lock lock[0] 100-109 again with the pid (b) + * that's still waiting + */ + lock[0].pid = cli->session->pid+1; + io.lockx.in.ulock_cnt = 0; + io.lockx.in.lock_cnt = 1; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_FILE_LOCK_CONFLICT); + + torture_assert(tctx, req->state <= SMBCLI_REQUEST_RECV, + "req should still wait"); + + /* Start the clock. */ + t = time_mono(NULL); + + /* + * n) + * Unlock (a) lock[1] 120-129 + */ + io.lockx.in.timeout = 0; + io.lockx.in.ulock_cnt = 1; + io.lockx.in.lock_cnt = 0; + io.lockx.in.locks = &lock[1]; + lock[1].pid = cli->session->pid; + status = smb_raw_lock(cli->tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * o) + * receive the successful blocked lock request (b) + */ + status = smbcli_request_simple_recv(req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Fail if this took more than 2 seconds. */ + torture_assert(tctx,!(time_mono(NULL) > t+2), talloc_asprintf(tctx, + "Blocking locks were not granted immediately (%s)\n", + __location__)); +done: + 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); + + torture_suite_add_1smb_test(suite, "stacking", test_stacking); + torture_suite_add_1smb_test(suite, "unlock", test_unlock); + torture_suite_add_1smb_test(suite, "multiple_unlock", + test_multiple_unlock); + torture_suite_add_1smb_test(suite, "zerobytelocks", test_zerobytelocks); + torture_suite_add_1smb_test(suite, "zerobyteread", test_zerobyteread); + torture_suite_add_1smb_test(suite, "multilock", test_multilock); + torture_suite_add_1smb_test(suite, "multilock2", test_multilock2); + torture_suite_add_1smb_test(suite, "multilock3", test_multilock3); + torture_suite_add_1smb_test(suite, "multilock4", test_multilock4); + torture_suite_add_1smb_test(suite, "multilock5", test_multilock5); + torture_suite_add_1smb_test(suite, "multilock6", test_multilock6); + + return suite; +} |