summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
authorGordon Ross <gwr@nexenta.com>2017-07-21 16:37:02 -0400
committerGordon Ross <gwr@nexenta.com>2019-06-08 20:48:57 -0400
commit94047d49916b669576decf2f622a1ee718646882 (patch)
tree6b446f44e97da3deccef4504c5f8bd82f14a35c8 /usr/src
parent148d1a4158dc830f7b293a2ceb62ee54c2ebd72f (diff)
downloadillumos-gate-94047d49916b669576decf2f622a1ee718646882.tar.gz
11016 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com> Reviewed by: Evan Layton <evan.layton@nexenta.com> Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com> Approved by: Garrett D'Amore <garrett@damore.org>
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c493
-rw-r--r--usr/src/cmd/smbsrv/Makefile4
-rw-r--r--usr/src/cmd/smbsrv/testoplock/.dbxrc23
-rw-r--r--usr/src/cmd/smbsrv/testoplock/Makefile121
-rwxr-xr-xusr/src/cmd/smbsrv/testoplock/Run-cmd.sh32
-rwxr-xr-xusr/src/cmd/smbsrv/testoplock/Run-tests.sh47
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case01.ref29
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case01.txt10
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case02.ref29
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case02.txt10
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case03.ref20
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case03.txt7
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case04.ref20
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case04.txt7
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case05.ref65
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case05.txt18
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case06.ref56
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case06.txt24
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case07.ref20
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case07.txt8
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case08.ref36
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case08.txt13
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case09.ref27
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case09.txt10
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case10.ref31
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case10.txt12
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case11.ref30
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case11.txt11
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case12.ref54
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case12.txt28
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case13.ref56
-rw-r--r--usr/src/cmd/smbsrv/testoplock/case13.txt25
-rw-r--r--usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h111
-rw-r--r--usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h227
-rw-r--r--usr/src/cmd/smbsrv/testoplock/tol_all.d106
-rw-r--r--usr/src/cmd/smbsrv/testoplock/tol_main.c641
-rw-r--r--usr/src/cmd/smbsrv/testoplock/tol_misc.c179
-rw-r--r--usr/src/lib/libfakekernel/common/rwlock.c4
-rw-r--r--usr/src/lib/libshare/smb/libshare_smb.c6
-rw-r--r--usr/src/lib/smbsrv/libfksmbsrv/Makefile.com3
-rw-r--r--usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c8
-rw-r--r--usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h15
-rw-r--r--usr/src/lib/smbsrv/libmlsvc/common/smb_share.c12
-rw-r--r--usr/src/man/man4/smb.415
-rw-r--r--usr/src/uts/common/Makefile.files3
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_create.c225
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_dispatch.c59
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_durable.c58
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_lease.c720
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_negotiate.c1
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_oplock.c304
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_query_dir.c2
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb2_write.c4
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c3545
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c188
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c63
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_common_open.c752
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_create.c5
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_delete.c30
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_fem.c156
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_init.c3
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_kshare.c1
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_lock.c12
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_locking_andx.c27
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_node.c18
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c8
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c8
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c11
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_ofile.c217
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_open_andx.c40
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_opipe.c26
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_oplock.c912
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_read.c7
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_server.c6
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_session.c5
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c4
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c655
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_tree.c3
-rw-r--r--usr/src/uts/common/fs/smbsrv/smb_write.c4
-rw-r--r--usr/src/uts/common/smb/ntstatus.h4
-rw-r--r--usr/src/uts/common/smbsrv/Makefile1
-rw-r--r--usr/src/uts/common/smbsrv/ntifs.h31
-rw-r--r--usr/src/uts/common/smbsrv/smb.h11
-rw-r--r--usr/src/uts/common/smbsrv/smb2.h36
-rw-r--r--usr/src/uts/common/smbsrv/smb2_kproto.h11
-rw-r--r--usr/src/uts/common/smbsrv/smb_kproto.h54
-rw-r--r--usr/src/uts/common/smbsrv/smb_ktypes.h123
-rw-r--r--usr/src/uts/common/smbsrv/smb_oplock.h252
-rw-r--r--usr/src/uts/common/smbsrv/smb_share.h6
89 files changed, 9493 insertions, 1821 deletions
diff --git a/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c
index 9397a95483..fe0f6556c7 100644
--- a/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c
+++ b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c
@@ -34,6 +34,7 @@
#include <smbsrv/smb.h>
#include <smbsrv/smb_ktypes.h>
#include <smbsrv/smb_token.h>
+#include <smbsrv/smb_oplock.h>
#ifndef _KMDB
#include "smbsrv_pcap.h"
@@ -52,7 +53,7 @@
#define ACE_TYPE_ENTRY(_v_) {_v_, #_v_}
#define SMB_COM_ENTRY(_v_, _x_) {#_v_, _x_}
-#define SMB_MDB_MAX_OPTS 9
+#define SMB_MDB_MAX_OPTS 10
#define SMB_OPT_SERVER 0x00000001
#define SMB_OPT_SESSION 0x00000002
@@ -102,7 +103,7 @@ typedef struct {
*/
typedef struct {
uint_t ex_mask;
- int (*ex_offset)(void);
+ int (*ex_offset)(void);
const char *ex_dcmd;
const char *ex_name;
} smb_exp_t;
@@ -477,12 +478,16 @@ static const char *smb2_cmd_names[SMB2__NCMDS] = {
"smb2_invalid_cmd"
};
+struct mdb_smb_oplock;
+
static int smb_sid_print(uintptr_t);
static int smb_dcmd_getopt(uint_t *, int, const mdb_arg_t *);
static int smb_dcmd_setopt(uint_t, int, mdb_arg_t *);
static int smb_obj_expand(uintptr_t, uint_t, const smb_exp_t *, ulong_t);
static int smb_obj_list(const char *, uint_t, uint_t);
static int smb_worker_findstack(uintptr_t);
+static int smb_node_get_oplock(uintptr_t, struct mdb_smb_oplock **);
+static int smb_node_oplock_cnt(struct mdb_smb_oplock *);
static void smb_inaddr_ntop(smb_inaddr_t *, char *, size_t);
static void get_enum(char *, size_t, const char *, int, const char *);
@@ -600,7 +605,7 @@ smb_server_exp_off_nbt_list(void)
"ld_session_list");
if (lds_off < 0) {
mdb_warn("cannot lookup: "
- "smb_listener_daemon_t.ld_session_list");
+ "smb_listener_daemon_t .ld_session_list");
return (-1);
}
GET_OFFSET(ll_off, smb_llist_t, ll_list);
@@ -622,7 +627,7 @@ smb_server_exp_off_tcp_list(void)
"ld_session_list");
if (lds_off < 0) {
mdb_warn("cannot lookup: "
- "smb_listener_daemon_t.ld_session_list");
+ "smb_listener_daemon_t .ld_session_list");
return (-1);
}
GET_OFFSET(ll_off, smb_llist_t, ll_list);
@@ -776,7 +781,7 @@ typedef struct mdb_smb_session {
volatile uint32_t s_file_cnt;
volatile uint32_t s_dir_cnt;
- char workstation[SMB_PI_MAX_HOST];
+ char workstation[SMB_PI_MAX_HOST];
} mdb_smb_session_t;
static int
@@ -930,6 +935,7 @@ smbsess_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
}
if (smb_obj_expand(addr, opts, smb_session_exp, indent))
return (DCMD_ERR);
+
return (DCMD_OK);
}
@@ -1596,9 +1602,12 @@ tree_flag_bits[] = {
{ "SPARSE",
SMB_TREE_SPARSE,
SMB_TREE_SPARSE },
- { "XMNT",
+ { "XMOUNTS",
SMB_TREE_TRAVERSE_MOUNTS,
SMB_TREE_TRAVERSE_MOUNTS },
+ { "FORCE_L2_OPLOCK",
+ SMB_TREE_FORCE_L2_OPLOCK,
+ SMB_TREE_FORCE_L2_OPLOCK },
{ NULL, 0, 0 }
};
@@ -1783,6 +1792,7 @@ typedef struct mdb_smb_ofile {
int f_mode;
cred_t *f_cr;
pid_t f_pid;
+ uintptr_t f_lease;
smb_dh_vers_t dh_vers;
} mdb_smb_ofile_t;
@@ -1859,6 +1869,7 @@ smbofile_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
mdb_printf("State: %d (%s)\n", of->f_state, state);
mdb_printf("DH Type: %d (%s)\n", of->dh_vers,
durable);
+ mdb_printf("Lease: %p\n", of->f_lease);
mdb_printf("SMB Node: %p\n", of->f_node);
mdb_printf("LLF Offset: 0x%llx (%s)\n",
of->f_llf_pos,
@@ -1881,11 +1892,17 @@ smbofile_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
"%<b>%<u>%-?s "
"%-5s "
"%-?s "
- "%-?s%</u>%</b>\n",
- "OFILE", "FID", "SMB NODE", "CRED");
-
- mdb_printf("%?p %-5u %-p %p\n", addr,
- of->f_fid, of->f_node, of->f_cr);
+ "%-?s "
+ "%-?s "
+ "%</u>%</b>\n",
+ "OFILE",
+ "FID",
+ "NODE",
+ "CRED",
+ "LEASE");
+
+ mdb_printf("%?p %-5u %-p %-p %-p\n", addr,
+ of->f_fid, of->f_node, of->f_cr, of->f_lease);
}
}
return (DCMD_OK);
@@ -2060,6 +2077,112 @@ smb_hashstat_walk_step(mdb_walk_state_t *wsp)
}
/*
+ * smbsrv_leases
+ */
+static int
+smbsrv_leases_dcmd(uintptr_t addr, uint_t flags, int argc,
+ const mdb_arg_t *argv)
+{
+ uint_t opts;
+ int ht_off;
+ uintptr_t ht_addr;
+
+ if (smb_dcmd_getopt(&opts, argc, argv))
+ return (DCMD_USAGE);
+
+ if (!(flags & DCMD_ADDRSPEC)) {
+ mdb_printf("require address of an smb_server_t\n");
+ return (DCMD_USAGE);
+ }
+
+ ht_off = mdb_ctf_offsetof_by_name("smb_server_t", "sv_lease_ht");
+ if (ht_off < 0) {
+ mdb_warn("No .sv_lease_ht in server (old kernel?)");
+ return (DCMD_ERR);
+ }
+ addr += ht_off;
+
+ if (mdb_vread(&ht_addr, sizeof (ht_addr), addr) <= 0) {
+ mdb_warn("failed to read server .sv_lease_ht");
+ return (DCMD_ERR);
+ }
+
+ if (mdb_pwalk_dcmd("smb_hash_walker", "smblease",
+ argc, argv, ht_addr) == -1) {
+ mdb_warn("failed to walk 'smb_lease'");
+ return (DCMD_ERR);
+ }
+ return (DCMD_OK);
+}
+
+typedef struct mdb_smb_lease {
+ struct smb_node *ls_node;
+ uint32_t ls_refcnt;
+ uint16_t ls_epoch;
+ uint8_t ls_key[SMB_LEASE_KEY_SZ];
+} mdb_smb_lease_t;
+
+static int
+smblease_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ mdb_smb_lease_t *ls;
+ uint_t opts;
+ int i;
+
+ if (smb_dcmd_getopt(&opts, argc, argv))
+ return (DCMD_USAGE);
+
+ if (!(flags & DCMD_ADDRSPEC)) {
+ mdb_printf("require address of an smb_lease_t\n");
+ return (DCMD_USAGE);
+ }
+
+ if (((opts & SMB_OPT_WALK) && (opts & SMB_OPT_OFILE)) ||
+ !(opts & SMB_OPT_WALK)) {
+
+ ls = mdb_zalloc(sizeof (*ls), UM_SLEEP | UM_GC);
+ if (mdb_ctf_vread(ls, SMBSRV_SCOPE "smb_lease_t",
+ "mdb_smb_lease_t", addr, 0) < 0) {
+ mdb_warn("failed to read smb_lease_t at %p", addr);
+ return (DCMD_ERR);
+ }
+ if (opts & SMB_OPT_VERBOSE) {
+
+ mdb_printf(
+ "%<b>%<u>SMB lease (%p):%</u>%</b>\n\n", addr);
+
+ mdb_printf("SMB Node: %p\n", ls->ls_node);
+ mdb_printf("Refcount: %u\n", ls->ls_refcnt);
+ mdb_printf("Epoch: %u\n", ls->ls_epoch);
+
+ mdb_printf("Key: [");
+ for (i = 0; i < SMB_LEASE_KEY_SZ; i++) {
+ mdb_printf(" %02x", ls->ls_key[i] & 0xFF);
+ if ((i & 3) == 3)
+ mdb_printf(" ");
+ }
+ mdb_printf(" ]\n");
+ } else {
+ if (DCMD_HDRSPEC(flags))
+ mdb_printf(
+ "%<b>%<u>"
+ "%-?s "
+ "%-?s "
+ "%-?s%</u>%</b>\n",
+ "LEASE", "SMB NODE", "KEY");
+
+ mdb_printf("%?p %-p [", addr, ls->ls_node);
+ for (i = 0; i < 8; i++) {
+ mdb_printf(" %02x", ls->ls_key[i] & 0xFF);
+ }
+ mdb_printf(" ...]\n");
+ }
+ }
+
+ return (DCMD_OK);
+}
+
+/*
* *****************************************************************************
* ******************************** smb_kshare_t *******************************
* *****************************************************************************
@@ -2337,7 +2460,6 @@ typedef struct mdb_smb_node {
smb_llist_t n_ofile_list;
smb_llist_t n_lock_list;
volatile int flags;
- smb_oplock_t n_oplock;
struct smb_node *n_dnode;
struct smb_node *n_unode;
char od_name[MAXNAMELEN];
@@ -2355,7 +2477,6 @@ typedef struct mdb_smb_node_old {
smb_llist_t n_ofile_list;
smb_llist_t n_lock_list;
volatile int flags;
- smb_oplock_t n_oplock;
struct smb_node *n_dnode;
struct smb_node *n_unode;
char od_name[MAXNAMELEN];
@@ -2393,10 +2514,12 @@ smbnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
int verbose = FALSE;
int print_full_path = FALSE;
int stack_trace = FALSE;
+ int ol_cnt = 0;
vnode_t vnode;
char od_name[MAXNAMELEN];
char path_name[1024];
- uintptr_t list_addr, oplock_addr;
+ uintptr_t list_addr;
+ struct mdb_smb_oplock *node_oplock;
if (mdb_getopts(argc, argv,
'v', MDB_OPT_SETBITS, TRUE, &verbose,
@@ -2449,11 +2572,17 @@ smbnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
}
}
}
+
+ rc = smb_node_get_oplock(addr, &node_oplock);
+ if (rc != DCMD_OK)
+ return (rc);
+ ol_cnt = smb_node_oplock_cnt(node_oplock);
+
if (verbose) {
- int nll_off, wll_off, nol_off, ll_off;
+ int nol_off, nll_off, wll_off, ll_off;
+ GET_OFFSET(nol_off, smb_node_t, n_ofile_list);
GET_OFFSET(nll_off, smb_node_t, n_lock_list);
- GET_OFFSET(nol_off, smb_node_t, n_oplock);
GET_OFFSET(ll_off, smb_llist_t, ll_list);
/* This one is optional (for now). */
/* GET_OFFSET(wll_off, smb_node_t, n_wlock_list); */
@@ -2466,7 +2595,18 @@ smbnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
mdb_printf("Name: %s\n", od_name);
if (print_full_path)
mdb_printf("V-node Path: %s\n", path_name);
+ mdb_printf("Reference Count: %u\n", node.n_refcnt);
mdb_printf("Ofiles: %u\n", node.n_ofile_list.ll_count);
+ if (node.n_ofile_list.ll_count != 0 && nol_off != -1) {
+ (void) mdb_inc_indent(SMB_DCMD_INDENT);
+ list_addr = addr + nol_off + ll_off;
+ if (mdb_pwalk_dcmd("list", "smbofile", 0,
+ NULL, list_addr)) {
+ mdb_warn("failed to walk node's ofiles");
+ }
+ (void) mdb_dec_indent(SMB_DCMD_INDENT);
+ }
+
mdb_printf("Granted Locks: %u\n",
node.n_lock_list.ll_count);
if (node.n_lock_list.ll_count != 0) {
@@ -2491,18 +2631,18 @@ smbnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
}
(void) mdb_dec_indent(SMB_DCMD_INDENT);
}
- if (node.n_oplock.ol_count == 0) {
- mdb_printf("Opportunistic Locks: 0\n");
+ if (ol_cnt == 0) {
+ mdb_printf("Opportunistic Locks: (none)\n");
} else {
- oplock_addr = addr + nol_off;
- mdb_printf("Opportunistic Lock: %p\n",
- oplock_addr);
- rc = mdb_call_dcmd("smboplock", oplock_addr,
+ mdb_printf("Opportunistic Locks:\n");
+ (void) mdb_inc_indent(SMB_DCMD_INDENT);
+ /* Takes node address */
+ rc = mdb_call_dcmd("smbnode_oplock", addr,
flags, argc, argv);
+ (void) mdb_dec_indent(SMB_DCMD_INDENT);
if (rc != DCMD_OK)
return (rc);
}
- mdb_printf("Reference Count: %u\n\n", node.n_refcnt);
} else {
if (DCMD_HDRSPEC(flags)) {
mdb_printf(
@@ -2521,7 +2661,7 @@ smbnode_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
mdb_printf("%-?p %-?p %-18s %-6d %-6d %-8d %-8d %-6d ",
addr, node.vp, od_name, node.n_ofile_list.ll_count,
node.n_lock_list.ll_count, node.n_wlock_list.ll_count,
- node.n_oplock.ol_count, node.n_refcnt);
+ ol_cnt, node.n_refcnt);
if (print_full_path)
mdb_printf("\t%s\n", path_name);
@@ -2758,53 +2898,139 @@ smblock_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
*/
typedef struct mdb_smb_oplock_grant {
- uint8_t og_breaking;
- uint8_t og_level;
- struct smb_ofile *og_ofile;
+ uint32_t og_state; /* latest sent to client */
+ uint8_t onlist_II;
+ uint8_t onlist_R;
+ uint8_t onlist_RH;
+ uint8_t onlist_RHBQ;
+ uint8_t BreakingToRead;
} mdb_smb_oplock_grant_t;
+static const mdb_bitmask_t
+oplock_bits[] = {
+ { "READ_CACHING",
+ READ_CACHING,
+ READ_CACHING },
+ { "HANDLE_CACHING",
+ HANDLE_CACHING,
+ HANDLE_CACHING },
+ { "WRITE_CACHING",
+ WRITE_CACHING,
+ WRITE_CACHING },
+ { "EXCLUSIVE",
+ EXCLUSIVE,
+ EXCLUSIVE },
+ { "MIXED_R_AND_RH",
+ MIXED_R_AND_RH,
+ MIXED_R_AND_RH },
+ { "LEVEL_TWO_OPLOCK",
+ LEVEL_TWO_OPLOCK,
+ LEVEL_TWO_OPLOCK },
+ { "LEVEL_ONE_OPLOCK",
+ LEVEL_ONE_OPLOCK,
+ LEVEL_ONE_OPLOCK },
+ { "BATCH_OPLOCK",
+ BATCH_OPLOCK,
+ BATCH_OPLOCK },
+ { "BREAK_TO_TWO",
+ BREAK_TO_TWO,
+ BREAK_TO_TWO },
+ { "BREAK_TO_NONE",
+ BREAK_TO_NONE,
+ BREAK_TO_NONE },
+ { "BREAK_TO_TWO_TO_NONE",
+ BREAK_TO_TWO_TO_NONE,
+ BREAK_TO_TWO_TO_NONE },
+ { "BREAK_TO_READ_CACHING",
+ BREAK_TO_READ_CACHING,
+ BREAK_TO_READ_CACHING },
+ { "BREAK_TO_HANDLE_CACHING",
+ BREAK_TO_HANDLE_CACHING,
+ BREAK_TO_HANDLE_CACHING },
+ { "BREAK_TO_WRITE_CACHING",
+ BREAK_TO_WRITE_CACHING,
+ BREAK_TO_WRITE_CACHING },
+ { "BREAK_TO_NO_CACHING",
+ BREAK_TO_NO_CACHING,
+ BREAK_TO_NO_CACHING },
+ { "NO_OPLOCK",
+ NO_OPLOCK,
+ NO_OPLOCK },
+ { NULL, 0, 0 }
+};
+
+/*
+ * Show smb_ofile_t oplock info
+ * address is the ofile
+ */
+
/*ARGSUSED*/
static int
-smboplockgrant_dcmd(uintptr_t addr, uint_t flags, int argc,
+smbofile_oplock_dcmd(uintptr_t addr, uint_t flags, int argc,
const mdb_arg_t *argv)
{
- mdb_smb_oplock_grant_t grant;
- char *level;
+ mdb_smb_oplock_grant_t og;
+ int verbose = FALSE;
+ static int og_off;
+
+ if (mdb_getopts(argc, argv,
+ 'v', MDB_OPT_SETBITS, TRUE, &verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
- /*
- * If this is the first invocation of the command, print a nice
- * header line for the output that will follow.
- */
- if (DCMD_HDRSPEC(flags)) {
- mdb_printf("%<u>%-16s %-10s %-16s%</u>\n",
- "Grants:", "LEVEL", "OFILE");
+ if (og_off <= 0) {
+ og_off = mdb_ctf_offsetof_by_name(
+ "smb_ofile_t", "f_oplock");
+ if (og_off < 0) {
+ mdb_warn("cannot lookup: smb_ofile_t .f_oplock");
+ return (DCMD_ERR);
+ }
}
- if (mdb_ctf_vread(&grant, SMBSRV_SCOPE "smb_oplock_grant_t",
- "mdb_smb_oplock_grant_t", addr, 0) < 0) {
- mdb_warn("failed to read oplock grant at %p", addr);
+ if (mdb_ctf_vread(&og, SMBSRV_SCOPE "smb_oplock_grant_t",
+ "mdb_smb_oplock_grant_t", addr + og_off, 0) < 0) {
+ mdb_warn("failed to read oplock grant in ofile at %p", addr);
return (DCMD_ERR);
}
- switch (grant.og_level) {
- case SMB_OPLOCK_EXCLUSIVE:
- level = "EXCLUSIVE";
- break;
- case SMB_OPLOCK_BATCH:
- level = "BATCH";
- break;
- case SMB_OPLOCK_LEVEL_II:
- level = "LEVEL_II";
- break;
- default:
- level = "UNKNOWN";
- break;
+ if (verbose) {
+ mdb_printf("%<b>%<u>SMB ofile (oplock_grant) "
+ "(%p):%</u>%</b>\n", addr);
+ mdb_printf("State: 0x%x <%b>\n",
+ og.og_state,
+ og.og_state,
+ oplock_bits);
+ mdb_printf("OnList_II: %d\n", og.onlist_II);
+ mdb_printf("OnList_R: %d\n", og.onlist_R);
+ mdb_printf("OnList_RH: %d\n", og.onlist_RH);
+ mdb_printf("OnList_RHBQ: %d\n", og.onlist_RHBQ);
+ mdb_printf("BrkToRead: %d\n", og.BreakingToRead);
+
+ } else {
+
+ if (DCMD_HDRSPEC(flags)) {
+ mdb_printf("%<u>%-16s %-10s %-16s%</u>\n",
+ "OFILE", "STATE", "OnList...");
+ }
+
+ mdb_printf("%-16p", addr);
+ mdb_printf(" 0x%x", og.og_state);
+ if (og.onlist_II)
+ mdb_printf(" II");
+ if (og.onlist_R)
+ mdb_printf(" R");
+ if (og.onlist_RH)
+ mdb_printf(" RH");
+ if (og.onlist_RHBQ)
+ mdb_printf(" RHBQ");
+ if (og.BreakingToRead)
+ mdb_printf(" BrkToRd");
+ mdb_printf("\n");
}
- mdb_printf("%-16p %-10s %-16p", addr, level, grant.og_ofile);
return (DCMD_OK);
}
@@ -2815,51 +3041,130 @@ smboplockgrant_dcmd(uintptr_t addr, uint_t flags, int argc,
*/
typedef struct mdb_smb_oplock {
- uint8_t ol_brk_pending;
- uint8_t ol_break;
- uint32_t ol_count; /* number of grants */
- list_t ol_grants; /* list of smb_oplock_grant_t */
+ struct smb_ofile *excl_open;
+ uint32_t ol_state;
+ int32_t cnt_II;
+ int32_t cnt_R;
+ int32_t cnt_RH;
+ int32_t cnt_RHBQ;
+ int32_t waiters;
} mdb_smb_oplock_t;
+/*
+ * Helpers for smbnode_dcmd and smbnode_oplock_dcmd
+ */
+
+/*
+ * Read the smb_oplock_t part of the node
+ * addr is the smb_node
+ */
+static int
+smb_node_get_oplock(uintptr_t addr, struct mdb_smb_oplock **ol_ret)
+{
+ mdb_smb_oplock_t *ol;
+ static int ol_off;
+
+ if (ol_off <= 0) {
+ ol_off = mdb_ctf_offsetof_by_name(
+ "smb_node_t", "n_oplock");
+ if (ol_off < 0) {
+ mdb_warn("cannot lookup: smb_node_t .n_oplock");
+ return (DCMD_ERR);
+ }
+ }
+
+ ol = mdb_alloc(sizeof (*ol), UM_SLEEP | UM_GC);
+
+ if (mdb_ctf_vread(ol, SMBSRV_SCOPE "smb_oplock_t",
+ "mdb_smb_oplock_t", addr + ol_off, 0) < 0) {
+ mdb_warn("failed to read smb_oplock in node at %p", addr);
+ return (DCMD_ERR);
+ }
+
+ *ol_ret = ol;
+ return (DCMD_OK);
+}
+
+/*
+ * Return the oplock count
+ */
+static int
+smb_node_oplock_cnt(struct mdb_smb_oplock *ol)
+{
+ int ol_cnt = 0;
+
+ /* Compute total oplock count. */
+ if (ol->excl_open != NULL)
+ ol_cnt++;
+ ol_cnt += ol->cnt_II;
+ ol_cnt += ol->cnt_R;
+ ol_cnt += ol->cnt_RH;
+
+ return (ol_cnt);
+}
+
+/*
+ * Show smb_node_t oplock info, and optionally the
+ * list of ofiles with oplocks on this node.
+ * Address is the smb_node_t.
+ */
+
/*ARGSUSED*/
static int
-smboplock_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+smbnode_oplock_dcmd(uintptr_t addr, uint_t flags, int argc,
+ const mdb_arg_t *argv)
{
- mdb_smb_oplock_t oplock;
- uintptr_t list_addr;
- int og_off;
+ mdb_smb_oplock_t *ol;
+ int verbose = FALSE;
+ int ol_cnt, rc;
+ int fl_off, ll_off;
+
+ if (mdb_getopts(argc, argv,
+ 'v', MDB_OPT_SETBITS, TRUE, &verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
- if (mdb_ctf_vread(&oplock, SMBSRV_SCOPE "smb_oplock_t",
- "mdb_smb_oplock_t", addr, 0) < 0) {
- mdb_warn("failed to read struct smb_oplock at %p", addr);
- return (DCMD_ERR);
+ rc = smb_node_get_oplock(addr, &ol);
+ if (rc != DCMD_OK)
+ return (rc);
+ ol_cnt = smb_node_oplock_cnt(ol);
+
+ if (verbose) {
+ mdb_printf("%<b>%<u>SMB node (oplock) "
+ "(%p):%</u>%</b>\n", addr);
+ mdb_printf("State: 0x%x <%b>\n",
+ ol->ol_state,
+ ol->ol_state,
+ oplock_bits);
+ mdb_printf("Exclusive Open: %p\n", ol->excl_open);
+ mdb_printf("cnt_II: %d\n", ol->cnt_II);
+ mdb_printf("cnt_R: %d\n", ol->cnt_R);
+ mdb_printf("cnt_RH: %d\n", ol->cnt_RH);
+ mdb_printf("cnt_RHBQ: %d\n", ol->cnt_RHBQ);
+ mdb_printf("waiters: %d\n", ol->waiters);
+ } else {
+ if (DCMD_HDRSPEC(flags)) {
+ mdb_printf("%<u>%-16s %-10s %-16s%</u>\n",
+ "NODE", "STATE", "OPLOCKS");
+ }
+ mdb_printf("%-16p 0x%x %d\n",
+ addr, ol->ol_state, ol_cnt);
}
- if (oplock.ol_count == 0)
+ if (ol_cnt == 0)
return (DCMD_OK);
- (void) mdb_inc_indent(SMB_DCMD_INDENT);
- switch (oplock.ol_break) {
- case SMB_OPLOCK_BREAK_TO_NONE:
- mdb_printf("Break Pending: BREAK_TO_NONE\n");
- break;
- case SMB_OPLOCK_BREAK_TO_LEVEL_II:
- mdb_printf(
- "Break Pending: BREAK_TO_LEVEL_II\n");
- break;
- default:
- break;
- }
+ GET_OFFSET(fl_off, smb_node_t, n_ofile_list);
+ GET_OFFSET(ll_off, smb_llist_t, ll_list);
- GET_OFFSET(og_off, smb_oplock_t, ol_grants);
- list_addr = addr + og_off;
+ (void) mdb_inc_indent(SMB_DCMD_INDENT);
- if (mdb_pwalk_dcmd("list", "smboplockgrant",
- argc, argv, list_addr)) {
- mdb_warn("failed to walk oplock grants");
+ if (mdb_pwalk_dcmd("list", "smbofile_oplock",
+ argc, argv, addr + fl_off + ll_off)) {
+ mdb_warn("failed to walk ofile oplocks");
}
(void) mdb_dec_indent(SMB_DCMD_INDENT);
@@ -3586,12 +3891,20 @@ static const mdb_dcmd_t dcmds[] = {
"[-v]",
"print smb_file_t information",
smbofile_dcmd },
- { "smboplock", NULL,
- "print smb_oplock_t information",
- smboplock_dcmd },
- { "smboplockgrant", NULL,
- "print smb_oplock_grant_t information",
- smboplockgrant_dcmd },
+ { "smbsrv_leases",
+ "[-v]",
+ "print lease table for a server",
+ smbsrv_leases_dcmd },
+ { "smblease",
+ "[-v]",
+ "print smb_lease_t information",
+ smblease_dcmd },
+ { "smbnode_oplock", NULL,
+ "print smb_node_t oplock information",
+ smbnode_oplock_dcmd },
+ { "smbofile_oplock", NULL,
+ "print smb_ofile_t oplock information",
+ smbofile_oplock_dcmd },
{ "smbace", "[-v]",
"print smb_ace_t information",
smbace_dcmd },
diff --git a/usr/src/cmd/smbsrv/Makefile b/usr/src/cmd/smbsrv/Makefile
index 193ce84c88..8e7699c252 100644
--- a/usr/src/cmd/smbsrv/Makefile
+++ b/usr/src/cmd/smbsrv/Makefile
@@ -22,11 +22,11 @@
# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2017 Nexenta Systems, Inc. All rights reserved.
#
SUBDIRS = smbadm smbd smbstat dtrace fksmbd bind-helper \
- test-msgbuf
+ test-msgbuf testoplock
MSGSUBDIRS = smbadm smbstat
include ../Makefile.cmd
diff --git a/usr/src/cmd/smbsrv/testoplock/.dbxrc b/usr/src/cmd/smbsrv/testoplock/.dbxrc
new file mode 100644
index 0000000000..92f30a38f7
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/.dbxrc
@@ -0,0 +1,23 @@
+
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+#
+
+set -o emacs
+
+# testoplock is always 32-bit
+setenv LD_LIBRARY_PATH ${ROOT}/usr/lib/smbsrv:${ROOT}/usr/lib:${ROOT}/lib
+
+echo 'Do one of: attach ${PID}'
+echo 'or: debug ${ROOT}/usr/lib/smbsrv/testoplock'
diff --git a/usr/src/cmd/smbsrv/testoplock/Makefile b/usr/src/cmd/smbsrv/testoplock/Makefile
new file mode 100644
index 0000000000..12fcbd0ac9
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/Makefile
@@ -0,0 +1,121 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+#
+
+
+PROG= testoplock
+
+OBJS_LOCAL= tol_main.o tol_misc.o
+OBJS_SMBSRV= smb_cmn_oplock.o
+OBJS_LIBSMB= smb_status_tbl.o
+
+OBJS= ${OBJS_LOCAL} ${OBJS_SMBSRV} ${OBJS_LIBSMB}
+
+SMBSRV_SRCDIR=../../../uts/common/fs/smbsrv
+SRCS= ${OBJS_LOCAL:.o=.c} \
+ ${OBJS_SMBSRV:%.o=${SMBSRV_SRCDIR}/%.c}
+
+include ../../Makefile.cmd
+include ../../Makefile.ctf
+
+# Note: need our sys includes _before_ ENVCPPFLAGS, proto etc.
+CPPFLAGS.first += -I.
+CPPFLAGS.first += -I../../../lib/libfakekernel/common
+CPPFLAGS.first += -I../../../lib/smbsrv/libfksmbsrv/common
+
+INCS += -I../../../uts/common
+
+CSTD= $(CSTD_GNU99)
+
+CFLAGS += $(CCVERBOSE)
+CFLAGS64 += $(CCVERBOSE)
+
+CPPFLAGS.master=$(DTEXTDOM) $(DTS_ERRNO)
+
+# CPPFLAGS is deliberatly set with a "=" and not a "+="...
+CPPFLAGS= $(CPPFLAGS.first) $(CPPFLAGS.master)
+
+CPPFLAGS += -D_REENTRANT
+CPPFLAGS += -DTESTJIG
+CPPFLAGS += -Dsyslog=smb_syslog
+CPPFLAGS += -D_LARGEFILE64_SOURCE=1
+# Always debug here
+CPPFLAGS += -DDEBUG
+CPPFLAGS += $(INCS)
+
+LDFLAGS += $(ZNOLAZYLOAD)
+LDFLAGS += '-R$$ORIGIN/..'
+LDLIBS += -lfakekernel -lcmdutils
+
+LINTFLAGS += -xerroff=E_NAME_DEF_NOT_USED2
+LINTFLAGS += -xerroff=E_NAME_USED_NOT_DEF2
+LINTFLAGS += -xerroff=E_INCONS_ARG_DECL2
+LINTFLAGS += -xerroff=E_INCONS_VAL_TYPE_DECL2
+
+ROOTSMBDDIR = $(ROOTLIB)/smbsrv
+ROOTSMBDFILE = $(PROG:%=$(ROOTSMBDDIR)/%)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+ $(LINK.c) -o $(PROG) $(OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+clean:
+ -$(RM) $(OBJS)
+
+lint: # lint_SRCS
+
+include ../../Makefile.targ
+
+install: all $(ROOTSMBDFILE)
+
+
+tol_main.o : tol_main.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -D_KMEMUSER -c tol_main.c
+ $(POST_PROCESS_O)
+
+tol_misc.o : tol_misc.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -D_FAKE_KERNEL \
+ -I../../../uts/common/smbsrv \
+ -I../../../common/smbsrv -c tol_misc.c
+ $(POST_PROCESS_O)
+
+# OBJS_SMBSRV
+%.o: ../../../uts/common/fs/smbsrv/%.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -D_FAKE_KERNEL \
+ -I../../../uts/common/smbsrv \
+ -I../../../common/smbsrv -c $<
+ $(POST_PROCESS_O)
+
+# OBJS_LIBSMB
+%.o: ../../../lib/smbsrv/libsmb/common/%.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
+ $(POST_PROCESS_O)
+
+
+$(ROOTSMBDDIR)/%: %
+ $(INS.file)
diff --git a/usr/src/cmd/smbsrv/testoplock/Run-cmd.sh b/usr/src/cmd/smbsrv/testoplock/Run-cmd.sh
new file mode 100755
index 0000000000..fa34046936
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/Run-cmd.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+#
+
+# Helper program to run fksmbd (user-space smbd for debugging)
+# using binaries from the proto area.
+
+[ -n "$ROOT" ] || {
+ echo "Need a bldenv to set ROOT=..."
+ exit 1;
+}
+
+# OK, setup env. to run it.
+
+LD_LIBRARY_PATH=$ROOT/usr/lib:$ROOT/lib
+export LD_LIBRARY_PATH
+
+# run with the passed options
+exec $ROOT/usr/lib/smbsrv/testoplock "$@"
diff --git a/usr/src/cmd/smbsrv/testoplock/Run-tests.sh b/usr/src/cmd/smbsrv/testoplock/Run-tests.sh
new file mode 100755
index 0000000000..aa5174095b
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/Run-tests.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+#
+
+# Helper program to run fksmbd (user-space smbd for debugging)
+# using binaries from the proto area.
+
+[ -n "$ROOT" ] || {
+ echo "Need a bldenv to set ROOT=..."
+ exit 1;
+}
+
+# OK, setup env. to run it.
+
+LD_LIBRARY_PATH=$ROOT/usr/lib:$ROOT/lib
+export LD_LIBRARY_PATH
+
+TOL=$ROOT/usr/lib/smbsrv/testoplock
+
+TESTS=${@:-case??.txt}
+
+# run the test cases
+for t in $TESTS
+do
+ name=${t%.txt}
+ $TOL < $name.txt > $name.tmp
+ if diff -u $name.tmp $name.ref >/dev/null 2>&1 ; then
+ echo "$name PASS"
+ rm $name.tmp
+ else
+ echo "$name FAIL"
+ diff -u $name.tmp $name.ref
+ fi
+done
diff --git a/usr/src/cmd/smbsrv/testoplock/case01.ref b/usr/src/cmd/smbsrv/testoplock/case01.ref
new file mode 100644
index 0000000000..6327c46b7e
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case01.ref
@@ -0,0 +1,29 @@
+open 1
+ open 1 OK
+req 1
+ req oplock fid=1 ret oplock=0x400 status=0x0 (SUCCESS)
+show
+ ol_state=0x410 ( BATCH_OPLOCK EXCLUSIVE )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease= OgState=0x400 Brk=0x0 Excl=Y onlist:
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x100, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+show
+ ol_state=0x100410 ( BREAK_TO_TWO BATCH_OPLOCK EXCLUSIVE )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease= OgState=0x400 Brk=0x100000 Excl=Y onlist:
+ack 1
+ ack: break fid=1, newstate=0x100, status=0x0 (SUCCESS)
+open 2
+ open 2 OK
+req 2 0x100
+ req oplock fid=2 ret oplock=0x100 status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=2 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+ fid=2 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
diff --git a/usr/src/cmd/smbsrv/testoplock/case01.txt b/usr/src/cmd/smbsrv/testoplock/case01.txt
new file mode 100644
index 0000000000..58eaa7f4b1
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case01.txt
@@ -0,0 +1,10 @@
+# Input for testoplock, case 01
+open 1
+req 1
+show
+brk-open 2
+show
+ack 1
+open 2
+req 2 0x100
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case02.ref b/usr/src/cmd/smbsrv/testoplock/case02.ref
new file mode 100644
index 0000000000..73488cbf27
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case02.ref
@@ -0,0 +1,29 @@
+open 1
+ open 1 OK
+req 1 0x807
+ req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease= OgState=0x807 Brk=0x0 Excl=Y onlist:
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+show
+ ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease= OgState=0x807 Brk=0x30000 Excl=Y onlist:
+ack 1
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+open 2
+ open 2 OK
+req 2 0x803
+ req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=2 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease= OgState=0x803 Brk=0x0 Excl=N onlist: RH
+ fid=2 Lease= OgState=0x803 Brk=0x0 Excl=N onlist: RH
diff --git a/usr/src/cmd/smbsrv/testoplock/case02.txt b/usr/src/cmd/smbsrv/testoplock/case02.txt
new file mode 100644
index 0000000000..996d41e75f
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case02.txt
@@ -0,0 +1,10 @@
+# Input for testoplock, case 02
+open 1
+req 1 0x807
+show
+brk-open 2
+show
+ack 1
+open 2
+req 2 0x803
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case03.ref b/usr/src/cmd/smbsrv/testoplock/case03.ref
new file mode 100644
index 0000000000..cdc7fe0089
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case03.ref
@@ -0,0 +1,20 @@
+open 1 3
+ open 1 OK
+req 1 0x803
+ req oplock fid=1 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+open 2 3
+ open 2 OK
+req 2 0x803
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=2 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist: RH
diff --git a/usr/src/cmd/smbsrv/testoplock/case03.txt b/usr/src/cmd/smbsrv/testoplock/case03.txt
new file mode 100644
index 0000000000..096936e0c7
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case03.txt
@@ -0,0 +1,7 @@
+# Input for testoplock, case 03
+open 1 3
+req 1 0x803
+show
+open 2 3
+req 2 0x803
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case04.ref b/usr/src/cmd/smbsrv/testoplock/case04.ref
new file mode 100644
index 0000000000..32df3767a9
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case04.ref
@@ -0,0 +1,20 @@
+open 1 3
+ open 1 OK
+req 1 0x801
+ req oplock fid=1 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=3 OgState=0x801 Brk=0x0 Excl=N onlist: R
+open 2 3
+ open 2 OK
+req 2 0x801
+*smb_oplock_ind_break fid=1 NewLevel=0x1, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=2 Lease=3 OgState=0x801 Brk=0x0 Excl=N onlist: R
diff --git a/usr/src/cmd/smbsrv/testoplock/case04.txt b/usr/src/cmd/smbsrv/testoplock/case04.txt
new file mode 100644
index 0000000000..c8316ea2d1
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case04.txt
@@ -0,0 +1,7 @@
+# Input for testoplock, case 04
+open 1 3
+req 1 0x801
+show
+open 2 3
+req 2 0x801
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case05.ref b/usr/src/cmd/smbsrv/testoplock/case05.ref
new file mode 100644
index 0000000000..639bf0023b
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case05.ref
@@ -0,0 +1,65 @@
+open 2 4
+ open 2 OK
+req 2 0x803
+ req oplock fid=2 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=2 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+open 3 4
+ open 3 OK
+req 3 0x803
+*smb_oplock_ind_break fid=2 NewLevel=0x3, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=3 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+open 1
+ open 1 OK
+brk-write 1
+*smb_oplock_ind_break fid=3 NewLevel=0x0, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-write 1 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x80003 ( BREAK_TO_NO_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=1
+ ofile_cnt=3
+ fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease=4 OgState=0x803 Brk=0x80000 Excl=N onlist: RHBQ(to none)
+ fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ack 3
+ ack: break fid=3, newstate=0x800, status=0x0 (SUCCESS)
+show
+ ol_state=0x10000000 ( NO_OPLOCK )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=3
+ fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease=4 OgState=0x800 Brk=0x0 Excl=N onlist:
+ fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+open 4 4
+ open 4 OK
+req 4 0x803
+ req oplock fid=4 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=4
+ fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease=4 OgState=0x800 Brk=0x0 Excl=N onlist:
+ fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=4 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+close 4
+*smb_oplock_ind_break fid=4 NewLevel=0x0, AckReq=0, ComplStatus=0x216 (OPLOCK_HANDLE_CLOSED)
+ close OK
+req 3 0x803
+ req oplock fid=3 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=3
+ fid=2 Lease=4 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease=4 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+ fid=1 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
diff --git a/usr/src/cmd/smbsrv/testoplock/case05.txt b/usr/src/cmd/smbsrv/testoplock/case05.txt
new file mode 100644
index 0000000000..f22f67abea
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case05.txt
@@ -0,0 +1,18 @@
+# Input for testoplock, case 05
+open 2 4
+req 2 0x803
+show
+open 3 4
+req 3 0x803
+show
+open 1
+brk-write 1
+show
+ack 3
+show
+open 4 4
+req 4 0x803
+show
+close 4
+req 3 0x803
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case06.ref b/usr/src/cmd/smbsrv/testoplock/case06.ref
new file mode 100644
index 0000000000..daa69c3dd1
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case06.ref
@@ -0,0 +1,56 @@
+open 1 1
+ open 1 OK
+req 1 0x801
+ req oplock fid=1 ret oplock=0x801 status=0x0 (SUCCESS)
+open 2 2
+ open 2 OK
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R
+ fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R
+brk-write 1
+*smb_oplock_ind_break fid=2 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS)
+ brk-write 1 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R
+ fid=2 Lease=2 OgState=0x800 Brk=0x0 Excl=N onlist:
+open 3 2
+ open 3 OK
+req 3 0x801
+ req oplock fid=3 ret oplock=0x801 status=0x0 (SUCCESS)
+close 3
+*smb_oplock_ind_break fid=3 NewLevel=0x0, AckReq=0, ComplStatus=0x216 (OPLOCK_HANDLE_CLOSED)
+ close OK
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R
+ fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R
+brk-write 2
+*smb_oplock_ind_break fid=1 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS)
+ brk-write 2 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist:
+ fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R
+brk-write 1
+*smb_oplock_ind_break fid=2 NewLevel=0x0, AckReq=0, ComplStatus=0x0 (SUCCESS)
+ brk-write 1 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x10000000 ( NO_OPLOCK )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist:
+ fid=2 Lease=2 OgState=0x800 Brk=0x0 Excl=N onlist:
diff --git a/usr/src/cmd/smbsrv/testoplock/case06.txt b/usr/src/cmd/smbsrv/testoplock/case06.txt
new file mode 100644
index 0000000000..c4af8377d0
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case06.txt
@@ -0,0 +1,24 @@
+# Input for testoplock, case 06
+# Modeled after smbtorture smb2.lease.nobreakself
+open 1 1
+req 1 0x801
+open 2 2
+req 2 0x801
+# both 1,2 should have R
+show
+# write 1 should leave 1:R 2:none
+brk-write 1
+show
+# upgrade 2 back to R
+open 3 2
+req 3 0x801
+close 3
+# ind_break will "move" the lease to h2 (1:R 2:R)
+req 2 0x801
+show
+# write 2 should leave 1:none 2:R
+brk-write 2
+show
+# write 1 should leave 1:none 2:none
+brk-write 1
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case07.ref b/usr/src/cmd/smbsrv/testoplock/case07.ref
new file mode 100644
index 0000000000..11b1fa415d
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case07.ref
@@ -0,0 +1,20 @@
+open 1 3
+ open 1 OK
+req 1 0x803
+ req oplock fid=1 ret oplock=0x803 status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=3 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+open 2 3
+ open 2 OK
+req 2 0x807
+*smb_oplock_ind_break fid=1 NewLevel=0x7, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=2) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=3 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=2 Lease=3 OgState=0x807 Brk=0x0 Excl=Y onlist:
diff --git a/usr/src/cmd/smbsrv/testoplock/case07.txt b/usr/src/cmd/smbsrv/testoplock/case07.txt
new file mode 100644
index 0000000000..56d90a0d7a
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case07.txt
@@ -0,0 +1,8 @@
+# Input for testoplock, case 07
+# Modeled after smbtorture smb2.lease.upgrade
+open 1 3
+req 1 0x803
+show
+open 2 3
+req 2 0x807
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case08.ref b/usr/src/cmd/smbsrv/testoplock/case08.ref
new file mode 100644
index 0000000000..7f28032990
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case08.ref
@@ -0,0 +1,36 @@
+open 1 1
+ open 1 OK
+req 1 0x805
+ req oplock fid=1 ret oplock=0x805 status=0x0 (SUCCESS)
+show
+ ol_state=0x15 ( EXCLUSIVE WRITE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=1 OgState=0x805 Brk=0x0 Excl=Y onlist:
+open 2 2
+ open 2 OK
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x1, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+show
+ ol_state=0x10015 ( BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x805 Brk=0x10000 Excl=Y onlist:
+ fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:
+ack 1
+ ack: break fid=1, newstate=0x801, status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R
+ fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=2 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R
+ fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R
diff --git a/usr/src/cmd/smbsrv/testoplock/case08.txt b/usr/src/cmd/smbsrv/testoplock/case08.txt
new file mode 100644
index 0000000000..ccc8f48711
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case08.txt
@@ -0,0 +1,13 @@
+# Input for testoplock, case 08
+# Modeled after smbtorture smb2.lease.upgrade3
+# sub-case: "R" "RH" "RW" "R"
+open 1 1
+req 1 0x805
+show
+open 2 2
+brk-open 2
+show
+ack 1
+show
+req 2 0x801
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case09.ref b/usr/src/cmd/smbsrv/testoplock/case09.ref
new file mode 100644
index 0000000000..4003b133db
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case09.ref
@@ -0,0 +1,27 @@
+open 1
+ open 1 OK
+req 1 0x100
+ req oplock fid=1 ret oplock=0x100 status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+open 2 2
+ open 2 OK
+brk-open 2
+ brk-open 2 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+ fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:
+req 2 0x803
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+ fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:
diff --git a/usr/src/cmd/smbsrv/testoplock/case09.txt b/usr/src/cmd/smbsrv/testoplock/case09.txt
new file mode 100644
index 0000000000..3683d3d83f
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case09.txt
@@ -0,0 +1,10 @@
+# Input for testoplock, case 09
+# Modeled after smbtorture smb2.lease.oplock
+open 1
+req 1 0x100
+show
+open 2 2
+brk-open 2
+show
+req 2 0x803
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case10.ref b/usr/src/cmd/smbsrv/testoplock/case10.ref
new file mode 100644
index 0000000000..9e0275d258
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case10.ref
@@ -0,0 +1,31 @@
+open 1
+ open 1 OK
+req 1 0x100
+ req oplock fid=1 ret oplock=0x100 status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+open 2 2
+ open 2 OK
+brk-open 2
+ brk-open 2 ret status=0x0 (SUCCESS)
+show
+ ol_state=0x100 ( LEVEL_TWO_OPLOCK )
+ Excl=n cnt_II=1 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+ fid=2 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:
+req 2 0x807
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+req 2 0x803
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+req 2 0x801
+ req oplock fid=2 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x101 ( LEVEL_TWO_OPLOCK READ_CACHING )
+ Excl=n cnt_II=1 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease= OgState=0x100 Brk=0x0 Excl=N onlist: II
+ fid=2 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R
diff --git a/usr/src/cmd/smbsrv/testoplock/case10.txt b/usr/src/cmd/smbsrv/testoplock/case10.txt
new file mode 100644
index 0000000000..10da33e970
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case10.txt
@@ -0,0 +1,12 @@
+# Input for testoplock, case 10
+# Modeled after smbtorture smb2.lease.oplock
+open 1
+req 1 0x100
+show
+open 2 2
+brk-open 2
+show
+req 2 0x807
+req 2 0x803
+req 2 0x801
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case11.ref b/usr/src/cmd/smbsrv/testoplock/case11.ref
new file mode 100644
index 0000000000..05bae87e04
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case11.ref
@@ -0,0 +1,30 @@
+open 1 1
+ open 1 OK
+req 1 0x807
+ req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:
+open 2
+ open 2 OK
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+ack 1
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+ fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+req 2 0x100
+ req oplock fid=2 ret oplock=0x0 status=0xc00000e2 (OPLOCK_NOT_GRANTED)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+ fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
diff --git a/usr/src/cmd/smbsrv/testoplock/case11.txt b/usr/src/cmd/smbsrv/testoplock/case11.txt
new file mode 100644
index 0000000000..7e09c98d73
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case11.txt
@@ -0,0 +1,11 @@
+# Input for testoplock, case 11
+# Modeled after smbtorture smb2.lease.break2
+open 1 1
+req 1 0x807
+show
+open 2
+brk-open 2
+ack 1
+show
+req 2 0x100
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case12.ref b/usr/src/cmd/smbsrv/testoplock/case12.ref
new file mode 100644
index 0000000000..bc36b7b6de
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case12.ref
@@ -0,0 +1,54 @@
+open 1 1
+ open 1 OK
+req 1 0x807
+ req oplock fid=1 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:
+open 2
+ open 2 OK
+brk-open 2
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 2 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+waiters 2 1
+ waiters 0 -> 1
+show
+ ol_state=0x30017 ( BREAK_TO_HANDLE_CACHING BREAK_TO_READ_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x807 Brk=0x30000 Excl=Y onlist:
+ fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+open 3
+ open 3 OK
+brk-open 3 4
+ brk-open 3 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+waiters 3 2
+ waiters 1 -> 2
+show
+ ol_state=0x80017 ( BREAK_TO_NO_CACHING EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=3
+ fid=1 Lease=1 OgState=0x807 Brk=0x30000 Excl=Y onlist:
+ fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ack 1 0x803
+*smb_oplock_ind_break fid=1 NewLevel=0x0, AckReq=1, ComplStatus=0x8000002e (CANNOT_GRANT_REQUESTED_OPLOCK)
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+show
+ ol_state=0x80003 ( BREAK_TO_NO_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=1
+ ofile_cnt=3
+ fid=1 Lease=1 OgState=0x803 Brk=0x80000 Excl=N onlist: RHBQ(to none)
+ fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ack 1 0x800
+ ack: break fid=1, newstate=0x800, status=0x0 (SUCCESS)
+show
+ ol_state=0x10000000 ( NO_OPLOCK )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=3
+ fid=1 Lease=1 OgState=0x800 Brk=0x0 Excl=N onlist:
+ fid=2 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=3 Lease= OgState=0x0 Brk=0x0 Excl=N onlist:
diff --git a/usr/src/cmd/smbsrv/testoplock/case12.txt b/usr/src/cmd/smbsrv/testoplock/case12.txt
new file mode 100644
index 0000000000..23b4f63092
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case12.txt
@@ -0,0 +1,28 @@
+# Input for testoplock, case 12
+# simulate smbtorture smb2.lease.breaking3
+#
+open 1 1
+req 1 0x807
+show
+#
+# a conflicting open (no oplock) is blocked until lease break ack
+open 2
+brk-open 2
+waiters 2 1
+show
+# should see lease break RWH to RH, and brk-open would block.
+# now a conflicting open with disp=overwrite(4), no oplock
+open 3
+brk-open 3 4
+waiters 3 2
+show
+# should see break_to_none pending (but no break ind yet)
+# and brk-open shoud block (break in progress)
+#
+# ack the first lease break above (RWH to RH)
+# should get a new break ind. (RH to none)
+ack 1 0x803
+show
+# got break ind?
+ack 1 0x800
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/case13.ref b/usr/src/cmd/smbsrv/testoplock/case13.ref
new file mode 100644
index 0000000000..a35a5992ce
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case13.ref
@@ -0,0 +1,56 @@
+open 1 1
+ open 1 OK
+req 1 0x801
+ req oplock fid=1 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x1 ( READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=1 OgState=0x801 Brk=0x0 Excl=N onlist: R
+
+open 2 1
+ open 2 OK
+brk-open 2
+ brk-open 2 ret status=0x0 (SUCCESS)
+req 2 0x807
+*smb_oplock_ind_break fid=1 NewLevel=0x7, AckReq=0, ComplStatus=0x215 (OPLOCK_SWITCHED_TO_NEW_HANDLE)
+ req oplock fid=2 ret oplock=0x807 status=0x0 (SUCCESS)
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=2) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x0 Brk=0x0 Excl=N onlist:
+ fid=2 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:
+
+move 2 1
+ move 2 1
+close 2
+ close OK
+show
+ ol_state=0x17 ( EXCLUSIVE WRITE_CACHING HANDLE_CACHING READ_CACHING )
+ Excl=Y (FID=1) cnt_II=0 cnt_R=0 cnt_RH=0 cnt_RHBQ=0
+ ofile_cnt=1
+ fid=1 Lease=1 OgState=0x807 Brk=0x0 Excl=Y onlist:
+
+open 3 2
+ open 3 OK
+brk-open 3
+*smb_oplock_ind_break fid=1 NewLevel=0x3, AckReq=1, ComplStatus=0x0 (SUCCESS)
+ brk-open 3 ret status=0x108 (OPLOCK_BREAK_IN_PROGRESS)
+ack 1
+ ack: break fid=1, newstate=0x803, status=0x0 (SUCCESS)
+show
+ ol_state=0x3 ( HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=0 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+ fid=3 Lease=2 OgState=0x0 Brk=0x0 Excl=N onlist:
+
+req 3 0x801
+ req oplock fid=3 ret oplock=0x801 status=0x0 (SUCCESS)
+show
+ ol_state=0x23 ( MIXED_R_AND_RH HANDLE_CACHING READ_CACHING )
+ Excl=n cnt_II=0 cnt_R=1 cnt_RH=1 cnt_RHBQ=0
+ ofile_cnt=2
+ fid=1 Lease=1 OgState=0x803 Brk=0x0 Excl=N onlist: RH
+ fid=3 Lease=2 OgState=0x801 Brk=0x0 Excl=N onlist: R
diff --git a/usr/src/cmd/smbsrv/testoplock/case13.txt b/usr/src/cmd/smbsrv/testoplock/case13.txt
new file mode 100644
index 0000000000..c8d012690d
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/case13.txt
@@ -0,0 +1,25 @@
+# Input for testoplock, case 13
+# simulate smbtorture smb2.lease.complex1
+#
+open 1 1
+req 1 0x801
+show
+
+# upgrade lease 1
+open 2 1
+brk-open 2
+req 2 0x807
+show
+
+move 2 1
+close 2
+show
+
+# contend via lease2
+open 3 2
+brk-open 3
+ack 1
+show
+
+req 3 0x801
+show
diff --git a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h
new file mode 100644
index 0000000000..4ccf839f51
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_kproto.h
@@ -0,0 +1,111 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * Function prototypes needed by the "testoplock" program
+ * (a small subset of what the SMB server uses)
+ */
+
+#ifndef _SMB_KPROTO_H_
+#define _SMB_KPROTO_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/varargs.h>
+#include <sys/cmn_err.h>
+#include <smbsrv/smb.h>
+#include <smbsrv/smb_ktypes.h>
+
+boolean_t smb_ofile_is_open(smb_ofile_t *);
+boolean_t smb_node_is_file(smb_node_t *);
+
+/*
+ * SMB locked list function prototypes
+ */
+void smb_llist_init(void);
+void smb_llist_fini(void);
+void smb_llist_constructor(smb_llist_t *, size_t, size_t);
+void smb_llist_destructor(smb_llist_t *);
+void smb_llist_enter(smb_llist_t *ll, krw_t);
+void smb_llist_exit(smb_llist_t *);
+void smb_llist_post(smb_llist_t *, void *, smb_dtorproc_t);
+void smb_llist_flush(smb_llist_t *);
+void smb_llist_insert_head(smb_llist_t *ll, void *obj);
+void smb_llist_insert_tail(smb_llist_t *ll, void *obj);
+void smb_llist_remove(smb_llist_t *ll, void *obj);
+int smb_llist_upgrade(smb_llist_t *ll);
+uint32_t smb_llist_get_count(smb_llist_t *ll);
+#define smb_llist_head(ll) list_head(&(ll)->ll_list)
+#define smb_llist_next(ll, obj) list_next(&(ll)->ll_list, obj)
+int smb_account_connected(smb_user_t *user);
+
+/*
+ * Common oplock functions
+ */
+uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *,
+ uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_BATCH(smb_node_t *, smb_ofile_t *,
+ uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_HANDLE(smb_node_t *, smb_ofile_t *);
+void smb_oplock_break_CLOSE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_READ(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_SETINFO(smb_node_t *,
+ smb_ofile_t *ofile, uint32_t InfoClass);
+uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *);
+
+void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
+
+/*
+ * Protocol-specific oplock functions
+ * (and "server-level" functions)
+ */
+void smb1_oplock_acquire(smb_request_t *, boolean_t);
+void smb1_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t);
+void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
+void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
+ uint32_t, boolean_t);
+void smb_oplock_send_brk(smb_request_t *);
+uint32_t smb_oplock_wait_break(smb_node_t *, int);
+
+int smb_lock_range_access(smb_request_t *, smb_node_t *,
+ uint64_t, uint64_t, boolean_t);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SMB_KPROTO_H_ */
diff --git a/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h
new file mode 100644
index 0000000000..16c770ef1d
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/smbsrv/smb_ktypes.h
@@ -0,0 +1,227 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * Structures and type definitions needed by the "testoplock" program
+ * (a small subset of what the SMB server uses)
+ */
+
+#ifndef _SMB_KTYPES_H
+#define _SMB_KTYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <sys/systm.h>
+#include <sys/cred.h>
+#include <sys/list.h>
+#include <sys/sdt.h>
+
+typedef struct smb_session smb_session_t;
+typedef struct smb_user smb_user_t;
+typedef struct smb_tree smb_tree_t;
+
+
+/*
+ * Destructor object used in the locked-list delete queue.
+ */
+#define SMB_DTOR_MAGIC 0x44544F52 /* DTOR */
+#define SMB_DTOR_VALID(d) \
+ ASSERT(((d) != NULL) && ((d)->dt_magic == SMB_DTOR_MAGIC))
+
+typedef void (*smb_dtorproc_t)(void *);
+
+typedef struct smb_dtor {
+ list_node_t dt_lnd;
+ uint32_t dt_magic;
+ void *dt_object;
+ smb_dtorproc_t dt_proc;
+} smb_dtor_t;
+
+typedef struct smb_llist {
+ krwlock_t ll_lock;
+ list_t ll_list;
+ uint32_t ll_count;
+ uint64_t ll_wrop;
+ kmutex_t ll_mutex;
+ list_t ll_deleteq;
+ uint32_t ll_deleteq_count;
+ boolean_t ll_flushing;
+} smb_llist_t;
+
+/*
+ * Per smb_node oplock state
+ */
+typedef struct smb_oplock {
+ kmutex_t ol_mutex;
+ boolean_t ol_fem; /* fem monitor installed? */
+ struct smb_ofile *excl_open;
+ uint32_t ol_state;
+ int32_t cnt_II;
+ int32_t cnt_R;
+ int32_t cnt_RH;
+ int32_t cnt_RHBQ;
+ int32_t waiters;
+ kcondvar_t WaitingOpenCV;
+} smb_oplock_t;
+
+/*
+ * Per smb_ofile oplock state
+ */
+typedef struct smb_oplock_grant {
+ /* smb protocol-level state */
+ uint32_t og_state; /* latest sent to client */
+ uint32_t og_breaking; /* BREAK_TO... flags */
+ uint16_t og_dialect; /* how to send breaks */
+ /* File-system level state */
+ uint8_t onlist_II;
+ uint8_t onlist_R;
+ uint8_t onlist_RH;
+ uint8_t onlist_RHBQ;
+ uint8_t BreakingToRead;
+} smb_oplock_grant_t;
+
+#define SMB_LEASE_KEY_SZ 16
+
+#define SMB_NODE_MAGIC 0x4E4F4445 /* 'NODE' */
+#define SMB_NODE_VALID(p) ASSERT((p)->n_magic == SMB_NODE_MAGIC)
+
+typedef enum {
+ SMB_NODE_STATE_AVAILABLE = 0,
+ SMB_NODE_STATE_DESTROYING
+} smb_node_state_t;
+
+/*
+ * waiting_event # of clients requesting FCN
+ * n_timestamps cached timestamps
+ * n_allocsz cached file allocation size
+ * n_dnode directory node
+ * n_unode unnamed stream node
+ * delete_on_close_cred credentials for delayed delete
+ */
+typedef struct smb_node {
+ list_node_t n_lnd;
+ uint32_t n_magic;
+ krwlock_t n_lock;
+ kmutex_t n_mutex;
+ smb_node_state_t n_state;
+ uint32_t n_refcnt;
+ uint32_t n_open_count;
+ volatile int flags;
+
+ smb_llist_t n_ofile_list;
+ smb_oplock_t n_oplock;
+} smb_node_t;
+
+#define NODE_FLAGS_WRITE_THROUGH 0x00100000
+#define NODE_FLAGS_DELETE_COMMITTED 0x20000000
+#define NODE_FLAGS_DELETE_ON_CLOSE 0x40000000
+
+/*
+ * Some flags for ofile structure
+ *
+ * SMB_OFLAGS_SET_DELETE_ON_CLOSE
+ * Set this flag when the corresponding open operation whose
+ * DELETE_ON_CLOSE bit of the CreateOptions is set. If any
+ * open file instance has this bit set, the NODE_FLAGS_DELETE_ON_CLOSE
+ * will be set for the file node upon close.
+ */
+
+/* SMB_OFLAGS_READONLY 0x0001 (obsolete) */
+#define SMB_OFLAGS_EXECONLY 0x0002
+#define SMB_OFLAGS_SET_DELETE_ON_CLOSE 0x0004
+#define SMB_OFLAGS_LLF_POS_VALID 0x0008
+
+#define SMB_OFILE_MAGIC 0x4F464C45 /* 'OFLE' */
+#define SMB_OFILE_VALID(p) \
+ ASSERT((p != NULL) && ((p)->f_magic == SMB_OFILE_MAGIC))
+
+/*
+ * This is the size of the per-handle "Lock Sequence" array.
+ * See LockSequenceIndex in [MS-SMB2] 2.2.26, and smb2_lock.c
+ */
+#define SMB_OFILE_LSEQ_MAX 64
+
+/* {arg_open,ofile}->dh_vers values */
+typedef enum {
+ SMB2_NOT_DURABLE = 0,
+ SMB2_DURABLE_V1,
+ SMB2_DURABLE_V2,
+ SMB2_RESILIENT,
+} smb_dh_vers_t;
+
+/*
+ * See the long "Ofile State Machine" comment in smb_ofile.c
+ */
+typedef enum {
+ SMB_OFILE_STATE_ALLOC = 0,
+ SMB_OFILE_STATE_OPEN,
+ SMB_OFILE_STATE_SAVE_DH,
+ SMB_OFILE_STATE_SAVING,
+ SMB_OFILE_STATE_CLOSING,
+ SMB_OFILE_STATE_CLOSED,
+ SMB_OFILE_STATE_ORPHANED,
+ SMB_OFILE_STATE_RECONNECT,
+ SMB_OFILE_STATE_EXPIRED,
+ SMB_OFILE_STATE_SENTINEL
+} smb_ofile_state_t;
+
+typedef struct smb_ofile {
+ list_node_t f_tree_lnd; /* t_ofile_list */
+ list_node_t f_node_lnd; /* n_ofile_list */
+ list_node_t f_dh_lnd; /* sv_persistid_ht */
+ uint32_t f_magic;
+ kmutex_t f_mutex;
+ smb_ofile_state_t f_state;
+
+ uint16_t f_fid;
+ uint16_t f_ftype;
+ uint32_t f_refcnt;
+ uint32_t f_granted_access;
+ uint32_t f_share_access;
+
+ smb_node_t *f_node;
+
+ smb_oplock_grant_t f_oplock;
+ uint8_t TargetOplockKey[SMB_LEASE_KEY_SZ];
+ uint8_t ParentOplockKey[SMB_LEASE_KEY_SZ];
+ struct smb_lease *f_lease;
+
+} smb_ofile_t;
+
+typedef struct smb_request {
+ list_node_t sr_session_lnd;
+ uint32_t sr_magic;
+ kmutex_t sr_mutex;
+} smb_request_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SMB_KTYPES_H */
diff --git a/usr/src/cmd/smbsrv/testoplock/tol_all.d b/usr/src/cmd/smbsrv/testoplock/tol_all.d
new file mode 100644
index 0000000000..3a8f2aef84
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/tol_all.d
@@ -0,0 +1,106 @@
+#!/usr/sbin/dtrace -s
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * User-level dtrace for testoplock
+ * Usage: dtrace -s tol.d -c ./testoplock
+ */
+
+#pragma D option flowindent
+
+self int trace;
+self int mask;
+
+/*
+ * Trace almost everything
+ */
+pid$target:testoplock::entry
+{
+ self->trace++;
+}
+
+/*
+ * If traced and not masked, print entry/return
+ */
+pid$target:testoplock::entry
+/self->trace > 0 && self->mask == 0/
+{
+ printf("\t0x%x", arg0);
+ printf("\t0x%x", arg1);
+ printf("\t0x%x", arg2);
+ printf("\t0x%x", arg3);
+ printf("\t0x%x", arg4);
+ printf("\t0x%x", arg5);
+}
+
+/* Skip the bsearch calls. */
+pid$target:testoplock:xlate_nt_status:entry
+{
+ self->mask++;
+}
+
+pid$target:testoplock:xlate_nt_status:return
+{
+ self->mask--;
+}
+
+pid$target:testoplock::return
+/self->trace > 0 && self->mask == 0/
+{
+ printf("\t0x%x", arg1);
+}
+
+pid$target:testoplock::return
+{
+ self->trace--;
+}
+
+/* ---------------------- */
+
+pid$target::smb_oplock_request:entry
+{
+ self->sr = arg0;
+ self->of = arg1;
+ self->statep = arg2;
+ this->state = *(uint32_t *)copyin(self->statep, 4);
+ printf(" entry state=0x%x\n", this->state);
+}
+
+pid$target::smb_oplock_request:return
+{
+ this->sr = (userland pid`smb_request_t *)self->sr;
+ this->state = *(uint32_t *)copyin(self->statep, 4);
+ printf(" return state=0x%x\n", this->state);
+ printf("\nsr->arg.open = ");
+ print(this->sr->arg.open);
+}
+
+pid$target::smb_oplock_break_cmn:entry
+{
+ this->node = (userland pid`smb_node_t *)arg0;
+ this->ofile = (userland pid`smb_ofile_t *)arg1;
+ printf("\nnode->n_oplock = ");
+ print(this->node->n_oplock);
+ printf("\nofile->f_oplock = ");
+ print(this->ofile->f_oplock);
+}
+
+pid$target::smb_oplock_ind_break:entry
+{
+ this->ofile = (userland pid`smb_ofile_t *)arg0;
+ printf("\nofile->f_oplock = ");
+ print(this->ofile->f_oplock);
+}
diff --git a/usr/src/cmd/smbsrv/testoplock/tol_main.c b/usr/src/cmd/smbsrv/testoplock/tol_main.c
new file mode 100644
index 0000000000..575c1f44fd
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/tol_main.c
@@ -0,0 +1,641 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * Test & debug program for oplocks
+ *
+ * This implements a simple command reader which accepts
+ * commands to simulate oplock events, and prints the
+ * state changes and actions that would happen after
+ * each event.
+ */
+
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <sys/stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+#define OPLOCK_CACHE_RWH (READ_CACHING | HANDLE_CACHING | WRITE_CACHING)
+#define OPLOCK_TYPE (LEVEL_TWO_OPLOCK | LEVEL_ONE_OPLOCK |\
+ BATCH_OPLOCK | OPLOCK_LEVEL_GRANULAR)
+
+#define MAXFID 10
+
+smb_node_t root_node, test_node;
+smb_ofile_t ofile_array[MAXFID];
+smb_request_t test_sr;
+uint32_t last_ind_break_level;
+char cmdbuf[100];
+
+extern const char *xlate_nt_status(uint32_t);
+
+#define BIT_DEF(name) { name, #name }
+
+struct bit_defs {
+ uint32_t mask;
+ const char *name;
+} state_bits[] = {
+ BIT_DEF(NO_OPLOCK),
+ BIT_DEF(BREAK_TO_NO_CACHING),
+ BIT_DEF(BREAK_TO_WRITE_CACHING),
+ BIT_DEF(BREAK_TO_HANDLE_CACHING),
+ BIT_DEF(BREAK_TO_READ_CACHING),
+ BIT_DEF(BREAK_TO_TWO_TO_NONE),
+ BIT_DEF(BREAK_TO_NONE),
+ BIT_DEF(BREAK_TO_TWO),
+ BIT_DEF(BATCH_OPLOCK),
+ BIT_DEF(LEVEL_ONE_OPLOCK),
+ BIT_DEF(LEVEL_TWO_OPLOCK),
+ BIT_DEF(MIXED_R_AND_RH),
+ BIT_DEF(EXCLUSIVE),
+ BIT_DEF(WRITE_CACHING),
+ BIT_DEF(HANDLE_CACHING),
+ BIT_DEF(READ_CACHING),
+ { 0, NULL }
+};
+
+/*
+ * Helper to print flags fields
+ */
+static void
+print_bits32(char *label, struct bit_defs *bit, uint32_t state)
+{
+ printf("%s0x%x (", label, state);
+ while (bit->mask != 0) {
+ if ((state & bit->mask) != 0)
+ printf(" %s", bit->name);
+ bit++;
+ }
+ printf(" )\n");
+}
+
+/*
+ * Command language:
+ *
+ */
+const char helpstr[] = "Commands:\n"
+ "help\t\tList commands\n"
+ "show\t\tShow OpLock state etc.\n"
+ "open FID\n"
+ "close FID\n"
+ "req FID [OplockLevel]\n"
+ "ack FID [OplockLevel]\n"
+ "brk-parent FID\n"
+ "brk-open [OverWrite]\n"
+ "brk-handle FID\n"
+ "brk-read FID\n"
+ "brk-write FID\n"
+ "brk-setinfo FID [InfoClass]\n"
+ "move FID1 FID2\n"
+ "waiters FID [count]\n";
+
+/*
+ * Command handlers
+ */
+
+static void
+do_show(void)
+{
+ smb_node_t *node = &test_node;
+ smb_oplock_t *ol = &node->n_oplock;
+ uint32_t state = ol->ol_state;
+ smb_ofile_t *f;
+
+ print_bits32(" ol_state=", state_bits, state);
+
+ if (ol->excl_open != NULL)
+ printf(" Excl=Y (FID=%d)", ol->excl_open->f_fid);
+ else
+ printf(" Excl=n");
+ printf(" cnt_II=%d cnt_R=%d cnt_RH=%d cnt_RHBQ=%d\n",
+ ol->cnt_II, ol->cnt_R, ol->cnt_RH, ol->cnt_RHBQ);
+
+ printf(" ofile_cnt=%d\n", node->n_ofile_list.ll_count);
+ FOREACH_NODE_OFILE(node, f) {
+ smb_oplock_grant_t *og = &f->f_oplock;
+ printf(" fid=%d Lease=%s OgState=0x%x Brk=0x%x",
+ f->f_fid,
+ f->TargetOplockKey, /* lease */
+ f->f_oplock.og_state,
+ f->f_oplock.og_breaking);
+ printf(" Excl=%s onlist: %s %s %s",
+ (ol->excl_open == f) ? "Y" : "N",
+ og->onlist_II ? "II" : "",
+ og->onlist_R ? "R" : "",
+ og->onlist_RH ? "RH" : "");
+ if (og->onlist_RHBQ) {
+ printf(" RHBQ(to %s)",
+ og->BreakingToRead ?
+ "read" : "none");
+ }
+ printf("\n");
+ }
+}
+
+static void
+do_open(int fid, char *arg2)
+{
+ smb_node_t *node = &test_node;
+ smb_ofile_t *ofile = &ofile_array[fid];
+
+ /*
+ * Simulate an open (minimal init)
+ */
+ if (ofile->f_refcnt) {
+ printf("open fid %d already opened\n");
+ return;
+ }
+
+ if (arg2 != NULL)
+ strlcpy((char *)ofile->TargetOplockKey, arg2,
+ SMB_LEASE_KEY_SZ);
+
+ ofile->f_refcnt++;
+ node->n_open_count++;
+ smb_llist_insert_tail(&node->n_ofile_list, ofile);
+ printf(" open %d OK\n", fid);
+}
+
+static void
+do_close(int fid)
+{
+ smb_node_t *node = &test_node;
+ smb_ofile_t *ofile = &ofile_array[fid];
+
+ /*
+ * Simulate an close
+ */
+ if (ofile->f_refcnt <= 0) {
+ printf(" close fid %d already closed\n");
+ return;
+ }
+ smb_oplock_break_CLOSE(ofile->f_node, ofile);
+
+ smb_llist_remove(&node->n_ofile_list, ofile);
+ node->n_open_count--;
+ ofile->f_refcnt--;
+
+ bzero(ofile->TargetOplockKey, SMB_LEASE_KEY_SZ);
+
+ printf(" close OK\n");
+}
+
+static void
+do_req(int fid, char *arg2)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t oplock = BATCH_OPLOCK;
+ uint32_t status;
+
+ if (arg2 != NULL)
+ oplock = strtol(arg2, NULL, 16);
+
+ /*
+ * Request an oplock
+ */
+ status = smb_oplock_request(&test_sr, ofile, &oplock);
+ if (status == 0)
+ ofile->f_oplock.og_state = oplock;
+ printf(" req oplock fid=%d ret oplock=0x%x status=0x%x (%s)\n",
+ fid, oplock, status, xlate_nt_status(status));
+}
+
+
+static void
+do_ack(int fid, char *arg2)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t oplock;
+ uint32_t status;
+
+ /* Default to level in last smb_oplock_ind_break() */
+ oplock = last_ind_break_level;
+ if (arg2 != NULL)
+ oplock = strtol(arg2, NULL, 16);
+
+ ofile->f_oplock.og_breaking = 0;
+ status = smb_oplock_ack_break(&test_sr, ofile, &oplock);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ printf(" ack: break fid=%d, break-in-progress\n", fid);
+ ofile->f_oplock.og_state = oplock;
+ }
+ if (status == 0)
+ ofile->f_oplock.og_state = oplock;
+
+ printf(" ack: break fid=%d, newstate=0x%x, status=0x%x (%s)\n",
+ fid, oplock, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_parent(int fid)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t status;
+
+ status = smb_oplock_break_PARENT(&test_node, ofile);
+ printf(" brk-parent %d ret status=0x%x (%s)\n",
+ fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_open(int fid, char *arg2)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t status;
+ int disp = FILE_OPEN;
+
+ if (arg2 != NULL)
+ disp = strtol(arg2, NULL, 16);
+
+ status = smb_oplock_break_OPEN(&test_node, ofile, 7, disp);
+ printf(" brk-open %d ret status=0x%x (%s)\n",
+ fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_handle(int fid)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t status;
+
+ status = smb_oplock_break_HANDLE(&test_node, ofile);
+ printf(" brk-handle %d ret status=0x%x (%s)\n",
+ fid, status, xlate_nt_status(status));
+
+}
+
+static void
+do_brk_read(int fid)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t status;
+
+ status = smb_oplock_break_READ(ofile->f_node, ofile);
+ printf(" brk-read %d ret status=0x%x (%s)\n",
+ fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_write(int fid)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t status;
+
+ status = smb_oplock_break_WRITE(ofile->f_node, ofile);
+ printf(" brk-write %d ret status=0x%x (%s)\n",
+ fid, status, xlate_nt_status(status));
+}
+
+static void
+do_brk_setinfo(int fid, char *arg2)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ uint32_t status;
+ int infoclass = FileEndOfFileInformation; /* 20 */
+
+ if (arg2 != NULL)
+ infoclass = strtol(arg2, NULL, 16);
+
+ status = smb_oplock_break_SETINFO(
+ &test_node, ofile, infoclass);
+ printf(" brk-setinfo %d ret status=0x%x (%s)\n",
+ fid, status, xlate_nt_status(status));
+
+}
+
+/*
+ * Move oplock to another FD, as specified,
+ * or any other available open
+ */
+static void
+do_move(int fid, char *arg2)
+{
+ smb_ofile_t *ofile = &ofile_array[fid];
+ smb_ofile_t *of2;
+ int fid2;
+
+ if (arg2 == NULL) {
+ fprintf(stderr, "move: FID2 required\n");
+ return;
+ }
+ fid2 = atoi(arg2);
+ if (fid2 <= 0 || fid2 >= MAXFID) {
+ fprintf(stderr, "move: bad FID2 %d\n", fid2);
+ return;
+ }
+ of2 = &ofile_array[fid2];
+
+ smb_oplock_move(&test_node, ofile, of2);
+ printf(" move %d %d\n", fid, fid2);
+}
+
+/*
+ * Set/clear oplock.waiters, which affects ack-break
+ */
+static void
+do_waiters(int fid, char *arg2)
+{
+ smb_node_t *node = &test_node;
+ smb_oplock_t *ol = &node->n_oplock;
+ int old, new = 0;
+
+ if (arg2 != NULL)
+ new = atoi(arg2);
+
+ old = ol->waiters;
+ ol->waiters = new;
+
+ printf(" waiters %d -> %d\n", old, new);
+}
+
+int
+main(int argc, char *argv[])
+{
+ smb_node_t *node = &test_node;
+ char *cmd;
+ char *arg1;
+ char *arg2;
+ char *savep;
+ char *sep = " \t\n";
+ char *prompt = NULL;
+ int fid;
+
+ if (isatty(0))
+ prompt = "> ";
+
+ smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t),
+ offsetof(smb_ofile_t, f_node_lnd));
+
+ for (fid = 0; fid < MAXFID; fid++) {
+ smb_ofile_t *f = &ofile_array[fid];
+
+ f->f_magic = SMB_OFILE_MAGIC;
+ mutex_init(&f->f_mutex, NULL, MUTEX_DEFAULT, NULL);
+ f->f_fid = fid;
+ f->f_ftype = SMB_FTYPE_DISK;
+ f->f_node = &test_node;
+ }
+
+ for (;;) {
+ if (prompt) {
+ fputs(prompt, stdout);
+ fflush(stdout);
+ }
+
+ cmd = fgets(cmdbuf, sizeof (cmdbuf), stdin);
+ if (cmd == NULL)
+ break;
+ if (cmd[0] == '#')
+ continue;
+
+ if (prompt == NULL) {
+ /* Put commands in the output too. */
+ fputs(cmdbuf, stdout);
+ }
+ cmd = strtok_r(cmd, sep, &savep);
+ if (cmd == NULL)
+ continue;
+
+ /*
+ * Commands with no args
+ */
+ if (0 == strcmp(cmd, "help")) {
+ fputs(helpstr, stdout);
+ continue;
+ }
+
+ if (0 == strcmp(cmd, "show")) {
+ do_show();
+ continue;
+ }
+
+ /*
+ * Commands with one arg (the FID)
+ */
+ arg1 = strtok_r(NULL, sep, &savep);
+ if (arg1 == NULL) {
+ fprintf(stderr, "%s missing arg1\n", cmd);
+ continue;
+ }
+ fid = atoi(arg1);
+ if (fid <= 0 || fid >= MAXFID) {
+ fprintf(stderr, "%s bad FID %d\n", cmd, fid);
+ continue;
+ }
+
+ if (0 == strcmp(cmd, "close")) {
+ do_close(fid);
+ continue;
+ }
+ if (0 == strcmp(cmd, "brk-parent")) {
+ do_brk_parent(fid);
+ continue;
+ }
+ if (0 == strcmp(cmd, "brk-handle")) {
+ do_brk_handle(fid);
+ continue;
+ }
+ if (0 == strcmp(cmd, "brk-read")) {
+ do_brk_read(fid);
+ continue;
+ }
+ if (0 == strcmp(cmd, "brk-write")) {
+ do_brk_write(fid);
+ continue;
+ }
+
+ /*
+ * Commands with an (optional) arg2.
+ */
+ arg2 = strtok_r(NULL, sep, &savep);
+
+ if (0 == strcmp(cmd, "open")) {
+ do_open(fid, arg2);
+ continue;
+ }
+ if (0 == strcmp(cmd, "req")) {
+ do_req(fid, arg2);
+ continue;
+ }
+ if (0 == strcmp(cmd, "ack")) {
+ do_ack(fid, arg2);
+ continue;
+ }
+ if (0 == strcmp(cmd, "brk-open")) {
+ do_brk_open(fid, arg2);
+ continue;
+ }
+ if (0 == strcmp(cmd, "brk-setinfo")) {
+ do_brk_setinfo(fid, arg2);
+ continue;
+ }
+ if (0 == strcmp(cmd, "move")) {
+ do_move(fid, arg2);
+ continue;
+ }
+ if (0 == strcmp(cmd, "waiters")) {
+ do_waiters(fid, arg2);
+ continue;
+ }
+
+ fprintf(stderr, "%s unknown command. Try help\n", cmd);
+ }
+ return (0);
+}
+
+/*
+ * A few functions called by the oplock code
+ * Stubbed out, and/or just print a message.
+ */
+
+boolean_t
+smb_node_is_file(smb_node_t *node)
+{
+ return (B_TRUE);
+}
+
+boolean_t
+smb_ofile_is_open(smb_ofile_t *ofile)
+{
+ return (ofile->f_refcnt != 0);
+}
+
+int
+smb_lock_range_access(
+ smb_request_t *sr,
+ smb_node_t *node,
+ uint64_t start,
+ uint64_t length,
+ boolean_t will_write)
+{
+ return (0);
+}
+
+/*
+ * Test code replacement for: smb_oplock_send_brk()
+ */
+static void
+test_oplock_send_brk(smb_ofile_t *ofile,
+ uint32_t NewLevel, boolean_t AckReq)
+{
+ smb_oplock_grant_t *og = &ofile->f_oplock;
+
+ /* Skip building a message. */
+
+ if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0)
+ NewLevel |= OPLOCK_LEVEL_GRANULAR;
+
+ /*
+ * In a real server, we would send a break to the client,
+ * and keep track (at the SMB level) whether this oplock
+ * was obtained via a lease or an old-style oplock.
+ */
+ if (AckReq) {
+ uint32_t BreakTo;
+
+ if ((og->og_state & OPLOCK_LEVEL_GRANULAR) != 0) {
+
+ BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT;
+ if (BreakTo == 0)
+ BreakTo = BREAK_TO_NO_CACHING;
+ } else {
+ if ((NewLevel & LEVEL_TWO_OPLOCK) != 0)
+ BreakTo = BREAK_TO_TWO;
+ else
+ BreakTo = BREAK_TO_NONE;
+ }
+ og->og_breaking = BreakTo;
+ last_ind_break_level = NewLevel;
+ /* Set og_state in do_ack */
+ } else {
+ og->og_state = NewLevel;
+ /* Clear og_breaking in do_ack */
+ }
+}
+
+/*
+ * Simplified version of what's in smb_srv_oplock.c
+ */
+void
+smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
+ boolean_t AckReq, uint32_t status)
+{
+ smb_oplock_grant_t *og = &ofile->f_oplock;
+
+ printf("*smb_oplock_ind_break fid=%d NewLevel=0x%x,"
+ " AckReq=%d, ComplStatus=0x%x (%s)\n",
+ ofile->f_fid, NewLevel, AckReq,
+ status, xlate_nt_status(status));
+
+ /*
+ * Note that the CompletionStatus from the FS level
+ * (smb_cmn_oplock.c) encodes what kind of action we
+ * need to take at the SMB level.
+ */
+ switch (status) {
+
+ case NT_STATUS_SUCCESS:
+ case NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK:
+ test_oplock_send_brk(ofile, NewLevel, AckReq);
+ break;
+
+ case NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE:
+ case NT_STATUS_OPLOCK_HANDLE_CLOSED:
+ og->og_state = OPLOCK_LEVEL_NONE;
+ break;
+
+ default:
+ /* Checked by caller. */
+ ASSERT(0);
+ break;
+ }
+}
+
+void
+smb_oplock_ind_break_in_ack(smb_request_t *sr, smb_ofile_t *ofile,
+ uint32_t NewLevel, boolean_t AckRequired)
+{
+ ASSERT(sr == &test_sr);
+ smb_oplock_ind_break(ofile, NewLevel, AckRequired, STATUS_CANT_GRANT);
+}
+
+uint32_t
+smb_oplock_wait_break(smb_node_t *node, int timeout)
+{
+ printf("*smb_oplock_wait_break (state=0x%x)\n",
+ node->n_oplock.ol_state);
+ return (0);
+}
+
+/*
+ * There are a couple DTRACE_PROBE* in smb_cmn_oplock.c but we're
+ * not linking with the user-level dtrace support, so just
+ * stub these out.
+ */
+void
+__dtrace_fksmb___probe1(char *n, unsigned long a)
+{
+}
+void
+__dtrace_fksmb___probe2(char *n, unsigned long a, unsigned long b)
+{
+}
diff --git a/usr/src/cmd/smbsrv/testoplock/tol_misc.c b/usr/src/cmd/smbsrv/testoplock/tol_misc.c
new file mode 100644
index 0000000000..55a36433ef
--- /dev/null
+++ b/usr/src/cmd/smbsrv/testoplock/tol_misc.c
@@ -0,0 +1,179 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * A few excerpts from smb_kutil.c
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/atomic.h>
+#include <sys/debug.h>
+#include <sys/time.h>
+#include <sys/stddef.h>
+#include <smbsrv/smb_kproto.h>
+
+
+/*
+ * smb_llist_constructor
+ *
+ * This function initializes a locked list.
+ */
+void
+smb_llist_constructor(
+ smb_llist_t *ll,
+ size_t size,
+ size_t offset)
+{
+ rw_init(&ll->ll_lock, NULL, RW_DEFAULT, NULL);
+ mutex_init(&ll->ll_mutex, NULL, MUTEX_DEFAULT, NULL);
+ list_create(&ll->ll_list, size, offset);
+ list_create(&ll->ll_deleteq, sizeof (smb_dtor_t),
+ offsetof(smb_dtor_t, dt_lnd));
+ ll->ll_count = 0;
+ ll->ll_wrop = 0;
+ ll->ll_deleteq_count = 0;
+ ll->ll_flushing = B_FALSE;
+}
+
+/*
+ * Flush the delete queue and destroy a locked list.
+ */
+void
+smb_llist_destructor(
+ smb_llist_t *ll)
+{
+ /* smb_llist_flush(ll); */
+
+ ASSERT(ll->ll_count == 0);
+ ASSERT(ll->ll_deleteq_count == 0);
+
+ rw_destroy(&ll->ll_lock);
+ list_destroy(&ll->ll_list);
+ list_destroy(&ll->ll_deleteq);
+ mutex_destroy(&ll->ll_mutex);
+}
+
+void
+smb_llist_enter(smb_llist_t *ll, krw_t mode)
+{
+ rw_enter(&ll->ll_lock, mode);
+}
+
+/*
+ * Exit the list lock and process the delete queue.
+ */
+void
+smb_llist_exit(smb_llist_t *ll)
+{
+ rw_exit(&ll->ll_lock);
+ /* smb_llist_flush(ll); */
+}
+
+/*
+ * smb_llist_upgrade
+ *
+ * This function tries to upgrade the lock of the locked list. It assumes the
+ * locked has already been entered in RW_READER mode. It first tries using the
+ * Solaris function rw_tryupgrade(). If that call fails the lock is released
+ * and reentered in RW_WRITER mode. In that last case a window is opened during
+ * which the contents of the list may have changed. The return code indicates
+ * whether or not the list was modified when the lock was exited.
+ */
+int smb_llist_upgrade(
+ smb_llist_t *ll)
+{
+ uint64_t wrop;
+
+ if (rw_tryupgrade(&ll->ll_lock) != 0) {
+ return (0);
+ }
+ wrop = ll->ll_wrop;
+ rw_exit(&ll->ll_lock);
+ rw_enter(&ll->ll_lock, RW_WRITER);
+ return (wrop != ll->ll_wrop);
+}
+
+/*
+ * smb_llist_insert_head
+ *
+ * This function inserts the object passed a the beginning of the list. This
+ * function assumes the lock of the list has already been entered.
+ */
+void
+smb_llist_insert_head(
+ smb_llist_t *ll,
+ void *obj)
+{
+ list_insert_head(&ll->ll_list, obj);
+ ++ll->ll_wrop;
+ ++ll->ll_count;
+}
+
+/*
+ * smb_llist_insert_tail
+ *
+ * This function appends to the object passed to the list. This function assumes
+ * the lock of the list has already been entered.
+ *
+ */
+void
+smb_llist_insert_tail(
+ smb_llist_t *ll,
+ void *obj)
+{
+ list_insert_tail(&ll->ll_list, obj);
+ ++ll->ll_wrop;
+ ++ll->ll_count;
+}
+
+/*
+ * smb_llist_remove
+ *
+ * This function removes the object passed from the list. This function assumes
+ * the lock of the list has already been entered.
+ */
+void
+smb_llist_remove(
+ smb_llist_t *ll,
+ void *obj)
+{
+ list_remove(&ll->ll_list, obj);
+ ++ll->ll_wrop;
+ --ll->ll_count;
+}
+
+/*
+ * smb_llist_get_count
+ *
+ * This function returns the number of elements in the specified list.
+ */
+uint32_t
+smb_llist_get_count(
+ smb_llist_t *ll)
+{
+ return (ll->ll_count);
+}
diff --git a/usr/src/lib/libfakekernel/common/rwlock.c b/usr/src/lib/libfakekernel/common/rwlock.c
index edc9bfd092..a018612987 100644
--- a/usr/src/lib/libfakekernel/common/rwlock.c
+++ b/usr/src/lib/libfakekernel/common/rwlock.c
@@ -10,7 +10,7 @@
*/
/*
- * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
/*
@@ -71,6 +71,8 @@ rw_exit(krwlock_t *rwlp)
if (_rw_write_held(&rwlp->rw_lock)) {
ASSERT(rwlp->rw_owner == _curthread());
rwlp->rw_owner = _KTHREAD_INVALID;
+ } else {
+ ASSERT(_rw_read_held(&rwlp->rw_lock));
}
(void) rw_unlock(&rwlp->rw_lock);
}
diff --git a/usr/src/lib/libshare/smb/libshare_smb.c b/usr/src/lib/libshare/smb/libshare_smb.c
index 79703b90ea..5bb4890da2 100644
--- a/usr/src/lib/libshare/smb/libshare_smb.c
+++ b/usr/src/lib/libshare/smb/libshare_smb.c
@@ -178,6 +178,7 @@ struct option_defs optdefs[] = {
{ SHOPT_GUEST, OPT_TYPE_BOOLEAN },
{ SHOPT_DFSROOT, OPT_TYPE_BOOLEAN },
{ SHOPT_DESCRIPTION, OPT_TYPE_STRING },
+ { SHOPT_FSO, OPT_TYPE_BOOLEAN },
{ SHOPT_QUOTAS, OPT_TYPE_BOOLEAN },
{ NULL, NULL }
};
@@ -917,6 +918,8 @@ struct smb_proto_option_defs {
disposition_validator, SMB_REFRESH_REFRESH },
{ SMB_CI_MAX_PROTOCOL, 0, MAX_VALUE_BUFLEN, max_protocol_validator,
SMB_REFRESH_REFRESH },
+ { SMB_CI_OPLOCK_ENABLE, 0, 0, true_false_validator,
+ SMB_REFRESH_REFRESH },
};
#define SMB_OPT_NUM \
@@ -2160,6 +2163,9 @@ smb_build_shareinfo(sa_share_t share, sa_resource_t resource, smb_share_t *si)
if (smb_saprop_getbool(opts, SHOPT_DFSROOT, B_FALSE))
si->shr_flags |= SMB_SHRF_DFSROOT;
+ if (smb_saprop_getbool(opts, SHOPT_FSO, B_FALSE))
+ si->shr_flags |= SMB_SHRF_FSO;
+
/* Quotas are enabled by default. */
si->shr_flags |= SMB_SHRF_QUOTAS;
if (!smb_saprop_getbool(opts, SHOPT_QUOTAS, B_TRUE))
diff --git a/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com b/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com
index db76d55f78..723a4924ec 100644
--- a/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com
+++ b/usr/src/lib/smbsrv/libfksmbsrv/Makefile.com
@@ -54,6 +54,7 @@ OBJS_FS_SMBSRV = \
smb_alloc.o \
smb_authenticate.o \
smb_close.o \
+ smb_cmn_oplock.o \
smb_cmn_rename.o \
smb_cmn_setfile.o \
smb_common_open.o \
@@ -108,6 +109,7 @@ OBJS_FS_SMBSRV = \
smb_session_setup_andx.o \
smb_set_fileinfo.o \
smb_signing.o \
+ smb_srv_oplock.o \
smb_thread.o \
smb_tree.o \
smb_trans2_create_directory.o \
@@ -131,6 +133,7 @@ OBJS_FS_SMBSRV = \
smb2_echo.o \
smb2_flush.o \
smb2_ioctl.o \
+ smb2_lease.o \
smb2_lock.o \
smb2_logoff.o \
smb2_negotiate.o \
diff --git a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c
index 80d62d0534..4f0d6bf299 100644
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_init.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/types.h>
@@ -49,17 +49,11 @@
* with Windows NT4.0. Previous experiments with NT4.0 resulted in directory
* listing problems so this buffer size is configurable based on the end-user
* environment. When in doubt use 37KB.
- *
- * smb_raw_mode: read_raw and write_raw supported (1) or NOT supported (0).
*/
int smb_maxbufsize = SMB_NT_MAXBUF;
-int smb_oplock_levelII = 1;
-int smb_oplock_timeout = OPLOCK_STD_TIMEOUT;
-int smb_oplock_min_timeout = OPLOCK_MIN_TIMEOUT;
int smb_flush_required = 1;
int smb_dirsymlink_enable = 1;
int smb_sign_debug = 0;
-int smb_raw_mode = 0;
int smb_shortnames = 1;
uint_t smb_audit_flags =
#ifdef DEBUG
diff --git a/usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h b/usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h
index 49e476e7a5..f42d3b9c6f 100644
--- a/usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h
+++ b/usr/src/lib/smbsrv/libfksmbsrv/common/sys/sunddi.h
@@ -21,6 +21,7 @@
/*
* Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
#ifndef _SYS_SUNDDI_H
@@ -101,19 +102,7 @@ extern int ddi_strtoul(const char *, char **, int, unsigned long *);
extern int ddi_strtoll(const char *, char **, int, longlong_t *);
extern int ddi_strtoull(const char *, char **, int, u_longlong_t *);
-/*
- * kiconv functions and their macros.
- */
-#define KICONV_IGNORE_NULL (0x0001)
-#define KICONV_REPLACE_INVALID (0x0002)
-
-extern kiconv_t kiconv_open(const char *, const char *);
-extern size_t kiconv(kiconv_t, char **, size_t *, char **, size_t *, int *);
-extern int kiconv_close(kiconv_t);
-extern size_t kiconvstr(const char *, const char *, char *, size_t *, char *,
- size_t *, int, int *);
-
-#endif /* _KERNEL */
+#endif /* _KERNEL || _FAKE_KERNEL */
#ifdef __cplusplus
}
diff --git a/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c b/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c
index e94938684c..1ab3ba640e 100644
--- a/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c
+++ b/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c
@@ -770,6 +770,10 @@ smb_shr_modify(smb_share_t *new_si)
si->shr_flags &= ~SMB_SHRF_DFSROOT;
si->shr_flags |= flag;
+ flag = (new_si->shr_flags & SMB_SHRF_FSO);
+ si->shr_flags &= ~SMB_SHRF_FSO;
+ si->shr_flags |= flag;
+
flag = (new_si->shr_flags & SMB_SHRF_QUOTAS);
si->shr_flags &= ~SMB_SHRF_QUOTAS;
si->shr_flags |= flag;
@@ -1769,6 +1773,12 @@ smb_shr_sa_get(sa_share_t share, sa_resource_t resource, smb_share_t *si)
free(val);
}
+ val = smb_shr_sa_getprop(opts, SHOPT_FSO);
+ if (val != NULL) {
+ smb_shr_sa_setflag(val, si, SMB_SHRF_FSO);
+ free(val);
+ }
+
val = smb_shr_sa_getprop(opts, SHOPT_QUOTAS);
if (val != NULL) {
/* Turn the flag on or off */
@@ -2546,6 +2556,8 @@ smb_shr_encode(smb_share_t *si, nvlist_t **nvlist)
rc |= nvlist_add_string(smb, SHOPT_GUEST, "true");
if ((si->shr_flags & SMB_SHRF_DFSROOT) != 0)
rc |= nvlist_add_string(smb, SHOPT_DFSROOT, "true");
+ if ((si->shr_flags & SMB_SHRF_FSO) != 0)
+ rc |= nvlist_add_string(smb, SHOPT_FSO, "true");
if ((si->shr_flags & SMB_SHRF_QUOTAS) != 0)
rc |= nvlist_add_string(smb, SHOPT_QUOTAS, "true");
diff --git a/usr/src/man/man4/smb.4 b/usr/src/man/man4/smb.4
index 27043028b2..5fbc3fca3c 100644
--- a/usr/src/man/man4/smb.4
+++ b/usr/src/man/man4/smb.4
@@ -347,6 +347,21 @@ string that represents a domain name. By default, no value is set.
.sp
.ne 2
.na
+\fB\fBoplock_enable\fR\fR
+.ad
+.sp .6
+.RS 4n
+Controls whether "oplocks" may be granted by the SMB server.
+The term "oplock" is short for "opportunistic lock", which is
+the legacy name for cache delegations in SMB.
+By default, oplocks are enabled.
+Note that if oplocks are disabled, file I/O perfrormance may be
+severely reduced.
+.RE
+
+.sp
+.ne 2
+.na
\fB\fBpdc\fR\fR
.ad
.sp .6
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index aa8669de46..097620a150 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -1123,6 +1123,7 @@ SMBSRV_OBJS += $(SMBSRV_SHARED_OBJS) \
smb_alloc.o \
smb_authenticate.o \
smb_close.o \
+ smb_cmn_oplock.o \
smb_cmn_rename.o \
smb_cmn_setfile.o \
smb_common_open.o \
@@ -1183,6 +1184,7 @@ SMBSRV_OBJS += $(SMBSRV_SHARED_OBJS) \
smb_set_fileinfo.o \
smb_sign_kcf.o \
smb_signing.o \
+ smb_srv_oplock.o \
smb_thread.o \
smb_tree.o \
smb_trans2_create_directory.o \
@@ -1206,6 +1208,7 @@ SMBSRV_OBJS += $(SMBSRV_SHARED_OBJS) \
smb2_echo.o \
smb2_flush.o \
smb2_ioctl.o \
+ smb2_lease.o \
smb2_lock.o \
smb2_logoff.o \
smb2_negotiate.o \
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_create.c b/usr/src/uts/common/fs/smbsrv/smb2_create.c
index 473f878ba3..6aab3c5127 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_create.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_create.c
@@ -24,6 +24,23 @@
#define DH_PERSISTENT SMB2_DHANDLE_FLAG_PERSISTENT
/*
+ * Compile-time check that the SMB2_LEASE_... definitions
+ * match the (internal) equivalents from ntifs.h
+ */
+#if SMB2_LEASE_NONE != OPLOCK_LEVEL_NONE
+#error "SMB2_LEASE_NONE"
+#endif
+#if SMB2_LEASE_READ_CACHING != OPLOCK_LEVEL_CACHE_READ
+#error "SMB2_LEASE_READ_CACHING"
+#endif
+#if SMB2_LEASE_HANDLE_CACHING != OPLOCK_LEVEL_CACHE_HANDLE
+#error "SMB2_LEASE_HANDLE_CACHING"
+#endif
+#if SMB2_LEASE_WRITE_CACHING != OPLOCK_LEVEL_CACHE_WRITE
+#error "SMB2_LEASE_WRITE_CACHING"
+#endif
+
+/*
* Some flags used locally to keep track of which Create Context
* names have been provided and/or requested.
*/
@@ -64,6 +81,7 @@ typedef struct smb2_create_ctx {
smb2_create_ctx_elem_t cc_out_max_access;
smb2_create_ctx_elem_t cc_out_file_id;
smb2_create_ctx_elem_t cc_out_aapl;
+ smb2_create_ctx_elem_t cc_out_req_lease;
smb2_create_ctx_elem_t cc_out_dh_request;
smb2_create_ctx_elem_t cc_out_dh_request_v2;
} smb2_create_ctx_t;
@@ -88,7 +106,6 @@ smb2_create(smb_request_t *sr)
smb_ofile_t *of = NULL;
uint16_t StructSize;
uint8_t SecurityFlags;
- uint8_t OplockLevel;
uint32_t ImpersonationLevel;
uint64_t SmbCreateFlags;
uint64_t Reserved4;
@@ -110,7 +127,6 @@ smb2_create(smb_request_t *sr)
* if we already have one, release it now.
*/
if (sr->fid_ofile != NULL) {
- smb_ofile_request_complete(sr->fid_ofile);
smb_ofile_release(sr->fid_ofile);
sr->fid_ofile = NULL;
}
@@ -283,15 +299,26 @@ smb2_create(smb_request_t *sr)
CCTX_TIMEWARP_TOKEN |
CCTX_QUERY_ON_DISK_ID);
+ /*
+ * Reconnect check needs to know if a lease was requested.
+ * The requested oplock level is ignored in reconnect, so
+ * using op_oplock_level to convey this info.
+ */
+ if (cctx.cc_in_flags & CCTX_REQUEST_LEASE)
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+ else
+ op->op_oplock_level = 0;
+
status = smb2_dh_reconnect(sr);
if (status != NT_STATUS_SUCCESS)
goto cmd_done;
/*
- * Skip most open execution during reconnect.
+ * Skip most open execution during reconnect,
+ * but need (reclaimed) oplock state in *op.
*/
of = sr->fid_ofile;
-
+ smb2_oplock_reconnect(sr);
goto reconnect_done;
}
@@ -301,28 +328,57 @@ smb2_create(smb_request_t *sr)
/*
* Validate the requested oplock level.
- * Convert the SMB2 oplock level into SMB1 form.
+ * Conversion to internal form is in smb2_oplock_acquire()
*/
switch (op->op_oplock_level) {
- case SMB2_OPLOCK_LEVEL_NONE:
- op->op_oplock_level = SMB_OPLOCK_NONE;
- break;
- case SMB2_OPLOCK_LEVEL_II:
- op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
- break;
- case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
- op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
+ case SMB2_OPLOCK_LEVEL_NONE: /* OPLOCK_LEVEL_NONE */
+ case SMB2_OPLOCK_LEVEL_II: /* OPLOCK_LEVEL_TWO */
+ case SMB2_OPLOCK_LEVEL_EXCLUSIVE: /* OPLOCK_LEVEL_ONE */
+ case SMB2_OPLOCK_LEVEL_BATCH: /* OPLOCK_LEVEL_BATCH */
+ /*
+ * Ignore lease create context (if any)
+ */
+ cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE;
break;
- case SMB2_OPLOCK_LEVEL_BATCH:
- op->op_oplock_level = SMB_OPLOCK_BATCH;
+
+ case SMB2_OPLOCK_LEVEL_LEASE: /* OPLOCK_LEVEL_GRANULAR */
+ /*
+ * Require a lease create context.
+ */
+ if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) == 0) {
+ cmn_err(CE_NOTE, "smb2:create, oplock=ff and no lease");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cmd_done;
+ }
+
+ /*
+ * Validate lease request state
+ * Only a few valid combinations.
+ */
+ switch (op->lease_state) {
+ case SMB2_LEASE_NONE:
+ case SMB2_LEASE_READ_CACHING:
+ case SMB2_LEASE_READ_CACHING | SMB2_LEASE_HANDLE_CACHING:
+ case SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING:
+ case SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING |
+ SMB2_LEASE_HANDLE_CACHING:
+ break;
+
+ default:
+ /*
+ * Invalid lease state flags
+ * Just force to "none".
+ */
+ op->lease_state = SMB2_LEASE_NONE;
+ break;
+ }
break;
- case SMB2_OPLOCK_LEVEL_LEASE: /* not yet */
+
default:
/* Unknown SMB2 oplock level. */
status = NT_STATUS_INVALID_PARAMETER;
goto cmd_done;
}
- op->op_oplock_levelII = B_TRUE;
/*
* Only disk trees get oplocks or leases.
@@ -385,28 +441,47 @@ smb2_create(smb_request_t *sr)
* non-durable handles in case we get the ioctl
* to set "resiliency" on this handle.
*/
- if (of->f_ftype == SMB_FTYPE_DISK) {
+ if (of->f_ftype == SMB_FTYPE_DISK)
smb_ofile_set_persistid(of);
+
+ /*
+ * [MS-SMB2] 3.3.5.9.8
+ * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
+ */
+ if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) != 0) {
+ status = smb2_lease_create(sr);
+ if (status != NT_STATUS_SUCCESS) {
+ if (op->action_taken == SMB_OACT_CREATED) {
+ smb_ofile_set_delete_on_close(sr, of);
+ }
+ goto cmd_done;
+ }
+ }
+ if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) {
+ smb2_lease_acquire(sr);
+ } else if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_NONE) {
+ smb2_oplock_acquire(sr);
}
/*
- * We're supposed to process Durable Handle requests
- * if any one of the following conditions is true:
+ * Make this a durable open, but only if:
+ * (durable handle requested and...)
*
- * 1. op_oplock_level == SMB_OPLOCK_BATCH
+ * 1. op_oplock_level == SMB2_OPLOCK_LEVEL_BATCH
* 2. A lease is requested with handle caching
* - for v1, the lease must not be on a directory
* 3. For v2, flags has "persistent" (tree is CA)
* (when tree not CA, turned off persist above)
*
- * Otherwise, the requests are ignored.
- * However, because we don't support leases or CA,
- * cases 2 and 3 are not of concern to us yet.
+ * Otherwise, DH requests are ignored, so we set
+ * dh_vers = not durable
*/
if ((cctx.cc_in_flags &
(CCTX_DH_REQUEST|CCTX_DH_REQUEST_V2)) != 0 &&
smb_node_is_file(of->f_node) &&
- (op->op_oplock_level == SMB_OPLOCK_BATCH)) {
+ ((op->op_oplock_level == SMB2_OPLOCK_LEVEL_BATCH) ||
+ (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE &&
+ (op->lease_state & OPLOCK_LEVEL_CACHE_HANDLE) != 0))) {
/*
* OK, make this handle "durable"
*/
@@ -456,7 +531,7 @@ reconnect_done:
case STYPE_DISKTREE:
case STYPE_PRINTQ:
if (op->create_options & FILE_DELETE_ON_CLOSE)
- smb_ofile_set_delete_on_close(of);
+ smb_ofile_set_delete_on_close(sr, of);
break;
}
@@ -499,6 +574,16 @@ reconnect_done:
status = 0;
}
+ /*
+ * If a lease was requested, and we got one...
+ */
+ if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) != 0 &&
+ op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)
+ cctx.cc_out_flags |= CCTX_REQUEST_LEASE;
+
+ /*
+ * If a durable handle was requested and we got one...
+ */
if ((cctx.cc_in_flags & CCTX_DH_REQUEST) != 0 &&
of->dh_vers == SMB2_DURABLE_V1) {
cctx.cc_out_flags |= CCTX_DH_REQUEST;
@@ -531,26 +616,6 @@ cmd_done:
}
/*
- * Convert the negotiated Oplock level back into
- * SMB2 encoding form.
- */
- switch (op->op_oplock_level) {
- default:
- case SMB_OPLOCK_NONE:
- OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
- break;
- case SMB_OPLOCK_LEVEL_II:
- OplockLevel = SMB2_OPLOCK_LEVEL_II;
- break;
- case SMB_OPLOCK_EXCLUSIVE:
- OplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
- break;
- case SMB_OPLOCK_BATCH:
- OplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
- break;
- }
-
- /*
* Encode the SMB2 Create reply
*/
attr = &op->fqi.fq_fattr;
@@ -558,7 +623,7 @@ cmd_done:
&sr->reply,
"wb.lTTTTqqllqqll",
89, /* StructSize */ /* w */
- OplockLevel, /* b */
+ op->op_oplock_level, /* b */
op->action_taken, /* l */
&attr->sa_crtime, /* T */
&attr->sa_vattr.va_atime, /* T */
@@ -790,6 +855,40 @@ smb2_decode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc)
op->create_timewarp = B_TRUE;
break;
+ /*
+ * Note: This handles both V1 and V2 leases,
+ * which differ only by their length.
+ */
+ case SMB2_CREATE_REQUEST_LEASE: /* ("RqLs") */
+ if (data_len == 52) {
+ op->lease_version = 2;
+ } else if (data_len == 32) {
+ op->lease_version = 1;
+ } else {
+ cmn_err(CE_NOTE, "Cctx RqLs bad len=0x%x",
+ data_len);
+ }
+ rc = smb_mbc_decodef(&cce->cce_mbc, "#cllq",
+ UUID_LEN, /* # */
+ op->lease_key, /* c */
+ &op->lease_state, /* l */
+ &op->lease_flags, /* l */
+ &nttime); /* (ignored) q */
+ if (rc != 0)
+ goto errout;
+ if (op->lease_version == 2) {
+ rc = smb_mbc_decodef(&cce->cce_mbc,
+ "#cw..",
+ UUID_LEN,
+ op->parent_lease_key,
+ &op->lease_epoch);
+ if (rc != 0)
+ goto errout;
+ } else {
+ bzero(op->parent_lease_key, UUID_LEN);
+ }
+ break;
+
case SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2: /* ("DH2C") */
rc = smb_mbc_decodef(&cce->cce_mbc, "qq#cl",
&op->dh_fileid.persistent, /* q */
@@ -926,6 +1025,34 @@ smb2_encode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc)
mbc->chain_offset - last_top);
}
+ if (cc->cc_out_flags & CCTX_REQUEST_LEASE) {
+ cce = &cc->cc_out_req_lease;
+
+ cce->cce_mbc.max_bytes = cce->cce_len = 32;
+ (void) smb_mbc_encodef(&cce->cce_mbc, "#cllq",
+ UUID_LEN, /* # */
+ op->lease_key, /* c */
+ op->lease_state, /* l */
+ op->lease_flags, /* l */
+ 0LL); /* q */
+ if (op->lease_version == 2) {
+ cce->cce_mbc.max_bytes = cce->cce_len = 52;
+ (void) smb_mbc_encodef(&cce->cce_mbc,
+ "#cw..",
+ UUID_LEN,
+ op->parent_lease_key,
+ op->lease_epoch);
+ }
+
+ last_top = mbc->chain_offset;
+ rc = smb2_encode_create_ctx_elem(mbc, cce,
+ SMB2_CREATE_REQUEST_LEASE);
+ if (rc)
+ return (NT_STATUS_INTERNAL_ERROR);
+ (void) smb_mbc_poke(mbc, last_top, "l",
+ mbc->chain_offset - last_top);
+ }
+
if (cc->cc_out_flags & CCTX_DH_REQUEST) {
cce = &cc->cc_out_dh_request;
@@ -1031,6 +1158,10 @@ smb2_free_create_ctx(smb2_create_ctx_t *cc)
cce = &cc->cc_out_aapl;
MBC_FLUSH(&cce->cce_mbc);
}
+ if (cc->cc_out_flags & CCTX_REQUEST_LEASE) {
+ cce = &cc->cc_out_req_lease;
+ MBC_FLUSH(&cce->cce_mbc);
+ }
if (cc->cc_out_flags & CCTX_DH_REQUEST) {
cce = &cc->cc_out_dh_request;
MBC_FLUSH(&cce->cce_mbc);
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c
index f1d3a67c3c..7c0fcea968 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c
@@ -22,6 +22,7 @@
smb_sdrc_t smb2_invalid_cmd(smb_request_t *);
static void smb2_tq_work(void *);
+static void smb2sr_run_postwork(smb_request_t *);
static const smb_disp_entry_t
smb2_disp_table[SMB2__NCMDS] = {
@@ -569,7 +570,6 @@ cmd_start:
* avoid dangling references: file, tree, user
*/
if (sr->fid_ofile != NULL) {
- smb_ofile_request_complete(sr->fid_ofile);
smb_ofile_release(sr->fid_ofile);
sr->fid_ofile = NULL;
}
@@ -842,6 +842,9 @@ cleanup:
if (disconnect)
smb_session_disconnect(session);
+ if (sr->sr_postwork != NULL)
+ smb2sr_run_postwork(sr);
+
mutex_enter(&sr->sr_mutex);
sr->sr_state = SMB_REQ_STATE_COMPLETED;
mutex_exit(&sr->sr_mutex);
@@ -1416,3 +1419,57 @@ smb2_dispatch_stats_update(smb_server_t *sv,
}
}
}
+
+/*
+ * Append new_sr to the postwork queue. sr->smb2_cmd_code encodes
+ * the action that should be run by this sr.
+ *
+ * This queue is rarely used (and normally empty) so we're OK
+ * using a simple "walk to tail and insert" here.
+ */
+void
+smb2sr_append_postwork(smb_request_t *top_sr, smb_request_t *new_sr)
+{
+ smb_request_t *last_sr;
+
+ ASSERT(top_sr->session->dialect >= SMB_VERS_2_BASE);
+
+ last_sr = top_sr;
+ while (last_sr->sr_postwork != NULL)
+ last_sr = last_sr->sr_postwork;
+
+ last_sr->sr_postwork = new_sr;
+}
+
+/*
+ * Run any "post work" that was appended to the main SR while it
+ * was running. This is called after the request has been sent
+ * for the main SR, and used in cases i.e. the oplock code, where
+ * we need to send something to the client only _after_ the main
+ * sr request has gone out.
+ */
+static void
+smb2sr_run_postwork(smb_request_t *top_sr)
+{
+ smb_request_t *post_sr; /* the one we're running */
+ smb_request_t *next_sr;
+
+ while ((post_sr = top_sr->sr_postwork) != NULL) {
+ next_sr = post_sr->sr_postwork;
+ top_sr->sr_postwork = next_sr;
+ post_sr->sr_postwork = NULL;
+
+ post_sr->sr_worker = top_sr->sr_worker;
+ post_sr->sr_state = SMB_REQ_STATE_ACTIVE;
+
+ switch (post_sr->smb2_cmd_code) {
+ case SMB2_OPLOCK_BREAK:
+ smb_oplock_send_brk(post_sr);
+ break;
+ default:
+ ASSERT(0);
+ }
+ post_sr->sr_state = SMB_REQ_STATE_COMPLETED;
+ smb_request_free(post_sr);
+ }
+}
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_durable.c b/usr/src/uts/common/fs/smbsrv/smb2_durable.c
index 5a253bc6d2..981b09a28e 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_durable.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_durable.c
@@ -93,14 +93,24 @@ smb_dh_should_save(smb_ofile_t *of)
if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_ALL)
return (B_TRUE);
- if (of->dh_vers == SMB2_RESILIENT)
+ switch (of->dh_vers) {
+ case SMB2_RESILIENT:
return (B_TRUE);
- if (!SMB_OFILE_OPLOCK_GRANTED(of))
- return (B_FALSE);
-
- if (of->f_oplock_grant.og_level == SMB_OPLOCK_BATCH)
- return (B_TRUE);
+ case SMB2_DURABLE_V2:
+ if (of->dh_persist)
+ return (B_TRUE);
+ /* FALLTHROUGH */
+ case SMB2_DURABLE_V1:
+ /* IS durable (v1 or v2) */
+ if ((of->f_oplock.og_state & (OPLOCK_LEVEL_BATCH |
+ OPLOCK_LEVEL_CACHE_HANDLE)) != 0)
+ return (B_TRUE);
+ /* FALLTHROUGH */
+ case SMB2_NOT_DURABLE:
+ default:
+ break;
+ }
return (B_FALSE);
}
@@ -127,6 +137,40 @@ static uint32_t
smb2_dh_reconnect_checks(smb_request_t *sr, smb_ofile_t *of)
{
smb_arg_open_t *op = &sr->sr_open;
+ char *fname;
+
+ if (of->f_lease != NULL) {
+ if (bcmp(sr->session->clnt_uuid,
+ of->f_lease->ls_clnt, 16) != 0)
+ return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+ if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_LEASE)
+ return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ if (bcmp(op->lease_key, of->f_lease->ls_key,
+ SMB_LEASE_KEY_SZ) != 0)
+ return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+ /*
+ * We're supposed to check the name is the same.
+ * Not really necessary to do this, so just do
+ * minimal effort (check last component)
+ */
+ fname = strrchr(op->fqi.fq_path.pn_path, '\\');
+ if (fname != NULL)
+ fname++;
+ else
+ fname = op->fqi.fq_path.pn_path;
+ if (smb_strcasecmp(fname, of->f_node->od_name, 0) != 0) {
+#ifdef DEBUG
+ cmn_err(CE_NOTE, "reconnect name <%s> of name <%s>",
+ fname, of->f_node->od_name);
+#endif
+ return (NT_STATUS_INVALID_PARAMETER);
+ }
+ } else {
+ if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)
+ return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ }
if (op->dh_vers == SMB2_DURABLE_V2) {
boolean_t op_persist =
@@ -218,8 +262,6 @@ smb2_dh_reconnect(smb_request_t *sr)
of->f_tree = tree;
of->f_fid = fid;
- op->op_oplock_level = of->f_oplock_grant.og_level;
-
smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
smb_llist_insert_tail(&tree->t_ofile_list, of);
smb_llist_exit(&tree->t_ofile_list);
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_lease.c b/usr/src/uts/common/fs/smbsrv/smb2_lease.c
new file mode 100644
index 0000000000..d2bf4805b3
--- /dev/null
+++ b/usr/src/uts/common/fs/smbsrv/smb2_lease.c
@@ -0,0 +1,720 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * Dispatch function for SMB2_OPLOCK_BREAK
+ */
+
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+/* StructSize for the two "break" message formats. */
+#define SSZ_OPLOCK 24
+#define SSZ_LEASE_ACK 36
+#define SSZ_LEASE_BRK 44
+
+#define NODE_FLAGS_DELETING (NODE_FLAGS_DELETE_ON_CLOSE |\
+ NODE_FLAGS_DELETE_COMMITTED)
+
+static const char lease_zero[UUID_LEN] = { 0 };
+
+static kmem_cache_t *smb_lease_cache = NULL;
+
+void
+smb2_lease_init()
+{
+ if (smb_lease_cache != NULL)
+ return;
+
+ smb_lease_cache = kmem_cache_create("smb_lease_cache",
+ sizeof (smb_lease_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
+}
+
+void
+smb2_lease_fini()
+{
+ if (smb_lease_cache != NULL) {
+ kmem_cache_destroy(smb_lease_cache);
+ smb_lease_cache = NULL;
+ }
+}
+
+static void
+smb2_lease_hold(smb_lease_t *ls)
+{
+ mutex_enter(&ls->ls_mutex);
+ ls->ls_refcnt++;
+ mutex_exit(&ls->ls_mutex);
+}
+
+void
+smb2_lease_rele(smb_lease_t *ls)
+{
+ smb_llist_t *bucket;
+
+ mutex_enter(&ls->ls_mutex);
+ ls->ls_refcnt--;
+ if (ls->ls_refcnt != 0) {
+ mutex_exit(&ls->ls_mutex);
+ return;
+ }
+ mutex_exit(&ls->ls_mutex);
+
+ /*
+ * Get the list lock, then re-check the refcnt
+ * and if it's still zero, unlink & destroy.
+ */
+ bucket = ls->ls_bucket;
+ smb_llist_enter(bucket, RW_WRITER);
+
+ mutex_enter(&ls->ls_mutex);
+ if (ls->ls_refcnt == 0)
+ smb_llist_remove(bucket, ls);
+ mutex_exit(&ls->ls_mutex);
+
+ if (ls->ls_refcnt == 0) {
+ mutex_destroy(&ls->ls_mutex);
+ kmem_cache_free(smb_lease_cache, ls);
+ }
+
+ smb_llist_exit(bucket);
+}
+
+/*
+ * Compute a hash from a uuid
+ * Based on mod_hash_bystr()
+ */
+static uint_t
+smb_hash_uuid(const uint8_t *uuid)
+{
+ char *k = (char *)uuid;
+ uint_t hash = 0;
+ uint_t g;
+ int i;
+
+ ASSERT(k);
+ for (i = 0; i < UUID_LEN; i++) {
+ hash = (hash << 4) + k[i];
+ if ((g = (hash & 0xf0000000)) != 0) {
+ hash ^= (g >> 24);
+ hash ^= g;
+ }
+ }
+ return (hash);
+}
+
+/*
+ * Add or update a lease table entry for a new ofile.
+ * (in the per-session lease table)
+ * See [MS-SMB2] 3.3.5.9.8
+ * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
+ */
+uint32_t
+smb2_lease_create(smb_request_t *sr)
+{
+ smb_arg_open_t *op = &sr->arg.open;
+ uint8_t *key = op->lease_key;
+ uint8_t *clnt = sr->session->clnt_uuid;
+ smb_ofile_t *of = sr->fid_ofile;
+ smb_hash_t *ht = sr->sr_server->sv_lease_ht;
+ smb_llist_t *bucket;
+ smb_lease_t *lease;
+ smb_lease_t *newlease;
+ size_t hashkey;
+ uint32_t status = NT_STATUS_INVALID_PARAMETER;
+
+ if (bcmp(key, lease_zero, UUID_LEN) == 0)
+ return (status);
+
+ /*
+ * Find or create, and add a ref for the new ofile.
+ */
+ hashkey = smb_hash_uuid(key);
+ hashkey &= (ht->num_buckets - 1);
+ bucket = &ht->buckets[hashkey].b_list;
+
+ newlease = kmem_cache_alloc(smb_lease_cache, KM_SLEEP);
+ bzero(newlease, sizeof (smb_lease_t));
+ mutex_init(&newlease->ls_mutex, NULL, MUTEX_DEFAULT, NULL);
+ newlease->ls_bucket = bucket;
+ newlease->ls_node = of->f_node;
+ newlease->ls_refcnt = 1;
+ newlease->ls_epoch = op->lease_epoch;
+ newlease->ls_version = op->lease_version;
+ bcopy(key, newlease->ls_key, UUID_LEN);
+ bcopy(clnt, newlease->ls_clnt, UUID_LEN);
+
+ smb_llist_enter(bucket, RW_WRITER);
+ for (lease = smb_llist_head(bucket); lease != NULL;
+ lease = smb_llist_next(bucket, lease)) {
+ /*
+ * Looking for this lease ID, on a node
+ * that's not being deleted.
+ */
+ if (bcmp(lease->ls_key, key, UUID_LEN) == 0 &&
+ bcmp(lease->ls_clnt, clnt, UUID_LEN) == 0 &&
+ (lease->ls_node->flags & NODE_FLAGS_DELETING) == 0)
+ break;
+ }
+ if (lease != NULL) {
+ /*
+ * Found existing lease. Make sure it refers to
+ * the same node...
+ */
+ if (lease->ls_node == of->f_node) {
+ smb2_lease_hold(lease);
+ } else {
+ /* Same lease ID, different node! */
+#ifdef DEBUG
+ cmn_err(CE_NOTE, "new lease on node %p (%s) "
+ "conflicts with existing node %p (%s)",
+ (void *) of->f_node,
+ of->f_node->od_name,
+ (void *) lease->ls_node,
+ lease->ls_node->od_name);
+#endif
+ DTRACE_PROBE2(dup_lease, smb_request_t, sr,
+ smb_lease_t, lease);
+ lease = NULL; /* error */
+ }
+ } else {
+ lease = newlease;
+ smb_llist_insert_head(bucket, lease);
+ newlease = NULL; /* don't free */
+ }
+ smb_llist_exit(bucket);
+
+ if (newlease != NULL) {
+ mutex_destroy(&newlease->ls_mutex);
+ kmem_cache_free(smb_lease_cache, newlease);
+ }
+
+ if (lease != NULL) {
+ of->f_lease = lease;
+ status = NT_STATUS_SUCCESS;
+ }
+
+ return (status);
+}
+
+/*
+ * Find the lease for a given: client_uuid, lease_key
+ * Returns the lease with a new ref.
+ */
+smb_lease_t *
+smb2_lease_lookup(smb_server_t *sv, uint8_t *clnt_uuid, uint8_t *lease_key)
+{
+ smb_hash_t *ht = sv->sv_lease_ht;
+ smb_llist_t *bucket;
+ smb_lease_t *lease;
+ size_t hashkey;
+
+ hashkey = smb_hash_uuid(lease_key);
+ hashkey &= (ht->num_buckets - 1);
+ bucket = &ht->buckets[hashkey].b_list;
+
+ smb_llist_enter(bucket, RW_READER);
+ lease = smb_llist_head(bucket);
+ while (lease != NULL) {
+ if (bcmp(lease->ls_key, lease_key, UUID_LEN) == 0 &&
+ bcmp(lease->ls_clnt, clnt_uuid, UUID_LEN) == 0) {
+ smb2_lease_hold(lease);
+ break;
+ }
+ lease = smb_llist_next(bucket, lease);
+ }
+ smb_llist_exit(bucket);
+
+ return (lease);
+}
+
+/*
+ * Find an smb_ofile_t in the current tree that shares the
+ * specified lease and has some oplock breaking flags set.
+ * If lease not found, NT_STATUS_OBJECT_NAME_NOT_FOUND.
+ * If ofile not breaking NT_STATUS_UNSUCCESSFUL.
+ * On success, ofile (held) in sr->fid_ofile.
+ */
+static uint32_t
+find_breaking_ofile(smb_request_t *sr, uint8_t *lease_key)
+{
+ smb_tree_t *tree = sr->tid_tree;
+ smb_lease_t *lease;
+ smb_llist_t *of_list;
+ smb_ofile_t *o;
+ uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+ SMB_TREE_VALID(tree);
+ of_list = &tree->t_ofile_list;
+
+ smb_llist_enter(of_list, RW_READER);
+ for (o = smb_llist_head(of_list); o != NULL;
+ o = smb_llist_next(of_list, o)) {
+
+ ASSERT(o->f_magic == SMB_OFILE_MAGIC);
+ ASSERT(o->f_tree == tree);
+
+ if ((lease = o->f_lease) == NULL)
+ continue; // no lease
+
+ if (bcmp(lease->ls_key, lease_key, UUID_LEN) != 0)
+ continue; // wrong lease
+
+ /*
+ * Now we know the lease exists, so if we don't
+ * find an ofile with breaking flags, return:
+ */
+ status = NT_STATUS_UNSUCCESSFUL;
+
+ if (o->f_oplock.og_breaking == 0)
+ continue; // not breaking
+
+ /* Found breaking ofile. */
+ if (smb_ofile_hold(o)) {
+ sr->fid_ofile = o;
+ status = NT_STATUS_SUCCESS;
+ break;
+ }
+ }
+ smb_llist_exit(of_list);
+
+ return (status);
+}
+
+/*
+ * This is called by smb2_oplock_break_ack when the struct size
+ * indicates this is a lease break (SZ_LEASE). See:
+ * [MS-SMB2] 3.3.5.22.2 Processing a Lease Acknowledgment
+ */
+smb_sdrc_t
+smb2_lease_break_ack(smb_request_t *sr)
+{
+ smb_lease_t *lease;
+ smb_ofile_t *ofile;
+ uint8_t LeaseKey[UUID_LEN];
+ uint32_t LeaseState;
+ uint32_t LeaseBreakTo;
+ uint32_t status;
+ int rc = 0;
+
+ if (sr->session->dialect < SMB_VERS_2_1)
+ return (SDRC_ERROR);
+
+ /*
+ * Decode an SMB2 Lease Acknowldgement
+ * [MS-SMB2] 2.2.24.2
+ * Note: Struct size decoded by caller.
+ */
+ rc = smb_mbc_decodef(
+ &sr->smb_data, "6.#cl8.",
+ /* reserved 6. */
+ UUID_LEN, /* # */
+ LeaseKey, /* c */
+ &LeaseState); /* l */
+ /* duration 8. */
+ if (rc != 0)
+ return (SDRC_ERROR);
+
+ status = find_breaking_ofile(sr, LeaseKey);
+
+ DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
+ if (status != 0)
+ goto errout;
+
+ /* Success, so have sr->fid_ofile and lease */
+ ofile = sr->fid_ofile;
+ lease = ofile->f_lease;
+
+ /*
+ * Process the lease break ack.
+ *
+ * If the new LeaseState has any bits in excess of
+ * the lease state we sent in the break, error...
+ */
+ LeaseBreakTo = (lease->ls_breaking >> BREAK_SHIFT) &
+ OPLOCK_LEVEL_CACHE_MASK;
+ if ((LeaseState & ~LeaseBreakTo) != 0) {
+ status = NT_STATUS_REQUEST_NOT_ACCEPTED;
+ goto errout;
+ }
+
+ ofile->f_oplock.og_breaking = 0;
+ lease->ls_breaking = 0;
+
+ LeaseState |= OPLOCK_LEVEL_GRANULAR;
+ status = smb_oplock_ack_break(sr, ofile, &LeaseState);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(ofile->f_node, 0);
+ status = NT_STATUS_SUCCESS;
+ }
+
+ ofile->f_oplock.og_state = LeaseState;
+ lease->ls_state = LeaseState &
+ OPLOCK_LEVEL_CACHE_MASK;
+
+errout:
+ sr->smb2_status = status;
+ DTRACE_SMB2_DONE(op__OplockBreak, smb_request_t *, sr);
+ if (status) {
+ smb2sr_put_error(sr, status);
+ return (SDRC_SUCCESS);
+ }
+
+ /*
+ * Encode an SMB2 Lease Ack. response
+ * [MS-SMB2] 2.2.25.2
+ */
+ LeaseState &= OPLOCK_LEVEL_CACHE_MASK;
+ (void) smb_mbc_encodef(
+ &sr->reply, "w6.#cl8.",
+ SSZ_LEASE_ACK, /* w */
+ /* reserved 6. */
+ UUID_LEN, /* # */
+ LeaseKey, /* c */
+ LeaseState); /* l */
+ /* duration 8. */
+
+ return (SDRC_SUCCESS);
+
+}
+
+/*
+ * Compose an SMB2 Lease Break Notification packet, including
+ * the SMB2 header and everything, in sr->reply.
+ * The caller will send it and free the request.
+ *
+ * [MS-SMB2] 2.2.23.2 Lease Break Notification
+ */
+void
+smb2_lease_break_notification(smb_request_t *sr, uint32_t NewLevel,
+ boolean_t AckReq)
+{
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_oplock_grant_t *og = &ofile->f_oplock;
+ smb_lease_t *ls = ofile->f_lease;
+ uint32_t oldcache;
+ uint32_t newcache;
+ uint16_t Epoch;
+ uint16_t Flags;
+
+ /*
+ * Convert internal level to SMB2
+ */
+ oldcache = og->og_state & OPLOCK_LEVEL_CACHE_MASK;
+ newcache = NewLevel & OPLOCK_LEVEL_CACHE_MASK;
+ if (ls->ls_version < 2)
+ Epoch = 0;
+ else
+ Epoch = ls->ls_epoch;
+
+ /*
+ * SMB2 Header
+ */
+ sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
+ sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR;
+ sr->smb_tid = 0;
+ sr->smb_pid = 0;
+ sr->smb2_ssnid = 0;
+ sr->smb2_messageid = UINT64_MAX;
+ (void) smb2_encode_header(sr, B_FALSE);
+
+ /*
+ * SMB2 Oplock Break, variable part
+ *
+ * [MS-SMB2] says the current lease state preceeds the
+ * new lease state, but that looks like an error...
+ */
+ Flags = AckReq ? SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED : 0;
+ (void) smb_mbc_encodef(
+ &sr->reply, "wwl#cll4.4.4.",
+ SSZ_LEASE_BRK, /* w */
+ Epoch, /* w */
+ Flags, /* l */
+ SMB_LEASE_KEY_SZ, /* # */
+ ls->ls_key, /* c */
+ oldcache, /* cur.st l */
+ newcache); /* new.st l */
+ /* reserved (4.4.4.) */
+}
+
+/*
+ * Client has an open handle and requests a lease.
+ * Convert SMB2 lease request info in to internal form,
+ * call common oplock code, convert result to SMB2.
+ *
+ * If necessary, "go async" here.
+ */
+void
+smb2_lease_acquire(smb_request_t *sr)
+{
+ smb_arg_open_t *op = &sr->arg.open;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ smb_lease_t *lease = ofile->f_lease;
+ uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ uint32_t have, want; /* lease flags */
+ boolean_t NewGrant = B_FALSE;
+
+ /* Only disk trees get oplocks. */
+ ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
+
+ /*
+ * Only plain files (for now).
+ * Later, test SMB2_CAP_DIRECTORY_LEASING
+ */
+ if (!smb_node_is_file(ofile->f_node)) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+
+ if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+
+ /*
+ * SMB2: Convert to internal form.
+ * Caller should have setup the lease.
+ */
+ ASSERT(op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE);
+ ASSERT(lease != NULL);
+ if (lease == NULL) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
+ (op->lease_state & CACHE_RWH);
+
+ /*
+ * Tree options may force shared oplocks,
+ * in which case we reduce the request.
+ */
+ if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
+ op->op_oplock_state &= ~WRITE_CACHING;
+ }
+
+ /*
+ * Disallow downgrade
+ *
+ * Note that open with a lease is not allowed to turn off
+ * any cache rights. If the client tries to "downgrade",
+ * any bits, just return the existing lease cache bits.
+ */
+ have = lease->ls_state & CACHE_RWH;
+ want = op->op_oplock_state & CACHE_RWH;
+ if ((have & ~want) != 0) {
+ op->op_oplock_state = have |
+ OPLOCK_LEVEL_GRANULAR;
+ goto done;
+ }
+
+ /*
+ * Handle oplock requests in three parts:
+ * a: Requests with WRITE_CACHING
+ * b: Requests with HANDLE_CACHING
+ * c: Requests with READ_CACHING
+ * reducing the request before b and c.
+ *
+ * In each: first check if the lease grants the
+ * (possibly reduced) request, in which case we
+ * leave the lease unchanged and return what's
+ * granted by the lease. Otherwise, try to get
+ * the oplock, and if the succeeds, wait for any
+ * breaks, update the lease, and return.
+ */
+
+ /*
+ * Try exclusive (request is RW or RWH)
+ */
+ if ((op->op_oplock_state & WRITE_CACHING) != 0) {
+ want = op->op_oplock_state & CACHE_RWH;
+ if (have == want)
+ goto done;
+
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
+ if (status == NT_STATUS_SUCCESS ||
+ status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ NewGrant = B_TRUE;
+ goto done;
+ }
+
+ /*
+ * We did not get the exclusive oplock.
+ *
+ * There are odd rules about lease upgrade.
+ * If the existing lease grants R and the
+ * client fails to upgrade it to "RWH"
+ * (presumably due to handle conflicts)
+ * then just return the existing lease,
+ * even though upgrade to RH would work.
+ */
+ if (have != 0) {
+ op->op_oplock_state = have |
+ OPLOCK_LEVEL_GRANULAR;
+ goto done;
+ }
+
+ /*
+ * Keep trying without write.
+ * Need to re-init op_oplock_state
+ */
+ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
+ (op->lease_state & CACHE_RH);
+ }
+
+ /*
+ * Try shared ("RH")
+ */
+ if ((op->op_oplock_state & HANDLE_CACHING) != 0) {
+ want = op->op_oplock_state & CACHE_RWH;
+ if (have == want)
+ goto done;
+
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
+ if (status == NT_STATUS_SUCCESS ||
+ status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ NewGrant = B_TRUE;
+ goto done;
+ }
+
+ /*
+ * We did not get "RH", probably because
+ * ther were (old style) Level II oplocks.
+ * Continue, try for just read.
+ */
+ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR |
+ (op->lease_state & CACHE_R);
+ }
+
+ /*
+ * Try shared ("R")
+ */
+ if ((op->op_oplock_state & READ_CACHING) != 0) {
+ want = op->op_oplock_state & CACHE_RWH;
+ if (have == want)
+ goto done;
+
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
+ if (status == NT_STATUS_SUCCESS ||
+ status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ NewGrant = B_TRUE;
+ goto done;
+ }
+
+ /*
+ * We did not get "R".
+ * Fall into "none".
+ */
+ }
+
+ /*
+ * None of the above were able to get an oplock.
+ * The lease has no caching rights, and we didn't
+ * add any in this request. Return it as-is.
+ */
+ op->op_oplock_state = OPLOCK_LEVEL_GRANULAR;
+
+done:
+ if (NewGrant) {
+ /*
+ * After a new oplock grant, the status return
+ * may indicate we need to wait for breaks.
+ */
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(ofile->f_node, 0);
+ status = NT_STATUS_SUCCESS;
+ }
+ ASSERT(status == NT_STATUS_SUCCESS);
+
+ /*
+ * Keep track of what we got (in ofile->f_oplock.og_state)
+ * so we'll know what we had when sending a break later.
+ * Also update the lease with the new oplock state.
+ * Also track which ofile on the lease owns the oplock.
+ * The og_dialect here is the oplock dialect, not the
+ * SMB dialect. Leasing, so SMB 2.1 (or later).
+ */
+ ofile->f_oplock.og_dialect = SMB_VERS_2_1;
+ ofile->f_oplock.og_state = op->op_oplock_state;
+ mutex_enter(&lease->ls_mutex);
+ lease->ls_state = op->op_oplock_state & CACHE_RWH;
+ lease->ls_oplock_ofile = ofile;
+ lease->ls_epoch++;
+ mutex_exit(&lease->ls_mutex);
+ }
+
+ /*
+ * Convert internal oplock state to SMB2
+ */
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+ op->lease_state = lease->ls_state & CACHE_RWH;
+ op->lease_flags = (lease->ls_breaking != 0) ?
+ SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
+ op->lease_epoch = lease->ls_epoch;
+ op->lease_version = lease->ls_version;
+}
+
+/*
+ * This ofile has a lease and is about to close.
+ * Called by smb_ofile_close when there's a lease.
+ *
+ * With leases, just one ofile on a lease owns the oplock.
+ * If an ofile with a lease is closed and it's the one that
+ * owns the oplock, try to move the oplock to another ofile
+ * on the same lease.
+ */
+void
+smb2_lease_ofile_close(smb_ofile_t *ofile)
+{
+ smb_node_t *node = ofile->f_node;
+ smb_lease_t *lease = ofile->f_lease;
+ smb_ofile_t *o;
+
+ /*
+ * If this ofile was not the oplock owner for this lease,
+ * we can leave things as they are.
+ */
+ if (lease->ls_oplock_ofile != ofile)
+ return;
+
+ /*
+ * Find another ofile to which we can move the oplock.
+ * The ofile must be open and allow a new ref.
+ */
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ FOREACH_NODE_OFILE(node, o) {
+ if (o == ofile)
+ continue;
+ if (o->f_lease != lease)
+ continue;
+ /* If we can get a hold, use this ofile. */
+ if (smb_ofile_hold(o))
+ break;
+ }
+ if (o == NULL) {
+ /* Normal for last close on a lease. */
+ smb_llist_exit(&node->n_ofile_list);
+ return;
+ }
+ smb_oplock_move(node, ofile, o);
+ lease->ls_oplock_ofile = o;
+
+ smb_llist_exit(&node->n_ofile_list);
+ smb_ofile_release(o);
+}
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c b/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c
index bdff5bdbb5..4b3d30c066 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c
@@ -24,6 +24,7 @@ static int smb2_negotiate_common(smb_request_t *, uint16_t);
uint32_t smb2srv_capabilities =
SMB2_CAP_DFS |
+ SMB2_CAP_LEASING |
SMB2_CAP_LARGE_MTU;
/*
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c
index 2736435bdc..84bd8ccafb 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_oplock.c
@@ -19,68 +19,122 @@
#include <smbsrv/smb2_kproto.h>
+#define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
+
+/* StructSize for the two "break" message formats. */
+#define SSZ_OPLOCK 24
+#define SSZ_LEASE 36
+
/*
* SMB2 Oplock Break Acknowledgement
- * [MS-SMB2 2.2.24]
+ * [MS-SMB2] 3.3.5.22.1 Processing an Oplock Acknowledgment
+ * Called via smb2_disp_table[]
*/
smb_sdrc_t
smb2_oplock_break_ack(smb_request_t *sr)
{
- smb_node_t *node;
+ smb_ofile_t *ofile;
smb2fid_t smb2fid;
uint32_t status;
- uint16_t StructSize;
- uint8_t OplockLevel;
- uint8_t brk;
+ uint32_t NewLevel;
+ uint8_t smbOplockLevel;
int rc = 0;
+ uint16_t StructSize;
+
+ /*
+ * Decode the SMB2 Oplock Break Ack (24 bytes) or
+ * Lease Break Ack (36 bytes), starting with just
+ * the StructSize, which tells us what this is.
+ */
+ rc = smb_mbc_decodef(&sr->smb_data, "w", &StructSize);
+ if (rc != 0)
+ return (SDRC_ERROR);
+
+ if (StructSize == SSZ_LEASE) {
+ /* See smb2_lease.c */
+ return (smb2_lease_break_ack(sr));
+ }
+ if (StructSize != SSZ_OPLOCK)
+ return (SDRC_ERROR);
/*
- * Decode the SMB2 Oplock Break Ack.
+ * Decode an SMB2 Oplock Break Ack.
+ * [MS-SMB2] 2.2.24.1
+ * Note: Struct size decoded above.
*/
rc = smb_mbc_decodef(
- &sr->smb_data, "wb5.qq",
- &StructSize, /* w */
- &OplockLevel, /* b */
+ &sr->smb_data, "b5.qq",
+ &smbOplockLevel, /* b */
/* reserved 5. */
&smb2fid.persistent, /* q */
&smb2fid.temporal); /* q */
- if (rc || StructSize != 24)
+ if (rc != 0)
return (SDRC_ERROR);
+ /* Find the ofile */
status = smb2sr_lookup_fid(sr, &smb2fid);
+ /* Success or NT_STATUS_FILE_CLOSED */
+
DTRACE_SMB2_START(op__OplockBreak, smb_request_t *, sr);
- if (status)
- goto errout;
- if ((node = sr->fid_ofile->f_node) == NULL) {
- /* Not a regular file */
- status = NT_STATUS_INVALID_PARAMETER;
+ if (status != 0)
goto errout;
- }
/*
- * Process the oplock break ack. We only expect levels
- * at or below the hightest break levels we send, which is
- * currently SMB2_OPLOCK_LEVEL_II.
+ * Process an (old-style) oplock break ack.
*/
- switch (OplockLevel) {
+ switch (smbOplockLevel) {
case SMB2_OPLOCK_LEVEL_NONE: /* 0x00 */
- brk = SMB_OPLOCK_BREAK_TO_NONE;
+ NewLevel = OPLOCK_LEVEL_NONE;
break;
-
case SMB2_OPLOCK_LEVEL_II: /* 0x01 */
- brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
+ NewLevel = OPLOCK_LEVEL_TWO;
break;
-
- /* We don't break to these levels (yet). */
case SMB2_OPLOCK_LEVEL_EXCLUSIVE: /* 0x08 */
+ NewLevel = OPLOCK_LEVEL_ONE;
+ break;
case SMB2_OPLOCK_LEVEL_BATCH: /* 0x09 */
+ NewLevel = OPLOCK_LEVEL_BATCH;
+ break;
case SMB2_OPLOCK_LEVEL_LEASE: /* 0xFF */
- default: /* gcc -Wuninitialized */
- status = NT_STATUS_INVALID_PARAMETER;
+ default:
+ NewLevel = OPLOCK_LEVEL_NONE;
+ break;
+ }
+
+ ofile = sr->fid_ofile;
+ ofile->f_oplock.og_breaking = 0;
+ status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ status = smb2sr_go_async(sr);
+ if (status != 0)
+ goto errout;
+ (void) smb_oplock_wait_break(ofile->f_node, 0);
+ status = 0;
+ }
+ if (status != 0) {
+ NewLevel = OPLOCK_LEVEL_NONE;
goto errout;
}
- smb_oplock_ack(node, sr->fid_ofile, brk);
+ ofile->f_oplock.og_state = NewLevel;
+ switch (NewLevel & OPLOCK_LEVEL_TYPE_MASK) {
+ case OPLOCK_LEVEL_NONE:
+ smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+ break;
+ case OPLOCK_LEVEL_TWO:
+ smbOplockLevel = SMB2_OPLOCK_LEVEL_II;
+ break;
+ case OPLOCK_LEVEL_ONE:
+ smbOplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ break;
+ case OPLOCK_LEVEL_BATCH:
+ smbOplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
+ break;
+ case OPLOCK_LEVEL_GRANULAR:
+ default:
+ smbOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+ break;
+ }
errout:
sr->smb2_status = status;
@@ -91,17 +145,17 @@ errout:
}
/*
- * Generate SMB2 Oplock Break response
- * [MS-SMB2] 2.2.25
+ * Encode an SMB2 Oplock Break Ack response
+ * [MS-SMB2] 2.2.25.1
*/
- StructSize = 24;
(void) smb_mbc_encodef(
&sr->reply, "wb5.qq",
- StructSize, /* w */
- OplockLevel, /* b */
+ SSZ_OPLOCK, /* w */
+ smbOplockLevel, /* b */
/* reserved 5. */
smb2fid.persistent, /* q */
smb2fid.temporal); /* q */
+
return (SDRC_SUCCESS);
}
@@ -111,21 +165,24 @@ errout:
* The caller will send it and free the request.
*/
void
-smb2_oplock_break_notification(smb_request_t *sr, uint8_t brk)
+smb2_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
{
smb_ofile_t *ofile = sr->fid_ofile;
smb2fid_t smb2fid;
uint16_t StructSize;
uint8_t OplockLevel;
- switch (brk) {
+ /*
+ * Convert internal level to SMB2
+ */
+ switch (NewLevel) {
default:
ASSERT(0);
/* FALLTHROUGH */
- case SMB_OPLOCK_BREAK_TO_NONE:
+ case OPLOCK_LEVEL_NONE:
OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
break;
- case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+ case OPLOCK_LEVEL_TWO:
OplockLevel = SMB2_OPLOCK_LEVEL_II;
break;
}
@@ -155,3 +212,176 @@ smb2_oplock_break_notification(smb_request_t *sr, uint8_t brk)
smb2fid.persistent, /* q */
smb2fid.temporal); /* q */
}
+
+/*
+ * Client has an open handle and requests an oplock.
+ * Convert SMB2 oplock request info in to internal form,
+ * call common oplock code, convert result to SMB2.
+ *
+ * If necessary, "go async" here.
+ */
+void
+smb2_oplock_acquire(smb_request_t *sr)
+{
+ smb_arg_open_t *op = &sr->arg.open;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ uint32_t status;
+
+ /* Only disk trees get oplocks. */
+ ASSERT((sr->tid_tree->t_res_type & STYPE_MASK) == STYPE_DISKTREE);
+
+ /* Only plain files... */
+ if (!smb_node_is_file(ofile->f_node)) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+
+ if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+
+ /*
+ * SMB2: Convert to internal form.
+ */
+ switch (op->op_oplock_level) {
+ case SMB2_OPLOCK_LEVEL_BATCH:
+ op->op_oplock_state = OPLOCK_LEVEL_BATCH;
+ break;
+ case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+ op->op_oplock_state = OPLOCK_LEVEL_ONE;
+ break;
+ case SMB2_OPLOCK_LEVEL_II:
+ op->op_oplock_state = OPLOCK_LEVEL_TWO;
+ break;
+ case SMB2_OPLOCK_LEVEL_LEASE:
+ ASSERT(0); /* Handled elsewhere */
+ /* FALLTHROUGH */
+ case SMB2_OPLOCK_LEVEL_NONE:
+ default:
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+
+ /*
+ * Tree options may force shared oplocks,
+ * in which case we reduce the request.
+ */
+ if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
+ op->op_oplock_state = OPLOCK_LEVEL_TWO;
+ }
+
+ /*
+ * Try exclusive first, if requested
+ */
+ if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) {
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
+ } else {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ }
+
+ /*
+ * If exclusive failed (or the tree forced shared oplocks)
+ * try for a shared oplock (Level II)
+ */
+ if (status == NT_STATUS_OPLOCK_NOT_GRANTED) {
+ op->op_oplock_state = OPLOCK_LEVEL_TWO;
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
+ }
+
+ /*
+ * Either of the above may have returned the
+ * status code that says we should wait.
+ */
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(ofile->f_node, 0);
+ status = 0;
+ }
+
+ /*
+ * Keep track of what we got (in ofile->f_oplock.og_state)
+ * so we'll know what we had when sending a break later.
+ * The og_dialect here is the oplock dialect, not the
+ * SMB dialect. No lease here, so SMB 2.0.
+ */
+ ofile->f_oplock.og_dialect = SMB_VERS_2_002;
+ switch (status) {
+ case NT_STATUS_SUCCESS:
+ ofile->f_oplock.og_state = op->op_oplock_state;
+ break;
+ case NT_STATUS_OPLOCK_NOT_GRANTED:
+ ofile->f_oplock.og_state = 0;
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ default:
+ /* Caller did not check args sufficiently? */
+ cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
+ sr->session->ip_addr_str, status);
+ ofile->f_oplock.og_state = 0;
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ return;
+ }
+
+ /*
+ * Have STATUS_SUCCESS
+ * Convert internal oplock state to SMB2
+ */
+ if (op->op_oplock_state & OPLOCK_LEVEL_GRANULAR) {
+ ASSERT(0);
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ } else if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
+ } else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ } else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
+ } else {
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+}
+
+/*
+ * smb2_oplock_reconnect() Helper for smb2_dh_reconnect
+ * Get oplock state into op->op_oplock_level etc.
+ *
+ * Similar to the end of smb2_lease_acquire (for leases) or
+ * the end of smb2_oplock_acquire (for old-style oplocks).
+ */
+void
+smb2_oplock_reconnect(smb_request_t *sr)
+{
+ smb_arg_open_t *op = &sr->arg.open;
+ smb_ofile_t *ofile = sr->fid_ofile;
+
+ op->op_oplock_state = ofile->f_oplock.og_state;
+ if (ofile->f_lease != NULL) {
+ smb_lease_t *ls = ofile->f_lease;
+
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
+ op->lease_state = ls->ls_state &
+ OPLOCK_LEVEL_CACHE_MASK;
+ op->lease_flags = (ls->ls_breaking != 0) ?
+ SMB2_LEASE_FLAG_BREAK_IN_PROGRESS : 0;
+ op->lease_epoch = ls->ls_epoch;
+ op->lease_version = ls->ls_version;
+ } else {
+ switch (op->op_oplock_state & OPLOCK_LEVEL_TYPE_MASK) {
+ default:
+ case OPLOCK_LEVEL_NONE:
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ break;
+ case OPLOCK_LEVEL_TWO:
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_II;
+ break;
+ case OPLOCK_LEVEL_ONE:
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ break;
+ case OPLOCK_LEVEL_BATCH:
+ op->op_oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
+ break;
+ }
+ }
+}
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c b/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c
index 5859314b47..61a42da2be 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c
@@ -309,7 +309,7 @@ static uint32_t
smb2_find_entries(smb_request_t *sr, smb_odir_t *od, smb2_find_args_t *args)
{
smb_odir_resume_t odir_resume;
- char *tbuf = NULL;
+ char *tbuf = NULL;
size_t tbuflen = 0;
uint16_t count;
uint16_t minsize;
diff --git a/usr/src/uts/common/fs/smbsrv/smb2_write.c b/usr/src/uts/common/fs/smbsrv/smb2_write.c
index e2df00de5a..2f4f40bc4b 100644
--- a/usr/src/uts/common/fs/smbsrv/smb2_write.c
+++ b/usr/src/uts/common/fs/smbsrv/smb2_write.c
@@ -132,8 +132,8 @@ smb2_write(smb_request_t *sr)
if (rc)
break;
of->f_written = B_TRUE;
- if (!smb_node_is_dir(of->f_node))
- smb_oplock_break_levelII(of->f_node);
+ /* This revokes read cache delegations. */
+ (void) smb_oplock_break_WRITE(of->f_node, of);
break;
case STYPE_IPC:
diff --git a/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c
new file mode 100644
index 0000000000..39d67dd824
--- /dev/null
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_oplock.c
@@ -0,0 +1,3545 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * (SMB1/SMB2) common (FS-level) Oplock support.
+ *
+ * This is the file-system (FS) level oplock code. This level
+ * knows about the rules by which various kinds of oplocks may
+ * coexist and how they interact. Note that this code should
+ * have NO knowledge of specific SMB protocol details. Those
+ * details are handled in smb_srv_oplock.c and related.
+ *
+ * This file is intentionally written to very closely follow the
+ * [MS-FSA] specification sections about oplocks. Almost every
+ * section of code is preceeded by a block of text from that
+ * specification describing the logic. Where the implementation
+ * differs from what the spec. describes, there are notes like:
+ * Implementation specific: ...
+ */
+
+#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+/*
+ * Several short-hand defines and enums used in this file.
+ */
+
+#define NODE_FLAGS_DELETING (NODE_FLAGS_DELETE_ON_CLOSE |\
+ NODE_FLAGS_DELETE_COMMITTED)
+
+static uint32_t
+smb_oplock_req_excl(
+ smb_ofile_t *ofile, /* in: the "Open" */
+ uint32_t *rop); /* in: "RequestedOplock", out:NewOplockLevel */
+
+static uint32_t
+smb_oplock_req_shared(
+ smb_ofile_t *ofile, /* the "Open" */
+ uint32_t *rop, /* in: "RequestedOplock", out:NewOplockLevel */
+ boolean_t GrantingInAck);
+
+static uint32_t smb_oplock_break_cmn(smb_node_t *node,
+ smb_ofile_t *ofile, uint32_t BreakCacheLevel);
+
+
+/*
+ * [MS-FSA] 2.1.4.12.2 Algorithm to Compare Oplock Keys
+ *
+ * The inputs for this algorithm are:
+ *
+ * OperationOpen: The Open used in the request that can
+ * cause an oplock to break.
+ * OplockOpen: The Open originally used to request the oplock,
+ * as specified in section 2.1.5.17.
+ * Flags: If unspecified it is considered to contain 0.
+ * Valid nonzero values are:
+ * PARENT_OBJECT
+ *
+ * This algorithm returns TRUE if the appropriate oplock key field of
+ * OperationOpen equals OplockOpen.TargetOplockKey, and FALSE otherwise.
+ *
+ * Note: Unlike many comparison functions, ARG ORDER MATTERS.
+ */
+
+static boolean_t
+CompareOplockKeys(smb_ofile_t *OperOpen, smb_ofile_t *OplockOpen, int flags)
+{
+ static const uint8_t key0[SMB_LEASE_KEY_SZ] = { 0 };
+
+ /*
+ * When we're called via FEM, (smb_oplock_break_...)
+ * the OperOpen arg is NULL because I/O outside of SMB
+ * doesn't have an "ofile". That's "not a match".
+ */
+ if (OperOpen == NULL)
+ return (B_FALSE);
+ ASSERT(OplockOpen != NULL);
+
+ /*
+ * If OperationOpen equals OplockOpen:
+ * Return TRUE.
+ */
+ if (OperOpen == OplockOpen)
+ return (B_TRUE);
+
+ /*
+ * If both OperationOpen.TargetOplockKey and
+ * OperationOpen.ParentOplockKey are empty
+ * or both OplockOpen.TargetOplockKey and
+ * OplockOpen.ParentOplockKey are empty:
+ * Return FALSE.
+ */
+ if (bcmp(OperOpen->TargetOplockKey, key0, sizeof (key0)) == 0 &&
+ bcmp(OperOpen->ParentOplockKey, key0, sizeof (key0)) == 0)
+ return (B_FALSE);
+ if (bcmp(OplockOpen->TargetOplockKey, key0, sizeof (key0)) == 0 &&
+ bcmp(OplockOpen->ParentOplockKey, key0, sizeof (key0)) == 0)
+ return (B_FALSE);
+
+ /*
+ * If OplockOpen.TargetOplockKey is empty or...
+ */
+ if (bcmp(OplockOpen->TargetOplockKey, key0, sizeof (key0)) == 0)
+ return (B_FALSE);
+
+ /*
+ * If Flags contains PARENT_OBJECT:
+ */
+ if ((flags & PARENT_OBJECT) != 0) {
+ /*
+ * If OperationOpen.ParentOplockKey is empty:
+ * Return FALSE.
+ */
+ if (bcmp(OperOpen->ParentOplockKey, key0, sizeof (key0)) == 0)
+ return (B_FALSE);
+
+ /*
+ * If OperationOpen.ParentOplockKey equals
+ * OplockOpen.TargetOplockKey:
+ * return TRUE, else FALSE
+ */
+ if (bcmp(OperOpen->ParentOplockKey,
+ OplockOpen->TargetOplockKey,
+ SMB_LEASE_KEY_SZ) == 0) {
+ return (B_TRUE);
+ }
+ } else {
+ /*
+ * ... from above:
+ * (Flags does not contain PARENT_OBJECT and
+ * OperationOpen.TargetOplockKey is empty):
+ * Return FALSE.
+ */
+ if (bcmp(OperOpen->TargetOplockKey, key0, sizeof (key0)) == 0)
+ return (B_FALSE);
+
+ /*
+ * If OperationOpen.TargetOplockKey equals
+ * OplockOpen.TargetOplockKey:
+ * Return TRUE, else FALSE
+ */
+ if (bcmp(OperOpen->TargetOplockKey,
+ OplockOpen->TargetOplockKey,
+ SMB_LEASE_KEY_SZ) == 0) {
+ return (B_TRUE);
+ }
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * 2.1.4.13 Algorithm to Recompute the State of a Shared Oplock
+ *
+ * The inputs for this algorithm are:
+ * ThisOplock: The Oplock on whose state is being recomputed.
+ */
+static void
+RecomputeOplockState(smb_node_t *node)
+{
+ smb_oplock_t *ol = &node->n_oplock;
+
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+ /*
+ * If ThisOplock.IIOplocks, ThisOplock.ROplocks, ThisOplock.RHOplocks,
+ * and ThisOplock.RHBreakQueue are all empty:
+ * Set ThisOplock.State to NO_OPLOCK.
+ */
+ if (ol->cnt_II == 0 && ol->cnt_R == 0 &&
+ ol->cnt_RH == 0 && ol->cnt_RHBQ == 0) {
+ ol->ol_state = NO_OPLOCK;
+ return;
+ }
+
+ /*
+ * Else If ThisOplock.ROplocks is not empty and either
+ * ThisOplock.RHOplocks or ThisOplock.RHBreakQueue are not empty:
+ * Set ThisOplock.State to
+ * (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH).
+ */
+ else if (ol->cnt_R != 0 && (ol->cnt_RH != 0 || ol->cnt_RHBQ != 0)) {
+ ol->ol_state = (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH);
+ }
+
+ /*
+ * Else If ThisOplock.ROplocks is empty and
+ * ThisOplock.RHOplocks is not empty:
+ * Set ThisOplock.State to (READ_CACHING|HANDLE_CACHING).
+ */
+ else if (ol->cnt_R == 0 && ol->cnt_RH != 0) {
+ ol->ol_state = (READ_CACHING|HANDLE_CACHING);
+ }
+
+ /*
+ * Else If ThisOplock.ROplocks is not empty and
+ * ThisOplock.IIOplocks is not empty:
+ * Set ThisOplock.State to (READ_CACHING|LEVEL_TWO_OPLOCK).
+ */
+ else if (ol->cnt_R != 0 && ol->cnt_II != 0) {
+ ol->ol_state = (READ_CACHING|LEVEL_TWO_OPLOCK);
+ }
+
+ /*
+ * Else If ThisOplock.ROplocks is not empty and
+ * ThisOplock.IIOplocks is empty:
+ * Set ThisOplock.State to READ_CACHING.
+ */
+ else if (ol->cnt_R != 0 && ol->cnt_II == 0) {
+ ol->ol_state = READ_CACHING;
+ }
+
+ /*
+ * Else If ThisOplock.ROplocks is empty and
+ * ThisOplock.IIOplocks is not empty:
+ * Set ThisOplock.State to LEVEL_TWO_OPLOCK.
+ */
+ else if (ol->cnt_R == 0 && ol->cnt_II != 0) {
+ ol->ol_state = LEVEL_TWO_OPLOCK;
+ }
+
+ else {
+ smb_ofile_t *o;
+ int cntBrkToRead;
+
+ /*
+ * ThisOplock.RHBreakQueue MUST be non-empty by this point.
+ */
+ ASSERT(ol->cnt_RHBQ != 0);
+
+ /*
+ * How many on RHBQ have BreakingToRead set?
+ */
+ cntBrkToRead = 0;
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RHBQ == 0)
+ continue;
+ if (o->f_oplock.BreakingToRead)
+ cntBrkToRead++;
+ }
+
+ /*
+ * If RHOpContext.BreakingToRead is TRUE for
+ * every RHOpContext on ThisOplock.RHBreakQueue:
+ */
+ if (cntBrkToRead == ol->cnt_RHBQ) {
+ /*
+ * Set ThisOplock.State to
+ * (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING).
+ */
+ ol->ol_state = (READ_CACHING|HANDLE_CACHING|
+ BREAK_TO_READ_CACHING);
+ }
+
+ /*
+ * Else If RHOpContext.BreakingToRead is FALSE for
+ * every RHOpContext on ThisOplock.RHBreakQueue:
+ */
+ else if (cntBrkToRead == 0) {
+ /*
+ * Set ThisOplock.State to
+ * (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING).
+ */
+ ol->ol_state = (READ_CACHING|HANDLE_CACHING|
+ BREAK_TO_NO_CACHING);
+ } else {
+ /*
+ * Set ThisOplock.State to
+ * (READ_CACHING|HANDLE_CACHING).
+ */
+ ol->ol_state = (READ_CACHING|HANDLE_CACHING);
+ }
+ }
+}
+
+/*
+ * [MS-FSA] 2.1.5.17 Server Requests an Oplock
+ *
+ * The server (caller) provides:
+ * Open - The Open on which the oplock is being requested. (ofile)
+ * Type - The type of oplock being requested. Valid values are as follows:
+ * LEVEL_TWO (Corresponds to SMB2_OPLOCK_LEVEL_II)
+ * LEVEL_ONE (Corresponds to SMB2_OPLOCK_LEVEL_EXCLUSIVE)
+ * LEVEL_BATCH (Corresponds to SMB2_OPLOCK_LEVEL_BATCH)
+ * LEVEL_GRANULAR (Corresponds to SMB2_OPLOCK_LEVEL_LEASE)
+ * RequestedOplockLevel - A combination of zero or more of the
+ * following flags (ignored if Type != LEVEL_GRANULAR)
+ * READ_CACHING
+ * HANDLE_CACHING
+ * WRITE_CACHING
+ *
+ * (Type + RequestedOplockLevel come in *statep)
+ *
+ * Returns:
+ * *statep = NewOplockLevel (possibly less than requested)
+ * containing: LEVEL_NONE, LEVEL_TWO + cache_flags
+ * NTSTATUS
+ */
+
+uint32_t
+smb_oplock_request(smb_request_t *sr, smb_ofile_t *ofile, uint32_t *statep)
+{
+ smb_node_t *node = ofile->f_node;
+ uint32_t type = *statep & OPLOCK_LEVEL_TYPE_MASK;
+ uint32_t level = *statep & OPLOCK_LEVEL_CACHE_MASK;
+ uint32_t status;
+
+ *statep = LEVEL_NONE;
+
+ /*
+ * If Open.Stream.StreamType is DirectoryStream:
+ * The operation MUST be failed with STATUS_INVALID_PARAMETER
+ * under either of the following conditions:
+ * * Type is not LEVEL_GRANULAR.
+ * * Type is LEVEL_GRANULAR but RequestedOplockLevel is
+ * neither READ_CACHING nor (READ_CACHING|HANDLE_CACHING).
+ */
+ if (!smb_node_is_file(node)) {
+ /* ofile is a directory. */
+ if (type != LEVEL_GRANULAR)
+ return (NT_STATUS_INVALID_PARAMETER);
+ if (level != READ_CACHING &&
+ level != (READ_CACHING|HANDLE_CACHING))
+ return (NT_STATUS_INVALID_PARAMETER);
+ /*
+ * We're not supporting directory leases yet.
+ * Todo.
+ */
+ return (NT_STATUS_OPLOCK_NOT_GRANTED);
+ }
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ /*
+ * If Type is LEVEL_ONE or LEVEL_BATCH:
+ * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED
+ * under either of the following conditions:
+ * Open.File.OpenList contains more than one Open
+ * whose Stream is the same as Open.Stream.
+ * Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+ * FILE_SYNCHRONOUS_IO_NONALERT.
+ * Request an exclusive oplock according to the algorithm in
+ * section 2.1.5.17.1, setting the algorithm's params as follows:
+ * Pass in the current Open.
+ * RequestedOplock = Type.
+ * The operation MUST at this point return any status code
+ * returned by the exclusive oplock request algorithm.
+ */
+ if (type == LEVEL_ONE || type == LEVEL_BATCH) {
+ if (node->n_open_count > 1) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ /* XXX: Should be a flag on the ofile. */
+ if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ *statep = type;
+ status = smb_oplock_req_excl(ofile, statep);
+ goto out;
+ }
+
+ /*
+ * Else If Type is LEVEL_TWO:
+ * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED under
+ * either of the following conditions:
+ * Open.Stream.ByteRangeLockList is not empty.
+ * Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+ * FILE_SYNCHRONOUS_IO_NONALERT.
+ * Request a shared oplock according to the algorithm in
+ * section 2.1.5.17.2, setting the algorithm's parameters as follows:
+ * Pass in the current Open.
+ * RequestedOplock = Type.
+ * GrantingInAck = FALSE.
+ * The operation MUST at this point return any status code
+ * returned by the shared oplock request algorithm.
+ */
+ if (type == LEVEL_TWO) {
+ if (smb_lock_range_access(sr, node, 0, ~0, B_FALSE) != 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ /* XXX: Should be a flag on the ofile. */
+ if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ *statep = type;
+ status = smb_oplock_req_shared(ofile, statep, B_FALSE);
+ goto out;
+ }
+
+ /*
+ * Else If Type is LEVEL_GRANULAR:
+ * Sub-cases on RequestedOplockLevel (our "level")
+ *
+ * This is the last Type, so error on !granular and then
+ * deal with the cache levels using one less indent.
+ */
+ if (type != LEVEL_GRANULAR) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ switch (level) {
+
+ /*
+ * If RequestedOplockLevel is READ_CACHING or
+ * (READ_CACHING|HANDLE_CACHING):
+ * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED
+ * under either of the following conditions:
+ * Open.Stream.ByteRangeLockList is not empty.
+ * Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+ * FILE_SYNCHRONOUS_IO_NONALERT.
+ * Request a shared oplock according to the algorithm in
+ * section 2.1.5.17.2, setting the parameters as follows:
+ * Pass in the current Open.
+ * RequestedOplock = RequestedOplockLevel.
+ * GrantingInAck = FALSE.
+ *
+ * The operation MUST at this point return any status code
+ * returned by the shared oplock request algorithm.
+ */
+ case READ_CACHING:
+ case (READ_CACHING|HANDLE_CACHING):
+ if (smb_lock_range_access(sr, node, 0, ~0, B_FALSE) != 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ /* XXX: Should be a flag on the ofile. */
+ if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ *statep = level;
+ status = smb_oplock_req_shared(ofile, statep, B_FALSE);
+ break;
+
+ /*
+ * Else If RequestedOplockLevel is
+ * (READ_CACHING|WRITE_CACHING) or
+ * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING):
+ * If Open.Mode contains either FILE_SYNCHRONOUS_IO_ALERT or
+ * FILE_SYNCHRONOUS_IO_NONALERT, the operation MUST be failed
+ * with STATUS_OPLOCK_NOT_GRANTED.
+ * Request an exclusive oplock according to the algorithm in
+ * section 2.1.5.17.1, setting the parameters as follows:
+ * Pass in the current Open.
+ * RequestedOplock = RequestedOplockLevel.
+ * The operation MUST at this point return any status code
+ * returned by the exclusive oplock request algorithm.
+ */
+ case (READ_CACHING | WRITE_CACHING):
+ case (READ_CACHING | WRITE_CACHING | HANDLE_CACHING):
+ /* XXX: Should be a flag on the ofile. */
+ if (node->flags & NODE_FLAGS_WRITE_THROUGH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ *statep = level;
+ status = smb_oplock_req_excl(ofile, statep);
+ break;
+
+ /*
+ * Else if RequestedOplockLevel is 0 (that is, no flags):
+ * The operation MUST return STATUS_SUCCESS at this point.
+ */
+ case 0:
+ *statep = 0;
+ status = NT_STATUS_SUCCESS;
+ break;
+
+ /*
+ * Else
+ * The operation MUST be failed with STATUS_INVALID_PARAMETER.
+ */
+ default:
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ /* Give caller back the "Granular" bit. */
+ if (status == NT_STATUS_SUCCESS)
+ *statep |= LEVEL_GRANULAR;
+
+out:
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
+ return (status);
+}
+
+/*
+ * 2.1.5.17.1 Algorithm to Request an Exclusive Oplock
+ *
+ * The inputs for requesting an exclusive oplock are:
+ * Open: The Open on which the oplock is being requested.
+ * RequestedOplock: The oplock type being requested. One of:
+ * LEVEL_ONE, LEVEL_BATCH, CACHE_RW, CACHE_RWH
+ *
+ * On completion, the object store MUST return:
+ * Status: An NTSTATUS code that specifies the result.
+ * NewOplockLevel: The type of oplock that the requested oplock has been
+ * broken (reduced) to. If a failure status is returned in Status,
+ * the value of this field is undefined. Valid values are as follows:
+ * LEVEL_NONE (that is, no oplock)
+ * LEVEL_TWO
+ * A combination of one or more of the following flags:
+ * READ_CACHING
+ * HANDLE_CACHING
+ * WRITE_CACHING
+ * AcknowledgeRequired: A Boolean value: TRUE if the server MUST
+ * acknowledge the oplock break; FALSE if not, as specified in
+ * section 2.1.5.18. If a failure status is returned in Status,
+ * the value of this field is undefined.
+ *
+ * Note: Stores NewOplockLevel in *rop
+ */
+static uint32_t
+smb_oplock_req_excl(
+ smb_ofile_t *ofile, /* in: the "Open" */
+ uint32_t *rop) /* in: "RequestedOplock", out:NewOplockLevel */
+{
+ smb_node_t *node = ofile->f_node;
+ smb_ofile_t *o;
+ boolean_t GrantExcl = B_FALSE;
+ uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
+
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+ /*
+ * If Open.Stream.Oplock is empty:
+ * Build a new Oplock object with fields initialized as follows:
+ * Oplock.State set to NO_OPLOCK.
+ * All other fields set to 0/empty.
+ * Store the new Oplock object in Open.Stream.Oplock.
+ * EndIf
+ *
+ * Implementation specific:
+ * Open.Stream.Oplock maps to: node->n_oplock
+ */
+ if (node->n_oplock.ol_state == 0) {
+ node->n_oplock.ol_state = NO_OPLOCK;
+ }
+
+ /*
+ * If Open.Stream.Oplock.State contains
+ * LEVEL_TWO_OPLOCK or NO_OPLOCK: ...
+ *
+ * Per ms, this is the "If" matching the unbalalanced
+ * "Else If" below (for which we requested clarification).
+ */
+ if ((node->n_oplock.ol_state & (LEVEL_TWO | NO_OPLOCK)) != 0) {
+
+ /*
+ * If Open.Stream.Oplock.State contains LEVEL_TWO_OPLOCK and
+ * RequestedOplock contains one or more of READ_CACHING,
+ * HANDLE_CACHING, or WRITE_CACHING, the operation MUST be
+ * failed with Status set to STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if ((node->n_oplock.ol_state & LEVEL_TWO) != 0 &&
+ (*rop & CACHE_RWH) != 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /*
+ * [ from dochelp@ms ]
+ *
+ * By this point if there is a level II oplock present,
+ * the caller can only be requesting an old-style oplock
+ * because we rejected enhanced oplock requests above.
+ * If the caller is requesting an old-style oplock our
+ * caller already verfied that there is only one handle
+ * open to this stream, and we've already verified that
+ * this request is for a legacy oplock, meaning that there
+ * can be at most one level II oplock (and no R oplocks),
+ * and the level II oplock belongs to this handle. Clear
+ * the level II oplock and grant the exclusive oplock.
+ */
+
+ /*
+ * If Open.Stream.Oplock.State is equal to LEVEL_TWO_OPLOCK:
+ * Remove the first Open ThisOpen from
+ * Open.Stream.Oplock.IIOplocks (there is supposed to be
+ * exactly one present), and notify the server of an
+ * oplock break according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point; this call
+ * to 2.1.5.17.3 completes some earlier call to 2.1.5.17.2.)
+ *
+ * Implementation specific:
+ *
+ * As explained above, the passed in ofile should be the
+ * only open file on this node. Out of caution, we'll
+ * walk the ofile list as usual here, making sure there
+ * are no LevelII oplocks remaining, as those may not
+ * coexist with the exclusive oplock were're creating
+ * in this call. Also, if the passed in ofile has a
+ * LevelII oplock, don't do an "ind break" up call on
+ * this ofile, as that would just cause an immediate
+ * "break to none" of the oplock we'll grant here.
+ * If there were other ofiles with LevelII oplocks,
+ * it would be appropriate to "ind break" those.
+ */
+ if ((node->n_oplock.ol_state & LEVEL_TWO) != 0) {
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_II == 0)
+ continue;
+ o->f_oplock.onlist_II = B_FALSE;
+ node->n_oplock.cnt_II--;
+ ASSERT(node->n_oplock.cnt_II >= 0);
+ if (o == ofile)
+ continue;
+ DTRACE_PROBE1(unexpected, smb_ofile_t, o);
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_SUCCESS);
+ }
+ }
+
+ /*
+ * Note the spec. had an extra "EndIf" here.
+ * Confirmed by dochelp@ms
+ */
+
+ /*
+ * If Open.File.OpenList contains more than one Open whose
+ * Stream is the same as Open.Stream, and NO_OPLOCK is present
+ * in Open.Stream.Oplock.State, the operation MUST be failed
+ * with Status set to STATUS_OPLOCK_NOT_GRANTED.
+ *
+ * Implementation specific:
+ * Allow other opens if they have the same lease ours,
+ * so we can upgrade RH to RWH (for example). Therefore
+ * only count opens with a different TargetOplockKey.
+ * Also ignore "attribute-only" opens.
+ */
+ if ((node->n_oplock.ol_state & NO_OPLOCK) != 0) {
+ FOREACH_NODE_OFILE(node, o) {
+ if (!smb_ofile_is_open(o))
+ continue;
+ if ((o->f_granted_access & FILE_DATA_ALL) == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, 0)) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * If Open.Stream.IsDeleted is TRUE and RequestedOplock
+ * contains HANDLE_CACHING, the operation MUST be failed
+ * with Status set to STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if (((node->flags & NODE_FLAGS_DELETING) != 0) &&
+ (*rop & HANDLE_CACHING) != 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /* Set GrantExclusiveOplock to TRUE. */
+ GrantExcl = B_TRUE;
+ }
+
+ /*
+ * "Else" If (Open.Stream.Oplock.State contains one or more of
+ * READ_CACHING, WRITE_CACHING, or HANDLE_CACHING) and
+ * (Open.Stream.Oplock.State contains none of (BREAK_ANY)) and
+ * (Open.Stream.Oplock.RHBreakQueue is empty):
+ */
+ else if ((node->n_oplock.ol_state & CACHE_RWH) != 0 &&
+ (node->n_oplock.ol_state & BREAK_ANY) == 0 &&
+ node->n_oplock.cnt_RHBQ == 0) {
+
+ /*
+ * This is a granular oplock and it is not breaking.
+ */
+
+ /*
+ * If RequestedOplock contains none of READ_CACHING,
+ * WRITE_CACHING, or HANDLE_CACHING, the operation
+ * MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if ((*rop & CACHE_RWH) == 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /*
+ * If Open.Stream.IsDeleted (already checked above)
+ */
+
+ /*
+ * Switch (Open.Stream.Oplock.State):
+ */
+ switch (node->n_oplock.ol_state) {
+
+ case CACHE_R:
+ /*
+ * If RequestedOplock is neither
+ * (READ_CACHING|WRITE_CACHING) nor
+ * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING),
+ * the operation MUST be failed with Status set
+ * to STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if (*rop != CACHE_RW && *rop != CACHE_RWH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /*
+ * For each Open ThisOpen in
+ * Open.Stream.Oplock.ROplocks:
+ * If ThisOpen.TargetOplockKey !=
+ * Open.TargetOplockKey, the operation
+ * MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_R == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, 0)) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ }
+
+ /*
+ * For each Open o in Open.Stream.Oplock.ROplocks:
+ * Remove o from Open.Stream.Oplock.ROplocks.
+ * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * BreakingOplockOpen = o.
+ * NewOplockLevel = RequestedOplock.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus =
+ * STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndFor
+ *
+ * Note: Upgrade to excl. on same lease.
+ * Won't send a break for this.
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_R == 0)
+ continue;
+ o->f_oplock.onlist_R = B_FALSE;
+ node->n_oplock.cnt_R--;
+ ASSERT(node->n_oplock.cnt_R >= 0);
+
+ smb_oplock_ind_break(o, *rop,
+ B_FALSE, STATUS_NEW_HANDLE);
+ }
+ /*
+ * Set GrantExclusiveOplock to TRUE.
+ * EndCase // _R
+ */
+ GrantExcl = B_TRUE;
+ break;
+
+ case CACHE_RH:
+ /*
+ * If RequestedOplock is not
+ * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING)
+ * or Open.Stream.Oplock.RHBreakQueue is not empty,
+ * the operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ * Note: Have RHBreakQueue==0 from above.
+ */
+ if (*rop != CACHE_RWH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /*
+ * For each Open ThisOpen in
+ * Open.Stream.Oplock.RHOplocks:
+ * If ThisOpen.TargetOplockKey !=
+ * Open.TargetOplockKey, the operation
+ * MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RH == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, 0)) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ }
+
+ /*
+ * For each Open o in Open.Stream.Oplock.RHOplocks:
+ * Remove o from Open.Stream.Oplock.RHOplocks.
+ * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * BreakingOplockOpen = o.
+ * NewOplockLevel = RequestedOplock.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus =
+ * STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndFor
+ *
+ * Note: Upgrade to excl. on same lease.
+ * Won't send a break for this.
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RH == 0)
+ continue;
+ o->f_oplock.onlist_RH = B_FALSE;
+ node->n_oplock.cnt_RH--;
+ ASSERT(node->n_oplock.cnt_RH >= 0);
+
+ smb_oplock_ind_break(o, *rop,
+ B_FALSE, STATUS_NEW_HANDLE);
+ }
+ /*
+ * Set GrantExclusiveOplock to TRUE.
+ * EndCase // _RH
+ */
+ GrantExcl = B_TRUE;
+ break;
+
+ case (CACHE_RWH | EXCLUSIVE):
+ /*
+ * If RequestedOplock is not
+ * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING),
+ * the operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if (*rop != CACHE_RWH) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ /* Deliberate FALL-THROUGH to next Case statement. */
+ /* FALLTHROUGH */
+
+ case (CACHE_RW | EXCLUSIVE):
+ /*
+ * If RequestedOplock is neither
+ * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING) nor
+ * (READ_CACHING|WRITE_CACHING), the operation MUST be
+ * failed with Status set to STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if (*rop != CACHE_RWH && *rop != CACHE_RW) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ o = node->n_oplock.excl_open;
+ if (o == NULL) {
+ ASSERT(0);
+ GrantExcl = B_TRUE;
+ break;
+ }
+
+ /*
+ * If Open.TargetOplockKey !=
+ * Open.Stream.Oplock.ExclusiveOpen.TargetOplockKey,
+ * the operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if (!CompareOplockKeys(ofile, o, 0)) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /*
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen =
+ * Open.Stream.Oplock.ExclusiveOpen.
+ * NewOplockLevel = RequestedOplock.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus =
+ * STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ *
+ * Set Open.Stream.Oplock.ExclusiveOpen to NULL.
+ * Set GrantExclusiveOplock to TRUE.
+ *
+ * Note: We will keep this exclusive oplock,
+ * but move it to a new handle on this lease.
+ * Won't send a break for this.
+ */
+ smb_oplock_ind_break(o, *rop,
+ B_FALSE, STATUS_NEW_HANDLE);
+ node->n_oplock.excl_open = o = NULL;
+ GrantExcl = B_TRUE;
+ break;
+
+ default:
+ /*
+ * The operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ */
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+
+ } /* switch n_oplock.ol_state */
+ } /* EndIf CACHE_RWH & !BREAK_ANY... */
+ else {
+ /*
+ * The operation MUST be failed with...
+ */
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /*
+ * If GrantExclusiveOplock is TRUE:
+ *
+ * Set Open.Stream.Oplock.ExclusiveOpen = Open.
+ * Set Open.Stream.Oplock.State =
+ * (RequestedOplock|EXCLUSIVE).
+ */
+ if (GrantExcl) {
+ node->n_oplock.excl_open = ofile;
+ node->n_oplock.ol_state = *rop | EXCLUSIVE;
+
+ /*
+ * This operation MUST be made cancelable...
+ * This operation waits until the oplock is
+ * broken or canceled, as specified in
+ * section 2.1.5.17.3.
+ *
+ * When the operation specified in section
+ * 2.1.5.17.3 is called, its following input
+ * parameters are transferred to this routine
+ * and then returned by it:
+ *
+ * Status is set to OplockCompletionStatus
+ * NewOplockLevel, AcknowledgeRequired...
+ * from the operation specified in
+ * section 2.1.5.17.3.
+ */
+ /* Keep *rop = ... from caller. */
+ if ((node->n_oplock.ol_state & BREAK_ANY) != 0) {
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ /* Caller does smb_oplock_wait_break() */
+ } else {
+ status = NT_STATUS_SUCCESS;
+ }
+ }
+
+out:
+ if (status == NT_STATUS_OPLOCK_NOT_GRANTED)
+ *rop = LEVEL_NONE;
+
+ return (status);
+}
+
+/*
+ * 2.1.5.17.2 Algorithm to Request a Shared Oplock
+ *
+ * The inputs for requesting a shared oplock are:
+ * Open: The Open on which the oplock is being requested.
+ * RequestedOplock: The oplock type being requested.
+ * GrantingInAck: A Boolean value, TRUE if this oplock is being
+ * requested as part of an oplock break acknowledgement,
+ * FALSE if not.
+ *
+ * On completion, the object store MUST return:
+ * Status: An NTSTATUS code that specifies the result.
+ * NewOplockLevel: The type of oplock that the requested oplock has been
+ * broken (reduced) to. If a failure status is returned in Status,
+ * the value of this field is undefined. Valid values are as follows:
+ * LEVEL_NONE (that is, no oplock)
+ * LEVEL_TWO
+ * A combination of one or more of the following flags:
+ * READ_CACHING
+ * HANDLE_CACHING
+ * WRITE_CACHING
+ * AcknowledgeRequired: A Boolean value: TRUE if the server MUST
+ * acknowledge the oplock break; FALSE if not, as specified in
+ * section 2.1.5.18. If a failure status is returned in Status,
+ * the value of this field is undefined.
+ *
+ * Note: Stores NewOplockLevel in *rop
+ */
+static uint32_t
+smb_oplock_req_shared(
+ smb_ofile_t *ofile, /* in: the "Open" */
+ uint32_t *rop, /* in: "RequestedOplock", out:NewOplockLevel */
+ boolean_t GrantingInAck)
+{
+ smb_node_t *node = ofile->f_node;
+ smb_ofile_t *o;
+ boolean_t OplockGranted = B_FALSE;
+ uint32_t status = NT_STATUS_OPLOCK_NOT_GRANTED;
+
+ ASSERT(RW_READ_HELD(&node->n_ofile_list.ll_lock));
+ ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
+
+ /*
+ * If Open.Stream.Oplock is empty:
+ * Build a new Oplock object with fields initialized as follows:
+ * Oplock.State set to NO_OPLOCK.
+ * All other fields set to 0/empty.
+ * Store the new Oplock object in Open.Stream.Oplock.
+ * EndIf
+ *
+ * Implementation specific:
+ * Open.Stream.Oplock maps to: node->n_oplock
+ */
+ if (node->n_oplock.ol_state == 0) {
+ node->n_oplock.ol_state = NO_OPLOCK;
+ }
+
+ /*
+ * If (GrantingInAck is FALSE) and (Open.Stream.Oplock.State
+ * contains one or more of BREAK_TO_TWO, BREAK_TO_NONE,
+ * BREAK_TO_TWO_TO_NONE, BREAK_TO_READ_CACHING,
+ * BREAK_TO_WRITE_CACHING, BREAK_TO_HANDLE_CACHING,
+ * BREAK_TO_NO_CACHING, or EXCLUSIVE), then:
+ * The operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ * EndIf
+ */
+ if (GrantingInAck == B_FALSE &&
+ (node->n_oplock.ol_state & (BREAK_ANY | EXCLUSIVE)) != 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ /* Switch (RequestedOplock): */
+ switch (*rop) {
+
+ case LEVEL_TWO:
+ /*
+ * The operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED if Open.Stream.Oplock.State
+ * is anything other than the following:
+ * NO_OPLOCK
+ * LEVEL_TWO_OPLOCK
+ * READ_CACHING
+ * (LEVEL_TWO_OPLOCK|READ_CACHING)
+ */
+ switch (node->n_oplock.ol_state) {
+ default:
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ case NO_OPLOCK:
+ case LEVEL_TWO:
+ case READ_CACHING:
+ case (LEVEL_TWO | READ_CACHING):
+ break;
+ }
+ /* Deliberate FALL-THROUGH to next Case statement. */
+ /* FALLTHROUGH */
+
+ case READ_CACHING:
+ /*
+ * The operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED if GrantingInAck is FALSE
+ * and Open.Stream.Oplock.State is anything other than...
+ */
+ switch (node->n_oplock.ol_state) {
+ default:
+ if (GrantingInAck == B_FALSE) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ break;
+ case NO_OPLOCK:
+ case LEVEL_TWO:
+ case READ_CACHING:
+ case (LEVEL_TWO | READ_CACHING):
+ case (READ_CACHING | HANDLE_CACHING):
+ case (READ_CACHING | HANDLE_CACHING | MIXED_R_AND_RH):
+ case (READ_CACHING | HANDLE_CACHING | BREAK_TO_READ_CACHING):
+ case (READ_CACHING | HANDLE_CACHING | BREAK_TO_NO_CACHING):
+ break;
+ }
+
+ if (GrantingInAck == B_FALSE) {
+ /*
+ * If there is an Open on
+ * Open.Stream.Oplock.RHOplocks
+ * whose TargetOplockKey is equal to
+ * Open.TargetOplockKey, the operation
+ * MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ *
+ * If there is an Open on
+ * Open.Stream.Oplock.RHBreakQueue
+ * whose TargetOplockKey is equal to
+ * Open.TargetOplockKey, the operation
+ * MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED.
+ *
+ * Implement both in one list walk.
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if ((o->f_oplock.onlist_RH ||
+ o->f_oplock.onlist_RHBQ) &&
+ CompareOplockKeys(ofile, o, 0)) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ }
+
+ /*
+ * If there is an Open ThisOpen on
+ * Open.Stream.Oplock.ROplocks whose
+ * TargetOplockKey is equal to Open.TargetOplockKey
+ * (there is supposed to be at most one present):
+ * * Remove ThisOpen from Open...ROplocks.
+ * * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * * BreakingOplockOpen = ThisOpen
+ * * NewOplockLevel = READ_CACHING
+ * * AcknowledgeRequired = FALSE
+ * * OplockCompletionStatus =
+ * STATUS_..._NEW_HANDLE
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ *
+ * If this SMB2 lease already has an "R" handle,
+ * we'll update that lease locally to point to
+ * this new handle.
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_R == 0)
+ continue;
+ if (CompareOplockKeys(ofile, o, 0)) {
+ o->f_oplock.onlist_R = B_FALSE;
+ node->n_oplock.cnt_R--;
+ ASSERT(node->n_oplock.cnt_R >= 0);
+ smb_oplock_ind_break(o,
+ CACHE_R, B_FALSE,
+ STATUS_NEW_HANDLE);
+ }
+ }
+ } /* EndIf !GrantingInAck */
+
+ /*
+ * If RequestedOplock equals LEVEL_TWO:
+ * Add Open to Open.Stream.Oplock.IIOplocks.
+ * Else // RequestedOplock equals READ_CACHING:
+ * Add Open to Open.Stream.Oplock.ROplocks.
+ * EndIf
+ */
+ if (*rop == LEVEL_TWO) {
+ ofile->f_oplock.onlist_II = B_TRUE;
+ node->n_oplock.cnt_II++;
+ } else {
+ /* (*rop == READ_CACHING) */
+ if (ofile->f_oplock.onlist_R == B_FALSE) {
+ ofile->f_oplock.onlist_R = B_TRUE;
+ node->n_oplock.cnt_R++;
+ }
+ }
+
+ /*
+ * Recompute Open.Stream.Oplock.State according to the
+ * algorithm in section 2.1.4.13, passing Open.Stream.Oplock
+ * as the ThisOplock parameter.
+ * Set OplockGranted to TRUE.
+ */
+ RecomputeOplockState(node);
+ OplockGranted = B_TRUE;
+ break;
+
+ case (READ_CACHING|HANDLE_CACHING):
+ /*
+ * The operation MUST be failed with Status set to
+ * STATUS_OPLOCK_NOT_GRANTED if GrantingInAck is FALSE
+ * and Open.Stream.Oplock.State is anything other than...
+ */
+ switch (node->n_oplock.ol_state) {
+ default:
+ if (GrantingInAck == B_FALSE) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+ break;
+ case NO_OPLOCK:
+ case READ_CACHING:
+ case (READ_CACHING | HANDLE_CACHING):
+ case (READ_CACHING | HANDLE_CACHING | MIXED_R_AND_RH):
+ case (READ_CACHING | HANDLE_CACHING | BREAK_TO_READ_CACHING):
+ case (READ_CACHING | HANDLE_CACHING | BREAK_TO_NO_CACHING):
+ break;
+ }
+
+ /*
+ * If Open.Stream.IsDeleted is TRUE, the operation MUST be
+ * failed with Status set to STATUS_OPLOCK_NOT_GRANTED.
+ */
+ if ((node->flags & NODE_FLAGS_DELETING) != 0) {
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ }
+
+ if (GrantingInAck == B_FALSE) {
+ /*
+ * If there is an Open ThisOpen on
+ * Open.Stream.Oplock.ROplocks whose
+ * TargetOplockKey is equal to Open.TargetOplockKey
+ * (there is supposed to be at most one present):
+ * * Remove ThisOpen from Open...ROplocks.
+ * * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * * BreakingOplockOpen = ThisOpen
+ * * NewOplockLevel = CACHE_RH
+ * * AcknowledgeRequired = FALSE
+ * * OplockCompletionStatus =
+ * STATUS_..._NEW_HANDLE
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ *
+ * If this SMB2 lease already has an "R" handle,
+ * we'll update that lease locally to point to
+ * this new handle (upgrade to "RH").
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_R == 0)
+ continue;
+ if (CompareOplockKeys(ofile, o, 0)) {
+ o->f_oplock.onlist_R = B_FALSE;
+ node->n_oplock.cnt_R--;
+ ASSERT(node->n_oplock.cnt_R >= 0);
+ smb_oplock_ind_break(o,
+ CACHE_RH, B_FALSE,
+ STATUS_NEW_HANDLE);
+ }
+ }
+
+ /*
+ * If there is an Open ThisOpen on
+ * Open.Stream.Oplock.RHOplocks whose
+ * TargetOplockKey is equal to Open.TargetOplockKey
+ * (there is supposed to be at most one present):
+ * XXX: Note, the spec. was missing a step:
+ * XXX: Remove the open from RHOplocks
+ * XXX: Confirm with MS dochelp
+ * * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * * BreakingOplockOpen = ThisOpen
+ * * NewOplockLevel =
+ * (READ_CACHING|HANDLE_CACHING)
+ * * AcknowledgeRequired = FALSE
+ * * OplockCompletionStatus =
+ * STATUS_..._NEW_HANDLE
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ *
+ * If this SMB2 lease already has an "RH" handle,
+ * we'll update that lease locally to point to
+ * this new handle.
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RH == 0)
+ continue;
+ if (CompareOplockKeys(ofile, o, 0)) {
+ o->f_oplock.onlist_RH = B_FALSE;
+ node->n_oplock.cnt_RH--;
+ ASSERT(node->n_oplock.cnt_RH >= 0);
+ smb_oplock_ind_break(o,
+ CACHE_RH, B_FALSE,
+ STATUS_NEW_HANDLE);
+ }
+ }
+ } /* EndIf !GrantingInAck */
+
+ /*
+ * Add Open to Open.Stream.Oplock.RHOplocks.
+ */
+ if (ofile->f_oplock.onlist_RH == B_FALSE) {
+ ofile->f_oplock.onlist_RH = B_TRUE;
+ node->n_oplock.cnt_RH++;
+ }
+
+ /*
+ * Recompute Open.Stream.Oplock.State according to the
+ * algorithm in section 2.1.4.13, passing Open.Stream.Oplock
+ * as the ThisOplock parameter.
+ * Set OplockGranted to TRUE.
+ */
+ RecomputeOplockState(node);
+ OplockGranted = B_TRUE;
+ break;
+
+ default:
+ /* No other value of RequestedOplock is possible. */
+ ASSERT(0);
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ goto out;
+ } /* EndSwitch (RequestedOplock) */
+
+ /*
+ * If OplockGranted is TRUE:
+ * This operation MUST be made cancelable by inserting it into
+ * CancelableOperations.CancelableOperationList.
+ * The operation waits until the oplock is broken or canceled,
+ * as specified in section 2.1.5.17.3.
+ * When the operation specified in section 2.1.5.17.3 is called,
+ * its following input parameters are transferred to this routine
+ * and returned by it:
+ * Status is set to OplockCompletionStatus from the
+ * operation specified in section 2.1.5.17.3.
+ * NewOplockLevel is set to NewOplockLevel from the
+ * operation specified in section 2.1.5.17.3.
+ * AcknowledgeRequired is set to AcknowledgeRequired from
+ * the operation specified in section 2.1.5.17.3.
+ * EndIf
+ */
+ if (OplockGranted) {
+ /* Note: *rop already set. */
+ if ((node->n_oplock.ol_state & BREAK_ANY) != 0) {
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ /* Caller does smb_oplock_wait_break() */
+ } else {
+ status = NT_STATUS_SUCCESS;
+ }
+ }
+
+out:
+ if (status == NT_STATUS_OPLOCK_NOT_GRANTED)
+ *rop = LEVEL_NONE;
+
+ return (status);
+}
+
+/*
+ * 2.1.5.17.3 Indicating an Oplock Break to the Server
+ * See smb_srv_oplock.c
+ */
+
+/*
+ * 2.1.5.18 Server Acknowledges an Oplock Break
+ *
+ * The server provides:
+ * Open - The Open associated with the oplock that has broken.
+ * Type - As part of the acknowledgement, the server indicates a
+ * new oplock it would like in place of the one that has broken.
+ * Valid values are as follows:
+ * LEVEL_NONE
+ * LEVEL_TWO
+ * LEVEL_GRANULAR - If this oplock type is specified,
+ * the server additionally provides:
+ * RequestedOplockLevel - A combination of zero or more of
+ * the following flags:
+ * READ_CACHING
+ * HANDLE_CACHING
+ * WRITE_CACHING
+ *
+ * If the server requests a new oplock and it is granted, the request
+ * does not complete until the oplock is broken; the operation waits for
+ * this to happen. Processing of an oplock break is described in
+ * section 2.1.5.17.3. Whether the new oplock is granted or not, the
+ * object store MUST return:
+ *
+ * Status - An NTSTATUS code indicating the result of the operation.
+ *
+ * If the server requests a new oplock and it is granted, then when the
+ * oplock breaks and the request finally completes, the object store MUST
+ * additionally return:
+ * NewOplockLevel: The type of oplock the requested oplock has
+ * been broken to. Valid values are as follows:
+ * LEVEL_NONE (that is, no oplock)
+ * LEVEL_TWO
+ * A combination of one or more of the following flags:
+ * READ_CACHING
+ * HANDLE_CACHING
+ * WRITE_CACHING
+ * AcknowledgeRequired: A Boolean value; TRUE if the server MUST
+ * acknowledge the oplock break, FALSE if not, as specified in
+ * section 2.1.5.17.2.
+ *
+ * Note: Stores NewOplockLevel in *rop
+ */
+uint32_t
+smb_oplock_ack_break(
+ smb_request_t *sr,
+ smb_ofile_t *ofile,
+ uint32_t *rop)
+{
+ smb_node_t *node = ofile->f_node;
+ uint32_t type = *rop & OPLOCK_LEVEL_TYPE_MASK;
+ uint32_t level = *rop & OPLOCK_LEVEL_CACHE_MASK;
+ uint32_t status = NT_STATUS_SUCCESS;
+ uint32_t BreakToLevel;
+ boolean_t NewOplockGranted = B_FALSE;
+ boolean_t ReturnBreakToNone = B_FALSE;
+ boolean_t FoundMatchingRHOplock = B_FALSE;
+ int other_keys;
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ /*
+ * If Open.Stream.Oplock is empty, the operation MUST be
+ * failed with Status set to STATUS_INVALID_OPLOCK_PROTOCOL.
+ */
+ if (node->n_oplock.ol_state == 0) {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+
+ if (type == LEVEL_NONE || type == LEVEL_TWO) {
+ /*
+ * If Open.Stream.Oplock.ExclusiveOpen is not equal to Open,
+ * the operation MUST be failed with Status set to
+ * STATUS_INVALID_OPLOCK_PROTOCOL.
+ */
+ if (node->n_oplock.excl_open != ofile) {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+
+ /*
+ * If Type is LEVEL_TWO and Open.Stream.Oplock.State
+ * contains BREAK_TO_TWO:
+ * Set Open.Stream.Oplock.State to LEVEL_TWO_OPLOCK.
+ * Set NewOplockGranted to TRUE.
+ */
+ if (type == LEVEL_TWO &&
+ (node->n_oplock.ol_state & BREAK_TO_TWO) != 0) {
+ node->n_oplock.ol_state = LEVEL_TWO;
+ NewOplockGranted = B_TRUE;
+ }
+
+ /*
+ * Else If Open.Stream.Oplock.State contains
+ * BREAK_TO_TWO or BREAK_TO_NONE:
+ * Set Open.Stream.Oplock.State to NO_OPLOCK.
+ */
+ else if ((node->n_oplock.ol_state &
+ (BREAK_TO_TWO | BREAK_TO_NONE)) != 0) {
+ node->n_oplock.ol_state = NO_OPLOCK;
+ }
+
+ /*
+ * Else If Open.Stream.Oplock.State contains
+ * BREAK_TO_TWO_TO_NONE:
+ * Set Open.Stream.Oplock.State to NO_OPLOCK.
+ * Set ReturnBreakToNone to TRUE.
+ */
+ else if ((node->n_oplock.ol_state &
+ BREAK_TO_TWO_TO_NONE) != 0) {
+ node->n_oplock.ol_state = NO_OPLOCK;
+ ReturnBreakToNone = B_TRUE;
+ }
+
+ /*
+ * Else
+ * The operation MUST be failed with Status set to
+ * STATUS_INVALID_OPLOCK_PROTOCOL.
+ */
+ else {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+
+ /*
+ * For each Open WaitingOpen on Open.Stream.Oplock.WaitList:
+ * Indicate that the operation associated with
+ * WaitingOpen can continue according to the
+ * algorithm in section 2.1.4.12.1, setting
+ * OpenToRelease = WaitingOpen.
+ * Remove WaitingOpen from Open.Stream.Oplock.WaitList.
+ * EndFor
+ */
+ if (node->n_oplock.waiters)
+ cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+ /*
+ * Set Open.Stream.Oplock.ExclusiveOpen to NULL.
+ */
+ node->n_oplock.excl_open = NULL;
+
+ if (NewOplockGranted) {
+ /*
+ * The operation waits until the newly-granted
+ * Level 2 oplock is broken, as specified in
+ * section 2.1.5.17.3.
+ *
+ * Here we have just Ack'ed a break-to-II
+ * so now get the level II oplock. We also
+ * checked for break-to-none above, so this
+ * will not need to wait for oplock breaks.
+ */
+ status = smb_oplock_req_shared(ofile, rop, B_TRUE);
+ }
+
+ else if (ReturnBreakToNone) {
+ /*
+ * In this case the server was expecting the oplock
+ * to break to Level 2, but because the oplock is
+ * actually breaking to None (that is, no oplock),
+ * the object store MUST indicate an oplock break
+ * to the server according to the algorithm in
+ * section 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * BreakingOplockOpen = Open.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (Because BreakingOplockOpen is equal to the
+ * passed-in Open, the operation ends at this point.)
+ *
+ * It should be OK to return the reduced oplock
+ * (*rop = LEVEL_NONE) here and avoid the need
+ * to send another oplock break. This is safe
+ * because we already have an Ack of the break
+ * to Level_II, and the additional break to none
+ * would use AckRequired = FALSE.
+ *
+ * If we followed the spec here, we'd have:
+ * smb_oplock_ind_break(ofile,
+ * LEVEL_NONE, B_FALSE,
+ * NT_STATUS_SUCCESS);
+ * (Or smb_oplock_ind_break_in_ack...)
+ */
+ *rop = LEVEL_NONE; /* Reduced from L2 */
+ }
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ } /* LEVEL_NONE or LEVEL_TWO */
+
+ if (type != LEVEL_GRANULAR) {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+
+ /* LEVEL_GRANULAR */
+
+ /*
+ * Let BREAK_LEVEL_MASK = (BREAK_TO_READ_CACHING |
+ * BREAK_TO_WRITE_CACHING | BREAK_TO_HANDLE_CACHING |
+ * BREAK_TO_NO_CACHING),
+ * R_AND_RH_GRANTED = (READ_CACHING | HANDLE_CACHING |
+ * MIXED_R_AND_RH),
+ * RH_GRANTED = (READ_CACHING | HANDLE_CACHING)
+ *
+ * (See BREAK_LEVEL_MASK in smb_oplock.h)
+ */
+#define RH_GRANTED (READ_CACHING|HANDLE_CACHING)
+#define R_AND_RH_GRANTED (RH_GRANTED|MIXED_R_AND_RH)
+
+ /*
+ * If there are no BREAK_LEVEL_MASK flags set, this is invalid,
+ * unless the state is R_AND_RH_GRANTED or RH_GRANTED, in which
+ * case we'll need to see if the RHBreakQueue is empty.
+ */
+
+ /*
+ * If (Open.Stream.Oplock.State does not contain any flag in
+ * BREAK_LEVEL_MASK and
+ * (Open.Stream.Oplock.State != R_AND_RH_GRANTED) and
+ * (Open.Stream.Oplock.State != RH_GRANTED)) or
+ * (((Open.Stream.Oplock.State == R_AND_RH_GRANTED) or
+ * (Open.Stream.Oplock.State == RH_GRANTED)) and
+ * Open.Stream.Oplock.RHBreakQueue is empty):
+ * The request MUST be failed with Status set to
+ * STATUS_INVALID_OPLOCK_PROTOCOL.
+ * EndIf
+ */
+ if ((node->n_oplock.ol_state & BREAK_LEVEL_MASK) == 0) {
+ if ((node->n_oplock.ol_state != R_AND_RH_GRANTED) &&
+ (node->n_oplock.ol_state != RH_GRANTED)) {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+ /* State is R_AND_RH_GRANTED or RH_GRANTED */
+ if (node->n_oplock.cnt_RHBQ == 0) {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+ }
+
+ /*
+ * Compute the "Break To" cache level from the
+ * BREAK_TO_... flags
+ */
+ switch (node->n_oplock.ol_state & BREAK_LEVEL_MASK) {
+ case (BREAK_TO_READ_CACHING | BREAK_TO_WRITE_CACHING |
+ BREAK_TO_HANDLE_CACHING):
+ BreakToLevel = CACHE_RWH;
+ break;
+ case (BREAK_TO_READ_CACHING | BREAK_TO_WRITE_CACHING):
+ BreakToLevel = CACHE_RW;
+ break;
+ case (BREAK_TO_READ_CACHING | BREAK_TO_HANDLE_CACHING):
+ BreakToLevel = CACHE_RH;
+ break;
+ case BREAK_TO_READ_CACHING:
+ BreakToLevel = READ_CACHING;
+ break;
+ case BREAK_TO_NO_CACHING:
+ default:
+ BreakToLevel = LEVEL_NONE;
+ break;
+ }
+
+ /* Switch Open.Stream.Oplock.State */
+ switch (node->n_oplock.ol_state) {
+
+ case (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH):
+ case (READ_CACHING|HANDLE_CACHING):
+ case (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING):
+ case (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING):
+ /*
+ * For each RHOpContext ThisContext in
+ * Open.Stream.Oplock.RHBreakQueue:
+ * If ThisContext.Open equals Open:
+ * (see below)
+ *
+ * Implementation skips the list walk, because
+ * we can get the ofile directly.
+ */
+ if (ofile->f_oplock.onlist_RHBQ) {
+ smb_ofile_t *o;
+
+ /*
+ * Set FoundMatchingRHOplock to TRUE.
+ * If ThisContext.BreakingToRead is FALSE:
+ * If RequestedOplockLevel is not 0 and
+ * Open.Stream.Oplock.WaitList is not empty:
+ * The object store MUST indicate an
+ * oplock break to the server according to
+ * the algorithm in section 2.1.5.17.3,
+ * setting the algorithm's params as follows:
+ * BreakingOplockOpen = Open.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = TRUE.
+ * OplockCompletionStatus =
+ * STATUS_CANNOT_GRANT_...
+ * (Because BreakingOplockOpen is equal to the
+ * passed Open, the operation ends at this point.)
+ * EndIf
+ */
+ FoundMatchingRHOplock = B_TRUE;
+ if (ofile->f_oplock.BreakingToRead == B_FALSE) {
+ if (level != 0 && node->n_oplock.waiters) {
+ /* The ofile stays on RHBQ */
+ smb_oplock_ind_break_in_ack(
+ sr, ofile,
+ LEVEL_NONE, B_TRUE);
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+ }
+
+ /*
+ * Else // ThisContext.BreakingToRead is TRUE.
+ * If Open.Stream.Oplock.WaitList is not empty and
+ * (RequestedOplockLevel is CACHE_RW or CACHE_RWH:
+ * The object store MUST indicate an oplock
+ * break to the server according to the
+ * algorithm in section 2.1.5.17.3, setting
+ * the algorithm's parameters as follows:
+ * * BreakingOplockOpen = Open
+ * * NewOplockLevel = READ_CACHING
+ * * AcknowledgeRequired = TRUE
+ * * OplockCompletionStatus =
+ * STATUS_CANNOT_GRANT...
+ * (Because BreakingOplockOpen is equal to the
+ * passed-in Open, the operation ends at this
+ * point.)
+ * EndIf
+ * EndIf
+ */
+ else { /* BreakingToRead is TRUE */
+ if (node->n_oplock.waiters &&
+ (level == CACHE_RW ||
+ level == CACHE_RWH)) {
+ /* The ofile stays on RHBQ */
+ smb_oplock_ind_break_in_ack(
+ sr, ofile,
+ CACHE_R, B_TRUE);
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+ }
+
+ /*
+ * Remove ThisContext from Open...RHBreakQueue.
+ */
+ ofile->f_oplock.onlist_RHBQ = B_FALSE;
+ node->n_oplock.cnt_RHBQ--;
+ ASSERT(node->n_oplock.cnt_RHBQ >= 0);
+
+ /*
+ * The operation waiting for the Read-Handle
+ * oplock to break can continue if there are
+ * no more Read-Handle oplocks outstanding, or
+ * if all the remaining Read-Handle oplocks
+ * have the same oplock key as the waiting
+ * operation.
+ *
+ * For each Open WaitingOpen on Open...WaitList:
+ *
+ * * If (Open...RHBreakQueue is empty) or
+ * (all RHOpContext.Open.TargetOplockKey values
+ * on Open.Stream.Oplock.RHBreakQueue are
+ * equal to WaitingOpen.TargetOplockKey):
+ * * Indicate that the operation assoc.
+ * with WaitingOpen can continue
+ * according to the algorithm in
+ * section 2.1.4.12.1, setting
+ * OpenToRelease = WaitingOpen.
+ * * Remove WaitingOpen from
+ * Open.Stream.Oplock.WaitList.
+ * * EndIf
+ * EndFor
+ */
+ other_keys = 0;
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RHBQ == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, 0))
+ other_keys++;
+ }
+ if (other_keys == 0)
+ cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+ /*
+ * If RequestedOplockLevel is 0 (that is, no flags):
+ * * Recompute Open.Stream.Oplock.State
+ * according to the algorithm in section
+ * 2.1.4.13, passing Open.Stream.Oplock as
+ * the ThisOplock parameter.
+ * * The algorithm MUST return Status set to
+ * STATUS_SUCCESS at this point.
+ */
+ if (level == 0) {
+ RecomputeOplockState(node);
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * Else If RequestedOplockLevel does not contain
+ * WRITE_CACHING:
+ * * The object store MUST request a shared oplock
+ * according to the algorithm in section
+ * 2.1.5.17.2, setting the algorithm's
+ * parameters as follows:
+ * * Open = current Open.
+ * * RequestedOplock =
+ * RequestedOplockLevel.
+ * * GrantingInAck = TRUE.
+ * * The operation MUST at this point return any
+ * status code returned by the shared oplock
+ * request algorithm.
+ */
+ else if ((level & WRITE_CACHING) == 0) {
+ *rop = level;
+ status = smb_oplock_req_shared(
+ ofile, rop, B_TRUE);
+ goto out;
+ }
+
+ /*
+ * Set Open.Stream.Oplock.ExclusiveOpen to
+ * ThisContext.Open.
+ * Set Open.Stream.Oplock.State to
+ * (RequestedOplockLevel|EXCLUSIVE).
+ * This operation MUST be made cancelable by
+ * inserting it into CancelableOperations...
+ * This operation waits until the oplock is
+ * broken or canceled, as specified in
+ * section 2.1.5.17.3.
+ *
+ * Implementation note:
+ *
+ * Once we assing ol_state below, there
+ * will be no BREAK_TO_... flags set,
+ * so no need to wait for oplock breaks.
+ */
+ node->n_oplock.excl_open = ofile;
+ node->n_oplock.ol_state = level | EXCLUSIVE;
+ status = NT_STATUS_SUCCESS;
+ } /* onlist_RHBQ */
+ if (FoundMatchingRHOplock == B_FALSE) {
+ /* The operation MUST be failed with Status... */
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+ break; /* case (READ_CACHING|HANDLE_CACHING...) */
+
+ case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING):
+ case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING):
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING|BREAK_TO_WRITE_CACHING):
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING|BREAK_TO_HANDLE_CACHING):
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING):
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_NO_CACHING):
+ /*
+ * If Open.Stream.Oplock.ExclusiveOpen != Open:
+ * * The operation MUST be failed with Status set to
+ * STATUS_INVALID_OPLOCK_PROTOCOL.
+ * EndIf
+ */
+ if (node->n_oplock.excl_open != ofile) {
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ goto out;
+ }
+
+ /*
+ * If Open.Stream.Oplock.WaitList is not empty and
+ * Open.Stream.Oplock.State does not contain HANDLE_CACHING
+ * and RequestedOplockLevel is CACHE_RWH:
+ * The object store MUST indicate an oplock break to
+ * the server according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's params as follows:
+ * * BreakingOplockOpen = Open.
+ * * NewOplockLevel = BreakToLevel (see above)
+ * * AcknowledgeRequired = TRUE.
+ * * OplockCompletionStatus =
+ * STATUS_CANNOT_GRANT_REQUESTED_OPLOCK.
+ * (Because BreakingOplockOpen is equal to the passed-in
+ * Open, the operation ends at this point.)
+ */
+ if (node->n_oplock.waiters &&
+ (node->n_oplock.ol_state & HANDLE_CACHING) == 0 &&
+ level == CACHE_RWH) {
+ smb_oplock_ind_break_in_ack(
+ sr, ofile,
+ BreakToLevel, B_TRUE);
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * Else If Open.Stream.IsDeleted is TRUE and
+ * RequestedOplockLevel contains HANDLE_CACHING:
+ */
+ else if (((node->flags & NODE_FLAGS_DELETING) != 0) &&
+ (level & HANDLE_CACHING) != 0) {
+
+ /*
+ * The object store MUST indicate an oplock break to
+ * the server according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's params as
+ * follows:
+ * * BreakingOplockOpen = Open.
+ * * NewOplockLevel = RequestedOplockLevel
+ * without HANDLE_CACHING (for example if
+ * RequestedOplockLevel is
+ * (READ_CACHING|HANDLE_CACHING), then
+ * NewOplockLevel would be just READ_CACHING).
+ * * AcknowledgeRequired = TRUE.
+ * * OplockCompletionStatus =
+ * STATUS_CANNOT_GRANT_REQUESTED_OPLOCK.
+ * (Because BreakingOplockOpen is equal to the
+ * passed-in Open, the operation ends at this point.)
+ */
+ level &= ~HANDLE_CACHING;
+ smb_oplock_ind_break_in_ack(
+ sr, ofile,
+ level, B_TRUE);
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * For each Open WaitingOpen on Open.Stream.Oplock.WaitList:
+ * * Indicate that the operation associated with
+ * WaitingOpen can continue according to the algorithm
+ * in section 2.1.4.12.1, setting OpenToRelease
+ * = WaitingOpen.
+ * * Remove WaitingOpen from Open.Stream.Oplock.WaitList.
+ * EndFor
+ */
+ cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+ /*
+ * If RequestedOplockLevel does not contain WRITE_CACHING:
+ * * Set Open.Stream.Oplock.ExclusiveOpen to NULL.
+ * EndIf
+ */
+ if ((level & WRITE_CACHING) == 0) {
+ node->n_oplock.excl_open = NULL;
+ }
+
+ /*
+ * If RequestedOplockLevel is 0 (that is, no flags):
+ * * Set Open.Stream.Oplock.State to NO_OPLOCK.
+ * * The operation returns Status set to STATUS_SUCCESS
+ * at this point.
+ */
+ if (level == 0) {
+ node->n_oplock.ol_state = NO_OPLOCK;
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * Deal with possibly still pending breaks.
+ * Two cases: R to none, RH to R or none.
+ *
+ * XXX: These two missing from [MS-FSA]
+ */
+
+ /*
+ * Breaking R to none? This is like:
+ * "If BreakCacheLevel contains READ_CACHING..."
+ * from smb_oplock_break_cmn.
+ */
+ if (level == CACHE_R && BreakToLevel == LEVEL_NONE) {
+ smb_oplock_ind_break_in_ack(
+ sr, ofile,
+ LEVEL_NONE, B_FALSE);
+ node->n_oplock.ol_state = NO_OPLOCK;
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * Breaking RH to R or RH to none? This is like:
+ * "If BreakCacheLevel equals HANDLE_CACHING..."
+ * from smb_oplock_break_cmn.
+ */
+ if (level == CACHE_RH &&
+ (BreakToLevel == CACHE_R ||
+ BreakToLevel == LEVEL_NONE)) {
+ smb_oplock_ind_break_in_ack(
+ sr, ofile,
+ BreakToLevel, B_TRUE);
+
+ ofile->f_oplock.BreakingToRead =
+ (BreakToLevel & READ_CACHING) ? 1: 0;
+
+ ASSERT(!(ofile->f_oplock.onlist_RHBQ));
+ ofile->f_oplock.onlist_RHBQ = B_TRUE;
+ node->n_oplock.cnt_RHBQ++;
+
+ RecomputeOplockState(node);
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * Else If RequestedOplockLevel does not contain WRITE_CACHING:
+ * * The object store MUST request a shared oplock
+ * according to the algorithm in section 2.1.5.17.2,
+ * setting the algorithm's parameters as follows:
+ * * Pass in the current Open.
+ * * RequestedOplock = RequestedOplockLevel.
+ * * GrantingInAck = TRUE.
+ * * The operation MUST at this point return any status
+ * returned by the shared oplock request algorithm.
+ */
+ if ((level & WRITE_CACHING) == 0) {
+ *rop = level;
+ status = smb_oplock_req_shared(ofile, rop, B_TRUE);
+ goto out;
+ }
+
+ /*
+ * Note that because this oplock is being set up as part of
+ * an acknowledgement of an exclusive oplock break,
+ * Open.Stream.Oplock.ExclusiveOpen was set
+ * at the time of the original oplock request;
+ * it contains Open.
+ * * Set Open.Stream.Oplock.State to
+ * (RequestedOplockLevel|EXCLUSIVE).
+ * * This operation MUST be made cancelable...
+ * * This operation waits until the oplock is broken or
+ * canceled, as specified in section 2.1.5.17.3.
+ *
+ * Implementation notes:
+ *
+ * This can only be a break from RWH to RW.
+ * The assignment of ol_state below means there will be
+ * no BREAK_TO_... bits set, and therefore no need for
+ * "waits until the oplock is broken" as described in
+ * the spec for this bit of code. Therefore, this will
+ * return SUCCESS instead of OPLOCK_BREAK_IN_PROGRESS.
+ */
+ node->n_oplock.ol_state = level | EXCLUSIVE;
+ status = NT_STATUS_SUCCESS;
+ break; /* case (READ_CACHING|WRITE_CACHING|...) */
+
+ default:
+ /* The operation MUST be failed with Status */
+ status = NT_STATUS_INVALID_OPLOCK_PROTOCOL;
+ break;
+
+ } /* Switch (oplock.state) */
+
+out:
+ /*
+ * The spec. describes waiting for a break here,
+ * but we let the caller do that (when needed) if
+ * status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS
+ */
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
+ if (status == NT_STATUS_INVALID_OPLOCK_PROTOCOL)
+ *rop = LEVEL_NONE;
+
+ if (status == NT_STATUS_SUCCESS &&
+ type == LEVEL_GRANULAR &&
+ *rop != LEVEL_NONE)
+ *rop |= LEVEL_GRANULAR;
+
+ return (status);
+}
+
+/*
+ * 2.1.4.12 Algorithm to Check for an Oplock Break
+ *
+ * The inputs for this algorithm are:
+ *
+ * Open: The Open being used in the request calling this algorithm.
+ *
+ * Oplock: The Oplock being checked.
+ *
+ * Operation: A code describing the operation being processed.
+ *
+ * OpParams: Parameters associated with the Operation code that are
+ * passed in from the calling request. For example, if Operation is
+ * OPEN, as specified in section 2.1.5.1, then OpParams will have the
+ * members DesiredAccess and CreateDisposition. Each of these is a
+ * parameter to the open request as specified in section 2.1.5.1.
+ * This parameter could be empty, depending on the Operation code.
+ *
+ * Flags: An optional parameter. If unspecified it is considered to
+ * contain 0. Valid nonzero values are:
+ * PARENT_OBJECT
+ *
+ * The algorithm uses the following local variables:
+ *
+ * Boolean values (initialized to FALSE):
+ * BreakToTwo, BreakToNone, NeedToWait
+ *
+ * BreakCacheLevel – MAY contain 0 or a combination of one or more of
+ * READ_CACHING, WRITE_CACHING, or HANDLE_CACHING, as specified in
+ * section 2.1.1.10. Initialized to 0.
+ * Note that there are only four legal nonzero combinations of flags
+ * for BreakCacheLevel:
+ * (READ_CACHING|WRITE_CACHING|HANDLE_CACHING)
+ * (READ_CACHING|WRITE_CACHING)
+ * WRITE_CACHING
+ * HANDLE_CACHING
+ *
+ * Algorithm: (all)
+ * If Oplock is not empty and Oplock.State is not NO_OPLOCK:
+ * If Flags contains PARENT_OBJECT:
+ * If Operation is OPEN, CLOSE, FLUSH_DATA,
+ * FS_CONTROL(set_encryption) or
+ * SET_INFORMATION(Basic, Allocation, EoF,
+ * Rename, Link, Shortname, VDL):
+ * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ * EndIf
+ * Else // Normal operation (not PARENT_OBJECT)
+ * Switch (Operation):
+ * Case OPEN, CLOSE, ...
+ * EndSwitch
+ * EndIf // not parent
+ * // Common section for all above
+ * If BreakToTwo is TRUE:
+ * ...
+ * Else If BreakToNone
+ * ...
+ * EndIf
+ * ...
+ * EndIf
+ *
+ * This implementation uses separate functions for each of:
+ * if (flags & PARENT)... else
+ * switch (Operation)...
+ */
+
+
+/*
+ * If Flags contains PARENT_OBJECT:
+ * ...
+ * Note that this function is unusual in that the node arg is
+ * the PARENT directory node, and ofile is NOT on the ofile list
+ * of that directory but one of the nodes under it.
+ *
+ * Note that until we implement directory leases, this is a no-op.
+ */
+uint32_t
+smb_oplock_break_PARENT(smb_node_t *node, smb_ofile_t *ofile)
+{
+ uint32_t BreakCacheLevel;
+
+ /*
+ * If Operation is OPEN, CLOSE, FLUSH_DATA,
+ * FS_CONTROL(set_encryption) or
+ * SET_INFORMATION(Basic, Allocation, EoF,
+ * Rename, Link, Shortname, VDL):
+ * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ * EndIf
+ */
+ BreakCacheLevel = PARENT_OBJECT |
+ (READ_CACHING|WRITE_CACHING);
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Helper for the cases where section 2.1.5.1 says:
+ *
+ * If Open.Stream.Oplock is not empty and Open.Stream.Oplock.State
+ * contains BATCH_OPLOCK, the object store MUST check for an oplock
+ * break according to the algorithm in section 2.1.4.12,
+ * with input values as follows:
+ * Open equal to this operation's Open
+ * Oplock equal to Open.Stream.Oplock
+ * Operation equal to "OPEN"
+ * OpParams containing two members:
+ * (DesiredAccess, CreateDisposition)
+ *
+ * So basically, just call smb_oplock_break_OPEN(), but
+ * only if there's a batch oplock.
+ */
+uint32_t
+smb_oplock_break_BATCH(smb_node_t *node, smb_ofile_t *ofile,
+ uint32_t DesiredAccess, uint32_t CreateDisposition)
+{
+ if ((node->n_oplock.ol_state & BATCH_OPLOCK) == 0)
+ return (0);
+
+ return (smb_oplock_break_OPEN(node, ofile,
+ DesiredAccess, CreateDisposition));
+}
+
+/*
+ * Case OPEN, as specified in section 2.1.5.1:
+ *
+ * Note: smb_ofile_open constructs a partially complete smb_ofile_t
+ * for this call, which can be considerd a "proposed open". This
+ * open may or may not turn into a usable open depending on what
+ * happens in the remainder of the ofile_open code path.
+ */
+uint32_t
+smb_oplock_break_OPEN(smb_node_t *node, smb_ofile_t *ofile,
+ uint32_t DesiredAccess, uint32_t CreateDisposition)
+{
+ uint32_t BreakCacheLevel = 0;
+ /* BreakToTwo, BreakToNone, NeedToWait */
+
+ /*
+ * If OpParams.DesiredAccess contains no flags other than
+ * FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, or SYNCHRONIZE,
+ * the algorithm returns at this point.
+ * EndIf
+ */
+ if ((DesiredAccess & ~(FILE_READ_ATTRIBUTES |
+ FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | READ_CONTROL)) == 0)
+ return (0);
+
+ /*
+ * If OpParams.CreateDisposition is FILE_SUPERSEDE,
+ * FILE_OVERWRITE, or FILE_OVERWRITE_IF:
+ * Set BreakToNone to TRUE, set BreakCacheLevel to
+ * (READ_CACHING|WRITE_CACHING).
+ * Else
+ * Set BreakToTwo to TRUE,
+ * set BreakCacheLevel to WRITE_CACHING.
+ * EndIf
+ */
+ if (CreateDisposition == FILE_SUPERSEDE ||
+ CreateDisposition == FILE_OVERWRITE ||
+ CreateDisposition == FILE_OVERWRITE_IF) {
+ BreakCacheLevel = BREAK_TO_NONE |
+ (READ_CACHING|WRITE_CACHING);
+ } else {
+ /*
+ * CreateDispositons: OPEN, OPEN_IF
+ */
+ BreakCacheLevel = BREAK_TO_TWO |
+ WRITE_CACHING;
+ }
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case OPEN_BREAK_H, as specified in section 2.1.5.1:
+ * Set BreakCacheLevel to HANDLE_CACHING.
+ * EndCase
+ */
+uint32_t
+smb_oplock_break_HANDLE(smb_node_t *node, smb_ofile_t *ofile)
+{
+ uint32_t BreakCacheLevel = HANDLE_CACHING;
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case CLOSE, as specified in section 2.1.5.4:
+ *
+ * The MS-FSA spec. describes sending oplock break indications
+ * (smb_oplock_ind_break ... NT_STATUS_OPLOCK_HANDLE_CLOSED)
+ * for several cases where the ofile we're closing has some
+ * oplock grants. We modify these slightly and use them to
+ * clear out the SMB-level oplock state. We could probably
+ * just skip most of these, as the caller knows this handle is
+ * closing and could just discard the SMB-level oplock state.
+ * For now, keeping this close to what the spec says.
+ */
+void
+smb_oplock_break_CLOSE(smb_node_t *node, smb_ofile_t *ofile)
+{
+ smb_ofile_t *o;
+
+ if (ofile == NULL) {
+ ASSERT(0);
+ return;
+ }
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ /*
+ * If Oplock.IIOplocks is not empty:
+ * For each Open ThisOpen in Oplock.IIOplocks:
+ * If ThisOpen == Open:
+ * Remove ThisOpen from Oplock.IIOplocks.
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ * EndFor
+ * Recompute Oplock.State according to the algorithm in
+ * section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+ * EndIf
+ */
+ if (node->n_oplock.cnt_II > 0) {
+ o = ofile; /* No need for list walk */
+ if (o->f_oplock.onlist_II) {
+ o->f_oplock.onlist_II = B_FALSE;
+ node->n_oplock.cnt_II--;
+ ASSERT(node->n_oplock.cnt_II >= 0);
+ /*
+ * The spec. says to do:
+ * smb_oplock_ind_break(o,
+ * LEVEL_NONE, B_FALSE,
+ * NT_STATUS_SUCCESS);
+ *
+ * We'll use STATUS_OPLOCK_HANDLE_CLOSED
+ * like all the other ind_break calls in
+ * this function, so the SMB-level will
+ * just clear out its oplock state.
+ */
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_OPLOCK_HANDLE_CLOSED);
+ }
+ RecomputeOplockState(node);
+ }
+
+ /*
+ * If Oplock.ROplocks is not empty:
+ * For each Open ThisOpen in Oplock.ROplocks:
+ * If ThisOpen == Open:
+ * Remove ThisOpen from Oplock.ROplocks.
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus =
+ * STATUS_OPLOCK_HANDLE_CLOSED.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ * EndFor
+ * Recompute Oplock.State according to the algorithm in
+ * section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+ * EndIf
+ */
+ if (node->n_oplock.cnt_R > 0) {
+ o = ofile; /* No need for list walk */
+ if (o->f_oplock.onlist_R) {
+ o->f_oplock.onlist_R = B_FALSE;
+ node->n_oplock.cnt_R--;
+ ASSERT(node->n_oplock.cnt_R >= 0);
+
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_OPLOCK_HANDLE_CLOSED);
+ }
+ RecomputeOplockState(node);
+ }
+
+ /*
+ * If Oplock.RHOplocks is not empty:
+ * For each Open ThisOpen in Oplock.RHOplocks:
+ * If ThisOpen == Open:
+ * Remove ThisOpen from Oplock.RHOplocks.
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus =
+ * STATUS_OPLOCK_HANDLE_CLOSED.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ * EndFor
+ * Recompute Oplock.State according to the algorithm in
+ * section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+ * EndIf
+ */
+ if (node->n_oplock.cnt_RH > 0) {
+ o = ofile; /* No need for list walk */
+ if (o->f_oplock.onlist_RH) {
+ o->f_oplock.onlist_RH = B_FALSE;
+ node->n_oplock.cnt_RH--;
+ ASSERT(node->n_oplock.cnt_RH >= 0);
+
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_OPLOCK_HANDLE_CLOSED);
+ }
+ RecomputeOplockState(node);
+ }
+
+ /*
+ * If Oplock.RHBreakQueue is not empty:
+ * For each RHOpContext ThisContext in Oplock.RHBreakQueue:
+ * If ThisContext.Open == Open:
+ * Remove ThisContext from Oplock.RHBreakQueue.
+ * EndIf
+ * EndFor
+ * Recompute Oplock.State according to the algorithm in
+ * section 2.1.4.13, passing Oplock as the ThisOplock parameter.
+ * For each Open WaitingOpen on Oplock.WaitList:
+ * If Oplock.RHBreakQueue is empty:
+ * (or) If the value of every
+ * RHOpContext.Open.TargetOplockKey
+ * on Oplock.RHBreakQueue is equal to
+ * WaitingOpen .TargetOplockKey:
+ * Indicate that the op. assoc. with
+ * WaitingOpen can continue according to
+ * the algorithm in section 2.1.4.12.1,
+ * setting OpenToRelease = WaitingOpen.
+ * Remove WaitingOpen from Oplock.WaitList.
+ * EndIf
+ * EndFor
+ * EndIf
+ */
+ if (node->n_oplock.cnt_RHBQ > 0) {
+ o = ofile; /* No need for list walk */
+ if (o->f_oplock.onlist_RHBQ) {
+ o->f_oplock.onlist_RHBQ = B_FALSE;
+ node->n_oplock.cnt_RHBQ--;
+ ASSERT(node->n_oplock.cnt_RHBQ >= 0);
+ }
+ RecomputeOplockState(node);
+ /*
+ * We don't keep a WaitingOpen list, so just
+ * wake them all and let them look at the
+ * updated Oplock.RHBreakQueue
+ */
+ cv_broadcast(&node->n_oplock.WaitingOpenCV);
+ }
+
+ /*
+ * If Open equals Open.Oplock.ExclusiveOpen
+ * If Oplock.State contains none of (BREAK_ANY):
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = Oplock.ExclusiveOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * OplockCompletionStatus equal to:
+ * STATUS_OPLOCK_HANDLE_CLOSED if
+ * Oplock.State contains any of
+ * READ_CACHING, WRITE_CACHING, or
+ * HANDLE_CACHING.
+ * STATUS_SUCCESS otherwise.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ * EndIf
+ * Set Oplock.ExclusiveOpen to NULL.
+ * Set Oplock.State to NO_OPLOCK.
+ * For each Open WaitingOpen on Oplock.WaitList:
+ * Indicate that the operation associated with WaitingOpen
+ * can continue according to the algorithm in section
+ * 2.1.4.12.1, setting OpenToRelease = WaitingOpen.
+ * Remove WaitingOpen from Oplock.WaitList.
+ * EndFor
+ * EndIf
+ *
+ * Modify this slightly from what the spec. says and only
+ * up-call the break with status STATUS_OPLOCK_HANDLE_CLOSED.
+ * The STATUS_SUCCESS case would do nothing at the SMB level,
+ * so we'll just skip that part.
+ */
+ if (ofile == node->n_oplock.excl_open) {
+ uint32_t level = node->n_oplock.ol_state & CACHE_RWH;
+ if (level != 0 &&
+ (node->n_oplock.ol_state & BREAK_ANY) == 0) {
+ smb_oplock_ind_break(ofile,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_OPLOCK_HANDLE_CLOSED);
+ }
+ node->n_oplock.excl_open = NULL;
+ node->n_oplock.ol_state = NO_OPLOCK;
+ cv_broadcast(&node->n_oplock.WaitingOpenCV);
+ }
+
+ /*
+ * The CLOSE sub-case of 2.1.5.4 (separate function here)
+ * happens to always leave BreakCacheLevel=0 (see 2.1.5.4)
+ * so there's never a need to call smb_oplock_break_cmn()
+ * in this function. If that changed and we were to have
+ * BreakCacheLevel != 0 here, then we'd need to call:
+ * smb_oplock_break_cmn(node, ofile, BreakCacheLevel);
+ */
+
+ if ((node->n_oplock.ol_state & BREAK_ANY) == 0)
+ cv_broadcast(&node->n_oplock.WaitingOpenCV);
+
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+}
+
+/*
+ * Case READ, as specified in section 2.1.5.2:
+ * Set BreakToTwo to TRUE
+ * Set BreakCacheLevel to WRITE_CACHING.
+ * EndCase
+ */
+uint32_t
+smb_oplock_break_READ(smb_node_t *node, smb_ofile_t *ofile)
+{
+ uint32_t BreakCacheLevel = BREAK_TO_TWO | WRITE_CACHING;
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case FLUSH_DATA, as specified in section 2.1.5.6:
+ * Set BreakToTwo to TRUE
+ * Set BreakCacheLevel to WRITE_CACHING.
+ * EndCase
+ * Callers just use smb_oplock_break_READ() -- same thing.
+ */
+
+/*
+ * Case LOCK_CONTROL, as specified in section 2.1.5.7:
+ * Note: Spec does fall-through to WRITE here.
+ *
+ * Case WRITE, as specified in section 2.1.5.3:
+ * Set BreakToNone to TRUE
+ * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ * EndCase
+ */
+uint32_t
+smb_oplock_break_WRITE(smb_node_t *node, smb_ofile_t *ofile)
+{
+ uint32_t BreakCacheLevel = BREAK_TO_NONE |
+ (READ_CACHING|WRITE_CACHING);
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case SET_INFORMATION, as specified in section 2.1.5.14:
+ * Switch (OpParams.FileInformationClass):
+ * Case FileEndOfFileInformation:
+ * Case FileAllocationInformation:
+ * Set BreakToNone to TRUE
+ * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ * EndCase
+ * Case FileRenameInformation:
+ * Case FileLinkInformation:
+ * Case FileShortNameInformation:
+ * Set BreakCacheLevel to HANDLE_CACHING.
+ * If Oplock.State contains BATCH_OPLOCK,
+ * set BreakToNone to TRUE.
+ * EndCase
+ * Case FileDispositionInformation:
+ * If OpParams.DeleteFile is TRUE,
+ * Set BreakCacheLevel to HANDLE_CACHING.
+ * EndCase
+ * EndSwitch
+ */
+uint32_t
+smb_oplock_break_SETINFO(smb_node_t *node, smb_ofile_t *ofile,
+ uint32_t InfoClass)
+{
+ uint32_t BreakCacheLevel = 0;
+
+ switch (InfoClass) {
+ case FileEndOfFileInformation:
+ case FileAllocationInformation:
+ BreakCacheLevel = BREAK_TO_NONE |
+ (READ_CACHING|WRITE_CACHING);
+ break;
+
+ case FileRenameInformation:
+ case FileLinkInformation:
+ case FileShortNameInformation:
+ BreakCacheLevel = HANDLE_CACHING;
+ if (node->n_oplock.ol_state & BATCH_OPLOCK) {
+ BreakCacheLevel |= BREAK_TO_NONE;
+ }
+ break;
+ case FileDispositionInformation:
+ /* Only called if (OpParams.DeleteFile is TRUE) */
+ BreakCacheLevel = HANDLE_CACHING;
+ break;
+
+ }
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * This one is not from the spec. It appears that Windows will
+ * open a handle for an SMB1 delete call (at least internally).
+ * We don't open a handle for delete, but do want to break as if
+ * we had done, so this breaks like a combination of:
+ * break_BATCH(... DELETE, FILE_OPEN_IF)
+ * break_HANDLE(...)
+ */
+uint32_t
+smb_oplock_break_DELETE(smb_node_t *node, smb_ofile_t *ofile)
+{
+ uint32_t BreakCacheLevel = HANDLE_CACHING;
+
+ if ((node->n_oplock.ol_state & BATCH_OPLOCK) != 0)
+ BreakCacheLevel |= BREAK_TO_TWO;
+
+ return (smb_oplock_break_cmn(node, ofile, BreakCacheLevel));
+}
+
+/*
+ * Case FS_CONTROL, as specified in section 2.1.5.9:
+ * If OpParams.ControlCode is FSCTL_SET_ZERO_DATA:
+ * Set BreakToNone to TRUE.
+ * Set BreakCacheLevel to (READ_CACHING|WRITE_CACHING).
+ * EndIf
+ * EndCase
+ * Callers just use smb_oplock_break_WRITE() -- same thing.
+ */
+
+/*
+ * Common section for all cases above
+ * Note: When called via FEM: ofile == NULL
+ */
+static uint32_t
+smb_oplock_break_cmn(smb_node_t *node,
+ smb_ofile_t *ofile, uint32_t BreakCacheLevel)
+{
+ smb_oplock_t *nol = &node->n_oplock;
+ uint32_t CmpFlags, status;
+ boolean_t BreakToTwo, BreakToNone, NeedToWait;
+ smb_ofile_t *o = NULL;
+
+ CmpFlags = (BreakCacheLevel & PARENT_OBJECT);
+ BreakToTwo = (BreakCacheLevel & BREAK_TO_TWO) != 0;
+ BreakToNone = (BreakCacheLevel & BREAK_TO_NONE) != 0;
+ BreakCacheLevel &= (READ_CACHING | WRITE_CACHING | HANDLE_CACHING);
+ NeedToWait = B_FALSE;
+ status = NT_STATUS_SUCCESS;
+
+ smb_llist_enter(&node->n_ofile_list, RW_READER);
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ if (node->n_oplock.ol_state == 0 ||
+ node->n_oplock.ol_state == NO_OPLOCK)
+ goto out;
+
+ if (BreakToTwo) {
+ /*
+ * If (Oplock.State != LEVEL_TWO_OPLOCK) and
+ * ((Oplock.ExclusiveOpen is empty) or
+ * (Oplock.ExclusiveOpen.TargetOplockKey !=
+ * Open.TargetOplockKey)):
+ */
+ if ((nol->ol_state != LEVEL_TWO_OPLOCK) &&
+ (((o = nol->excl_open) == NULL) ||
+ !CompareOplockKeys(ofile, o, CmpFlags))) {
+
+ /*
+ * If (Oplock.State contains EXCLUSIVE) and
+ * (Oplock.State contains none of READ_CACHING,
+ * WRITE_CACHING, or HANDLE_CACHING):
+ */
+ if ((nol->ol_state & EXCLUSIVE) != 0 &&
+ (nol->ol_state & CACHE_RWH) == 0) {
+ /*
+ * If Oplock.State contains none of:
+ * BREAK_TO_NONE,
+ * BREAK_TO_TWO,
+ * BREAK_TO_TWO_TO_NONE,
+ * BREAK_TO_READ_CACHING,
+ * BREAK_TO_WRITE_CACHING,
+ * BREAK_TO_HANDLE_CACHING,
+ * BREAK_TO_NO_CACHING:
+ */
+ if ((nol->ol_state & BREAK_ANY) == 0) {
+
+ /*
+ * Oplock.State MUST contain either
+ * LEVEL_ONE_OPLOCK or BATCH_OPLOCK.
+ * Set BREAK_TO_TWO in Oplock.State.
+ */
+ ASSERT((nol->ol_state &
+ (LEVEL_ONE | LEVEL_BATCH)) != 0);
+ nol->ol_state |= BREAK_TO_TWO;
+
+ /*
+ * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * BreakingOplockOpen =
+ * Oplock.ExclusiveOpen.
+ * NewOplockLevel = LEVEL_TWO.
+ * AcknowledgeRequired = TRUE.
+ * Compl_Status = STATUS_SUCCESS.
+ * (The operation does not end at this
+ * point; this call to 2.1.5.17.3
+ * completes some earlier call to
+ * 2.1.5.17.1.)
+ */
+ smb_oplock_ind_break(o,
+ LEVEL_TWO, B_TRUE,
+ NT_STATUS_SUCCESS);
+ }
+
+ /*
+ * The operation that called this algorithm
+ * MUST be made cancelable by ...
+ * The operation that called this algorithm
+ * waits until the oplock break is
+ * acknowledged, as specified in section
+ * 2.1.5.18, or the operation is canceled.
+ */
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ /* Caller does smb_oplock_wait_break() */
+ }
+ }
+ } else if (BreakToNone) {
+ /*
+ * If (Oplock.State == LEVEL_TWO_OPLOCK) or
+ * (Oplock.ExclusiveOpen is empty) or
+ * (Oplock.ExclusiveOpen.TargetOplockKey !=
+ * Open.TargetOplockKey):
+ */
+ if (nol->ol_state == LEVEL_TWO_OPLOCK ||
+ (((o = nol->excl_open) == NULL) ||
+ !CompareOplockKeys(ofile, o, CmpFlags))) {
+
+ /*
+ * If (Oplock.State != NO_OPLOCK) and
+ * (Oplock.State contains neither
+ * WRITE_CACHING nor HANDLE_CACHING):
+ */
+ if (nol->ol_state != NO_OPLOCK &&
+ (nol->ol_state &
+ (WRITE_CACHING | HANDLE_CACHING)) == 0) {
+
+ /*
+ * If Oplock.State contains none of:
+ * LEVEL_TWO_OPLOCK,
+ * BREAK_TO_NONE,
+ * BREAK_TO_TWO,
+ * BREAK_TO_TWO_TO_NONE,
+ * BREAK_TO_READ_CACHING,
+ * BREAK_TO_WRITE_CACHING,
+ * BREAK_TO_HANDLE_CACHING, or
+ * BREAK_TO_NO_CACHING:
+ */
+ if ((nol->ol_state &
+ (LEVEL_TWO_OPLOCK | BREAK_ANY)) == 0) {
+
+ /*
+ * There could be a READ_CACHING-only
+ * oplock here. Those are broken later.
+ *
+ * If Oplock.State contains READ_CACHING
+ * go to the LeaveBreakToNone label.
+ * Set BREAK_TO_NONE in Oplock.State.
+ */
+ if ((nol->ol_state & READ_CACHING) != 0)
+ goto LeaveBreakToNone;
+ nol->ol_state |= BREAK_TO_NONE;
+
+ /*
+ * Notify the server of an oplock break
+ * according to the algorithm in section
+ * 2.1.5.17.3, setting the algorithm's
+ * parameters as follows:
+ * BreakingOplockOpen =
+ * Oplock.ExclusiveOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = TRUE.
+ * Commpl_Status = STATUS_SUCCESS.
+ * (The operation does not end at this
+ * point; this call to 2.1.5.17.3
+ * completes some earlier call to
+ * 2.1.5.17.1.)
+ */
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_TRUE,
+ NT_STATUS_SUCCESS);
+ }
+
+ /*
+ * Else If Oplock.State equals LEVEL_TWO_OPLOCK
+ * or (LEVEL_TWO_OPLOCK|READ_CACHING):
+ */
+ else if (nol->ol_state == LEVEL_TWO ||
+ nol->ol_state == (LEVEL_TWO|READ_CACHING)) {
+
+ /*
+ * For each Open O in Oplock.IIOplocks:
+ * Remove O from Oplock.IIOplocks.
+ * Notify the server of an oplock
+ * break according to the algorithm
+ * in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * Compl_Status = STATUS_SUCCESS.
+ * (The operation does not end at
+ * this point; this call to
+ * 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_II == 0)
+ continue;
+ o->f_oplock.onlist_II = B_FALSE;
+ nol->cnt_II--;
+ ASSERT(nol->cnt_II >= 0);
+
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_SUCCESS);
+ }
+ /*
+ * If Oplock.State equals
+ * (LEVEL_TWO_OPLOCK|READ_CACHING):
+ * Set Oplock.State = READ_CACHING.
+ * Else
+ * Set Oplock.State = NO_OPLOCK.
+ * EndIf
+ * Go to the LeaveBreakToNone label.
+ */
+ if (nol->ol_state ==
+ (LEVEL_TWO_OPLOCK | READ_CACHING)) {
+ nol->ol_state = READ_CACHING;
+ } else {
+ nol->ol_state = NO_OPLOCK;
+ }
+ goto LeaveBreakToNone;
+ }
+
+ /*
+ * Else If Oplock.State contains BREAK_TO_TWO:
+ * Clear BREAK_TO_TWO from Oplock.State.
+ * Set BREAK_TO_TWO_TO_NONE in Oplock.State
+ * EndIf
+ */
+ else if (nol->ol_state & BREAK_TO_TWO) {
+ nol->ol_state &= ~BREAK_TO_TWO;
+ nol->ol_state |= BREAK_TO_TWO_TO_NONE;
+ }
+
+ /*
+ * If Oplock.ExclusiveOpen is not empty,
+ * and Oplock.Excl_Open.TargetOplockKey
+ * equals Open.TargetOplockKey,
+ * go to the LeaveBreakToNone label.
+ */
+ if (o != NULL &&
+ CompareOplockKeys(ofile, o, CmpFlags))
+ goto LeaveBreakToNone;
+
+ /*
+ * The operation that called this algorithm
+ * MUST be made cancelable by ...
+ * The operation that called this algorithm
+ * waits until the opl. break is acknowledged,
+ * as specified in section 2.1.5.18, or the
+ * operation is canceled.
+ */
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ /* Caller does smb_oplock_wait_break() */
+ }
+ }
+ }
+
+LeaveBreakToNone:
+
+ /*
+ * if (BreakCacheLevel != 0) and (pp 37)
+ * If Oplock.State contains any flags that are in BreakCacheLevel:
+ * (Body of that "If" was here to just above the out label.)
+ */
+ if ((nol->ol_state & BreakCacheLevel) == 0)
+ goto out;
+
+ /*
+ * If Oplock.ExclusiveOpen is not empty, call the
+ * algorithm in section 2.1.4.12.2, passing
+ * Open as the OperationOpen parameter,
+ * Oplock.ExclusiveOpen as the OplockOpen parameter,
+ * and Flags as the Flagsparameter.
+ * If the algorithm returns TRUE:
+ * The algorithm returns at this point.
+ */
+ if ((o = nol->excl_open) != NULL &&
+ CompareOplockKeys(ofile, o, CmpFlags) == B_TRUE) {
+ status = NT_STATUS_SUCCESS;
+ goto out;
+ }
+
+ /*
+ * Switch (Oplock.State):
+ */
+ switch (nol->ol_state) {
+
+ case (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH):
+ case READ_CACHING:
+ case (LEVEL_TWO_OPLOCK|READ_CACHING):
+ /*
+ * If BreakCacheLevel contains READ_CACHING:
+ */
+ if ((BreakCacheLevel & READ_CACHING) != 0) {
+ /*
+ * For each Open ThisOpen in Oplock.ROplocks:
+ * Call the algorithm in section 2.1.4.12.2, pass:
+ * Open as the OperationOpen parameter,
+ * ThisOpen as the OplockOpen parameter,
+ * and Flags as the Flagsparameter.
+ * If the algorithm returns FALSE:
+ * Remove ThisOpen from Oplock.ROplocks.
+ * Notify the server of an oplock break
+ * according to the algorithm in
+ * section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = FALSE.
+ * Compl_Status = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.2.)
+ * EndIf
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_R == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+ o->f_oplock.onlist_R = B_FALSE;
+ nol->cnt_R--;
+ ASSERT(nol->cnt_R >= 0);
+
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_FALSE,
+ NT_STATUS_SUCCESS);
+ }
+ }
+ }
+ /*
+ * If Oplock.State equals
+ * (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH):
+ * // Do nothing; FALL THROUGH to next Case statement.
+ * Else
+ * Recompute Oplock.State according to the
+ * algorithm in section 2.1.4.13, passing
+ * Oplock as the ThisOplock parameter.
+ * EndIf
+ */
+ if (nol->ol_state ==
+ (READ_CACHING|HANDLE_CACHING|MIXED_R_AND_RH))
+ goto case_cache_rh;
+
+ RecomputeOplockState(node);
+ break;
+ /* EndCase XXX Note: spec. swapped this with prev. Endif. */
+
+ case_cache_rh:
+ case (READ_CACHING|HANDLE_CACHING):
+
+ /*
+ * If BreakCacheLevel equals HANDLE_CACHING:
+ */
+ if (BreakCacheLevel == HANDLE_CACHING) {
+
+ /*
+ * For each Open ThisOpen in Oplock.RHOplocks:
+ * If ThisOpen.OplockKey != Open.OplockKey:
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RH == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+
+ /*
+ * Remove ThisOpen from
+ * Oplock.RHOplocks.
+ */
+ o->f_oplock.onlist_RH = B_FALSE;
+ nol->cnt_RH--;
+ ASSERT(nol->cnt_RH >= 0);
+
+ /*
+ * Notify the server of an oplock break
+ * according to the algorithm in
+ * section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = READ_CACHING.
+ * AcknowledgeRequired = TRUE.
+ * Compl_Status = STATUS_SUCCESS.
+ * (The operation does not end at this
+ * point; this call to 2.1.5.17.3
+ * completes some earlier call to
+ * 2.1.5.17.2.)
+ */
+ smb_oplock_ind_break(o,
+ READ_CACHING, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ /*
+ * Initialize a new RHOpContext object,
+ * setting its fields as follows:
+ * RHOpCtx.Open = ThisOpen.
+ * RHOpCtx.BreakingToRead = TRUE.
+ * Add the new RHOpContext object to
+ * Oplock.RHBreakQueue.
+ * Set NeedToWait to TRUE.
+ */
+ o->f_oplock.BreakingToRead = B_TRUE;
+ ASSERT(!(o->f_oplock.onlist_RHBQ));
+ o->f_oplock.onlist_RHBQ = B_TRUE;
+ nol->cnt_RHBQ++;
+
+ NeedToWait = B_TRUE;
+ }
+ }
+ }
+
+ /*
+ * Else If BreakCacheLevel contains both
+ * READ_CACHING and WRITE_CACHING:
+ */
+ else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+ (READ_CACHING | WRITE_CACHING)) {
+
+ /*
+ * For each RHOpContext ThisContext in
+ * Oplock.RHBreakQueue:
+ * Call the algorithm in section 2.1.4.12.2,
+ * passing Open as the OperationOpen parameter,
+ * ThisContext.Open as the OplockOpen parameter,
+ * and Flags as the Flags parameter.
+ * If the algorithm returns FALSE:
+ * Set ThisContext.BreakingToRead to FALSE.
+ * If BreakCacheLevel & HANDLE_CACHING:
+ * Set NeedToWait to TRUE.
+ * EndIf
+ * EndIf
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RHBQ == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+ o->f_oplock.BreakingToRead = B_FALSE;
+ if (BreakCacheLevel & HANDLE_CACHING)
+ NeedToWait = B_TRUE;
+ }
+ }
+
+ /*
+ * For each Open ThisOpen in Oplock.RHOplocks:
+ * Call the algorithm in section 2.1.4.12.2,
+ * passing Open as the OperationOpen parameter,
+ * ThisOpen as the OplockOpen parameter, and
+ * Flags as the Flagsparameter.
+ * If the algorithm returns FALSE:
+ * Remove ThisOpen from Oplock.RHOplocks.
+ * Notify the server of an oplock break
+ * according to the algorithm in
+ * section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = ThisOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = TRUE.
+ * Compl_Status = STATUS_SUCCESS.
+ * (The operation does not end at this
+ * point; this call to 2.1.5.17.3
+ * completes some earlier call to
+ * 2.1.5.17.2.)
+ * Initialize a new RHOpContext object,
+ * setting its fields as follows:
+ * RHOpCtx.Open = ThisOpen.
+ * RHOpCtx.BreakingToRead = FALSE
+ * Add the new RHOpContext object to
+ * Oplock.RHBreakQueue.
+ * If BreakCacheLevel contains
+ * HANDLE_CACHING:
+ * Set NeedToWait to TRUE.
+ * EndIf
+ * EndIf
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RH == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+ o->f_oplock.onlist_RH = B_FALSE;
+ nol->cnt_RH--;
+ ASSERT(nol->cnt_RH >= 0);
+
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ o->f_oplock.BreakingToRead = B_FALSE;
+ ASSERT(!(o->f_oplock.onlist_RHBQ));
+ o->f_oplock.onlist_RHBQ = B_TRUE;
+ nol->cnt_RHBQ++;
+
+ if (BreakCacheLevel & HANDLE_CACHING)
+ NeedToWait = B_TRUE;
+ }
+ }
+ }
+
+// If the oplock is explicitly losing HANDLE_CACHING, RHBreakQueue is
+// not empty, and the algorithm has not yet decided to wait, this operation
+// might have to wait if there is an oplock on RHBreakQueue with a
+// non-matching key. This is done because even if this operation didn't
+// cause a break of a currently-granted Read-Handle caching oplock, it
+// might have done so had a currently-breaking oplock still been granted.
+
+ /*
+ * If (NeedToWait is FALSE) and
+ * (Oplock.RHBreakQueue is empty) and (XXX: Not empty)
+ * (BreakCacheLevel contains HANDLE_CACHING):
+ * For each RHOpContext ThisContex in Oplock.RHBreakQueue:
+ * If ThisContext.Open.OplockKey != Open.OplockKey:
+ * Set NeedToWait to TRUE.
+ * Break out of the For loop.
+ * EndIf
+ * EndFor
+ * EndIf
+ * Recompute Oplock.State according to the algorithm in
+ * section 2.1.4.13, passing Oplock as ThisOplock.
+ */
+ if (NeedToWait == B_FALSE &&
+ (BreakCacheLevel & HANDLE_CACHING) != 0) {
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RHBQ == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+ NeedToWait = B_TRUE;
+ break;
+ }
+ }
+ }
+ RecomputeOplockState(node);
+ break;
+
+ case (READ_CACHING|HANDLE_CACHING|BREAK_TO_READ_CACHING):
+ /*
+ * If BreakCacheLevel contains READ_CACHING:
+ */
+ if ((BreakCacheLevel & READ_CACHING) != 0) {
+ /*
+ * For each RHOpContext ThisContext in
+ * Oplock.RHBreakQueue:
+ * Call the algorithm in section 2.1.4.12.2,
+ * passing Open = OperationOpen parameter,
+ * ThisContext.Open = OplockOpen parameter,
+ * and Flags as the Flags parameter.
+ * If the algorithm returns FALSE:
+ * Set ThisCtx.BreakingToRead = FALSE.
+ * EndIf
+ * Recompute Oplock.State according to the
+ * algorithm in section 2.1.4.13, passing
+ * Oplock as the ThisOplock parameter.
+ * EndFor
+ */
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RHBQ == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+ o->f_oplock.BreakingToRead = B_FALSE;
+ }
+ }
+ RecomputeOplockState(node);
+ }
+ /* FALLTHROUGH */
+
+ case (READ_CACHING|HANDLE_CACHING|BREAK_TO_NO_CACHING):
+ /*
+ * If BreakCacheLevel contains HANDLE_CACHING:
+ * For each RHOpContext ThisContext in Oplock.RHBreakQueue:
+ * If ThisContext.Open.OplockKey != Open.OplockKey:
+ * Set NeedToWait to TRUE.
+ * Break out of the For loop.
+ * EndIf
+ * EndFor
+ * EndIf
+ */
+ if ((BreakCacheLevel & HANDLE_CACHING) != 0) {
+ FOREACH_NODE_OFILE(node, o) {
+ if (o->f_oplock.onlist_RHBQ == 0)
+ continue;
+ if (!CompareOplockKeys(ofile, o, CmpFlags)) {
+ NeedToWait = B_TRUE;
+ break;
+ }
+ }
+ }
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|EXCLUSIVE):
+ /*
+ * If BreakCacheLevel contains both
+ * READ_CACHING and WRITE_CACHING:
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = Oplock.ExclusiveOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = TRUE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING| \
+ * EXCLUSIVE|BREAK_TO_NO_CACHING).
+ * Set NeedToWait to TRUE.
+ */
+ if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+ (READ_CACHING | WRITE_CACHING)) {
+ o = nol->excl_open;
+ ASSERT(o != NULL);
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|
+ EXCLUSIVE|BREAK_TO_NO_CACHING);
+ NeedToWait = B_TRUE;
+ }
+
+ /*
+ * Else If BreakCacheLevel contains WRITE_CACHING:
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = Oplock.ExclusiveOpen.
+ * NewOplockLevel = READ_CACHING.
+ * AcknowledgeRequired = TRUE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * EXCLUSIVE|BREAK_TO_READ_CACHING).
+ * Set NeedToWait to TRUE.
+ * EndIf
+ */
+ else if ((BreakCacheLevel & WRITE_CACHING) != 0) {
+ o = nol->excl_open;
+ ASSERT(o != NULL);
+ smb_oplock_ind_break(o,
+ READ_CACHING, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|
+ EXCLUSIVE|BREAK_TO_READ_CACHING);
+ NeedToWait = B_TRUE;
+ }
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE):
+ /*
+ * If BreakCacheLevel equals WRITE_CACHING:
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = Oplock.ExclusiveOpen.
+ * NewOplockLevel = (READ_CACHING|HANDLE_CACHING).
+ * AcknowledgeRequired = TRUE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|
+ * BREAK_TO_READ_CACHING|
+ * BREAK_TO_HANDLE_CACHING).
+ * Set NeedToWait to TRUE.
+ */
+ if (BreakCacheLevel == WRITE_CACHING) {
+ o = nol->excl_open;
+ ASSERT(o != NULL);
+ smb_oplock_ind_break(o,
+ CACHE_RH, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|
+ EXCLUSIVE|BREAK_TO_READ_CACHING|
+ BREAK_TO_HANDLE_CACHING);
+ NeedToWait = B_TRUE;
+ }
+
+ /*
+ * Else If BreakCacheLevel equals HANDLE_CACHING:
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = Oplock.ExclusiveOpen.
+ * NewOplockLevel = (READ_CACHING|WRITE_CACHING).
+ * AcknowledgeRequired = TRUE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|
+ * BREAK_TO_READ_CACHING|
+ * BREAK_TO_WRITE_CACHING).
+ * Set NeedToWait to TRUE.
+ */
+ else if (BreakCacheLevel == HANDLE_CACHING) {
+ o = nol->excl_open;
+ ASSERT(o != NULL);
+ smb_oplock_ind_break(o,
+ CACHE_RW, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|
+ EXCLUSIVE|BREAK_TO_READ_CACHING|
+ BREAK_TO_WRITE_CACHING);
+ NeedToWait = B_TRUE;
+ }
+
+ /*
+ * Else If BreakCacheLevel contains both
+ * READ_CACHING and WRITE_CACHING:
+ * Notify the server of an oplock break according to
+ * the algorithm in section 2.1.5.17.3, setting the
+ * algorithm's parameters as follows:
+ * BreakingOplockOpen = Oplock.ExclusiveOpen.
+ * NewOplockLevel = LEVEL_NONE.
+ * AcknowledgeRequired = TRUE.
+ * OplockCompletionStatus = STATUS_SUCCESS.
+ * (The operation does not end at this point;
+ * this call to 2.1.5.17.3 completes some
+ * earlier call to 2.1.5.17.1.)
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|
+ * BREAK_TO_NO_CACHING).
+ * Set NeedToWait to TRUE.
+ * EndIf
+ */
+ else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+ (READ_CACHING | WRITE_CACHING)) {
+ o = nol->excl_open;
+ ASSERT(o != NULL);
+ smb_oplock_ind_break(o,
+ LEVEL_NONE, B_TRUE,
+ NT_STATUS_SUCCESS);
+
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|
+ EXCLUSIVE|BREAK_TO_NO_CACHING);
+ NeedToWait = B_TRUE;
+ }
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING):
+ /*
+ * If BreakCacheLevel contains READ_CACHING:
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * EXCLUSIVE|BREAK_TO_NO_CACHING).
+ * EndIf
+ * If BreakCacheLevel contains either
+ * READ_CACHING or WRITE_CACHING:
+ * Set NeedToWait to TRUE.
+ * EndIf
+ */
+ if ((BreakCacheLevel & READ_CACHING) != 0) {
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|
+ EXCLUSIVE|BREAK_TO_NO_CACHING);
+ }
+ if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) != 0) {
+ NeedToWait = B_TRUE;
+ }
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING):
+ /*
+ * If BreakCacheLevel contains either
+ * READ_CACHING or WRITE_CACHING:
+ * Set NeedToWait to TRUE.
+ * EndIf
+ */
+ if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) != 0) {
+ NeedToWait = B_TRUE;
+ }
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING|BREAK_TO_WRITE_CACHING):
+ /*
+ * If BreakCacheLevel == WRITE_CACHING:
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING).
+ * Else If BreakCacheLevel contains both
+ * READ_CACHING and WRITE_CACHING:
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING).
+ * EndIf
+ * Set NeedToWait to TRUE.
+ */
+ if (BreakCacheLevel == WRITE_CACHING) {
+ nol->ol_state = (READ_CACHING|WRITE_CACHING|
+ HANDLE_CACHING|EXCLUSIVE|BREAK_TO_READ_CACHING);
+ }
+ else if ((BreakCacheLevel & (READ_CACHING | WRITE_CACHING)) ==
+ (READ_CACHING | WRITE_CACHING)) {
+ nol->ol_state = (READ_CACHING|WRITE_CACHING|
+ HANDLE_CACHING|EXCLUSIVE|BREAK_TO_NO_CACHING);
+ }
+ NeedToWait = B_TRUE;
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING|BREAK_TO_HANDLE_CACHING):
+ /*
+ * If BreakCacheLevel == HANDLE_CACHING:
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|
+ * BREAK_TO_READ_CACHING).
+ * Else If BreakCacheLevel contains READ_CACHING:
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|
+ * BREAK_TO_NO_CACHING).
+ * EndIf
+ * Set NeedToWait to TRUE.
+ */
+ if (BreakCacheLevel == HANDLE_CACHING) {
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|
+ HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING);
+ }
+ else if ((BreakCacheLevel & READ_CACHING) != 0) {
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|
+ HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_NO_CACHING);
+ }
+ NeedToWait = B_TRUE;
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_READ_CACHING):
+ /*
+ * If BreakCacheLevel contains READ_CACHING,
+ * Set Oplock.State to (READ_CACHING|WRITE_CACHING|
+ * HANDLE_CACHING|EXCLUSIVE|
+ * BREAK_TO_NO_CACHING).
+ * EndIf
+ * Set NeedToWait to TRUE.
+ */
+ if ((BreakCacheLevel & READ_CACHING) != 0) {
+ nol->ol_state =
+ (READ_CACHING|WRITE_CACHING|
+ HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_NO_CACHING);
+ }
+ NeedToWait = B_TRUE;
+ break;
+
+ case (READ_CACHING|WRITE_CACHING|HANDLE_CACHING|EXCLUSIVE|
+ BREAK_TO_NO_CACHING):
+ NeedToWait = B_TRUE;
+ break;
+
+ } /* Switch */
+
+ if (NeedToWait) {
+ /*
+ * The operation that called this algorithm MUST be
+ * made cancelable by inserting it into
+ * CancelableOperations.CancelableOperationList.
+ * The operation that called this algorithm waits until
+ * the oplock break is acknowledged, as specified in
+ * section 2.1.5.18, or the operation is canceled.
+ */
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ /* Caller does smb_oplock_wait_break() */
+ }
+
+out:
+ mutex_exit(&node->n_oplock.ol_mutex);
+ smb_llist_exit(&node->n_ofile_list);
+
+ return (status);
+}
+
+/*
+ * smb_oplock_move()
+ *
+ * Helper function for smb2_lease_ofile_close, where we're closing the
+ * ofile that has the oplock for a given lease, and need to move that
+ * oplock to another handle with the same lease.
+ *
+ * This is not described in [MS-FSA], so presumably Windows does this
+ * by keeping oplock objects separate from the open files (no action
+ * needed in the FSA layer). We keep the oplock state as part of the
+ * ofile, so we need to relocate the oplock state in this case.
+ *
+ * Note that in here, we're moving state for both the FSA level and
+ * the SMB level (which is unusual) but this is the easiest way to
+ * make sure we move the state without any other effects.
+ */
+void
+smb_oplock_move(smb_node_t *node,
+ smb_ofile_t *fr_ofile, smb_ofile_t *to_ofile)
+{
+ /*
+ * These are the two common states for an ofile with
+ * a lease that's not the one holding the oplock.
+ * Log if it's not either of these.
+ */
+ static const smb_oplock_grant_t og0 = { 0 };
+ static const smb_oplock_grant_t og8 = {
+ .og_state = OPLOCK_LEVEL_GRANULAR, 0 };
+ smb_oplock_grant_t og_tmp;
+
+ ASSERT(fr_ofile->f_node == node);
+ ASSERT(to_ofile->f_node == node);
+
+ mutex_enter(&node->n_oplock.ol_mutex);
+
+ /*
+ * The ofile to which we're moving the oplock
+ * should NOT have any oplock state. However,
+ * as long as we just swap state between the
+ * two oplocks, we won't invalidate any of
+ * the node's "onlist" counts etc.
+ */
+ if (bcmp(&to_ofile->f_oplock, &og0, sizeof (og0)) != 0 &&
+ bcmp(&to_ofile->f_oplock, &og8, sizeof (og8)) != 0) {
+#ifdef DEBUG
+ cmn_err(CE_NOTE, "smb_oplock_move: not empty?");
+#endif
+ DTRACE_PROBE2(dst__not__empty,
+ smb_node_t, node, smb_ofile_t, to_ofile);
+ }
+
+ og_tmp = to_ofile->f_oplock;
+ to_ofile->f_oplock = fr_ofile->f_oplock;
+ fr_ofile->f_oplock = og_tmp;
+
+ if (node->n_oplock.excl_open == fr_ofile)
+ node->n_oplock.excl_open = to_ofile;
+
+ mutex_exit(&node->n_oplock.ol_mutex);
+}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c b/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c
index 9997538c03..098e203fe0 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_rename.c
@@ -20,11 +20,11 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/synch.h>
-#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <sys/nbmlock.h>
@@ -36,6 +36,7 @@
static int smb_rename_check_stream(smb_fqi_t *, smb_fqi_t *);
static int smb_rename_check_attr(smb_request_t *, smb_node_t *, uint16_t);
static int smb_rename_lookup_src(smb_request_t *);
+static uint32_t smb_rename_check_src(smb_request_t *, smb_fqi_t *);
static void smb_rename_release_src(smb_request_t *);
static uint32_t smb_rename_errno2status(int);
@@ -99,7 +100,7 @@ smb_common_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
smb_node_t *tnode;
char *new_name, *path;
DWORD status;
- int rc, count;
+ int rc;
tnode = sr->tid_tree->t_snode;
path = dst_fqi->fq_path.pn_path;
@@ -111,23 +112,37 @@ smb_common_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
/*
* The source node may already have been provided,
- * i.e. when called by SMB1/SMB2 smb_setinfo_rename.
- * Not provided by smb_com_rename, smb_com_nt_rename.
+ * i.e. when called by SMB1/SMB2 smb_setinfo_rename
+ * with an ofile. When we have an ofile, open has
+ * already checked for sharing violations. For
+ * path-based operations, do sharing check here.
*/
if (src_fqi->fq_fnode) {
- smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
- smb_node_ref(src_fqi->fq_fnode);
smb_node_ref(src_fqi->fq_dnode);
+ smb_node_ref(src_fqi->fq_fnode);
} else {
/* lookup and validate src node */
rc = smb_rename_lookup_src(sr);
if (rc != 0)
return (smb_rename_errno2status(rc));
+ /* Holding refs on dnode, fnode */
}
-
src_fnode = src_fqi->fq_fnode;
src_dnode = src_fqi->fq_dnode;
+ /* Break oplocks, and check share modes. */
+ status = smb_rename_check_src(sr, src_fqi);
+ if (status != NT_STATUS_SUCCESS) {
+ smb_node_release(src_fqi->fq_fnode);
+ smb_node_release(src_fqi->fq_dnode);
+ return (status);
+ }
+ /*
+ * NB: src_fnode is now "in crit" (critical section)
+ * as if we did smb_node_start_crit(..., RW_READER);
+ * Call smb_rename_release_src(sr) on errors.
+ */
+
/*
* Find the destination dnode and last component.
* May already be provided, i.e. when called via
@@ -234,24 +249,22 @@ smb_common_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
return (NT_STATUS_OBJECT_NAME_COLLISION);
}
- (void) smb_oplock_break(sr, dst_fnode,
- SMB_OPLOCK_BREAK_TO_NONE | SMB_OPLOCK_BREAK_BATCH);
+ status = smb_oplock_break_DELETE(dst_fnode, NULL);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(dst_fnode, 0);
+ status = 0;
+ }
+ if (status != 0) {
+ smb_rename_release_src(sr);
+ smb_node_release(dst_fnode);
+ smb_node_release(dst_dnode);
+ return (status);
+ }
- /*
- * Wait (a little) for the oplock break to be
- * responded to by clients closing handles.
- * Hold node->n_lock as reader to keep new
- * ofiles from showing up after we check.
- */
smb_node_rdlock(dst_fnode);
- for (count = 0; count <= 12; count++) {
- status = smb_node_delete_check(dst_fnode);
- if (status != NT_STATUS_SHARING_VIOLATION)
- break;
- smb_node_unlock(dst_fnode);
- delay(MSEC_TO_TICK(100));
- smb_node_rdlock(dst_fnode);
- }
+ status = smb_node_delete_check(dst_fnode);
if (status != NT_STATUS_SUCCESS) {
smb_node_unlock(dst_fnode);
smb_rename_release_src(sr);
@@ -434,23 +447,31 @@ smb_make_link(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
/* The source node may already have been provided */
if (src_fqi->fq_fnode) {
- smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
- smb_node_ref(src_fqi->fq_fnode);
smb_node_ref(src_fqi->fq_dnode);
+ smb_node_ref(src_fqi->fq_fnode);
} else {
/* lookup and validate src node */
rc = smb_rename_lookup_src(sr);
if (rc != 0)
return (smb_rename_errno2status(rc));
+ /* Holding refs on dnode, fnode */
}
/* Not valid to create hardlink for directory */
if (smb_node_is_dir(src_fqi->fq_fnode)) {
- smb_rename_release_src(sr);
+ smb_node_release(src_fqi->fq_dnode);
+ smb_node_release(src_fqi->fq_fnode);
return (NT_STATUS_FILE_IS_A_DIRECTORY);
}
/*
+ * Unlike in rename, we will not unlink the src,
+ * so skip the smb_rename_check_src() call, and
+ * just "start crit" instead.
+ */
+ smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+
+ /*
* Find the destination dnode and last component.
* May already be provided, i.e. when called via
* SMB1 trans2 setinfo.
@@ -510,24 +531,19 @@ smb_make_link(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
/*
* smb_rename_lookup_src
*
- * Lookup the src node, checking for sharing violations and
- * breaking any existing BATCH oplock.
- * Populate sr->arg.dirop.fqi
+ * Lookup the src node for a path-based link or rename.
*
- * Upon success, the dnode and fnode will have holds and the
- * fnode will be in a critical section. These should be
- * released using smb_rename_release_src().
+ * On success, fills in sr->arg.dirop.fqi, and returns with
+ * holds on the source dnode and fnode.
*
* Returns errno values.
*/
static int
smb_rename_lookup_src(smb_request_t *sr)
{
- smb_node_t *src_node, *tnode;
- DWORD status;
- int rc;
- int count;
+ smb_node_t *tnode;
char *path;
+ int rc;
smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
@@ -541,6 +557,7 @@ smb_rename_lookup_src(smb_request_t *sr)
&src_fqi->fq_dnode, src_fqi->fq_last_comp);
if (rc != 0)
return (rc);
+ /* hold fq_dnode */
rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode);
@@ -548,43 +565,93 @@ smb_rename_lookup_src(smb_request_t *sr)
smb_node_release(src_fqi->fq_dnode);
return (rc);
}
- src_node = src_fqi->fq_fnode;
+ /* hold fq_dnode, fq_fnode */
- rc = smb_rename_check_attr(sr, src_node, src_fqi->fq_sattr);
+ rc = smb_rename_check_attr(sr, src_fqi->fq_fnode, src_fqi->fq_sattr);
if (rc != 0) {
smb_node_release(src_fqi->fq_fnode);
smb_node_release(src_fqi->fq_dnode);
return (rc);
}
+ return (0);
+}
+
+/*
+ * smb_rename_check_src
+ *
+ * Check for sharing violations on the file we'll unlink, and
+ * break oplocks for the rename operation. Note that we've
+ * already done oplock breaks associated with opening a handle
+ * on the file to rename.
+ *
+ * On success, returns with fnode in a critical section,
+ * as if smb_node_start_crit were called with the node.
+ * Caller should release using smb_rename_release_src().
+ */
+static uint32_t
+smb_rename_check_src(smb_request_t *sr, smb_fqi_t *src_fqi)
+{
+ smb_node_t *src_node = src_fqi->fq_fnode;
+ uint32_t status;
+
/*
* Break BATCH oplock before ofile checks. If a client
* has a file open, this will force a flush or close,
* which may affect the outcome of any share checking.
+ *
+ * This operation may have either a handle or path for
+ * the source node (that will be unlinked via rename).
+ */
+
+ if (sr->fid_ofile != NULL) {
+ status = smb_oplock_break_SETINFO(src_node, sr->fid_ofile,
+ FileRenameInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(src_node, 0);
+ status = 0;
+ }
+
+ /*
+ * Sharing violations were checked at open time.
+ * Just "start crit" to be consistent with the
+ * state returned for path-based rename.
+ */
+ smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+ return (NT_STATUS_SUCCESS);
+ }
+
+ /*
+ * This code path operates without a real open, so
+ * break oplocks now as if we opened for delete.
+ * Note: SMB2 does only ofile-based rename.
+ *
+ * Todo: Use an "internal open" for path-based
+ * rename and delete, then delete this code.
*/
- (void) smb_oplock_break(sr, src_node,
- SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
+ ASSERT(sr->session->dialect < SMB_VERS_2_BASE);
+ status = smb_oplock_break_DELETE(src_node, NULL);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb_oplock_wait_break(src_node, 0);
+ }
/*
- * Wait (a little) for the oplock break to be
- * responded to by clients closing handles.
- * Hold node->n_lock as reader to keep new
- * ofiles from showing up after we check.
+ * Path-based access to the src file (no ofile)
+ * so check for sharing violations here.
*/
smb_node_rdlock(src_node);
- for (count = 0; count <= 12; count++) {
- status = smb_node_rename_check(src_node);
- if (status != NT_STATUS_SHARING_VIOLATION)
- break;
- smb_node_unlock(src_node);
- delay(MSEC_TO_TICK(100));
- smb_node_rdlock(src_node);
- }
+ status = smb_node_rename_check(src_node);
if (status != NT_STATUS_SUCCESS) {
smb_node_unlock(src_node);
- smb_node_release(src_fqi->fq_fnode);
- smb_node_release(src_fqi->fq_dnode);
- return (EPIPE); /* = ERRbadshare */
+ return (status);
+ }
+
+ status = smb_oplock_break_SETINFO(src_node, NULL,
+ FileRenameInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb_oplock_wait_break(src_node, 0);
}
/*
@@ -605,15 +672,10 @@ smb_rename_lookup_src(smb_request_t *sr)
status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME);
if (status != NT_STATUS_SUCCESS) {
smb_node_end_crit(src_node);
- smb_node_release(src_fqi->fq_fnode);
- smb_node_release(src_fqi->fq_dnode);
- if (status == NT_STATUS_SHARING_VIOLATION)
- return (EPIPE); /* = ERRbadshare */
- return (EACCES);
}
- /* NB: Caller expects holds on src_fqi fnode, dnode */
- return (0);
+ /* NB: Caller expects to be "in crit" on fnode. */
+ return (status);
}
/*
diff --git a/usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c b/usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c
index c3a4685680..53ff9b3cf6 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_cmn_setfile.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
/*
@@ -29,7 +29,7 @@
* SMB2 Set File Info
*/
-#include <smbsrv/smb_kproto.h>
+#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>
/*
@@ -108,6 +108,7 @@ smb_set_eof_info(smb_request_t *sr, smb_setinfo_t *si)
smb_attr_t *attr = &si->si_attr;
smb_node_t *node = si->si_node;
uint64_t eof;
+ uint32_t status;
int rc;
if (smb_mbc_decodef(&si->si_data, "q", &eof) != 0)
@@ -116,10 +117,16 @@ smb_set_eof_info(smb_request_t *sr, smb_setinfo_t *si)
if (smb_node_is_dir(node))
return (NT_STATUS_INVALID_PARAMETER);
- /* If opened by path, break exclusive oplock */
- if (sr->fid_ofile == NULL)
- (void) smb_oplock_break(sr, node,
- SMB_OPLOCK_BREAK_EXCLUSIVE | SMB_OPLOCK_BREAK_TO_NONE);
+ status = smb_oplock_break_SETINFO(node, sr->fid_ofile,
+ FileEndOfFileInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(node, 0);
+ status = 0;
+ }
+ if (status != 0)
+ return (status);
bzero(attr, sizeof (*attr));
attr->sa_mask = SMB_AT_SIZE;
@@ -128,7 +135,6 @@ smb_set_eof_info(smb_request_t *sr, smb_setinfo_t *si)
if (rc != 0)
return (smb_errno2status(rc));
- smb_oplock_break_levelII(node);
return (0);
}
@@ -144,6 +150,7 @@ smb_set_alloc_info(smb_request_t *sr, smb_setinfo_t *si)
smb_attr_t *attr = &si->si_attr;
smb_node_t *node = si->si_node;
uint64_t allocsz;
+ uint32_t status;
int rc;
if (smb_mbc_decodef(&si->si_data, "q", &allocsz) != 0)
@@ -152,10 +159,16 @@ smb_set_alloc_info(smb_request_t *sr, smb_setinfo_t *si)
if (smb_node_is_dir(node))
return (NT_STATUS_INVALID_PARAMETER);
- /* If opened by path, break exclusive oplock */
- if (sr->fid_ofile == NULL)
- (void) smb_oplock_break(sr, node,
- SMB_OPLOCK_BREAK_EXCLUSIVE | SMB_OPLOCK_BREAK_TO_NONE);
+ status = smb_oplock_break_SETINFO(node, sr->fid_ofile,
+ FileAllocationInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(node, 0);
+ status = 0;
+ }
+ if (status != 0)
+ return (status);
bzero(attr, sizeof (*attr));
attr->sa_mask = SMB_AT_ALLOCSZ;
@@ -164,7 +177,6 @@ smb_set_alloc_info(smb_request_t *sr, smb_setinfo_t *si)
if (rc != 0)
return (smb_errno2status(rc));
- smb_oplock_break_levelII(node);
return (0);
}
@@ -211,6 +223,7 @@ smb_set_disposition_info(smb_request_t *sr, smb_setinfo_t *si)
smb_node_t *node = si->si_node;
smb_ofile_t *of = sr->fid_ofile;
uint8_t mark_delete;
+ uint32_t status;
uint32_t flags = 0;
if (smb_mbc_decodef(&si->si_data, "b", &mark_delete) != 0)
@@ -219,13 +232,27 @@ smb_set_disposition_info(smb_request_t *sr, smb_setinfo_t *si)
if ((of == NULL) || !(smb_ofile_granted_access(of) & DELETE))
return (NT_STATUS_ACCESS_DENIED);
- if (mark_delete) {
- if (SMB_TREE_SUPPORTS_CATIA(sr))
- flags |= SMB_CATIA;
- return (smb_node_set_delete_on_close(node, of->f_cr, flags));
- } else {
+ if (mark_delete == 0) {
smb_node_reset_delete_on_close(node);
+ return (NT_STATUS_SUCCESS);
}
- return (NT_STATUS_SUCCESS);
+ /*
+ * Break any oplock handle caching.
+ */
+ status = smb_oplock_break_SETINFO(node, of,
+ FileDispositionInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(node, 0);
+ status = 0;
+ }
+ if (status != 0)
+ return (status);
+
+ if (SMB_TREE_SUPPORTS_CATIA(sr))
+ flags |= SMB_CATIA;
+
+ return (smb_node_set_delete_on_close(node, of->f_cr, flags));
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_common_open.c b/usr/src/uts/common/fs/smbsrv/smb_common_open.c
index d551da5979..161f2790f6 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_common_open.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_common_open.c
@@ -43,13 +43,9 @@ int smb_session_ofile_max = 32768;
static volatile uint32_t smb_fids = 0;
#define SMB_UNIQ_FID() atomic_inc_32_nv(&smb_fids)
-static uint32_t smb_open_subr(smb_request_t *);
extern uint32_t smb_is_executable(char *);
static void smb_delete_new_object(smb_request_t *);
static int smb_set_open_attributes(smb_request_t *, smb_ofile_t *);
-static void smb_open_oplock_break(smb_request_t *, smb_node_t *);
-static boolean_t smb_open_attr_only(smb_arg_open_t *);
-static boolean_t smb_open_overwrite(smb_arg_open_t *);
/*
* smb_access_generic_to_file
@@ -175,40 +171,7 @@ smb_ofun_to_crdisposition(uint16_t ofun)
}
/*
- * Retry opens to avoid spurious sharing violations, due to timing
- * issues between closes and opens. The client that already has the
- * file open may be in the process of closing it.
- */
-uint32_t
-smb_common_open(smb_request_t *sr)
-{
- smb_arg_open_t *parg;
- uint32_t status = NT_STATUS_SUCCESS;
- int count;
-
- parg = kmem_alloc(sizeof (*parg), KM_SLEEP);
- bcopy(&sr->arg.open, parg, sizeof (*parg));
-
- for (count = 0; count <= 4; count++) {
- if (count != 0)
- delay(MSEC_TO_TICK(400));
-
- status = smb_open_subr(sr);
- if (status != NT_STATUS_SHARING_VIOLATION)
- break;
-
- bcopy(parg, &sr->arg.open, sizeof (*parg));
- }
-
- if (status == NT_STATUS_NO_SUCH_FILE)
- status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
-
- kmem_free(parg, sizeof (*parg));
- return (status);
-}
-
-/*
- * smb_open_subr
+ * smb_common_open
*
* Notes on write-through behaviour. It looks like pre-LM0.12 versions
* of the protocol specify the write-through mode when a file is opened,
@@ -251,7 +214,7 @@ smb_common_open(smb_request_t *sr)
*
* 3. The DOS readonly bit affects only data and some metadata.
* The following metadata can be changed regardless of the readonly bit:
- * - security descriptors
+ * - security descriptors
* - DOS attributes
* - timestamps
*
@@ -285,28 +248,38 @@ smb_common_open(smb_request_t *sr)
* 4. Opening an existing file or directory
* The request attributes are ignored.
*/
-static uint32_t
-smb_open_subr(smb_request_t *sr)
+uint32_t
+smb_common_open(smb_request_t *sr)
{
- boolean_t created = B_FALSE;
- boolean_t last_comp_found = B_FALSE;
- smb_node_t *node = NULL;
+ smb_server_t *sv = sr->sr_server;
+ smb_tree_t *tree = sr->tid_tree;
+ smb_node_t *fnode = NULL;
smb_node_t *dnode = NULL;
smb_node_t *cur_node = NULL;
smb_arg_open_t *op = &sr->sr_open;
- int rc;
- smb_ofile_t *of;
+ smb_pathname_t *pn = &op->fqi.fq_path;
+ smb_ofile_t *of = NULL;
smb_attr_t new_attr;
+ hrtime_t shrlock_t0;
int max_requested = 0;
uint32_t max_allowed;
uint32_t status = NT_STATUS_SUCCESS;
int is_dir;
- smb_error_t err;
+ int rc;
boolean_t is_stream = B_FALSE;
int lookup_flags = SMB_FOLLOW_LINKS;
- uint32_t uniq_fid;
- smb_pathname_t *pn = &op->fqi.fq_path;
- smb_server_t *sv = sr->sr_server;
+ uint32_t uniq_fid = 0;
+ uint16_t tree_fid = 0;
+ boolean_t created = B_FALSE;
+ boolean_t last_comp_found = B_FALSE;
+ boolean_t opening_incr = B_FALSE;
+ boolean_t dnode_held = B_FALSE;
+ boolean_t dnode_wlock = B_FALSE;
+ boolean_t fnode_held = B_FALSE;
+ boolean_t fnode_wlock = B_FALSE;
+ boolean_t fnode_shrlk = B_FALSE;
+ boolean_t did_open = B_FALSE;
+ boolean_t did_break_handle = B_FALSE;
/* Get out now if we've been cancelled. */
mutex_enter(&sr->sr_mutex);
@@ -344,6 +317,9 @@ smb_open_subr(smb_request_t *sr)
return (NT_STATUS_TOO_MANY_OPENED_FILES);
}
+ if (smb_idpool_alloc(&tree->t_fid_pool, &tree_fid))
+ return (NT_STATUS_TOO_MANY_OPENED_FILES);
+
/* This must be NULL at this point */
sr->fid_ofile = NULL;
@@ -368,36 +344,49 @@ smb_open_subr(smb_request_t *sr)
*/
if ((rc = smb_threshold_enter(&sv->sv_opipe_ct)) != 0) {
status = RPC_NT_SERVER_TOO_BUSY;
- return (status);
+ goto errout;
}
/*
- * No further processing for IPC, we need to either
- * raise an exception or return success here.
+ * Most of IPC open is handled in smb_opipe_open()
*/
uniq_fid = SMB_UNIQ_FID();
- status = smb_opipe_open(sr, uniq_fid);
+ op->create_options = 0;
+ of = smb_ofile_alloc(sr, op, NULL, SMB_FTYPE_MESG_PIPE,
+ tree_fid, uniq_fid);
+ tree_fid = 0; // given to the ofile
+ status = smb_opipe_open(sr, of);
smb_threshold_exit(&sv->sv_opipe_ct);
- return (status);
+ if (status != NT_STATUS_SUCCESS)
+ goto errout;
+ return (NT_STATUS_SUCCESS);
default:
- return (NT_STATUS_BAD_DEVICE_TYPE);
+ status = NT_STATUS_BAD_DEVICE_TYPE;
+ goto errout;
}
smb_pathname_init(sr, pn, pn->pn_path);
- if (!smb_pathname_validate(sr, pn))
- return (sr->smb_error.status);
+ if (!smb_pathname_validate(sr, pn)) {
+ status = sr->smb_error.status;
+ goto errout;
+ }
if (strlen(pn->pn_path) >= SMB_MAXPATHLEN) {
- return (NT_STATUS_OBJECT_PATH_INVALID);
+ status = NT_STATUS_OBJECT_PATH_INVALID;
+ goto errout;
}
if (is_dir) {
- if (!smb_validate_dirname(sr, pn))
- return (sr->smb_error.status);
+ if (!smb_validate_dirname(sr, pn)) {
+ status = sr->smb_error.status;
+ goto errout;
+ }
} else {
- if (!smb_validate_object_name(sr, pn))
- return (sr->smb_error.status);
+ if (!smb_validate_object_name(sr, pn)) {
+ status = sr->smb_error.status;
+ goto errout;
+ }
}
cur_node = op->fqi.fq_dnode ?
@@ -407,8 +396,20 @@ smb_open_subr(smb_request_t *sr)
sr->tid_tree->t_snode, cur_node, &op->fqi.fq_dnode,
op->fqi.fq_last_comp);
if (rc != 0) {
- return (smb_errno2status(rc));
+ status = smb_errno2status(rc);
+ goto errout;
}
+ dnode = op->fqi.fq_dnode;
+ dnode_held = B_TRUE;
+
+ /*
+ * Lock the parent dir node in case another create
+ * request to the same parent directory comes in.
+ * Drop this once either lookup succeeds, or we've
+ * created the object in this directory.
+ */
+ smb_node_wrlock(dnode);
+ dnode_wlock = B_TRUE;
/*
* If the access mask has only DELETE set (ignore
@@ -426,46 +427,49 @@ smb_open_subr(smb_request_t *sr)
if (rc == 0) {
last_comp_found = B_TRUE;
+ fnode_held = B_TRUE;
+
/*
* Need the DOS attributes below, where we
* check the search attributes (sattr).
+ * Also UID, for owner check below.
*/
- op->fqi.fq_fattr.sa_mask = SMB_AT_DOSATTR;
+ op->fqi.fq_fattr.sa_mask = SMB_AT_DOSATTR | SMB_AT_UID;
rc = smb_node_getattr(sr, op->fqi.fq_fnode, zone_kcred(),
NULL, &op->fqi.fq_fattr);
if (rc != 0) {
- smb_node_release(op->fqi.fq_fnode);
- smb_node_release(op->fqi.fq_dnode);
- return (NT_STATUS_INTERNAL_ERROR);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto errout;
}
} else if (rc == ENOENT) {
last_comp_found = B_FALSE;
op->fqi.fq_fnode = NULL;
rc = 0;
} else {
- smb_node_release(op->fqi.fq_dnode);
- return (smb_errno2status(rc));
+ status = smb_errno2status(rc);
+ goto errout;
}
-
/*
* The uniq_fid is a CIFS-server-wide unique identifier for an ofile
* which is used to uniquely identify open instances for the
* VFS share reservation and POSIX locks.
*/
-
uniq_fid = SMB_UNIQ_FID();
if (last_comp_found) {
- node = op->fqi.fq_fnode;
+ smb_node_unlock(dnode);
+ dnode_wlock = B_FALSE;
+
+ fnode = op->fqi.fq_fnode;
dnode = op->fqi.fq_dnode;
- if (!smb_node_is_file(node) && !smb_node_is_dir(node) &&
- !smb_node_is_symlink(node)) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_ACCESS_DENIED);
+ if (!smb_node_is_file(fnode) &&
+ !smb_node_is_dir(fnode) &&
+ !smb_node_is_symlink(fnode)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto errout;
}
/*
@@ -475,18 +479,16 @@ smb_open_subr(smb_request_t *sr)
* - the target is NOT a directory and client requires that
* it MUST be.
*/
- if (smb_node_is_dir(node)) {
+ if (smb_node_is_dir(fnode)) {
if (op->create_options & FILE_NON_DIRECTORY_FILE) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_FILE_IS_A_DIRECTORY);
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto errout;
}
} else {
if ((op->create_options & FILE_DIRECTORY_FILE) ||
(op->nt_flags & NT_CREATE_FLAG_OPEN_TARGET_DIR)) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_NOT_A_DIRECTORY);
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto errout;
}
}
@@ -494,42 +496,37 @@ smb_open_subr(smb_request_t *sr)
* No more open should be accepted when "Delete on close"
* flag is set.
*/
- if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_DELETE_PENDING);
+ if (fnode->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
+ status = NT_STATUS_DELETE_PENDING;
+ goto errout;
}
/*
* Specified file already exists so the operation should fail.
*/
if (op->create_disposition == FILE_CREATE) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_OBJECT_NAME_COLLISION);
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto errout;
}
/*
* Windows seems to check read-only access before file
* sharing check.
*
- * Check to see if the file is currently readonly (irrespective
+ * Check to see if the file is currently readonly (regardless
* of whether this open will make it readonly).
+ * Readonly is ignored on directories.
*/
- if (SMB_PATHFILE_IS_READONLY(sr, node)) {
- /* Files data only */
- if (!smb_node_is_dir(node)) {
- if (op->desired_access & (FILE_WRITE_DATA |
- FILE_APPEND_DATA)) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_ACCESS_DENIED);
- }
- if (op->create_options & FILE_DELETE_ON_CLOSE) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_CANNOT_DELETE);
- }
+ if (SMB_PATHFILE_IS_READONLY(sr, fnode) &&
+ !smb_node_is_dir(fnode)) {
+ if (op->desired_access &
+ (FILE_WRITE_DATA | FILE_APPEND_DATA)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto errout;
+ }
+ if (op->create_options & FILE_DELETE_ON_CLOSE) {
+ status = NT_STATUS_CANNOT_DELETE;
+ goto errout;
}
}
@@ -539,15 +536,13 @@ smb_open_subr(smb_request_t *sr)
if (!smb_sattr_check(op->fqi.fq_fattr.sa_dosattr,
op->dattr)) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_ACCESS_DENIED);
+ status = NT_STATUS_ACCESS_DENIED;
+ goto errout;
}
- if (smb_node_is_dir(node)) {
- smb_node_release(node);
- smb_node_release(dnode);
- return (NT_STATUS_ACCESS_DENIED);
+ if (smb_node_is_dir(fnode)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto errout;
}
}
@@ -558,35 +553,30 @@ smb_open_subr(smb_request_t *sr)
(op->create_disposition == FILE_OVERWRITE))
op->desired_access |= FILE_WRITE_DATA;
- status = smb_fsop_access(sr, sr->user_cr, node,
+ status = smb_fsop_access(sr, sr->user_cr, fnode,
op->desired_access);
- if (status != NT_STATUS_SUCCESS) {
- smb_node_release(node);
- smb_node_release(dnode);
-
- /* SMB1 specific? NT_STATUS_PRIVILEGE_NOT_HELD */
- if (status == NT_STATUS_PRIVILEGE_NOT_HELD) {
- return (status);
- } else {
- return (NT_STATUS_ACCESS_DENIED);
- }
- }
+ if (status != NT_STATUS_SUCCESS)
+ goto errout;
if (max_requested) {
- smb_fsop_eaccess(sr, sr->user_cr, node, &max_allowed);
+ smb_fsop_eaccess(sr, sr->user_cr, fnode, &max_allowed);
op->desired_access |= max_allowed;
}
+
+ /*
+ * File owner should always get read control + read attr.
+ */
+ if (crgetuid(sr->user_cr) == op->fqi.fq_fattr.sa_vattr.va_uid)
+ op->desired_access |=
+ (READ_CONTROL | FILE_READ_ATTRIBUTES);
+
/*
* According to MS "dochelp" mail in Mar 2015, any handle
* on which read or write access is granted implicitly
* gets "read attributes", even if it was not requested.
- * This avoids unexpected access failures later that
- * would happen if these were not granted.
*/
- if ((op->desired_access & FILE_DATA_ALL) != 0) {
- op->desired_access |= (READ_CONTROL |
- FILE_READ_ATTRIBUTES);
- }
+ if ((op->desired_access & FILE_DATA_ALL) != 0)
+ op->desired_access |= FILE_READ_ATTRIBUTES;
/*
* Oplock break is done prior to sharing checks as the break
@@ -595,33 +585,175 @@ smb_open_subr(smb_request_t *sr)
* DELETE_ON_CLOSE. This may block, so set the file opening
* count before oplock stuff.
*/
- smb_node_inc_opening_count(node);
- smb_open_oplock_break(sr, node);
+ of = smb_ofile_alloc(sr, op, fnode, SMB_FTYPE_DISK,
+ tree_fid, uniq_fid);
+ tree_fid = 0; // given to the ofile
- if ((node->flags & NODE_FLAGS_DELETE_COMMITTED) != 0) {
+ smb_node_inc_opening_count(fnode);
+ opening_incr = B_TRUE;
+
+ /*
+ * XXX Supposed to do share access checks next.
+ * [MS-FSA] describes that as part of access check:
+ * 2.1.5.1.2.1 Alg... Check Access to an Existing File
+ *
+ * If CreateDisposition is FILE_OPEN or FILE_OPEN_IF:
+ * If Open.Stream.Oplock is not empty and
+ * Open.Stream.Oplock.State contains BATCH_OPLOCK,
+ * the object store MUST check for an oplock
+ * break according to the algorithm in section 2.1.4.12,
+ * with input values as follows:
+ * Open equal to this operation's Open
+ * Oplock equal to Open.Stream.Oplock
+ * Operation equal to "OPEN"
+ * OpParams containing two members:
+ * DesiredAccess, CreateDisposition
+ *
+ * It's not clear how Windows would ask the FS layer if
+ * the file has a BATCH oplock. We'll use a call to the
+ * common oplock code, which calls smb_oplock_break_OPEN
+ * only if the oplock state contains BATCH_OPLOCK.
+ * See: smb_oplock_break_BATCH()
+ *
+ * Also note: There's a nearly identical section in the
+ * spec. at the start of the "else" part of the above
+ * "if (disposition is overwrite, overwrite_if)" so this
+ * section (oplock break, the share mode check, and the
+ * next oplock_break_HANDLE) are all factored out to be
+ * in all cases above that if/else from the spec.
+ */
+ status = smb_oplock_break_BATCH(fnode, of,
+ op->desired_access, op->create_disposition);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(fnode, 0);
+ status = 0;
+ }
+ if (status != NT_STATUS_SUCCESS)
+ goto errout;
+
+ /*
+ * Check for sharing violations, and if any,
+ * do oplock break of handle caching.
+ *
+ * Need node_wrlock during shrlock checks,
+ * and not locked during oplock breaks etc.
+ */
+ shrlock_t0 = gethrtime();
+ shrlock_again:
+ smb_node_wrlock(fnode);
+ fnode_wlock = B_TRUE;
+ status = smb_fsop_shrlock(sr->user_cr, fnode, uniq_fid,
+ op->desired_access, op->share_access);
+ smb_node_unlock(fnode);
+ fnode_wlock = B_FALSE;
+
+ /*
+ * [MS-FSA] "OPEN_BREAK_H"
+ * If the (proposed) new open would violate sharing rules,
+ * indicate an oplock break with OPEN_BREAK_H (to break
+ * handle level caching rights) then try again.
+ */
+ if (status == NT_STATUS_SHARING_VIOLATION &&
+ did_break_handle == B_FALSE) {
+ did_break_handle = B_TRUE;
+
+ status = smb_oplock_break_HANDLE(fnode, of);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(fnode, 0);
+ status = 0;
+ } else {
+ /*
+ * Even when the oplock layer does NOT
+ * give us the special status indicating
+ * we should wait, it may have scheduled
+ * taskq jobs that may close handles.
+ * Give those a chance to run before we
+ * check again for sharing violations.
+ */
+ delay(MSEC_TO_TICK(10));
+ }
+ if (status != NT_STATUS_SUCCESS)
+ goto errout;
+
+ goto shrlock_again;
+ }
+
+ /*
+ * SMB1 expects a 1 sec. delay before returning a
+ * sharing violation error. If breaking oplocks
+ * above took less than a sec, wait some more.
+ * See: smbtorture base.defer_open
+ */
+ if (status == NT_STATUS_SHARING_VIOLATION &&
+ sr->session->dialect < SMB_VERS_2_BASE) {
+ hrtime_t t1 = shrlock_t0 + NANOSEC;
+ hrtime_t now = gethrtime();
+ if (now < t1) {
+ delay(NSEC_TO_TICK_ROUNDUP(t1 - now));
+ }
+ }
+
+ if (status != NT_STATUS_SUCCESS)
+ goto errout;
+ fnode_shrlk = B_TRUE;
+
+ /*
+ * The [MS-FSA] spec. describes this oplock break as
+ * part of the sharing access checks. See:
+ * 2.1.5.1.2.2 Algorithm to Check Sharing Access...
+ * At the end of the share mode tests described there,
+ * if it has not returned "sharing violation", it
+ * specifies a call to the alg. in sec. 2.1.4.12,
+ * that boils down to: smb_oplock_break_OPEN()
+ */
+ status = smb_oplock_break_OPEN(fnode, of,
+ op->desired_access,
+ op->create_disposition);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(fnode, 0);
+ status = 0;
+ }
+ if (status != NT_STATUS_SUCCESS)
+ goto errout;
+
+ if ((fnode->flags & NODE_FLAGS_DELETE_COMMITTED) != 0) {
/*
* Breaking the oplock caused the file to be deleted,
- * so let's bail and pretend the file wasn't found
+ * so let's bail and pretend the file wasn't found.
+ * Have to duplicate much of the logic found a the
+ * "errout" label here.
+ *
+ * This code path is exercised by smbtorture
+ * smb2.durable-open.delete_on_close1
*/
- smb_node_dec_opening_count(node);
- smb_node_release(node);
+ DTRACE_PROBE1(node_deleted, smb_node_t, fnode);
+ smb_ofile_free(of);
+ of = NULL;
last_comp_found = B_FALSE;
- goto create;
- }
- smb_node_wrlock(node);
+ /*
+ * Get all the holds and locks into the state
+ * they would have if lookup had failed.
+ */
+ fnode_shrlk = B_FALSE;
+ smb_fsop_unshrlock(sr->user_cr, fnode, uniq_fid);
- /*
- * Check for sharing violations
- */
- status = smb_fsop_shrlock(sr->user_cr, node, uniq_fid,
- op->desired_access, op->share_access);
- if (status == NT_STATUS_SHARING_VIOLATION) {
- smb_node_unlock(node);
- smb_node_dec_opening_count(node);
- smb_node_release(node);
- smb_node_release(dnode);
- return (status);
+ opening_incr = B_FALSE;
+ smb_node_dec_opening_count(fnode);
+
+ fnode_held = B_FALSE;
+ smb_node_release(fnode);
+
+ dnode_wlock = B_TRUE;
+ smb_node_wrlock(dnode);
+
+ goto create;
}
/*
@@ -648,31 +780,21 @@ smb_open_subr(smb_request_t *sr)
new_attr.sa_dosattr = op->dattr;
new_attr.sa_vattr.va_size = 0;
new_attr.sa_mask = SMB_AT_DOSATTR | SMB_AT_SIZE;
- rc = smb_fsop_setattr(sr, sr->user_cr, node, &new_attr);
+ rc = smb_fsop_setattr(sr, sr->user_cr, fnode,
+ &new_attr);
if (rc != 0) {
- smb_fsop_unshrlock(sr->user_cr, node, uniq_fid);
- smb_node_unlock(node);
- smb_node_dec_opening_count(node);
- smb_node_release(node);
- smb_node_release(dnode);
- return (smb_errno2status(rc));
+ status = smb_errno2status(rc);
+ goto errout;
}
/*
* If file is being replaced, remove existing streams
*/
- if (SMB_IS_STREAM(node) == 0) {
+ if (SMB_IS_STREAM(fnode) == 0) {
status = smb_fsop_remove_streams(sr,
- sr->user_cr, node);
- if (status != 0) {
- smb_fsop_unshrlock(sr->user_cr, node,
- uniq_fid);
- smb_node_unlock(node);
- smb_node_dec_opening_count(node);
- smb_node_release(node);
- smb_node_release(dnode);
- return (status);
- }
+ sr->user_cr, fnode);
+ if (status != 0)
+ goto errout;
}
op->action_taken = SMB_OACT_TRUNCATED;
@@ -701,30 +823,24 @@ create:
if ((op->create_disposition == FILE_OPEN) ||
(op->create_disposition == FILE_OVERWRITE)) {
- smb_node_release(dnode);
- return (NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto errout;
}
if (pn->pn_fname && smb_is_invalid_filename(pn->pn_fname)) {
- smb_node_release(dnode);
- return (NT_STATUS_OBJECT_NAME_INVALID);
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto errout;
}
/*
* Don't create in directories marked "Delete on close".
*/
if (dnode->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
- smb_node_release(dnode);
- return (NT_STATUS_DELETE_PENDING);
+ status = NT_STATUS_DELETE_PENDING;
+ goto errout;
}
/*
- * lock the parent dir node in case another create
- * request to the same parent directory comes in.
- */
- smb_node_wrlock(dnode);
-
- /*
* Create always sets the DOS attributes, type, and mode
* in the if/else below (different for file vs directory).
* Don't set the readonly bit until smb_set_open_attributes
@@ -774,29 +890,6 @@ create:
rc = smb_fsop_create(sr, sr->user_cr, dnode,
op->fqi.fq_last_comp, &new_attr, &op->fqi.fq_fnode);
-
- if (rc != 0) {
- smb_node_unlock(dnode);
- smb_node_release(dnode);
- return (smb_errno2status(rc));
- }
-
- node = op->fqi.fq_fnode;
- smb_node_inc_opening_count(node);
- smb_node_wrlock(node);
-
- status = smb_fsop_shrlock(sr->user_cr, node, uniq_fid,
- op->desired_access, op->share_access);
-
- if (status == NT_STATUS_SHARING_VIOLATION) {
- smb_node_unlock(node);
- smb_node_dec_opening_count(node);
- smb_delete_new_object(sr);
- smb_node_release(node);
- smb_node_unlock(dnode);
- smb_node_release(dnode);
- return (status);
- }
} else {
op->dattr |= FILE_ATTRIBUTE_DIRECTORY;
new_attr.sa_dosattr = op->dattr;
@@ -805,22 +898,35 @@ create:
rc = smb_fsop_mkdir(sr, sr->user_cr, dnode,
op->fqi.fq_last_comp, &new_attr, &op->fqi.fq_fnode);
- if (rc != 0) {
- smb_node_unlock(dnode);
- smb_node_release(dnode);
- return (smb_errno2status(rc));
- }
-
- node = op->fqi.fq_fnode;
- smb_node_inc_opening_count(node);
- smb_node_wrlock(node);
}
+ if (rc != 0) {
+ status = smb_errno2status(rc);
+ goto errout;
+ }
+
+ smb_node_unlock(dnode);
+ dnode_wlock = B_FALSE;
created = B_TRUE;
op->action_taken = SMB_OACT_CREATED;
+ fnode = op->fqi.fq_fnode;
+ fnode_held = B_TRUE;
+
+ smb_node_inc_opening_count(fnode);
+ opening_incr = B_TRUE;
+
+ smb_node_wrlock(fnode);
+ fnode_wlock = B_TRUE;
+
+ status = smb_fsop_shrlock(sr->user_cr, fnode, uniq_fid,
+ op->desired_access, op->share_access);
+ if (status != 0)
+ goto errout;
+ fnode_shrlk = B_TRUE;
+
if (max_requested) {
- smb_fsop_eaccess(sr, sr->user_cr, node, &max_allowed);
+ smb_fsop_eaccess(sr, sr->user_cr, fnode, &max_allowed);
op->desired_access |= max_allowed;
}
/*
@@ -830,83 +936,80 @@ create:
* unexpected access failures later.
*/
op->desired_access |= (READ_CONTROL | FILE_READ_ATTRIBUTES);
- }
-
- status = NT_STATUS_SUCCESS;
- of = smb_ofile_open(sr, node, op, SMB_FTYPE_DISK, uniq_fid,
- &err);
- if (of == NULL) {
- status = err.status;
+ /*
+ * MS-FSA 2.1.5.1.1
+ * If the Oplock member of the DirectoryStream in
+ * Link.ParentFile.StreamList (ParentOplock) is
+ * not empty ... oplock break on the parent...
+ * (dnode is the parent directory)
+ *
+ * This compares of->ParentOplockKey with each
+ * oplock of->TargetOplockKey and breaks...
+ * so it's OK that we're passing an OF that's
+ * NOT a member of dnode->n_ofile_list
+ *
+ * The break never blocks, so ignore the return.
+ */
+ of = smb_ofile_alloc(sr, op, fnode, SMB_FTYPE_DISK,
+ tree_fid, uniq_fid);
+ tree_fid = 0; // given to the ofile
+ (void) smb_oplock_break_PARENT(dnode, of);
}
/*
- * We might have blocked in smb_ofile_open long enough so a
- * tree disconnect might have happened. In that case, we've
- * just added an ofile to a tree that's disconnecting, and
- * need to undo that to avoid interfering with tear-down of
- * the tree connection.
+ * We might have blocked in smb_oplock_break_OPEN long enough
+ * so a tree disconnect might have happened. In that case,
+ * we would be adding an ofile to a tree that's disconnecting,
+ * which would interfere with tear-down. If so, error out.
*/
- if (status == NT_STATUS_SUCCESS &&
- !smb_tree_is_connected(sr->tid_tree)) {
+ if (!smb_tree_is_connected(sr->tid_tree)) {
status = NT_STATUS_INVALID_PARAMETER;
+ goto errout;
+ }
+
+ /*
+ * Moved this up from smb_ofile_open()
+ */
+ if ((rc = smb_fsop_open(fnode, of->f_mode, of->f_cr)) != 0) {
+ status = smb_errno2status(rc);
+ goto errout;
}
/*
+ * Complete this open (add to ofile lists)
+ */
+ smb_ofile_open(sr, op, of);
+ did_open = B_TRUE;
+
+ /*
* This MUST be done after ofile creation, so that explicitly
* set timestamps can be remembered on the ofile, and setting
* the readonly flag won't affect access via this open.
*/
- if (status == NT_STATUS_SUCCESS) {
- if ((rc = smb_set_open_attributes(sr, of)) != 0) {
- status = smb_errno2status(rc);
- }
- }
-
- if (status == NT_STATUS_SUCCESS) {
- /*
- * We've already done access checks above,
- * and want this call to succeed even when
- * !(desired_access & FILE_READ_ATTRIBUTES),
- * so pass kcred here.
- */
- op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
- rc = smb_node_getattr(sr, node, zone_kcred(), of,
- &op->fqi.fq_fattr);
- if (rc != 0) {
- status = NT_STATUS_INTERNAL_ERROR;
- }
+ if ((rc = smb_set_open_attributes(sr, of)) != 0) {
+ status = smb_errno2status(rc);
+ goto errout;
}
/*
- * smb_fsop_unshrlock is a no-op if node is a directory
- * smb_fsop_unshrlock is done in smb_ofile_close
+ * We've already done access checks above,
+ * and want this call to succeed even when
+ * !(desired_access & FILE_READ_ATTRIBUTES),
+ * so pass kcred here.
*/
- if (status != NT_STATUS_SUCCESS) {
- if (of == NULL) {
- smb_fsop_unshrlock(sr->user_cr, node, uniq_fid);
- } else {
- smb_ofile_close(of, 0);
- smb_ofile_release(of);
- }
- if (created)
- smb_delete_new_object(sr);
- smb_node_unlock(node);
- smb_node_dec_opening_count(node);
- smb_node_release(node);
- if (created)
- smb_node_unlock(dnode);
- smb_node_release(dnode);
- return (status);
- }
+ op->fqi.fq_fattr.sa_mask = SMB_AT_ALL;
+ (void) smb_node_getattr(sr, fnode, zone_kcred(), of,
+ &op->fqi.fq_fattr);
/*
* Propagate the write-through mode from the open params
* to the node: see the notes in the function header.
+ * XXX: write_through should be a flag on the ofile.
*/
if (sr->sr_cfg->skc_sync_enable ||
(op->create_options & FILE_WRITE_THROUGH))
- node->flags |= NODE_FLAGS_WRITE_THROUGH;
+ fnode->flags |= NODE_FLAGS_WRITE_THROUGH;
/*
* Set up the fileid and dosattr in open_param for response
@@ -921,95 +1024,62 @@ create:
sr->smb_fid = of->f_fid;
sr->fid_ofile = of;
- if (smb_node_is_file(node)) {
- smb_oplock_acquire(sr, node, of);
+ if (smb_node_is_file(fnode)) {
op->dsize = op->fqi.fq_fattr.sa_vattr.va_size;
} else {
/* directory or symlink */
- op->op_oplock_level = SMB_OPLOCK_NONE;
op->dsize = 0;
}
- smb_node_dec_opening_count(node);
+ /*
+ * Note: oplock_acquire happens in callers, because
+ * how that happens is protocol-specific.
+ */
- smb_node_unlock(node);
- if (created)
+ if (fnode_wlock)
+ smb_node_unlock(fnode);
+ if (opening_incr)
+ smb_node_dec_opening_count(fnode);
+ if (fnode_held)
+ smb_node_release(fnode);
+ if (dnode_wlock)
smb_node_unlock(dnode);
-
- smb_node_release(node);
- smb_node_release(dnode);
+ if (dnode_held)
+ smb_node_release(dnode);
return (NT_STATUS_SUCCESS);
-}
-
-/*
- * smb_open_oplock_break
- *
- * If the node has an ofile opened with share access none,
- * (smb_node_share_check = FALSE) only break BATCH oplock.
- * Otherwise:
- * If overwriting, break to SMB_OPLOCK_NONE, else
- * If opening for anything other than attribute access,
- * break oplock to LEVEL_II.
- */
-static void
-smb_open_oplock_break(smb_request_t *sr, smb_node_t *node)
-{
- smb_arg_open_t *op = &sr->sr_open;
- uint32_t flags = 0;
- int rc = 0;
- if (sr->session->dialect >= SMB_VERS_2_BASE &&
- sr->smb2_async == B_FALSE)
- flags |= SMB_OPLOCK_BREAK_NOWAIT;
+errout:
+ if (did_open) {
+ smb_ofile_close(of, 0);
+ /* Don't also ofile_free */
+ } else if (of != NULL) {
+ smb_ofile_free(of);
+ }
- if (!smb_node_share_check(node))
- flags |= SMB_OPLOCK_BREAK_BATCH;
+ if (fnode_shrlk)
+ smb_fsop_unshrlock(sr->user_cr, fnode, uniq_fid);
- if (smb_open_overwrite(op)) {
- flags |= SMB_OPLOCK_BREAK_TO_NONE;
- rc = smb_oplock_break(sr, node, flags);
- } else if (!smb_open_attr_only(op)) {
- flags |= SMB_OPLOCK_BREAK_TO_LEVEL_II;
- rc = smb_oplock_break(sr, node, flags);
+ if (created) {
+ /* Try to roll-back create. */
+ smb_delete_new_object(sr);
}
- if (sr->session->dialect >= SMB_VERS_2_BASE &&
- rc == EAGAIN) {
- (void) smb2sr_go_async(sr);
- flags &= ~SMB_OPLOCK_BREAK_NOWAIT;
- (void) smb_oplock_break(sr, node, flags);
- }
-}
+ if (fnode_wlock)
+ smb_node_unlock(fnode);
+ if (opening_incr)
+ smb_node_dec_opening_count(fnode);
+ if (fnode_held)
+ smb_node_release(fnode);
+ if (dnode_wlock)
+ smb_node_unlock(dnode);
+ if (dnode_held)
+ smb_node_release(dnode);
-/*
- * smb_open_attr_only
- *
- * Determine if file is being opened for attribute access only.
- * This is used to determine whether it is necessary to break
- * existing oplocks on the file.
- */
-static boolean_t
-smb_open_attr_only(smb_arg_open_t *op)
-{
- if (((op->desired_access & ~(FILE_READ_ATTRIBUTES |
- FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | READ_CONTROL)) == 0) &&
- (op->create_disposition != FILE_SUPERSEDE) &&
- (op->create_disposition != FILE_OVERWRITE)) {
- return (B_TRUE);
- }
- return (B_FALSE);
-}
+ if (tree_fid != 0)
+ smb_idpool_free(&tree->t_fid_pool, tree_fid);
-static boolean_t
-smb_open_overwrite(smb_arg_open_t *op)
-{
- if ((op->create_disposition == FILE_SUPERSEDE) ||
- (op->create_disposition == FILE_OVERWRITE_IF) ||
- (op->create_disposition == FILE_OVERWRITE)) {
- return (B_TRUE);
- }
- return (B_FALSE);
+ return (status);
}
/*
diff --git a/usr/src/uts/common/fs/smbsrv/smb_create.c b/usr/src/uts/common/fs/smbsrv/smb_create.c
index 2cb387b99a..cd25aa53f7 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_create.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_create.c
@@ -196,9 +196,12 @@ smb_common_create(smb_request_t *sr)
} else {
op->op_oplock_level = SMB_OPLOCK_NONE;
}
- op->op_oplock_levelII = B_FALSE;
status = smb_common_open(sr);
+ if (status == 0 && op->op_oplock_level != SMB_OPLOCK_NONE) {
+ /* Oplock req. in op->op_oplock_level etc. */
+ smb1_oplock_acquire(sr, B_FALSE);
+ }
if (op->op_oplock_level == SMB_OPLOCK_NONE) {
sr->smb_flg &=
diff --git a/usr/src/uts/common/fs/smbsrv/smb_delete.c b/usr/src/uts/common/fs/smbsrv/smb_delete.c
index 3902764d23..f0159fc971 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_delete.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_delete.c
@@ -470,7 +470,7 @@ smb_delete_check_dosattr(smb_request_t *sr, smb_error_t *err)
static int
smb_delete_remove_file(smb_request_t *sr, smb_error_t *err)
{
- int rc, count;
+ int rc;
uint32_t status;
smb_fqi_t *fqi;
smb_node_t *node;
@@ -484,28 +484,22 @@ smb_delete_remove_file(smb_request_t *sr, smb_error_t *err)
* has a file open, this will force a flush or close,
* which may affect the outcome of any share checking.
*/
- (void) smb_oplock_break(sr, node,
- SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
+ status = smb_oplock_break_DELETE(node, NULL);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb_oplock_wait_break(node, 0);
+ status = 0;
+ }
+ if (status != 0) {
+ err->status = status;
+ return (-1);
+ }
- /*
- * Wait (a little) for the oplock break to be
- * responded to by clients closing handles.
- * Hold node->n_lock as reader to keep new
- * ofiles from showing up after we check.
- */
smb_node_rdlock(node);
- for (count = 0; count <= 12; count++) {
- status = smb_node_delete_check(node);
- if (status != NT_STATUS_SHARING_VIOLATION)
- break;
- smb_node_unlock(node);
- delay(MSEC_TO_TICK(100));
- smb_node_rdlock(node);
- }
+ status = smb_node_delete_check(node);
if (status != NT_STATUS_SUCCESS) {
+ smb_node_unlock(node);
smb_delete_error(err, NT_STATUS_SHARING_VIOLATION,
ERRDOS, ERROR_SHARING_VIOLATION);
- smb_node_unlock(node);
return (-1);
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_fem.c b/usr/src/uts/common/fs/smbsrv/smb_fem.c
index d87106069b..c41ddddac8 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_fem.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_fem.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
* Copyright 2015 Joyent, Inc.
*/
@@ -81,7 +81,6 @@ static int smb_fem_oplock_write(femarg_t *, uio_t *, int, cred_t *,
struct caller_context *);
static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *,
caller_context_t *);
-static int smb_fem_oplock_rwlock(femarg_t *, int, caller_context_t *);
static int smb_fem_oplock_space(femarg_t *, int, flock64_t *, int,
offset_t, cred_t *, caller_context_t *);
static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *,
@@ -92,13 +91,12 @@ static const fs_operation_def_t smb_oplock_tmpl[] = {
VOPNAME_READ, { .femop_read = smb_fem_oplock_read },
VOPNAME_WRITE, { .femop_write = smb_fem_oplock_write },
VOPNAME_SETATTR, { .femop_setattr = smb_fem_oplock_setattr },
- VOPNAME_RWLOCK, { .femop_rwlock = smb_fem_oplock_rwlock },
VOPNAME_SPACE, { .femop_space = smb_fem_oplock_space },
VOPNAME_VNEVENT, { .femop_vnevent = smb_fem_oplock_vnevent },
NULL, NULL
};
-static int smb_fem_oplock_break(femarg_t *, caller_context_t *, uint32_t);
+static int smb_fem_oplock_wait(smb_node_t *, caller_context_t *);
/*
* smb_fem_init
@@ -428,15 +426,36 @@ smb_fem_oplock_open(
cred_t *cr,
caller_context_t *ct)
{
- uint32_t flags;
+ smb_node_t *node;
+ uint32_t status;
int rc = 0;
if (ct != &smb_ct) {
- if (mode & (FWRITE|FTRUNC))
- flags = SMB_OPLOCK_BREAK_TO_NONE;
- else
- flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
- rc = smb_fem_oplock_break(arg, ct, flags);
+ uint32_t req_acc = FILE_READ_DATA;
+ uint32_t cr_disp = FILE_OPEN_IF;
+
+ node = (smb_node_t *)(arg->fa_fnode->fn_available);
+ SMB_NODE_VALID(node);
+
+ /*
+ * Get req_acc, cr_disp just accurate enough so
+ * the oplock break call does the right thing.
+ */
+ if (mode & FWRITE) {
+ req_acc = FILE_READ_DATA | FILE_WRITE_DATA;
+ cr_disp = (mode & FTRUNC) ?
+ FILE_OVERWRITE_IF : FILE_OPEN_IF;
+ } else {
+ req_acc = FILE_READ_DATA;
+ cr_disp = FILE_OPEN_IF;
+ }
+
+ status = smb_oplock_break_OPEN(node, NULL,
+ req_acc, cr_disp);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+ rc = smb_fem_oplock_wait(node, ct);
+ else if (status != 0)
+ rc = EIO;
}
if (rc == 0)
rc = vnext_open(arg, mode, cr, ct);
@@ -457,11 +476,19 @@ smb_fem_oplock_read(
cred_t *cr,
caller_context_t *ct)
{
+ smb_node_t *node;
+ uint32_t status;
int rc = 0;
if (ct != &smb_ct) {
- rc = smb_fem_oplock_break(arg, ct,
- SMB_OPLOCK_BREAK_TO_LEVEL_II);
+ node = (smb_node_t *)(arg->fa_fnode->fn_available);
+ SMB_NODE_VALID(node);
+
+ status = smb_oplock_break_READ(node, NULL);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+ rc = smb_fem_oplock_wait(node, ct);
+ else if (status != 0)
+ rc = EIO;
}
if (rc == 0)
rc = vnext_read(arg, uiop, ioflag, cr, ct);
@@ -482,10 +509,20 @@ smb_fem_oplock_write(
cred_t *cr,
caller_context_t *ct)
{
+ smb_node_t *node;
+ uint32_t status;
int rc = 0;
- if (ct != &smb_ct)
- rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
+ if (ct != &smb_ct) {
+ node = (smb_node_t *)(arg->fa_fnode->fn_available);
+ SMB_NODE_VALID(node);
+
+ status = smb_oplock_break_WRITE(node, NULL);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+ rc = smb_fem_oplock_wait(node, ct);
+ else if (status != 0)
+ rc = EIO;
+ }
if (rc == 0)
rc = vnext_write(arg, uiop, ioflag, cr, ct);
@@ -500,34 +537,23 @@ smb_fem_oplock_setattr(
cred_t *cr,
caller_context_t *ct)
{
+ smb_node_t *node;
+ uint32_t status;
int rc = 0;
- if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0)
- rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
- if (rc == 0)
- rc = vnext_setattr(arg, vap, flags, cr, ct);
- return (rc);
-}
-
-static int
-smb_fem_oplock_rwlock(
- femarg_t *arg,
- int write_lock,
- caller_context_t *ct)
-{
- uint32_t flags;
- int rc = 0;
+ if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0) {
+ node = (smb_node_t *)(arg->fa_fnode->fn_available);
+ SMB_NODE_VALID(node);
- if (ct != &smb_ct) {
- if (write_lock)
- flags = SMB_OPLOCK_BREAK_TO_NONE;
- else
- flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
- rc = smb_fem_oplock_break(arg, ct, flags);
+ status = smb_oplock_break_SETINFO(node, NULL,
+ FileEndOfFileInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+ rc = smb_fem_oplock_wait(node, ct);
+ else if (status != 0)
+ rc = EIO;
}
if (rc == 0)
- rc = vnext_rwlock(arg, write_lock, ct);
-
+ rc = vnext_setattr(arg, vap, flags, cr, ct);
return (rc);
}
@@ -541,10 +567,21 @@ smb_fem_oplock_space(
cred_t *cr,
caller_context_t *ct)
{
+ smb_node_t *node;
+ uint32_t status;
int rc = 0;
- if (ct != &smb_ct)
- rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
+ if (ct != &smb_ct) {
+ node = (smb_node_t *)(arg->fa_fnode->fn_available);
+ SMB_NODE_VALID(node);
+
+ status = smb_oplock_break_SETINFO(node, NULL,
+ FileAllocationInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+ rc = smb_fem_oplock_wait(node, ct);
+ else if (status != 0)
+ rc = EIO;
+ }
if (rc == 0)
rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct);
return (rc);
@@ -572,28 +609,33 @@ smb_fem_oplock_vnevent(
char *name,
caller_context_t *ct)
{
- uint32_t flags;
+ smb_node_t *node;
+ uint32_t status;
int rc = 0;
if (ct != &smb_ct) {
+ node = (smb_node_t *)(arg->fa_fnode->fn_available);
+ SMB_NODE_VALID(node);
+
switch (vnevent) {
case VE_REMOVE:
case VE_PRE_RENAME_DEST:
case VE_RENAME_DEST:
- flags = SMB_OPLOCK_BREAK_TO_NONE |
- SMB_OPLOCK_BREAK_BATCH;
- rc = smb_fem_oplock_break(arg, ct, flags);
+ status = smb_oplock_break_HANDLE(node, NULL);
break;
case VE_PRE_RENAME_SRC:
case VE_RENAME_SRC:
- flags = SMB_OPLOCK_BREAK_TO_LEVEL_II |
- SMB_OPLOCK_BREAK_BATCH;
- rc = smb_fem_oplock_break(arg, ct, flags);
+ status = smb_oplock_break_SETINFO(node, NULL,
+ FileRenameInformation);
break;
default:
- rc = 0;
+ status = 0;
break;
}
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
+ rc = smb_fem_oplock_wait(node, ct);
+ else if (status != 0)
+ rc = EIO;
}
if (rc == 0)
rc = vnext_vnevent(arg, vnevent, dvp, name, ct);
@@ -601,24 +643,22 @@ smb_fem_oplock_vnevent(
return (rc);
}
+int smb_fem_oplock_timeout = 5000; /* mSec. */
+
static int
-smb_fem_oplock_break(femarg_t *arg, caller_context_t *ct, uint32_t flags)
+smb_fem_oplock_wait(smb_node_t *node, caller_context_t *ct)
{
- smb_node_t *node;
- int rc;
-
- node = (smb_node_t *)((arg)->fa_fnode->fn_available);
- SMB_NODE_VALID(node);
+ int rc = 0;
ASSERT(ct != &smb_ct);
if (ct && (ct->cc_flags & CC_DONTBLOCK)) {
- flags |= SMB_OPLOCK_BREAK_NOWAIT;
- rc = smb_oplock_break(NULL, node, flags);
- if (rc == EAGAIN)
- ct->cc_flags |= CC_WOULDBLOCK;
+ ct->cc_flags |= CC_WOULDBLOCK;
+ rc = EAGAIN;
} else {
- rc = smb_oplock_break(NULL, node, flags);
+ (void) smb_oplock_wait_break(node,
+ smb_fem_oplock_timeout);
+ rc = 0;
}
return (rc);
diff --git a/usr/src/uts/common/fs/smbsrv/smb_init.c b/usr/src/uts/common/fs/smbsrv/smb_init.c
index dd4a15b948..2de046774d 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_init.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_init.c
@@ -64,9 +64,6 @@ static int smb_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
* environment. When in doubt use 37KB.
*/
int smb_maxbufsize = SMB_NT_MAXBUF;
-int smb_oplock_levelII = 1;
-int smb_oplock_timeout = OPLOCK_STD_TIMEOUT;
-int smb_oplock_min_timeout = OPLOCK_MIN_TIMEOUT;
int smb_flush_required = 1;
int smb_dirsymlink_enable = 1;
int smb_sign_debug = 0;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_kshare.c b/usr/src/uts/common/fs/smbsrv/smb_kshare.c
index 3302eee0a1..ec95c9aca4 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_kshare.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_kshare.c
@@ -891,6 +891,7 @@ smb_kshare_decode(nvlist_t *share)
SMB_SHRF_DFSROOT);
tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_QUOTAS,
SMB_SHRF_QUOTAS);
+ tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_FSO, SMB_SHRF_FSO);
tmp.shr_flags |= smb_kshare_decode_bool(smb, SHOPT_AUTOHOME,
SMB_SHRF_AUTOHOME);
diff --git a/usr/src/uts/common/fs/smbsrv/smb_lock.c b/usr/src/uts/common/fs/smbsrv/smb_lock.c
index e12e3be6fd..02786ce18d 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_lock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_lock.c
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
/*
@@ -65,7 +65,7 @@ static void smb_lock_free(smb_lock_t *);
uint32_t
smb_lock_get_lock_count(smb_node_t *node, smb_ofile_t *of)
{
- smb_lock_t *lock;
+ smb_lock_t *lock;
smb_llist_t *llist;
uint32_t count = 0;
@@ -330,8 +330,10 @@ break_loop:
smb_llist_exit(&node->n_lock_list);
- if (result == NT_STATUS_SUCCESS)
- smb_oplock_break_levelII(node);
+ if (result == NT_STATUS_SUCCESS) {
+ /* This revokes read cache delegations. */
+ (void) smb_oplock_break_WRITE(node, file);
+ }
return (result);
}
@@ -345,7 +347,7 @@ break_loop:
*
* Return values
* NT_STATUS_SUCCESS lock access granted.
- * NT_STATUS_FILE_LOCK_CONFLICT access denied due to lock conflict.
+ * NT_STATUS_FILE_LOCK_CONFLICT access denied due to lock conflict.
*/
int
smb_lock_range_access(
diff --git a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
index 63db25f4b6..8028cfe8c9 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_locking_andx.c
@@ -248,11 +248,11 @@ smb_com_locking_andx(smb_request_t *sr)
unsigned short unlock_num; /* # unlock range structs */
unsigned short lock_num; /* # lock range structs */
DWORD result;
- int rc;
+ int rc;
uint32_t ltype;
+ uint32_t status;
smb_ofile_t *ofile;
uint16_t tmp_pid; /* locking uses 16-bit pids */
- uint8_t brk;
uint32_t lrv_tot;
struct lreq *lrv_ul;
struct lreq *lrv_lk;
@@ -288,11 +288,16 @@ smb_com_locking_andx(smb_request_t *sr)
ltype = SMB_LOCK_TYPE_READWRITE;
if (lock_type & LOCKING_ANDX_OPLOCK_RELEASE) {
+ uint32_t NewLevel;
if (oplock_level == 0)
- brk = SMB_OPLOCK_BREAK_TO_NONE;
+ NewLevel = OPLOCK_LEVEL_NONE;
else
- brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
- smb_oplock_ack(ofile->f_node, ofile, brk);
+ NewLevel = OPLOCK_LEVEL_TWO;
+ status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb_oplock_wait_break(ofile->f_node, 0);
+ status = 0;
+ }
if (unlock_num == 0 && lock_num == 0)
return (SDRC_NO_REPLY);
}
@@ -415,21 +420,25 @@ out:
* The caller will send it and free the request.
*/
void
-smb1_oplock_break_notification(smb_request_t *sr, uint8_t brk)
+smb1_oplock_break_notification(smb_request_t *sr, uint32_t NewLevel)
{
smb_ofile_t *ofile = sr->fid_ofile;
uint16_t fid;
uint8_t lock_type;
uint8_t oplock_level;
- switch (brk) {
+ /*
+ * Convert internal level to SMB1
+ */
+ switch (NewLevel) {
default:
ASSERT(0);
/* FALLTHROUGH */
- case SMB_OPLOCK_BREAK_TO_NONE:
+ case OPLOCK_LEVEL_NONE:
oplock_level = 0;
break;
- case SMB_OPLOCK_BREAK_TO_LEVEL_II:
+
+ case OPLOCK_LEVEL_TWO:
oplock_level = 1;
break;
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_node.c b/usr/src/uts/common/fs/smbsrv/smb_node.c
index 2d16395ad3..63756f9037 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_node.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_node.c
@@ -751,6 +751,10 @@ smb_node_open_check(smb_node_t *node, uint32_t desired_access,
break;
default:
ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+ DTRACE_PROBE3(conflict3,
+ smb_ofile_t, of,
+ uint32_t, desired_access,
+ uint32_t, share_access);
smb_llist_exit(&node->n_ofile_list);
return (status);
}
@@ -783,6 +787,7 @@ smb_node_rename_check(smb_node_t *node)
break;
default:
ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+ DTRACE_PROBE1(conflict1, smb_ofile_t, of);
smb_llist_exit(&node->n_ofile_list);
return (status);
}
@@ -820,6 +825,7 @@ smb_node_delete_check(smb_node_t *node)
break;
default:
ASSERT(status == NT_STATUS_SHARING_VIOLATION);
+ DTRACE_PROBE1(conflict1, smb_ofile_t, of);
smb_llist_exit(&node->n_ofile_list);
return (status);
}
@@ -1185,9 +1191,6 @@ smb_node_alloc(
node->delete_on_close_cred = NULL;
node->n_delete_on_close_flags = 0;
node->n_oplock.ol_fem = B_FALSE;
- node->n_oplock.ol_xthread = NULL;
- node->n_oplock.ol_count = 0;
- node->n_oplock.ol_break = SMB_OPLOCK_NO_BREAK;
(void) strlcpy(node->od_name, od_name, sizeof (node->od_name));
if (strcmp(od_name, XATTR_DIR) == 0)
@@ -1218,8 +1221,6 @@ smb_node_free(smb_node_t *node)
VERIFY(node->n_lock_list.ll_count == 0);
VERIFY(node->n_wlock_list.ll_count == 0);
VERIFY(node->n_ofile_list.ll_count == 0);
- VERIFY(node->n_oplock.ol_count == 0);
- VERIFY(node->n_oplock.ol_xthread == NULL);
VERIFY(node->n_oplock.ol_fem == B_FALSE);
VERIFY(MUTEX_NOT_HELD(&node->n_mutex));
VERIFY(!RW_LOCK_HELD(&node->n_lock));
@@ -1245,10 +1246,8 @@ smb_node_constructor(void *buf, void *un, int kmflags)
offsetof(smb_lock_t, l_lnd));
smb_llist_constructor(&node->n_wlock_list, sizeof (smb_lock_t),
offsetof(smb_lock_t, l_lnd));
- cv_init(&node->n_oplock.ol_cv, NULL, CV_DEFAULT, NULL);
mutex_init(&node->n_oplock.ol_mutex, NULL, MUTEX_DEFAULT, NULL);
- list_create(&node->n_oplock.ol_grants, sizeof (smb_oplock_grant_t),
- offsetof(smb_oplock_grant_t, og_lnd));
+ cv_init(&node->n_oplock.WaitingOpenCV, NULL, CV_DEFAULT, NULL);
rw_init(&node->n_lock, NULL, RW_DEFAULT, NULL);
mutex_init(&node->n_mutex, NULL, MUTEX_DEFAULT, NULL);
smb_node_create_audit_buf(node, kmflags);
@@ -1268,12 +1267,11 @@ smb_node_destructor(void *buf, void *un)
smb_node_destroy_audit_buf(node);
mutex_destroy(&node->n_mutex);
rw_destroy(&node->n_lock);
- cv_destroy(&node->n_oplock.ol_cv);
+ cv_destroy(&node->n_oplock.WaitingOpenCV);
mutex_destroy(&node->n_oplock.ol_mutex);
smb_llist_destructor(&node->n_lock_list);
smb_llist_destructor(&node->n_wlock_list);
smb_llist_destructor(&node->n_ofile_list);
- list_destroy(&node->n_oplock.ol_grants);
}
/*
diff --git a/usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c b/usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c
index 291942fc7c..edeff905e5 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_create_andx.c
@@ -290,13 +290,15 @@ smb_com_nt_create_andx(struct smb_request *sr)
op->fqi.fq_dnode = op->dir->f_node;
}
- op->op_oplock_levelII = B_TRUE;
-
status = smb_common_open(sr);
if (status != NT_STATUS_SUCCESS) {
smbsr_status(sr, status, 0, 0);
return (SDRC_ERROR);
}
+ if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+ /* Oplock req. in op->op_oplock_level etc. */
+ smb1_oplock_acquire(sr, B_TRUE);
+ }
/*
* NB: after the above smb_common_open() success,
@@ -309,7 +311,7 @@ smb_com_nt_create_andx(struct smb_request *sr)
case STYPE_DISKTREE:
case STYPE_PRINTQ:
if (op->create_options & FILE_DELETE_ON_CLOSE)
- smb_ofile_set_delete_on_close(of);
+ smb_ofile_set_delete_on_close(sr, of);
DirFlag = smb_node_is_dir(of->f_node) ? 1 : 0;
break;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c
index dbb4efaefb..b2494d8f7d 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_create.c
@@ -201,13 +201,15 @@ smb_nt_transact_create(smb_request_t *sr, smb_xa_t *xa)
op->fqi.fq_dnode = op->dir->f_node;
}
- op->op_oplock_levelII = B_TRUE;
-
status = smb_common_open(sr);
if (status != NT_STATUS_SUCCESS) {
smbsr_status(sr, status, 0, 0);
return (SDRC_ERROR);
}
+ if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+ /* Oplock req. in op->op_oplock_level etc. */
+ smb1_oplock_acquire(sr, B_TRUE);
+ }
/*
* NB: after the above smb_common_open() success,
@@ -220,7 +222,7 @@ smb_nt_transact_create(smb_request_t *sr, smb_xa_t *xa)
case STYPE_DISKTREE:
case STYPE_PRINTQ:
if (op->create_options & FILE_DELETE_ON_CLOSE)
- smb_ofile_set_delete_on_close(of);
+ smb_ofile_set_delete_on_close(sr, of);
DirFlag = smb_node_is_dir(of->f_node) ? 1 : 0;
break;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c
index f355e314bc..b2a1349043 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_ioctl.c
@@ -38,10 +38,6 @@ static uint32_t smb_nt_trans_ioctl_enum_snaps(smb_request_t *, smb_xa_t *);
/*
* This table defines the list of FSCTL values for which we'll
* call a funtion to perform specific processing.
- *
- * Note: If support is added for FSCTL_SET_ZERO_DATA, it must break
- * any oplocks on the file to none:
- * smb_oplock_break(sr, node, SMB_OPLOCK_BREAK_TO_NONE);
*/
static const struct {
uint32_t fcode;
@@ -222,7 +218,12 @@ smb_nt_trans_ioctl_set_sparse(smb_request_t *sr, smb_xa_t *xa)
* smb_nt_trans_ioctl_set_zero_data
*
* Check that the request is valid on the specified file.
- * The implementation is a noop.
+ * The implementation is a noop. XXX - bug!
+ * XXX: We have this in the fsclt module now. Call that.
+ *
+ * Note: When support is added for FSCTL_SET_ZERO_DATA, it must
+ * break any oplocks on the file to none:
+ * (void) smb_oplock_break_WRITE(node, ofile);
*/
/* ARGSUSED */
static uint32_t
diff --git a/usr/src/uts/common/fs/smbsrv/smb_ofile.c b/usr/src/uts/common/fs/smbsrv/smb_ofile.c
index 9521d6f277..046f550710 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_ofile.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_ofile.c
@@ -226,7 +226,7 @@
* Transition T7
*
* This transition occurs in smb_session_durable_timers() and
- * smb_oplock_sched_async_break(). The ofile will soon be closed.
+ * smb_oplock_send_brk(). The ofile will soon be closed.
* In the former case, f_timeout_offset nanoseconds have passed since
* the ofile was orphaned. In the latter, an oplock break occured
* on the ofile while it was orphaned.
@@ -281,6 +281,7 @@
#include <smbsrv/smb_fsops.h>
#include <sys/time.h>
+/* XXX: May need to actually assign GUIDs for these. */
/* Don't leak object addresses */
#define SMB_OFILE_PERSISTID(of) \
((uintptr_t)&smb_cache_ofile ^ (uintptr_t)(of))
@@ -295,29 +296,26 @@ static int smb_ofile_netinfo_init(smb_ofile_t *, smb_netfileinfo_t *);
static void smb_ofile_netinfo_fini(smb_netfileinfo_t *);
/*
- * smb_ofile_open
+ * smb_ofile_alloc
+ * Allocate an ofile and fill in it's "up" pointers, but
+ * do NOT link it into the tree's list of ofiles or the
+ * node's list of ofiles. An ofile in this state is a
+ * "proposed" open passed to the oplock break code.
+ *
+ * If we don't get as far as smb_ofile_open with this OF,
+ * call smb_ofile_free() to free this object.
*/
smb_ofile_t *
-smb_ofile_open(
+smb_ofile_alloc(
smb_request_t *sr,
- smb_node_t *node,
- struct open_param *op,
+ smb_arg_open_t *op,
+ smb_node_t *node, /* optional (may be NULL) */
uint16_t ftype,
- uint32_t uniqid,
- smb_error_t *err)
+ uint16_t tree_fid,
+ uint32_t uniqid)
{
smb_tree_t *tree = sr->tid_tree;
smb_ofile_t *of;
- uint16_t fid;
- smb_attr_t attr;
- int rc;
-
- if (smb_idpool_alloc(&tree->t_fid_pool, &fid)) {
- err->status = NT_STATUS_TOO_MANY_OPENED_FILES;
- err->errcls = ERRDOS;
- err->errcode = ERROR_TOO_MANY_OPEN_FILES;
- return (NULL);
- }
of = kmem_cache_alloc(smb_cache_ofile, KM_SLEEP);
bzero(of, sizeof (smb_ofile_t));
@@ -327,10 +325,10 @@ smb_ofile_open(
list_create(&of->f_notify.nc_waiters, sizeof (smb_request_t),
offsetof(smb_request_t, sr_waiters));
- of->f_state = SMB_OFILE_STATE_OPEN;
+ of->f_state = SMB_OFILE_STATE_ALLOC;
of->f_refcnt = 1;
of->f_ftype = ftype;
- of->f_fid = fid;
+ of->f_fid = tree_fid;
/* of->f_persistid see smb2_create */
of->f_uniqid = uniqid;
of->f_opened_by_pid = sr->smb_pid;
@@ -344,9 +342,24 @@ smb_ofile_open(
of->f_session = tree->t_session;
(void) memset(of->f_lock_seq, -1, SMB_OFILE_LSEQ_MAX);
+ of->f_mode = smb_fsop_amask_to_omode(of->f_granted_access);
+ if ((of->f_granted_access & FILE_DATA_ALL) == FILE_EXECUTE)
+ of->f_flags |= SMB_OFLAGS_EXECONLY;
+
+ /*
+ * In case a lease is requested, copy the lease keys now so
+ * any oplock breaks during open don't break those on our
+ * other handles that might have the same lease.
+ */
+ bcopy(op->lease_key, of->TargetOplockKey, SMB_LEASE_KEY_SZ);
+ bcopy(op->parent_lease_key, of->ParentOplockKey, SMB_LEASE_KEY_SZ);
+
/*
* grab a ref for of->f_user and of->f_tree
- * released in smb_ofile_delete() or smb2_dh_reconnect()
+ * We know the user and tree must be "live" because
+ * this SR holds references to them. The node ref. is
+ * held by our caller, until smb_ofile_open puts this
+ * ofile on the node ofile list with smb_node_add_ofile.
*/
smb_user_hold_internal(sr->uid_user);
smb_tree_hold_internal(tree);
@@ -354,81 +367,54 @@ smb_ofile_open(
of->f_tree = tree;
of->f_node = node;
- if (ftype == SMB_FTYPE_MESG_PIPE) {
- /* See smb_opipe_open. */
- of->f_pipe = op->pipe;
- smb_server_inc_pipes(of->f_server);
- } else {
- ASSERT(ftype == SMB_FTYPE_DISK); /* Regular file, not a pipe */
- ASSERT(node);
+ return (of);
+}
- /*
- * Note that the common open path often adds bits like
- * READ_CONTROL, so the logic "is this open exec-only"
- * needs to look at only the FILE_DATA_ALL bits.
- */
- if ((of->f_granted_access & FILE_DATA_ALL) == FILE_EXECUTE)
- of->f_flags |= SMB_OFLAGS_EXECONLY;
+/*
+ * smb_ofile_open
+ *
+ * Complete an open on an ofile that was previously allocated by
+ * smb_ofile_alloc, by putting it on the tree ofile list and
+ * (if it's a file) the node ofile list.
+ */
+void
+smb_ofile_open(
+ smb_request_t *sr,
+ smb_arg_open_t *op,
+ smb_ofile_t *of)
+{
+ smb_tree_t *tree = sr->tid_tree;
+ smb_node_t *node = of->f_node;
- /*
- * This is an "internal" getattr because we need the
- * UID and DOS attributes. Don't want to fail here
- * due to permissions, so use kcred.
- */
- bzero(&attr, sizeof (smb_attr_t));
- attr.sa_mask = SMB_AT_UID | SMB_AT_DOSATTR;
- rc = smb_node_getattr(NULL, node, zone_kcred(), NULL, &attr);
- if (rc != 0) {
- err->status = NT_STATUS_INTERNAL_ERROR;
- err->errcls = ERRDOS;
- err->errcode = ERROR_INTERNAL_ERROR;
- goto errout;
- }
- if (crgetuid(of->f_cr) == attr.sa_vattr.va_uid) {
- /*
- * Add this bit for the file's owner even if it's not
- * specified in the request (Windows behavior).
- */
- of->f_granted_access |= FILE_READ_ATTRIBUTES;
- }
+ ASSERT(of->f_state == SMB_OFILE_STATE_ALLOC);
+ of->f_state = SMB_OFILE_STATE_OPEN;
- if (smb_node_is_file(node)) {
- of->f_mode =
- smb_fsop_amask_to_omode(of->f_granted_access);
- if (smb_fsop_open(node, of->f_mode, of->f_cr) != 0) {
- err->status = NT_STATUS_ACCESS_DENIED;
- err->errcls = ERRDOS;
- err->errcode = ERROR_ACCESS_DENIED;
- goto errout;
- }
- }
+ switch (of->f_ftype) {
+ case SMB_FTYPE_BYTE_PIPE:
+ case SMB_FTYPE_MESG_PIPE:
+ /* See smb_opipe_open. */
+ of->f_pipe = op->pipe;
+ smb_server_inc_pipes(of->f_server);
+ break;
+ case SMB_FTYPE_DISK:
+ case SMB_FTYPE_PRINTER:
+ /* Regular file, not a pipe */
+ ASSERT(node != NULL);
smb_node_inc_open_ofiles(node);
smb_node_add_ofile(node, of);
smb_node_ref(node);
smb_server_inc_files(of->f_server);
+ break;
+ default:
+ ASSERT(0);
}
smb_llist_enter(&tree->t_ofile_list, RW_WRITER);
smb_llist_insert_tail(&tree->t_ofile_list, of);
smb_llist_exit(&tree->t_ofile_list);
atomic_inc_32(&tree->t_open_files);
atomic_inc_32(&of->f_session->s_file_cnt);
- return (of);
-
-errout:
- smb_tree_release(of->f_tree);
- smb_user_release(of->f_user);
- crfree(of->f_cr);
-
- list_destroy(&of->f_notify.nc_waiters);
- mutex_destroy(&of->f_mutex);
-
- of->f_magic = 0;
- kmem_cache_free(smb_cache_ofile, of);
- smb_idpool_free(&tree->t_fid_pool, fid);
-
- return (NULL);
}
/*
@@ -472,7 +458,11 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec)
case SMB_FTYPE_DISK:
if (of->f_persistid != 0)
smb_ofile_del_persistid(of);
+ if (of->f_lease != NULL)
+ smb2_lease_ofile_close(of);
+ smb_oplock_break_CLOSE(of->f_node, of);
/* FALLTHROUGH */
+
case SMB_FTYPE_PRINTER: /* or FTYPE_DISK */
/*
* In here we make changes to of->f_pending_attr
@@ -512,7 +502,6 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec)
if (smb_node_is_file(of->f_node)) {
(void) smb_fsop_close(of->f_node, of->f_mode,
of->f_cr);
- smb_oplock_release(of->f_node, of);
} else {
/*
* If there was an odir, close it.
@@ -546,6 +535,7 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec)
* Leave allocsz zero when no open files,
* just to avoid confusion, because it's
* only updated when there are opens.
+ * XXX: Just do this on _every_ close.
*/
mutex_enter(&of->f_node->n_mutex);
if (of->f_node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
@@ -852,35 +842,6 @@ smb_ofile_release(smb_ofile_t *of)
}
/*
- * smb_ofile_request_complete
- *
- * During oplock acquisition, all other oplock requests on the node
- * are blocked until the acquire request completes and the response
- * is on the wire.
- * Call smb_oplock_broadcast to notify the node that the request
- * has completed.
- *
- * THIS MECHANISM RELIES ON THE FACT THAT THE OFILE IS NOT REMOVED
- * FROM THE SR UNTIL REQUEST COMPLETION (when the sr is destroyed)
- */
-void
-smb_ofile_request_complete(smb_ofile_t *of)
-{
- SMB_OFILE_VALID(of);
-
- switch (of->f_ftype) {
- case SMB_FTYPE_DISK:
- ASSERT(of->f_node);
- smb_oplock_broadcast(of->f_node);
- break;
- case SMB_FTYPE_MESG_PIPE:
- break;
- default:
- break;
- }
-}
-
-/*
* smb_ofile_lookup_by_fid
*
* Find the open file whose fid matches the one specified in the request.
@@ -1322,6 +1283,7 @@ smb_ofile_save_dh(void *arg)
/*
* Delete an ofile.
*
+ * Approximately the inverse of smb_ofile_alloc()
* Called via smb_llist_post (after smb_llist_exit)
* when the last ref. on this ofile has gone.
*
@@ -1340,7 +1302,6 @@ smb_ofile_delete(void *arg)
SMB_OFILE_VALID(of);
ASSERT(of->f_refcnt == 0);
ASSERT(of->f_state == SMB_OFILE_STATE_CLOSED);
- ASSERT(!SMB_OFILE_OPLOCK_GRANTED(of));
if (tree != NULL) {
ASSERT(of->f_user != NULL);
@@ -1375,6 +1336,8 @@ smb_ofile_delete(void *arg)
* flushes the delete queue before we do). Synchronize.
*/
mutex_enter(&of->f_mutex);
+ of->f_state = SMB_OFILE_STATE_ALLOC;
+ DTRACE_PROBE1(ofile__exit, smb_ofile_t, of);
mutex_exit(&of->f_mutex);
switch (of->f_ftype) {
@@ -1388,6 +1351,10 @@ smb_ofile_delete(void *arg)
MBC_FLUSH(&of->f_notify.nc_buffer);
if (of->f_odir != NULL)
smb_odir_release(of->f_odir);
+ if (of->f_lease != NULL) {
+ smb2_lease_rele(of->f_lease);
+ of->f_lease = NULL;
+ }
/* FALLTHROUGH */
case SMB_FTYPE_PRINTER:
/*
@@ -1401,6 +1368,19 @@ smb_ofile_delete(void *arg)
break;
}
+ smb_ofile_free(of);
+}
+
+void
+smb_ofile_free(smb_ofile_t *of)
+{
+ smb_tree_t *tree = of->f_tree;
+
+ ASSERT(of->f_state == SMB_OFILE_STATE_ALLOC);
+
+ /* Make sure it's not in the persistid hash. */
+ ASSERT(of->f_persistid == 0);
+
if (tree != NULL) {
if (of->f_fid != 0)
smb_idpool_free(&tree->t_fid_pool, of->f_fid);
@@ -1635,8 +1615,21 @@ smb_ofile_getcred(smb_ofile_t *of)
* the fid on which the DeleteOnClose was requested.
*/
void
-smb_ofile_set_delete_on_close(smb_ofile_t *of)
+smb_ofile_set_delete_on_close(smb_request_t *sr, smb_ofile_t *of)
{
+ uint32_t status;
+
+ /*
+ * Break any oplock handle caching.
+ */
+ status = smb_oplock_break_SETINFO(of->f_node, of,
+ FileDispositionInformation);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ if (sr->session->dialect >= SMB_VERS_2_BASE)
+ (void) smb2sr_go_async(sr);
+ (void) smb_oplock_wait_break(of->f_node, 0);
+ }
+
mutex_enter(&of->f_mutex);
of->f_flags |= SMB_OFLAGS_SET_DELETE_ON_CLOSE;
mutex_exit(&of->f_mutex);
diff --git a/usr/src/uts/common/fs/smbsrv/smb_open_andx.c b/usr/src/uts/common/fs/smbsrv/smb_open_andx.c
index ca35b03340..d1d3b30048 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_open_andx.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_open_andx.c
@@ -243,7 +243,7 @@ smb_com_open(smb_request_t *sr)
{
struct open_param *op = &sr->arg.open;
smb_ofile_t *of;
- smb_attr_t attr;
+ uint32_t mtime_sec;
uint32_t status;
uint16_t file_attr;
int rc;
@@ -265,18 +265,25 @@ smb_com_open(smb_request_t *sr)
} else {
op->op_oplock_level = SMB_OPLOCK_NONE;
}
- op->op_oplock_levelII = B_FALSE;
if (smb_open_dsize_check && op->dsize > UINT_MAX) {
smbsr_error(sr, 0, ERRDOS, ERRbadaccess);
return (SDRC_ERROR);
}
+ /*
+ * The real open call. Note: this gets attributes into
+ * op->fqi.fq_fattr (SMB_AT_ALL). We need those below.
+ */
status = smb_common_open(sr);
if (status != NT_STATUS_SUCCESS) {
smbsr_status(sr, status, 0, 0);
return (SDRC_ERROR);
}
+ if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+ /* Oplock req. in op->op_oplock_level etc. */
+ smb1_oplock_acquire(sr, B_FALSE);
+ }
/*
* NB: after the above smb_common_open() success,
@@ -291,19 +298,14 @@ smb_com_open(smb_request_t *sr)
}
file_attr = op->dattr & FILE_ATTRIBUTE_MASK;
- bzero(&attr, sizeof (attr));
- attr.sa_mask = SMB_AT_MTIME;
- rc = smb_node_getattr(sr, of->f_node, of->f_cr, of, &attr);
- if (rc != 0) {
- smbsr_errno(sr, rc);
- goto errout;
- }
+ mtime_sec = smb_time_gmt_to_local(sr,
+ op->fqi.fq_fattr.sa_vattr.va_mtime.tv_sec);
rc = smbsr_encode_result(sr, 7, 0, "bwwllww",
7,
sr->smb_fid,
file_attr,
- smb_time_gmt_to_local(sr, attr.sa_vattr.va_mtime.tv_sec),
+ mtime_sec,
(uint32_t)op->dsize,
op->omode,
(uint16_t)0); /* bcc */
@@ -311,7 +313,6 @@ smb_com_open(smb_request_t *sr)
if (rc == 0)
return (SDRC_SUCCESS);
-errout:
smb_ofile_close(of, 0);
return (SDRC_ERROR);
}
@@ -349,10 +350,10 @@ smb_pre_open_andx(smb_request_t *sr)
* The openx_flags use some "extended" flags that
* happen to match some of the NtCreateX flags.
*/
- if (openx_flags & NT_CREATE_FLAG_REQUEST_OPLOCK)
- op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
- else if (openx_flags & NT_CREATE_FLAG_REQUEST_OPBATCH)
+ if (openx_flags & NT_CREATE_FLAG_REQUEST_OPBATCH)
op->op_oplock_level = SMB_OPLOCK_BATCH;
+ else if (openx_flags & NT_CREATE_FLAG_REQUEST_OPLOCK)
+ op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
else
op->op_oplock_level = SMB_OPLOCK_NONE;
if (openx_flags & NT_CREATE_FLAG_EXTENDED_RESPONSE)
@@ -401,8 +402,6 @@ smb_com_open_andx(smb_request_t *sr)
if (op->omode & SMB_DA_WRITE_THROUGH)
op->create_options |= FILE_WRITE_THROUGH;
- op->op_oplock_levelII = B_FALSE;
-
if (smb_open_dsize_check && op->dsize > UINT_MAX) {
smbsr_error(sr, 0, ERRDOS, ERRbadaccess);
return (SDRC_ERROR);
@@ -413,6 +412,10 @@ smb_com_open_andx(smb_request_t *sr)
smbsr_status(sr, status, 0, 0);
return (SDRC_ERROR);
}
+ if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+ /* Oplock req. in op->op_oplock_level etc. */
+ smb1_oplock_acquire(sr, B_FALSE);
+ }
/*
* NB: after the above smb_common_open() success,
@@ -563,13 +566,16 @@ smb_com_trans2_open2(smb_request_t *sr, smb_xa_t *xa)
} else {
op->op_oplock_level = SMB_OPLOCK_NONE;
}
- op->op_oplock_levelII = B_FALSE;
status = smb_common_open(sr);
if (status != NT_STATUS_SUCCESS) {
smbsr_status(sr, status, 0, 0);
return (SDRC_ERROR);
}
+ if (op->op_oplock_level != SMB_OPLOCK_NONE) {
+ /* Oplock req. in op->op_oplock_level etc. */
+ smb1_oplock_acquire(sr, B_FALSE);
+ }
if (op->op_oplock_level != SMB_OPLOCK_NONE)
op->action_taken |= SMB_OACT_OPLOCK;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_opipe.c b/usr/src/uts/common/fs/smbsrv/smb_opipe.c
index ac73e9494b..83b74ee075 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_opipe.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_opipe.c
@@ -268,11 +268,10 @@ out:
* Returns 0 on success, Otherwise an NT status code.
*/
int
-smb_opipe_open(smb_request_t *sr, uint32_t uniqid)
+smb_opipe_open(smb_request_t *sr, smb_ofile_t *ofile)
{
smb_arg_open_t *op = &sr->sr_open;
smb_attr_t *ap = &op->fqi.fq_fattr;
- smb_ofile_t *ofile;
smb_opipe_t *opipe;
smb_error_t err;
@@ -292,19 +291,24 @@ smb_opipe_open(smb_request_t *sr, uint32_t uniqid)
}
/*
- * Note: If smb_ofile_open succeeds, the new ofile is
- * in the FID lists can can be used by I/O requests.
+ * We might have blocked in smb_opipe_connect long enough so
+ * a tree disconnect might have happened. In that case, we
+ * would be adding an ofile to a tree that's disconnecting,
+ * which would interfere with tear-down.
*/
- op->create_options = 0;
- op->pipe = opipe;
- ofile = smb_ofile_open(sr, NULL, op,
- SMB_FTYPE_MESG_PIPE, uniqid, &err);
- op->pipe = NULL;
- if (ofile == NULL) {
+ if (!smb_tree_is_connected(sr->tid_tree)) {
smb_opipe_dealloc(opipe);
- return (err.status);
+ return (NT_STATUS_NETWORK_NAME_DELETED);
}
+ /*
+ * Note: The new opipe is given to smb_ofile_open
+ * via op->pipe
+ */
+ op->pipe = opipe;
+ smb_ofile_open(sr, op, ofile);
+ op->pipe = NULL;
+
/* An "up" pointer, for debug. */
opipe->p_ofile = ofile;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_oplock.c
index 9d78cefead..7be36ebf42 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c
@@ -20,877 +20,139 @@
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
/*
- * smb_oplock_wait / smb_oplock_broadcast
- * When an oplock is being acquired, we must ensure that the acquisition
- * response is submitted to the network stack before any other operation
- * is permitted on the oplock.
- * In smb_oplock_acquire, oplock.ol_xthread is set to point to the worker
- * thread processing the command that is granting the oplock.
- * Other threads accessing the oplock will be suspended in smb_oplock_wait().
- * They will be awakened when the worker thread referenced in 'ol_xthread'
- * calls smb_oplock_broadcast().
- *
- * The purpose of this mechanism is to prevent another thread from
- * triggering an oplock break before the response conveying the grant
- * has been sent.
+ * smb1 oplock support
*/
#include <smbsrv/smb_kproto.h>
-#include <sys/nbmlock.h>
-
-#define SMB_OPLOCK_IS_EXCLUSIVE(level) \
- (((level) == SMB_OPLOCK_EXCLUSIVE) || \
- ((level) == SMB_OPLOCK_BATCH))
-
-static int smb_oplock_install_fem(smb_node_t *);
-static void smb_oplock_uninstall_fem(smb_node_t *);
-
-static void smb_oplock_wait(smb_node_t *);
-static void smb_oplock_wait_ack(smb_node_t *, uint32_t);
-static void smb_oplock_timedout(smb_node_t *);
-
-static smb_oplock_grant_t *smb_oplock_set_grant(smb_ofile_t *, uint8_t);
-void smb_oplock_clear_grant(smb_oplock_grant_t *);
-static int smb_oplock_insert_grant(smb_node_t *, smb_oplock_grant_t *);
-static void smb_oplock_remove_grant(smb_node_t *, smb_oplock_grant_t *);
-static smb_oplock_grant_t *smb_oplock_exclusive_grant(list_t *);
-static smb_oplock_grant_t *smb_oplock_get_grant(smb_oplock_t *, smb_ofile_t *);
-
-static void smb_oplock_sched_async_break(smb_oplock_grant_t *, uint8_t);
-static void smb_oplock_async_break(void *);
-static void smb_oplock_break_levelII_locked(smb_node_t *);
-
-/*
- * smb_oplock_install_fem
- * Install fem monitor for cross protocol oplock breaking.
- */
-static int
-smb_oplock_install_fem(smb_node_t *node)
-{
- ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
-
- if (node->n_oplock.ol_fem == B_FALSE) {
- if (smb_fem_oplock_install(node) != 0) {
- cmn_err(CE_NOTE, "No oplock granted: "
- "failed to install fem monitor %s",
- node->vp->v_path);
- return (-1);
- }
- node->n_oplock.ol_fem = B_TRUE;
- }
- return (0);
-}
-
-/*
- * smb_oplock_uninstall_fem
- * Uninstall fem monitor for cross protocol oplock breaking.
- */
-static void
-smb_oplock_uninstall_fem(smb_node_t *node)
-{
- ASSERT(MUTEX_HELD(&node->n_oplock.ol_mutex));
-
- if (node->n_oplock.ol_fem) {
- smb_fem_oplock_uninstall(node);
- node->n_oplock.ol_fem = B_FALSE;
- }
-}
-/*
- * This provides a way to fully disable oplocks, i.e. for testing.
- * You _really_ do _not_ want to turn this off, because if you do,
- * the clients send you very small read requests, and a _lot_ more
- * of them. The skc_oplock_enable parameter can be used to enable
- * or disable exclusive oplocks. Disabling that can be helpful
- * when there are clients not responding to oplock breaks.
- */
-int smb_oplocks_enabled = 1;
+#define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE)
/*
- * smb_oplock_acquire
- *
- * Attempt to acquire an oplock. Clients will request EXCLUSIVE or BATCH,
- * but might only be granted LEVEL_II or NONE.
- *
- * If oplocks are not supported on the tree, or node, grant NONE.
- * If nobody else has the file open, grant the requested level.
- * If any of the following are true, grant NONE:
- * - there is an exclusive oplock on the node
- * - op->op_oplock_levelII is B_FALSE (LEVEL_II not supported by open cmd.
- * - LEVEL_II oplocks are not supported for the session
- * - a BATCH oplock is requested on a named stream
- * - there are any range locks on the node (SMB writers)
- * Otherwise, grant LEVEL_II.
- *
- * ol->ol_xthread is set to the current thread to lock the oplock against
- * other operations until the acquire response is on the wire. When the
- * acquire response is on the wire, smb_oplock_broadcast() is called to
- * reset ol->ol_xthread and wake any waiting threads.
+ * Client has an open handle and requests an oplock.
+ * Convert SMB1 oplock request info in to internal form,
+ * call common oplock code, convert result to SMB1.
*/
void
-smb_oplock_acquire(smb_request_t *sr, smb_node_t *node, smb_ofile_t *ofile)
+smb1_oplock_acquire(smb_request_t *sr, boolean_t level2ok)
{
- smb_oplock_t *ol;
- smb_oplock_grant_t *og;
- list_t *grants;
- smb_arg_open_t *op;
- smb_tree_t *tree;
- smb_session_t *session;
-
- SMB_NODE_VALID(node);
- SMB_OFILE_VALID(ofile);
-
- ASSERT(node == SMB_OFILE_GET_NODE(ofile));
- ASSERT(RW_LOCK_HELD(&node->n_lock));
-
- op = &sr->sr_open;
+ smb_arg_open_t *op = &sr->arg.open;
+ smb_ofile_t *ofile = sr->fid_ofile;
+ uint32_t status;
- if (smb_oplocks_enabled == 0 ||
- (op->op_oplock_level == SMB_OPLOCK_NONE) ||
- ((op->op_oplock_level == SMB_OPLOCK_BATCH) &&
- SMB_IS_STREAM(node))) {
+ /* Only disk trees get oplocks. */
+ if ((sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) {
op->op_oplock_level = SMB_OPLOCK_NONE;
return;
}
- ol = &node->n_oplock;
- grants = &ol->ol_grants;
-
- mutex_enter(&ol->ol_mutex);
- smb_oplock_wait(node);
- tree = SMB_OFILE_GET_TREE(ofile);
- session = SMB_OFILE_GET_SESSION(ofile);
-
- /*
- * Even if there are no other opens, we might want to
- * grant only a Level II (shared) oplock so we avoid
- * ever granting exclusive oplocks.
- *
- * Borrowing the SMB_TREE_OPLOCKS flag to enable/disable
- * exclusive oplocks (for now). See skc_oplock_enable,
- * which can now be taken as "exclusive oplock enable".
- * Should rename this parameter, and/or implement a new
- * multi-valued parameter for oplock enables.
- */
- if ((node->n_open_count > 1) ||
- (node->n_opening_count > 1) ||
- !smb_tree_has_feature(tree, SMB_TREE_OPLOCKS) ||
- smb_vop_other_opens(node->vp, ofile->f_mode)) {
- /*
- * There are other opens.
- */
- if ((!op->op_oplock_levelII) ||
- (!smb_session_levelII_oplocks(session)) ||
- (smb_oplock_exclusive_grant(grants) != NULL) ||
- (smb_lock_range_access(sr, node, 0, ~0, B_FALSE))) {
- /*
- * LevelII (shared) oplock not allowed,
- * so reply with "none".
- */
- op->op_oplock_level = SMB_OPLOCK_NONE;
- mutex_exit(&ol->ol_mutex);
- return;
- }
-
- op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
- }
-
- og = smb_oplock_set_grant(ofile, op->op_oplock_level);
-
- /*
- * When we're sending an oplock break, we may not have a
- * session (ofile->f_session == NULL) so we use og_dialect
- * to tell oplock break what kind of break to send.
- */
- if (ofile->f_session->dialect >= SMB_VERS_2_BASE)
- og->og_dialect = DIALECT_SMB2002;
- else if (op->op_oplock_levelII &&
- smb_session_levelII_oplocks(session))
- og->og_dialect = NT_LM_0_12;
- else
- og->og_dialect = LANMAN2_1;
-
- if (smb_oplock_insert_grant(node, og) != 0) {
- smb_oplock_clear_grant(og);
+ if (!smb_tree_has_feature(sr->tid_tree, SMB_TREE_OPLOCKS)) {
op->op_oplock_level = SMB_OPLOCK_NONE;
- mutex_exit(&ol->ol_mutex);
return;
}
- ol->ol_xthread = curthread;
- mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_break
- *
- * Break granted oplocks according to the following rules:
- *
- * If there's an exclusive oplock granted on the node
- * - if the BREAK_BATCH flags is specified and the oplock is not
- * a batch oplock, no break is required.
- * - if the session doesn't support LEVEL II oplocks, and 'brk' is
- * BREAK_TO_LEVEL_II, do a BREAK_TO_NONE.
- * - if the oplock is already breaking update the break level (if
- * the requested break is to a lesser level), otherwise send an
- * oplock break.
- * Wait for acknowledgement of the break (unless NOWAIT flag is set)
- *
- * Otherwise:
- * If there are level II oplocks granted on the node, and the flags
- * indicate that they should be broken (BREAK_TO_NONE specified,
- * BREAK_EXCLUSIVE, BREAK_BATCH not specified) queue the levelII
- * break request for asynchronous processing.
- *
- * Returns:
- * 0 - oplock broken (or no break required)
- * EAGAIN - oplock break request sent and would block
- * awaiting the reponse but NOWAIT was specified
- *
- * NB: sr == NULL when called by FEM framework.
- */
-int
-smb_oplock_break(smb_request_t *sr, smb_node_t *node, uint32_t flags)
-{
- smb_oplock_t *ol;
- smb_oplock_grant_t *og;
- smb_ofile_t *ofile;
- list_t *grants;
- uint32_t timeout;
- uint8_t brk;
-
- SMB_NODE_VALID(node);
- ol = &node->n_oplock;
- grants = &ol->ol_grants;
-
- mutex_enter(&ol->ol_mutex);
- smb_oplock_wait(node);
-
- og = list_head(grants);
- if (og == NULL) {
- mutex_exit(&ol->ol_mutex);
- return (0);
- }
-
- SMB_OPLOCK_GRANT_VALID(og);
- ofile = og->og_ofile; /* containing struct */
-
- /* break levelII oplocks */
- if (og->og_level == SMB_OPLOCK_LEVEL_II) {
- mutex_exit(&ol->ol_mutex);
-
- if ((flags & SMB_OPLOCK_BREAK_TO_NONE) &&
- !(flags & SMB_OPLOCK_BREAK_EXCLUSIVE) &&
- !(flags & SMB_OPLOCK_BREAK_BATCH)) {
- smb_oplock_break_levelII(node);
- }
- return (0);
- }
-
- /* break exclusive oplock */
- if ((flags & SMB_OPLOCK_BREAK_BATCH) &&
- (og->og_level != SMB_OPLOCK_BATCH)) {
- mutex_exit(&ol->ol_mutex);
- return (0);
- }
+ if (!smb_session_levelII_oplocks(sr->session))
+ level2ok = B_FALSE;
- if ((flags & SMB_OPLOCK_BREAK_TO_LEVEL_II) &&
- (og->og_dialect >= NT_LM_0_12)) {
- brk = SMB_OPLOCK_BREAK_TO_LEVEL_II;
- } else {
- brk = SMB_OPLOCK_BREAK_TO_NONE;
- }
+ /* Common code checks file type. */
- switch (ol->ol_break) {
- case SMB_OPLOCK_NO_BREAK:
- ol->ol_break = brk;
- smb_oplock_sched_async_break(og, brk);
+ /*
+ * SMB1: Convert to internal form.
+ */
+ switch (op->op_oplock_level) {
+ case SMB_OPLOCK_BATCH:
+ op->op_oplock_state = OPLOCK_LEVEL_BATCH;
break;
- case SMB_OPLOCK_BREAK_TO_LEVEL_II:
- if (brk == SMB_OPLOCK_BREAK_TO_NONE)
- ol->ol_break = SMB_OPLOCK_BREAK_TO_NONE;
+ case SMB_OPLOCK_EXCLUSIVE:
+ op->op_oplock_state = OPLOCK_LEVEL_ONE;
break;
- case SMB_OPLOCK_BREAK_TO_NONE:
- default:
+ case SMB_OPLOCK_LEVEL_II:
+ op->op_oplock_state = OPLOCK_LEVEL_TWO;
break;
- }
-
- if (flags & SMB_OPLOCK_BREAK_NOWAIT) {
- mutex_exit(&ol->ol_mutex);
- return (EAGAIN);
- }
-
- if (sr != NULL && sr->uid_user == ofile->f_user) {
- timeout = smb_oplock_min_timeout;
- } else {
- timeout = smb_oplock_timeout;
- }
-
- mutex_exit(&ol->ol_mutex);
- smb_oplock_wait_ack(node, timeout);
- return (0);
-}
-
-/*
- * smb_oplock_break_levelII
- *
- * This is called after a file is modified in some way. If there are
- * LevelII (shared) oplocks, break those to none. If there is an
- * exclusive oplock, there can be no LevelII oplocks, so do nothing.
- *
- * LevelII (shared) oplock breaks are processed asynchronously.
- * Unlike exclusive oplock breaks, the thread initiating the break
- * is NOT blocked while the request is processed.
- *
- * There may be a thread with exclusive rights to oplock state for
- * this node (via ol_xthread in smb_oplock_wait) and if so, we must
- * avoid breaking oplocks until that's out of the way. However, we
- * really don't want to block here, so when ol_xthread is set, we'll
- * just mark that a "break level II to none" is pending, and let the
- * exclusive thread do this work when it's done being exclusive.
- */
-void
-smb_oplock_break_levelII(smb_node_t *node)
-{
- smb_oplock_t *ol;
-
- ol = &node->n_oplock;
- mutex_enter(&ol->ol_mutex);
-
- /* Instead of: smb_oplock_wait() ... */
- if (ol->ol_xthread != NULL) {
- /* Defer the call to smb_oplock_broadcast(). */
- ol->ol_brk_pending = SMB_OPLOCK_BREAK_TO_NONE;
- } else {
- /* Equivalent of smb_oplock_wait() done. */
- smb_oplock_break_levelII_locked(node);
- }
-
- mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_break_levelII_locked
- * Internal helper for smb_oplock_break_levelII()
- *
- * Called with the oplock mutex already held, and _after_
- * (the equivalent of) an smb_oplock_wait().
- */
-static void
-smb_oplock_break_levelII_locked(smb_node_t *node)
-{
- smb_oplock_t *ol;
- smb_oplock_grant_t *og;
- list_t *grants;
-
- ol = &node->n_oplock;
- grants = &ol->ol_grants;
-
- ASSERT(MUTEX_HELD(&ol->ol_mutex));
- ASSERT(ol->ol_xthread == NULL);
-
- while ((og = list_head(grants)) != NULL) {
- SMB_OPLOCK_GRANT_VALID(og);
-
- /*
- * If there's an exclusive oplock, there are
- * no LevelII oplocks, so do nothing.
- */
- if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
- break;
-
- smb_oplock_sched_async_break(og, SMB_OPLOCK_BREAK_TO_NONE);
- /*
- * If oplock break had to do a "local" ack, the
- * oplock grant will have been already removed.
- */
- if (og->og_magic == SMB_OPLOCK_GRANT_MAGIC) {
- smb_oplock_remove_grant(node, og);
- smb_oplock_clear_grant(og);
- }
- }
-}
-
-/*
- * Schedule an asynchronous call to smb_oplock_async_break.
- * Try hard to avoid waiting for any oplock work here, or
- * callers coming in from FEM etc. may suffer delays.
- *
- * The caller holds the oplock mutex, and will
- * signal ol_cv after we return.
- *
- * Pulled forward some changes from the leasing work.
- */
-static void
-smb_oplock_sched_async_break(smb_oplock_grant_t *og, uint8_t brk)
-{
- smb_ofile_t *ofile;
- smb_request_t *sr = NULL;
- smb_server_t *sv;
-
- ofile = og->og_ofile; /* containing struct */
- SMB_OFILE_VALID(ofile);
- sv = ofile->f_server;
-
- /*
- * We're going to schedule a request that will have a
- * reference to this ofile. Get the hold first.
- */
- if (!smb_ofile_hold_olbrk(ofile)) {
- /* It's closing (or whatever). Nothing to do. */
+ case SMB_OPLOCK_NONE:
+ default:
+ op->op_oplock_level = SMB_OPLOCK_NONE;
return;
}
/*
- * We need a request allocated on the session that owns
- * this ofile in order to safely send on that session.
- *
- * Note that while we hold a ref. on the ofile, it's
- * f_session will not change. An ofile in state
- * _ORPHANED will have f_session == NULL, but the
- * f_session won't _change_ while we have a ref,
- * and won't be torn down under our feet.
- *
- * If f_session is NULL, or it's in a state that doesn't
- * allow new requests, use the special "server" session.
+ * Tree options may force shared oplocks
*/
- if (ofile->f_session != NULL)
- sr = smb_request_alloc(ofile->f_session, 0);
- if (sr == NULL)
- sr = smb_request_alloc(sv->sv_session, 0);
-
- sr->sr_state = SMB_REQ_STATE_SUBMITTED;
- sr->user_cr = zone_kcred();
- sr->fid_ofile = ofile;
- /* Leave tid_tree, uid_user NULL. */
- sr->arg.olbrk = *og; /* struct copy */
- sr->arg.olbrk.og_breaking = brk;
-
- (void) taskq_dispatch(
- sv->sv_worker_pool,
- smb_oplock_async_break, sr, TQ_SLEEP);
-}
-
-/*
- * smb_oplock_async_break
- *
- * Called via the taskq to handle an asynchronous oplock break.
- * Send an oplock break over the wire, or if we can't,
- * then process the oplock break locally.
- *
- * Note that we have sr->fid_ofile here but all the other
- * normal sr members are NULL: uid_user, tid_tree.
- * Also sr->session may or may not be the same session as
- * the ofile came from (ofile->f_session) depending on
- * whether this is a "live" open or an orphaned DH,
- * where ofile->f_session will be NULL.
- *
- * Given that we don't always have a session, we determine
- * the oplock type from f_oplock.og_dialect.
- */
-static void
-smb_oplock_async_break(void *arg)
-{
- smb_request_t *sr = arg;
- smb_ofile_t *ofile = sr->fid_ofile;
- smb_oplock_grant_t *og = &sr->arg.olbrk;
- uint8_t brk = og->og_breaking;
- int rc;
-
- SMB_REQ_VALID(sr);
-
- mutex_enter(&sr->sr_mutex);
- sr->sr_worker = curthread;
- sr->sr_state = SMB_REQ_STATE_ACTIVE;
- mutex_exit(&sr->sr_mutex);
-
- /* smb_oplock_send_brk */
+ if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) {
+ op->op_oplock_state = OPLOCK_LEVEL_TWO;
+ }
/*
- * Build the break message in sr->reply.
- * It's free'd in smb_request_free().
+ * Try exclusive first, if requested
*/
- sr->reply.max_bytes = MLEN;
- if (og->og_dialect >= DIALECT_SMB2002) {
- smb2_oplock_break_notification(sr, brk);
+ if ((op->op_oplock_state & BATCH_OR_EXCL) != 0) {
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
} else {
- ASSERT(og->og_dialect > LANMAN2_1 ||
- brk == SMB_OPLOCK_BREAK_TO_NONE);
- smb1_oplock_break_notification(sr, brk);
+ status = NT_STATUS_OPLOCK_NOT_GRANTED;
}
/*
- * Try to send the break message to the client.
+ * If exclusive failed (or tree forced shared oplocks)
+ * and if the caller supports Level II, try shared.
*/
- if (sr->session == ofile->f_session)
- rc = smb_session_send(sr->session, 0, &sr->reply);
- else
- rc = ENOTCONN;
-
- if (rc == 0) {
- /*
- * OK, we were able to send the break message.
- * Caller deals with wait.
- */
- goto out;
- } else {
- /*
- * We were unable to send the oplock break request.
- * Generally, that means we have no connection to this
- * client right now, and this ofile will have state
- * SMB_OFILE_STATE_ORPHANED. We either close the handle
- * or break the oplock locally, in which case the client
- * gets the updated oplock state when they reconnect.
- * Decide whether to keep or close.
- */
-
- /*
- * See similar logic in smb_dh_should_save
- */
- switch (ofile->dh_vers) {
- case SMB2_RESILIENT:
- break; /* keep DH */
-
- case SMB2_DURABLE_V2:
- if (ofile->dh_persist)
- break; /* keep DH */
- /* FALLTHROUGH */
- case SMB2_DURABLE_V1:
- /* IS durable (v1 or v2) */
- /* If new_level & BATCH, keep. (not until leases) */
- /* FALLTHROUGH */
- case SMB2_NOT_DURABLE:
- default:
- smb_ofile_close(ofile, 0);
- return;
- }
- /* Keep this ofile (durable handle). */
-
- /*
- * We do the update locally that the client would have
- * done if they had received our oplock break message.
- */
- smb_oplock_ack(ofile->f_node, ofile, brk);
- }
-
-out:
- sr->sr_state = SMB_REQ_STATE_COMPLETED;
- smb_request_free(sr);
-}
-
-/*
- * smb_oplock_wait_ack
- *
- * Timed wait for an oplock break acknowledgement (or oplock release).
- */
-static void
-smb_oplock_wait_ack(smb_node_t *node, uint32_t timeout)
-{
- smb_oplock_t *ol;
- clock_t time;
-
- ol = &node->n_oplock;
- mutex_enter(&ol->ol_mutex);
- time = MSEC_TO_TICK(timeout) + ddi_get_lbolt();
-
- while (ol->ol_break != SMB_OPLOCK_NO_BREAK) {
- if (cv_timedwait(&ol->ol_cv, &ol->ol_mutex, time) < 0) {
- smb_oplock_timedout(node);
- cv_broadcast(&ol->ol_cv);
- break;
- }
- }
- mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_timedout
- *
- * An oplock break has not been acknowledged within timeout
- * 'smb_oplock_timeout'.
- * Set oplock grant to the desired break level.
- */
-static void
-smb_oplock_timedout(smb_node_t *node)
-{
- smb_oplock_t *ol;
- smb_oplock_grant_t *og;
- list_t *grants;
-
- ol = &node->n_oplock;
- grants = &ol->ol_grants;
-
- ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
- og = smb_oplock_exclusive_grant(grants);
- if (og) {
- switch (ol->ol_break) {
- case SMB_OPLOCK_BREAK_TO_NONE:
- og->og_level = SMB_OPLOCK_NONE;
- smb_oplock_remove_grant(node, og);
- smb_oplock_clear_grant(og);
- break;
- case SMB_OPLOCK_BREAK_TO_LEVEL_II:
- og->og_level = SMB_OPLOCK_LEVEL_II;
- break;
- default:
- SMB_PANIC();
- }
- }
- ol->ol_break = SMB_OPLOCK_NO_BREAK;
-}
-
-/*
- * smb_oplock_release
- *
- * Release the oplock granted on ofile 'of'.
- * Wake any threads waiting for an oplock break acknowledgement for
- * this oplock.
- * This is called when the ofile is being closed.
- */
-void
-smb_oplock_release(smb_node_t *node, smb_ofile_t *of)
-{
- smb_oplock_t *ol;
- smb_oplock_grant_t *og;
-
- ol = &node->n_oplock;
- mutex_enter(&ol->ol_mutex);
- smb_oplock_wait(node);
-
- og = smb_oplock_get_grant(ol, of);
- if (og) {
- smb_oplock_remove_grant(node, og);
- smb_oplock_clear_grant(og);
-
- if (ol->ol_break != SMB_OPLOCK_NO_BREAK) {
- ol->ol_break = SMB_OPLOCK_NO_BREAK;
- cv_broadcast(&ol->ol_cv);
- }
+ if (status == NT_STATUS_OPLOCK_NOT_GRANTED && level2ok) {
+ op->op_oplock_state = OPLOCK_LEVEL_TWO;
+ status = smb_oplock_request(sr, ofile,
+ &op->op_oplock_state);
}
- mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_ack
- *
- * Process oplock acknowledgement received for ofile 'of'.
- * - oplock.ol_break is the break level that was requested.
- * - brk is the break level being acknowledged by the client.
- *
- * Update the oplock grant level to the lesser of ol_break and brk.
- * If the grant is now SMB_OPLOCK_NONE, remove the grant from the
- * oplock's grant list and delete it.
- * If the requested break level (ol_break) was NONE and the brk is
- * LEVEL_II, send another oplock break (NONE). Do not wait for an
- * acknowledgement.
- * Wake any threads waiting for the oplock break acknowledgement.
- */
-void
-smb_oplock_ack(smb_node_t *node, smb_ofile_t *of, uint8_t brk)
-{
- smb_oplock_t *ol;
- smb_oplock_grant_t *og;
-
- ol = &node->n_oplock;
- mutex_enter(&ol->ol_mutex);
- smb_oplock_wait(node);
-
- if ((ol->ol_break == SMB_OPLOCK_NO_BREAK) ||
- ((og = smb_oplock_get_grant(ol, of)) == NULL)) {
- mutex_exit(&ol->ol_mutex);
- return;
+ /*
+ * Either of the above may have returned the
+ * status code that says we should wait.
+ */
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ (void) smb_oplock_wait_break(ofile->f_node, 0);
+ status = 0;
}
- switch (brk) {
- case SMB_OPLOCK_BREAK_TO_NONE:
- og->og_level = SMB_OPLOCK_NONE;
- break;
- case SMB_OPLOCK_BREAK_TO_LEVEL_II:
- if (ol->ol_break == SMB_OPLOCK_BREAK_TO_LEVEL_II) {
- og->og_level = SMB_OPLOCK_LEVEL_II;
- } else {
- /* SMB_OPLOCK_BREAK_TO_NONE */
- og->og_level = SMB_OPLOCK_NONE;
- smb_oplock_sched_async_break(og,
- SMB_OPLOCK_BREAK_TO_NONE);
- }
+ /*
+ * Keep track of what we got (in ofile->f_oplock.og_state)
+ * so we'll know what we had when sending a break later.
+ * The og_dialect here is the oplock dialect, which may be
+ * different than SMB dialect. Pre-NT clients did not
+ * support "Level II" oplocks. If we're talking to a
+ * client that didn't set the CAP_LEVEL_II_OPLOCKS in
+ * its capabilities, let og_dialect = LANMAN2_1.
+ */
+ ofile->f_oplock.og_dialect = (level2ok) ?
+ NT_LM_0_12 : LANMAN2_1;
+ switch (status) {
+ case NT_STATUS_SUCCESS:
+ ofile->f_oplock.og_state = op->op_oplock_state;
break;
+ case NT_STATUS_OPLOCK_NOT_GRANTED:
+ ofile->f_oplock.og_state = 0;
+ op->op_oplock_level = SMB_OPLOCK_NONE;
+ return;
default:
- SMB_PANIC();
- }
-
- if (og->og_level == SMB_OPLOCK_NONE) {
- smb_oplock_remove_grant(node, og);
- smb_oplock_clear_grant(og);
- }
-
- ol->ol_break = SMB_OPLOCK_NO_BREAK;
- cv_broadcast(&ol->ol_cv);
-
- mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_broadcast
- *
- * Called when an open with oplock request completes.
- *
- * ol->ol_xthread identifies the thread that was performing an oplock
- * acquire. Other threads may be blocked awaiting completion of the
- * acquire.
- * If the calling thread is ol_xthread, wake any waiting threads.
- */
-void
-smb_oplock_broadcast(smb_node_t *node)
-{
- smb_oplock_t *ol;
-
- SMB_NODE_VALID(node);
- ol = &node->n_oplock;
-
- mutex_enter(&ol->ol_mutex);
- if ((ol->ol_xthread != NULL) && (ol->ol_xthread == curthread)) {
- ol->ol_xthread = NULL;
- if (ol->ol_brk_pending) {
- ol->ol_brk_pending = 0;
- smb_oplock_break_levelII_locked(node);
- }
- cv_broadcast(&ol->ol_cv);
- }
- mutex_exit(&ol->ol_mutex);
-}
-
-/*
- * smb_oplock_wait
- *
- * Wait for the completion of an oplock acquire.
- * If ol_xthread is not NULL and doesn't contain the pointer to the
- * context of the calling thread, the caller will sleep until the
- * ol_xthread is reset to NULL (via smb_oplock_broadcast()).
- */
-static void
-smb_oplock_wait(smb_node_t *node)
-{
- smb_oplock_t *ol;
-
- ol = &node->n_oplock;
- ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
- if ((ol->ol_xthread != NULL) && (ol->ol_xthread != curthread)) {
- while (ol->ol_xthread != NULL)
- cv_wait(&ol->ol_cv, &ol->ol_mutex);
- }
-}
-
-/*
- * smb_oplock_set_grant
- */
-static smb_oplock_grant_t *
-smb_oplock_set_grant(smb_ofile_t *of, uint8_t level)
-{
- smb_oplock_grant_t *og;
-
- og = &of->f_oplock_grant;
-
- og->og_magic = SMB_OPLOCK_GRANT_MAGIC;
- og->og_breaking = 0;
- og->og_level = level;
- og->og_ofile = of;
-
- return (og);
-}
-
-/*
- * smb_oplock_clear_grant
- */
-void
-smb_oplock_clear_grant(smb_oplock_grant_t *og)
-{
- bzero(og, sizeof (smb_oplock_grant_t));
-}
-
-/*
- * smb_oplock_insert_grant
- *
- * If there are no grants in the oplock's list install the fem
- * monitor.
- * Insert the grant into the list and increment the grant count.
- */
-static int
-smb_oplock_insert_grant(smb_node_t *node, smb_oplock_grant_t *og)
-{
- smb_oplock_t *ol = &node->n_oplock;
-
- ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
- if (ol->ol_count == 0) {
- if (smb_oplock_install_fem(node) != 0)
- return (-1);
+ /* Caller did not check args sufficiently? */
+ cmn_err(CE_NOTE, "clnt %s oplock req. err 0x%x",
+ sr->session->ip_addr_str, status);
+ ofile->f_oplock.og_state = 0;
+ op->op_oplock_level = SMB_OPLOCK_NONE;
+ return;
}
- list_insert_tail(&ol->ol_grants, og);
- ++ol->ol_count;
- return (0);
-}
-
-/*
- * smb_oplock_remove_grant
- *
- * Remove the oplock grant from the list, decrement the grant count
- * and, if there are no other grants in the list, uninstall the fem
- * monitor.
- */
-static void
-smb_oplock_remove_grant(smb_node_t *node, smb_oplock_grant_t *og)
-{
- smb_oplock_t *ol = &node->n_oplock;
-
- ASSERT(MUTEX_HELD(&ol->ol_mutex));
- ASSERT(ol->ol_count > 0);
-
- list_remove(&ol->ol_grants, og);
- if (--ol->ol_count == 0)
- smb_oplock_uninstall_fem(node);
-}
-
-/*
- * smb_oplock_exclusive_grant
- *
- * If an exclusive (EXCLUSIVE or BATCH) oplock grant exists,
- * return it. Otherwise return NULL.
- */
-static smb_oplock_grant_t *
-smb_oplock_exclusive_grant(list_t *grants)
-{
- smb_oplock_grant_t *og;
-
- og = list_head(grants);
- if (og) {
- SMB_OPLOCK_GRANT_VALID(og);
- if (SMB_OPLOCK_IS_EXCLUSIVE(og->og_level))
- return (og);
+ /*
+ * Have STATUS_SUCCESS
+ * Convert internal oplock state to SMB1
+ */
+ if (op->op_oplock_state & OPLOCK_LEVEL_BATCH) {
+ op->op_oplock_level = SMB_OPLOCK_BATCH;
+ } else if (op->op_oplock_state & OPLOCK_LEVEL_ONE) {
+ op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
+ } else if (op->op_oplock_state & OPLOCK_LEVEL_TWO) {
+ op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
+ } else {
+ op->op_oplock_level = SMB_OPLOCK_NONE;
}
- return (NULL);
-}
-
-/*
- * smb_oplock_get_grant
- *
- * Find oplock grant corresponding to the specified ofile.
- */
-static smb_oplock_grant_t *
-smb_oplock_get_grant(smb_oplock_t *ol, smb_ofile_t *ofile)
-{
- ASSERT(MUTEX_HELD(&ol->ol_mutex));
-
- if (SMB_OFILE_OPLOCK_GRANTED(ofile))
- return (&ofile->f_oplock_grant);
- else
- return (NULL);
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_read.c b/usr/src/uts/common/fs/smbsrv/smb_read.c
index 2f1c86af4b..70ad0a8b4b 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_read.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_read.c
@@ -385,11 +385,8 @@ smb_com_read_andx(smb_request_t *sr)
* function. We can't move the fid lookup here because lock-and-read
* requires the fid to do locking before attempting the read.
*
- * Reading from a file should break oplocks on the file to LEVEL_II.
- * A call to smb_oplock_break(SMB_OPLOCK_BREAK_TO_LEVEL_II) is not
- * required as it is a no-op. If there's anything greater than a
- * LEVEL_II oplock on the file, the oplock MUST be owned by the ofile
- * on which the read is occuring and therefore would not be broken.
+ * Reading from a file does not break oplocks because any need for
+ * breaking before read is handled in open.
*
* Returns errno values.
*/
diff --git a/usr/src/uts/common/fs/smbsrv/smb_server.c b/usr/src/uts/common/fs/smbsrv/smb_server.c
index f7697039a8..eb0912a1fd 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_server.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_server.c
@@ -315,6 +315,7 @@ smb_server_g_init(void)
smb_codepage_init();
smb_mbc_init(); /* smb_mbc_cache */
smb_node_init(); /* smb_node_cache, lists */
+ smb2_lease_init();
smb_cache_request = kmem_cache_create("smb_request_cache",
sizeof (smb_request_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
@@ -371,6 +372,7 @@ smb_server_g_fini(void)
kmem_cache_destroy(smb_cache_event);
kmem_cache_destroy(smb_cache_lock);
+ smb2_lease_fini();
smb_node_fini();
smb_mbc_fini();
smb_codepage_fini();
@@ -421,6 +423,9 @@ smb_server_create(void)
sv->sv_persistid_ht = smb_hash_create(sizeof (smb_ofile_t),
offsetof(smb_ofile_t, f_dh_lnd), SMB_OFILE_HASH_NBUCKETS);
+ sv->sv_lease_ht = smb_hash_create(sizeof (smb_lease_t),
+ offsetof(smb_lease_t, ls_lnd), SMB_LEASE_HASH_NBUCKETS);
+
smb_llist_constructor(&sv->sv_session_list, sizeof (smb_session_t),
offsetof(smb_session_t, s_lnd));
@@ -536,6 +541,7 @@ smb_server_delete(void)
smb_thread_destroy(&sv->si_thread_timers);
mutex_destroy(&sv->sv_mutex);
+ smb_hash_destroy(sv->sv_lease_ht);
smb_hash_destroy(sv->sv_persistid_ht);
cv_destroy(&sv->sv_cv);
sv->sv_magic = 0;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_session.c b/usr/src/uts/common/fs/smbsrv/smb_session.c
index 8989fdb07d..1ed8563a1b 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_session.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_session.c
@@ -1482,7 +1482,6 @@ smb_request_free(smb_request_t *sr)
ASSERT(sr->r_xa == NULL);
if (sr->fid_ofile != NULL) {
- smb_ofile_request_complete(sr->fid_ofile);
smb_ofile_release(sr->fid_ofile);
}
@@ -1533,10 +1532,6 @@ smb_session_levelII_oplocks(smb_session_t *session)
{
SMB_SESSION_VALID(session);
- /* Clients using SMB2 and later always know about oplocks. */
- if (session->dialect > NT_LM_0_12)
- return (B_TRUE);
-
/* Older clients only do Level II oplocks if negotiated. */
if ((session->capabilities & CAP_LEVEL_II_OPLOCKS) != 0)
return (B_TRUE);
diff --git a/usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c b/usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c
index bfa07bb1d2..ebeeab60bd 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_session_setup_andx.c
@@ -235,10 +235,6 @@ smb_com_session_setup_andx(smb_request_t *sr)
sr->session->smb_msg_size = sinfo->ssi_maxbufsize;
sr->session->smb_max_mpx = sinfo->ssi_maxmpxcount;
sr->session->capabilities = sinfo->ssi_capabilities;
-
- if (!smb_oplock_levelII)
- sr->session->capabilities &= ~CAP_LEVEL_II_OPLOCKS;
-
sr->session->native_os = sinfo->ssi_native_os;
sr->session->native_lm = sinfo->ssi_native_lm;
}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c
new file mode 100644
index 0000000000..86ce24c0b0
--- /dev/null
+++ b/usr/src/uts/common/fs/smbsrv/smb_srv_oplock.c
@@ -0,0 +1,655 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * (SMB1/SMB2) Server-level Oplock support.
+ *
+ * Conceptually, this is a separate layer on top of the
+ * file system (FS) layer oplock code in smb_cmn_oplock.c.
+ * If these layers were more distinct, the FS layer would
+ * need to use call-back functions (installed from here)
+ * to "indicate an oplock break to the server" (see below).
+ * As these layers are all in the same kernel module, the
+ * delivery of these break indications just uses a direct
+ * function call to smb_oplock_ind_break() below.
+ *
+ * This layer is responsible for handling the break indication,
+ * which often requires scheduling a taskq job in the server,
+ * and sending an oplock break mesage to the client using
+ * the appropriate protocol for the open handle affected.
+ *
+ * The details of composing an oplock break message, the
+ * protocol-specific details of requesting an oplock, and
+ * returning that oplock to the client are in the files:
+ * smb_oplock.c, smb2_oplock.c, smb2_lease.c
+ */
+
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_oplock.h>
+
+/*
+ * Verify relationship between BREAK_TO_... and CACHE bits,
+ * used when setting the BREAK_TO_... below.
+ */
+#if BREAK_TO_READ_CACHING != (READ_CACHING << BREAK_SHIFT)
+#error "BREAK_TO_READ_CACHING"
+#endif
+#if BREAK_TO_HANDLE_CACHING != (HANDLE_CACHING << BREAK_SHIFT)
+#error "BREAK_TO_HANDLE_CACHING"
+#endif
+#if BREAK_TO_WRITE_CACHING != (WRITE_CACHING << BREAK_SHIFT)
+#error "BREAK_TO_WRITE_CACHING"
+#endif
+#define CACHE_RWH (READ_CACHING | WRITE_CACHING | HANDLE_CACHING)
+
+/*
+ * This is the timeout used in the thread that sends an
+ * oplock break and waits for the client to respond
+ * before it breaks the oplock locally.
+ */
+int smb_oplock_timeout_ack = 30000; /* mSec. */
+
+/*
+ * This is the timeout used in threads that have just
+ * finished some sort of oplock request and now must
+ * wait for (possibly multiple) breaks to complete.
+ * This value must be at least a couple seconds LONGER
+ * than the ack timeout above so that I/O callers won't
+ * give up waiting before the local ack timeout.
+ */
+int smb_oplock_timeout_def = 45000; /* mSec. */
+
+static void smb_oplock_async_break(void *);
+static void smb_oplock_hdl_clear(smb_ofile_t *);
+
+
+/*
+ * 2.1.5.17.3 Indicating an Oplock Break to the Server
+ *
+ * The inputs for indicating an oplock break to the server are:
+ *
+ * BreakingOplockOpen: The Open used to request the oplock
+ * that is now breaking.
+ * NewOplockLevel: The type of oplock the requested oplock
+ * has been broken to. Valid values are as follows:
+ * LEVEL_NONE (that is, no oplock)
+ * LEVEL_TWO
+ * A combination of one or more of the following flags:
+ * READ_CACHING
+ * HANDLE_CACHING
+ * WRITE_CACHING
+ * AcknowledgeRequired: A Boolean value; TRUE if the server
+ * MUST acknowledge the oplock break, FALSE if not,
+ * as specified in section 2.1.5.18.
+ * OplockCompletionStatus: The NTSTATUS code to return to the server.
+ *
+ * This algorithm simply represents the completion of an oplock request,
+ * as specified in section 2.1.5.17.1 or section 2.1.5.17.2. The server
+ * is expected to associate the return status from this algorithm with
+ * BreakingOplockOpen, which is the Open passed in when it requested
+ * the oplock that is now breaking.
+ *
+ * It is important to note that because several oplocks can be outstanding
+ * in parallel, although this algorithm represents the completion of an
+ * oplock request, it might not result in the completion of the algorithm
+ * that called it. In particular, calling this algorithm will result in
+ * completion of the caller only if BreakingOplockOpen is the same as the
+ * Open with which the calling algorithm was itself called. To mitigate
+ * confusion, each algorithm that refers to this section will specify
+ * whether that algorithm's operation terminates at that point or not.
+ *
+ * The object store MUST return OplockCompletionStatus,
+ * AcknowledgeRequired, and NewOplockLevel to the server (the algorithm is
+ * as specified in section 2.1.5.17.1 and section 2.1.5.17.2).
+ *
+ * Implementation:
+ *
+ * We use two versions of this function:
+ * smb_oplock_ind_break_in_ack
+ * smb_oplock_ind_break
+ *
+ * The first is used when we're handling an Oplock Break Ack.
+ * The second is used when other operations cause a break,
+ * generally in one of the smb_oplock_break_... functions.
+ *
+ * Note that these are call-back functions that may be called with the
+ * node ofile list rwlock held and the node oplock mutex entered, so
+ * these should ONLY schedule oplock break work, and MUST NOT attempt
+ * any actions that might require either of those locks.
+ */
+
+/*
+ * smb_oplock_ind_break_in_ack
+ *
+ * Variant of smb_oplock_ind_break() for the oplock Ack handler.
+ * When we need to indicate another oplock break from within the
+ * Ack handler (during the Ack. of some previous oplock break)
+ * we need to make sure this new break indication goes out only
+ * AFTER the reply to the current break ack. is sent out.
+ *
+ * In this case, we always have an SR (the break ack) so we can
+ * append the "ind break" work to the current SR and let the
+ * request hander thread do this work after the reply is sent.
+ * Note: this is always an SMB2 or later request, because this
+ * only happens for "granular" oplocks, which are SMB2-only.
+ *
+ * This is mostly the same as smb_oplock_ind_break() except:
+ * - The only CompletionStatus possible is STATUS_CANT_GRANT.
+ * - Instead of taskq_dispatch this appends the new SR to
+ * the "post work" queue on the current SR.
+ *
+ * Note called with the node ofile list rwlock held and
+ * the oplock mutex entered.
+ */
+void
+smb_oplock_ind_break_in_ack(smb_request_t *ack_sr, smb_ofile_t *ofile,
+ uint32_t NewLevel, boolean_t AckRequired)
+{
+ smb_request_t *new_sr;
+
+ /*
+ * This should happen only with SMB2 or later,
+ * but in case that ever changes...
+ */
+ if (ack_sr->session->dialect < SMB_VERS_2_BASE) {
+ smb_oplock_ind_break(ofile, NewLevel,
+ AckRequired, STATUS_CANT_GRANT);
+ return;
+ }
+
+ /*
+ * We're going to schedule a request that will have a
+ * reference to this ofile. Get the hold first.
+ */
+ if (!smb_ofile_hold_olbrk(ofile)) {
+ /* It's closing (or whatever). Nothing to do. */
+ return;
+ }
+
+ /*
+ * When called from Ack processing, we want to use a
+ * request on the session doing the ack. If we can't
+ * allocate a request on that session (because it's
+ * now disconnecting) just fall-back to the normal
+ * oplock break code path which deals with that.
+ * Once we have a request on the ack session, that
+ * session won't go away until the request is done.
+ */
+ new_sr = smb_request_alloc(ack_sr->session, 0);
+ if (new_sr == NULL) {
+ smb_oplock_ind_break(ofile, NewLevel,
+ AckRequired, STATUS_CANT_GRANT);
+ smb_ofile_release(ofile);
+ return;
+ }
+
+ new_sr->sr_state = SMB_REQ_STATE_SUBMITTED;
+ new_sr->smb2_async = B_TRUE;
+ new_sr->user_cr = zone_kcred();
+ new_sr->fid_ofile = ofile;
+ /* Leave tid_tree, uid_user NULL. */
+ new_sr->arg.olbrk.NewLevel = NewLevel;
+ new_sr->arg.olbrk.AckRequired = AckRequired;
+
+ /*
+ * Using smb2_cmd_code to indicate what to call.
+ * work func. will call smb_oplock_send_brk
+ */
+ new_sr->smb2_cmd_code = SMB2_OPLOCK_BREAK;
+ smb2sr_append_postwork(ack_sr, new_sr);
+}
+
+/*
+ * smb_oplock_ind_break
+ *
+ * This is the function described in [MS-FSA] 2.1.5.17.3
+ * which is called many places in the oplock break code.
+ *
+ * Schedule a request & taskq job to do oplock break work
+ * as requested by the FS-level code (smb_cmn_oplock.c).
+ *
+ * Note called with the node ofile list rwlock held and
+ * the oplock mutex entered.
+ */
+void
+smb_oplock_ind_break(smb_ofile_t *ofile, uint32_t NewLevel,
+ boolean_t AckRequired, uint32_t CompletionStatus)
+{
+ smb_server_t *sv = ofile->f_server;
+ smb_request_t *sr = NULL;
+
+ /*
+ * See notes at smb_oplock_async_break re. CompletionStatus
+ * Check for any invalid codes here, so assert happens in
+ * the thread passing an unexpected value.
+ * The real work happens in a taskq job.
+ */
+ switch (CompletionStatus) {
+
+ case NT_STATUS_SUCCESS:
+ case STATUS_CANT_GRANT:
+ /* Send break via taskq job. */
+ break;
+
+ case STATUS_NEW_HANDLE:
+ case NT_STATUS_OPLOCK_HANDLE_CLOSED:
+ smb_oplock_hdl_clear(ofile);
+ return;
+
+ default:
+ ASSERT(0);
+ return;
+ }
+
+ /*
+ * We're going to schedule a request that will have a
+ * reference to this ofile. Get the hold first.
+ */
+ if (!smb_ofile_hold_olbrk(ofile)) {
+ /* It's closing (or whatever). Nothing to do. */
+ return;
+ }
+
+ /*
+ * We need a request allocated on the session that owns
+ * this ofile in order to safely send on that session.
+ *
+ * Note that while we hold a ref. on the ofile, it's
+ * f_session will not change. An ofile in state
+ * _ORPHANED will have f_session == NULL, but the
+ * f_session won't _change_ while we have a ref,
+ * and won't be torn down under our feet.
+ *
+ * If f_session is NULL, or it's in a state that doesn't
+ * allow new requests, use the special "server" session.
+ */
+ if (ofile->f_session != NULL)
+ sr = smb_request_alloc(ofile->f_session, 0);
+ if (sr == NULL)
+ sr = smb_request_alloc(sv->sv_session, 0);
+
+ sr->sr_state = SMB_REQ_STATE_SUBMITTED;
+ sr->smb2_async = B_TRUE;
+ sr->user_cr = zone_kcred();
+ sr->fid_ofile = ofile;
+ /* Leave tid_tree, uid_user NULL. */
+ sr->arg.olbrk.NewLevel = NewLevel;
+ sr->arg.olbrk.AckRequired = AckRequired;
+ sr->smb2_status = CompletionStatus;
+
+ (void) taskq_dispatch(
+ sv->sv_worker_pool,
+ smb_oplock_async_break, sr, TQ_SLEEP);
+}
+
+/*
+ * smb_oplock_async_break
+ *
+ * Called via the taskq to handle an asynchronous oplock break.
+ * We have a hold on the ofile, which will be released in
+ * smb_request_free (via sr->fid_ofile)
+ *
+ * Note we have: sr->uid_user == NULL, sr->tid_tree == NULL.
+ * Nothing called here needs those.
+ *
+ * Note that NewLevel as provided by the FS up-call does NOT
+ * include the GRANULAR flag. The SMB level is expected to
+ * keep track of how each oplock was acquired (by lease or
+ * traditional oplock request) and put the GRANULAR flag
+ * back into the oplock state when calling down to the
+ * FS-level code. Also note that the lease break message
+ * carries only the cache flags, not the GRANULAR flag.
+ */
+static void
+smb_oplock_async_break(void *arg)
+{
+ smb_request_t *sr = arg;
+ uint32_t CompletionStatus;
+
+ SMB_REQ_VALID(sr);
+
+ CompletionStatus = sr->smb2_status;
+ sr->smb2_status = NT_STATUS_SUCCESS;
+
+ mutex_enter(&sr->sr_mutex);
+ sr->sr_worker = curthread;
+ sr->sr_state = SMB_REQ_STATE_ACTIVE;
+ mutex_exit(&sr->sr_mutex);
+
+ /*
+ * Note that the CompletionStatus from the FS level
+ * (smb_cmn_oplock.c) encodes what kind of action we
+ * need to take at the SMB level.
+ */
+ switch (CompletionStatus) {
+
+ case STATUS_CANT_GRANT:
+ case NT_STATUS_SUCCESS:
+ smb_oplock_send_brk(sr);
+ break;
+
+ default:
+ /* Checked by caller. */
+ ASSERT(0);
+ break;
+ }
+
+ sr->sr_state = SMB_REQ_STATE_COMPLETED;
+ smb_request_free(sr);
+}
+
+#ifdef DEBUG
+int smb_oplock_debug_wait = 0;
+#endif
+
+/*
+ * Send an oplock break over the wire, or if we can't,
+ * then process the oplock break locally.
+ *
+ * Note that we have sr->fid_ofile here but all the other
+ * normal sr members are NULL: uid_user, tid_tree.
+ * Also sr->session may or may not be the same session as
+ * the ofile came from (ofile->f_session) depending on
+ * whether this is a "live" open or an orphaned DH,
+ * where ofile->f_session will be NULL.
+ *
+ * Given that we don't always have a session, we determine
+ * the oplock type (lease etc) from f_oplock.og_dialect.
+ */
+void
+smb_oplock_send_brk(smb_request_t *sr)
+{
+ smb_ofile_t *ofile;
+ smb_lease_t *lease;
+ uint32_t NewLevel;
+ boolean_t AckReq;
+ uint32_t status;
+ int rc;
+
+ ofile = sr->fid_ofile;
+ NewLevel = sr->arg.olbrk.NewLevel;
+ AckReq = sr->arg.olbrk.AckRequired;
+ lease = ofile->f_lease;
+
+ /*
+ * Build the break message in sr->reply.
+ * It's free'd in smb_request_free().
+ * Also updates the lease and NewLevel.
+ */
+ sr->reply.max_bytes = MLEN;
+ if (ofile->f_oplock.og_dialect >= SMB_VERS_2_BASE) {
+ if (lease != NULL) {
+ /*
+ * Oplock state has changed, so
+ * update the epoch.
+ */
+ mutex_enter(&lease->ls_mutex);
+ lease->ls_epoch++;
+ mutex_exit(&lease->ls_mutex);
+
+ /* Note, needs "old" state in og_state */
+ smb2_lease_break_notification(sr,
+ (NewLevel & CACHE_RWH), AckReq);
+ NewLevel |= OPLOCK_LEVEL_GRANULAR;
+ } else {
+ smb2_oplock_break_notification(sr, NewLevel);
+ }
+ } else {
+ /*
+ * SMB1 clients should only get Level II oplocks if they
+ * set the capability indicating they know about them.
+ */
+ if (NewLevel == OPLOCK_LEVEL_TWO &&
+ ofile->f_oplock.og_dialect < NT_LM_0_12)
+ NewLevel = OPLOCK_LEVEL_NONE;
+ smb1_oplock_break_notification(sr, NewLevel);
+ }
+
+ /*
+ * Keep track of what we last sent to the client,
+ * preserving the GRANULAR flag (if a lease).
+ * If we're expecting an ACK, set og_breaking
+ * (and maybe lease->ls_breaking) so we can
+ * later find the ofile with breaks pending.
+ */
+ if (AckReq) {
+ uint32_t BreakTo;
+
+ if (lease != NULL) {
+ BreakTo = (NewLevel & CACHE_RWH) << BREAK_SHIFT;
+ if (BreakTo == 0)
+ BreakTo = BREAK_TO_NO_CACHING;
+ lease->ls_breaking = BreakTo;
+ } else {
+ if ((NewLevel & LEVEL_TWO_OPLOCK) != 0)
+ BreakTo = BREAK_TO_TWO;
+ else
+ BreakTo = BREAK_TO_NONE;
+ }
+ /* Will update og_state in ack. */
+ ofile->f_oplock.og_breaking = BreakTo;
+ } else {
+ if (lease != NULL)
+ lease->ls_state = NewLevel & CACHE_RWH;
+ ofile->f_oplock.og_state = NewLevel;
+ }
+
+ /*
+ * Try to send the break message to the client.
+ * When we get to multi-channel, this is supposed to
+ * try to send on every channel before giving up.
+ */
+ if (sr->session == ofile->f_session)
+ rc = smb_session_send(sr->session, 0, &sr->reply);
+ else
+ rc = ENOTCONN;
+
+ if (rc == 0) {
+ /*
+ * OK, we were able to send the break message.
+ * If no ack. required, we're done.
+ */
+ if (!AckReq)
+ return;
+
+ /*
+ * We're expecting an ACK. Wait in this thread
+ * so we can log clients that don't respond.
+ *
+ * If debugging, may want to break after a
+ * short wait to look into why we might be
+ * holding up progress. (i.e. locks?)
+ */
+#ifdef DEBUG
+ if (smb_oplock_debug_wait > 0) {
+ status = smb_oplock_wait_break(ofile->f_node,
+ smb_oplock_debug_wait);
+ if (status == 0)
+ return;
+ cmn_err(CE_NOTE, "clnt %s oplock break wait debug",
+ sr->session->ip_addr_str);
+ debug_enter("oplock_wait");
+ }
+#endif
+ status = smb_oplock_wait_break(ofile->f_node,
+ smb_oplock_timeout_ack);
+ if (status == 0)
+ return;
+
+ cmn_err(CE_NOTE, "clnt %s oplock break timeout",
+ sr->session->ip_addr_str);
+ DTRACE_PROBE1(break_timeout, smb_ofile_t, ofile);
+
+ /*
+ * Will do local ack below. Note, after timeout,
+ * do a break to none or "no caching" regardless
+ * of what the passed in cache level was.
+ * That means: clear all except GRANULAR.
+ */
+ NewLevel &= OPLOCK_LEVEL_GRANULAR;
+ } else {
+ /*
+ * We were unable to send the oplock break request.
+ * Generally, that means we have no connection to this
+ * client right now, and this ofile will have state
+ * SMB_OFILE_STATE_ORPHANED. We either close the handle
+ * or break the oplock locally, in which case the client
+ * gets the updated oplock state when they reconnect.
+ * Decide whether to keep or close.
+ *
+ * Relevant [MS-SMB2] sections:
+ *
+ * 3.3.4.6 Object Store Indicates an Oplock Break
+ * If Open.Connection is NULL, Open.IsResilient is FALSE,
+ * Open.IsDurable is FALSE and Open.IsPersistent is FALSE,
+ * the server SHOULD close the Open as specified in...
+ *
+ * 3.3.4.7 Object Store Indicates a Lease Break
+ * If Open.Connection is NULL, the server MUST close the
+ * Open as specified in ... for the following cases:
+ * - Open.IsResilient is FALSE, Open.IsDurable is FALSE,
+ * and Open.IsPersistent is FALSE.
+ * - Lease.BreakToLeaseState does not contain
+ * ...HANDLE_CACHING and Open.IsDurable is TRUE.
+ * If Lease.LeaseOpens is empty, (... local ack to "none").
+ */
+
+ /*
+ * See similar logic in smb_dh_should_save
+ */
+ switch (ofile->dh_vers) {
+ case SMB2_RESILIENT:
+ break; /* keep DH */
+
+ case SMB2_DURABLE_V2:
+ if (ofile->dh_persist)
+ break; /* keep DH */
+ /* FALLTHROUGH */
+ case SMB2_DURABLE_V1:
+ /* IS durable (v1 or v2) */
+ if ((NewLevel & (OPLOCK_LEVEL_BATCH |
+ OPLOCK_LEVEL_CACHE_HANDLE)) != 0)
+ break; /* keep DH */
+ /* FALLTHROUGH */
+ case SMB2_NOT_DURABLE:
+ default:
+ smb_ofile_close(ofile, 0);
+ return;
+ }
+ /* Keep this ofile (durable handle). */
+
+ if (!AckReq) {
+ /* Nothing more to do. */
+ return;
+ }
+ }
+
+ /*
+ * We get here after either an oplock break ack timeout,
+ * or a send failure for a durable handle type that we
+ * preserve rather than just close. Do local ack.
+ */
+ ofile->f_oplock.og_breaking = 0;
+ if (lease != NULL)
+ lease->ls_breaking = 0;
+
+ status = smb_oplock_ack_break(sr, ofile, &NewLevel);
+ if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+ /* Not expecting this status return. */
+ cmn_err(CE_NOTE, "clnt local oplock ack wait?");
+ (void) smb_oplock_wait_break(ofile->f_node,
+ smb_oplock_timeout_ack);
+ status = 0;
+ }
+ if (status != 0) {
+ cmn_err(CE_NOTE, "clnt local oplock ack, "
+ "status=0x%x", status);
+ }
+
+ /* Update og_state as if we heard from the client. */
+ ofile->f_oplock.og_state = NewLevel;
+ if (lease != NULL) {
+ lease->ls_state = NewLevel & CACHE_RWH;
+ }
+}
+
+/*
+ * See: NT_STATUS_OPLOCK_HANDLE_CLOSED above,
+ * and: STATUS_NEW_HANDLE
+ *
+ * The FS-level oplock layer calls this to update the
+ * SMB-level state when a handle loses its oplock.
+ */
+static void
+smb_oplock_hdl_clear(smb_ofile_t *ofile)
+{
+ smb_lease_t *lease = ofile->f_lease;
+
+ if (lease != NULL) {
+ if (lease->ls_oplock_ofile == ofile) {
+ /* Last close on the lease. */
+ lease->ls_oplock_ofile = NULL;
+ }
+ }
+ ofile->f_oplock.og_state = 0;
+ ofile->f_oplock.og_breaking = 0;
+}
+
+/*
+ * Wait up to "timeout" mSec. for the current oplock "breaking" flags
+ * to be cleared (by smb_oplock_ack_break or smb_oplock_break_CLOSE).
+ *
+ * Callers of the above public oplock functions:
+ * smb_oplock_request()
+ * smb_oplock_ack_break()
+ * smb_oplock_break_OPEN() ...
+ * check for return status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS
+ * and call this function to wait for the break to complete.
+ *
+ * Most callers should use this default timeout, which they get
+ * by passing zero as the timeout arg. This include places where
+ * we're about to do something that invalidates some cache.
+ */
+uint32_t
+smb_oplock_wait_break(smb_node_t *node, int timeout) /* mSec. */
+{
+ smb_oplock_t *ol;
+ clock_t time, rv;
+ uint32_t status = 0;
+
+ if (timeout == 0)
+ timeout = smb_oplock_timeout_def;
+
+ SMB_NODE_VALID(node);
+ ol = &node->n_oplock;
+
+ mutex_enter(&ol->ol_mutex);
+ time = MSEC_TO_TICK(timeout) + ddi_get_lbolt();
+
+ while ((ol->ol_state & BREAK_ANY) != 0) {
+ ol->waiters++;
+ rv = cv_timedwait(&ol->WaitingOpenCV,
+ &ol->ol_mutex, time);
+ ol->waiters--;
+ if (rv < 0) {
+ status = NT_STATUS_CANNOT_BREAK_OPLOCK;
+ break;
+ }
+ }
+
+ mutex_exit(&ol->ol_mutex);
+
+ return (status);
+}
diff --git a/usr/src/uts/common/fs/smbsrv/smb_tree.c b/usr/src/uts/common/fs/smbsrv/smb_tree.c
index af1f54f968..da7c2f7416 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_tree.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_tree.c
@@ -1161,6 +1161,9 @@ smb_tree_get_flags(const smb_kshare_t *si, vfs_t *vfsp, smb_tree_t *tree)
if (si->shr_flags & SMB_SHRF_ABE)
flags |= SMB_TREE_ABE;
+ if (si->shr_flags & SMB_SHRF_FSO)
+ flags |= SMB_TREE_FORCE_L2_OPLOCK;
+
if (ssn->s_cfg.skc_oplock_enable) {
/* if 'smb' zfs property: oplocks=enabled */
flags |= SMB_TREE_OPLOCKS;
diff --git a/usr/src/uts/common/fs/smbsrv/smb_write.c b/usr/src/uts/common/fs/smbsrv/smb_write.c
index 7280893042..ad8f2b2b2b 100644
--- a/usr/src/uts/common/fs/smbsrv/smb_write.c
+++ b/usr/src/uts/common/fs/smbsrv/smb_write.c
@@ -528,8 +528,8 @@ smb_common_write(smb_request_t *sr, smb_rw_param_t *param)
*/
ofile->f_written = B_TRUE;
- if (!smb_node_is_dir(node))
- smb_oplock_break_levelII(node);
+ /* This revokes read cache delegations. */
+ (void) smb_oplock_break_WRITE(node, ofile);
param->rw_count = lcount;
break;
diff --git a/usr/src/uts/common/smb/ntstatus.h b/usr/src/uts/common/smb/ntstatus.h
index b7bfbbb3a9..00b84ad538 100644
--- a/usr/src/uts/common/smb/ntstatus.h
+++ b/usr/src/uts/common/smb/ntstatus.h
@@ -21,7 +21,7 @@
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
#ifndef _SMB_NTSTATUS_H
@@ -783,7 +783,7 @@ extern "C" {
#define NT_STATUS_ILLEGAL_ELEMENT_ADDRESS 0xC0000285
#define NT_STATUS_MAGAZINE_NOT_PRESENT 0xC0000286
#define NT_STATUS_REINITIALIZATION_NEEDED 0xC0000287
-/* NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 */
+/* NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 */
/* NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 */
#define NT_STATUS_ENCRYPTION_FAILED 0xC000028A
#define NT_STATUS_DECRYPTION_FAILED 0xC000028B
diff --git a/usr/src/uts/common/smbsrv/Makefile b/usr/src/uts/common/smbsrv/Makefile
index a80be7497f..8b000be80f 100644
--- a/usr/src/uts/common/smbsrv/Makefile
+++ b/usr/src/uts/common/smbsrv/Makefile
@@ -49,6 +49,7 @@ HDRS= alloc.h \
smb_kproto.h \
smb_kstat.h \
smb_ktypes.h \
+ smb_oplock.h \
smb_privilege.h \
smb_share.h \
smb_signing.h \
diff --git a/usr/src/uts/common/smbsrv/ntifs.h b/usr/src/uts/common/smbsrv/ntifs.h
index bb3d7146c1..ca5570823d 100644
--- a/usr/src/uts/common/smbsrv/ntifs.h
+++ b/usr/src/uts/common/smbsrv/ntifs.h
@@ -22,7 +22,7 @@
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2016 by Delphix. All rights reserved.
*/
@@ -198,6 +198,25 @@ extern "C" {
#define FILE_VALID_SET_FLAGS 0x00000036
/*
+ * "Granular" oplock flags; [MS-FSA], WinDDK/ntifs.h
+ * Same as smb2.h SMB2_LEASE_...
+ */
+#define OPLOCK_LEVEL_CACHE_READ 0x01
+#define OPLOCK_LEVEL_CACHE_HANDLE 0x02
+#define OPLOCK_LEVEL_CACHE_WRITE 0x04
+#define OPLOCK_LEVEL_CACHE_MASK 0x07
+
+/*
+ * [MS-FSA] oplock types (also "levels")
+ */
+#define OPLOCK_LEVEL_NONE 0
+#define OPLOCK_LEVEL_TWO 0x100
+#define OPLOCK_LEVEL_ONE 0x200
+#define OPLOCK_LEVEL_BATCH 0x400
+#define OPLOCK_LEVEL_GRANULAR 0x800
+#define OPLOCK_LEVEL_TYPE_MASK 0xf00
+
+/*
* Define the file information class values used by the NT DDK and HAL.
*/
typedef enum _FILE_INFORMATION_CLASS {
@@ -368,7 +387,7 @@ typedef enum _FILE_FS_INFORMATION_CLASS {
* If this flag is not set,
* the ACE is an effective ACE which controls access to the object
* to which it is attached.
- * Both effective and inherit-only ACEs can be inherited
+ * Both effective and inherit-only ACEs can be inherited
* depending on the state of the other inheritance flags.
*
* INHERITED_ACE: Windows 2000/XP: Indicates that the ACE was inherited.
@@ -559,10 +578,10 @@ typedef struct smb_acl {
typedef struct smb_sd {
uint8_t sd_revision;
uint16_t sd_control;
- smb_sid_t *sd_owner; /* SID file owner */
- smb_sid_t *sd_group; /* SID group (for POSIX) */
- smb_acl_t *sd_sacl; /* ACL System (audits) */
- smb_acl_t *sd_dacl; /* ACL Discretionary (perm) */
+ smb_sid_t *sd_owner; /* SID file owner */
+ smb_sid_t *sd_group; /* SID group (for POSIX) */
+ smb_acl_t *sd_sacl; /* ACL System (audits) */
+ smb_acl_t *sd_dacl; /* ACL Discretionary (perm) */
} smb_sd_t;
/*
diff --git a/usr/src/uts/common/smbsrv/smb.h b/usr/src/uts/common/smbsrv/smb.h
index e7f319ad46..772f59e8c7 100644
--- a/usr/src/uts/common/smbsrv/smb.h
+++ b/usr/src/uts/common/smbsrv/smb.h
@@ -213,6 +213,17 @@ typedef uint32_t smb_utime_t;
FILE_RESERVE_OPFILTER))
/*
+ * Oplocks levels as expressed in the SMB procotol, i.e.
+ * in nt_create_andx and nt_transact_create responses.
+ * The FS-level oplock interface flags are in ntifs.h
+ * (See OPLOCK_LEVEL_...)
+ */
+#define SMB_OPLOCK_NONE 0
+#define SMB_OPLOCK_EXCLUSIVE 1
+#define SMB_OPLOCK_BATCH 2
+#define SMB_OPLOCK_LEVEL_II 3
+
+/*
* Define the filter flags for NtNotifyChangeDirectoryFile
*/
#define FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001
diff --git a/usr/src/uts/common/smbsrv/smb2.h b/usr/src/uts/common/smbsrv/smb2.h
index 225d3afb15..c52fee29b4 100644
--- a/usr/src/uts/common/smbsrv/smb2.h
+++ b/usr/src/uts/common/smbsrv/smb2.h
@@ -10,7 +10,7 @@
*/
/*
- * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
*/
#ifndef _SMB_SMB2_H
@@ -190,13 +190,29 @@ typedef enum {
* SMB2 Create (open)
*/
-/* SMB2 requested oplock levels */
+/*
+ * SMB2 requested oplock levels
+ * Corresponds to ntifs.h OPLOCK_LEVEL_... but NOT the same!
+ */
#define SMB2_OPLOCK_LEVEL_NONE 0x00
#define SMB2_OPLOCK_LEVEL_II 0x01
#define SMB2_OPLOCK_LEVEL_EXCLUSIVE 0x08
#define SMB2_OPLOCK_LEVEL_BATCH 0x09
#define SMB2_OPLOCK_LEVEL_LEASE 0xFF
+/*
+ * SMB2 create request lease "type"
+ * Note: Same as ntifs.h OPLOCK_LEVEL_CACHE...
+ */
+#define SMB2_LEASE_NONE 0x00
+#define SMB2_LEASE_READ_CACHING 0x01
+#define SMB2_LEASE_HANDLE_CACHING 0x02
+#define SMB2_LEASE_WRITE_CACHING 0x04
+
+/* SMB2 create lease flags */
+#define SMB2_LEASE_FLAG_BREAK_IN_PROGRESS 0x00000002
+#define SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET 0x00000004
+
/* SMB2 impersonation levels */
#define SMB2_IMPERSONATION_ANONYMOUS 0x00
#define SMB2_IMPERSONATION_IDENTIFICATION 0x01
@@ -289,19 +305,10 @@ typedef enum {
* Client is MacOS X looking for MacOS-specific extensions.
*/
-/* SMB2 create request lease */
-#define SMB2_LEASE_NONE 0x00
-#define SMB2_LEASE_READ_CACHING 0x01
-#define SMB2_LEASE_HANDLE_CACHING 0x02
-#define SMB2_LEASE_WRITE_CACHING 0x04
-
-/* SMB2 lease break notification flags */
-#define SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED 0x01
-
/*
* SMB2 Close
*/
-#define SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB 0x0001
+#define SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB 0x0001
/*
* SMB2 Write
@@ -350,7 +357,7 @@ typedef enum {
/*
* SMB2 Ioctl Request
*/
-#define SMB2_0_IOCTL_IS_FSCTL 0x00000001
+#define SMB2_0_IOCTL_IS_FSCTL 0x00000001
/*
@@ -390,6 +397,9 @@ typedef enum {
*/
#define SMB2_WATCH_TREE 0x00000001
+/* SMB2 Oplock Break: lease break notification flags */
+#define SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED 0x01
+
#ifdef __cplusplus
}
#endif
diff --git a/usr/src/uts/common/smbsrv/smb2_kproto.h b/usr/src/uts/common/smbsrv/smb2_kproto.h
index b96fc2c8ea..00eb2eac5c 100644
--- a/usr/src/uts/common/smbsrv/smb2_kproto.h
+++ b/usr/src/uts/common/smbsrv/smb2_kproto.h
@@ -42,6 +42,7 @@ void smb2_dispatch_stats_update(smb_server_t *,
int smb2sr_newrq(smb_request_t *);
void smb2sr_work(smb_request_t *);
uint32_t smb2sr_go_async(smb_request_t *);
+void smb2sr_append_postwork(smb_request_t *, smb_request_t *);
int smb2_decode_header(smb_request_t *);
int smb2_encode_header(smb_request_t *, boolean_t);
@@ -78,6 +79,7 @@ smb_sdrc_t smb2_change_notify(smb_request_t *);
smb_sdrc_t smb2_query_info(smb_request_t *);
smb_sdrc_t smb2_set_info(smb_request_t *);
smb_sdrc_t smb2_oplock_break_ack(smb_request_t *);
+smb_sdrc_t smb2_lease_break_ack(smb_request_t *);
int smb2_newrq_negotiate(smb_request_t *);
int smb2_newrq_cancel(smb_request_t *);
@@ -100,6 +102,15 @@ uint32_t smb2_setinfo_fs(smb_request_t *, smb_setinfo_t *, int);
uint32_t smb2_setinfo_sec(smb_request_t *, smb_setinfo_t *, uint32_t);
uint32_t smb2_setinfo_quota(smb_request_t *, smb_setinfo_t *);
+void smb2_oplock_acquire(smb_request_t *sr);
+void smb2_oplock_reconnect(smb_request_t *sr);
+void smb2_lease_acquire(smb_request_t *sr);
+uint32_t smb2_lease_create(smb_request_t *sr);
+void smb2_lease_rele(smb_lease_t *);
+void smb2_lease_init(void);
+void smb2_lease_fini(void);
+void smb2_lease_ofile_close(smb_ofile_t *);
+
void smb2_durable_timers(smb_server_t *);
uint32_t smb2_dh_reconnect(smb_request_t *);
diff --git a/usr/src/uts/common/smbsrv/smb_kproto.h b/usr/src/uts/common/smbsrv/smb_kproto.h
index 2ea85de069..fed5fd1493 100644
--- a/usr/src/uts/common/smbsrv/smb_kproto.h
+++ b/usr/src/uts/common/smbsrv/smb_kproto.h
@@ -38,6 +38,7 @@ extern "C" {
#include <sys/types.h>
#include <sys/param.h>
+#include <sys/varargs.h>
#include <sys/systm.h>
#include <sys/debug.h>
#include <sys/kmem.h>
@@ -58,9 +59,6 @@ extern "C" {
extern int smb_maxbufsize;
extern int smb_flush_required;
extern int smb_dirsymlink_enable;
-extern int smb_oplock_levelII;
-extern int smb_oplock_timeout;
-extern int smb_oplock_min_timeout;
extern int smb_shortnames;
extern int smb_sign_debug;
extern uint_t smb_audit_flags;
@@ -259,17 +257,38 @@ void smb_close_all_connections(void);
int smb_net_id(uint32_t);
/*
- * oplock functions - node operations
+ * Common oplock functions
*/
-void smb_oplock_acquire(smb_request_t *sr, smb_node_t *, smb_ofile_t *);
-void smb_oplock_release(smb_node_t *, smb_ofile_t *);
-int smb_oplock_break(smb_request_t *, smb_node_t *, uint32_t);
-void smb_oplock_break_levelII(smb_node_t *);
-void smb_oplock_ack(smb_node_t *, smb_ofile_t *, uint8_t);
-void smb_oplock_broadcast(smb_node_t *);
+uint32_t smb_oplock_request(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_ack_break(smb_request_t *, smb_ofile_t *, uint32_t *);
+uint32_t smb_oplock_break_PARENT(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_OPEN(smb_node_t *, smb_ofile_t *,
+ uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_BATCH(smb_node_t *, smb_ofile_t *,
+ uint32_t DesiredAccess, uint32_t CreateDisposition);
+uint32_t smb_oplock_break_HANDLE(smb_node_t *, smb_ofile_t *);
+void smb_oplock_break_CLOSE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_READ(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_WRITE(smb_node_t *, smb_ofile_t *);
+uint32_t smb_oplock_break_SETINFO(smb_node_t *,
+ smb_ofile_t *ofile, uint32_t InfoClass);
+uint32_t smb_oplock_break_DELETE(smb_node_t *, smb_ofile_t *);
+
+void smb_oplock_move(smb_node_t *, smb_ofile_t *, smb_ofile_t *);
-void smb1_oplock_break_notification(smb_request_t *, uint8_t);
-void smb2_oplock_break_notification(smb_request_t *, uint8_t);
+/*
+ * Protocol-specific oplock functions
+ * (and "server-level" functions)
+ */
+void smb1_oplock_acquire(smb_request_t *, boolean_t);
+void smb1_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_oplock_break_notification(smb_request_t *, uint32_t);
+void smb2_lease_break_notification(smb_request_t *, uint32_t, boolean_t);
+void smb_oplock_ind_break(smb_ofile_t *, uint32_t, boolean_t, uint32_t);
+void smb_oplock_ind_break_in_ack(smb_request_t *, smb_ofile_t *,
+ uint32_t, boolean_t);
+void smb_oplock_send_brk(smb_request_t *);
+uint32_t smb_oplock_wait_break(smb_node_t *, int);
/*
* range lock functions - node operations
@@ -394,7 +413,7 @@ int smb_net_send_uio(smb_session_t *, struct uio *);
* SMB RPC interface
*/
void smb_opipe_dealloc(smb_opipe_t *);
-int smb_opipe_open(smb_request_t *, uint32_t);
+int smb_opipe_open(smb_request_t *, smb_ofile_t *);
void smb_opipe_close(smb_ofile_t *);
int smb_opipe_read(smb_request_t *, struct uio *);
int smb_opipe_write(smb_request_t *, struct uio *);
@@ -630,9 +649,11 @@ smb_ofile_t *smb_ofile_lookup_by_fid(smb_request_t *, uint16_t);
smb_ofile_t *smb_ofile_lookup_by_uniqid(smb_tree_t *, uint32_t);
smb_ofile_t *smb_ofile_lookup_by_persistid(smb_request_t *, uint64_t);
boolean_t smb_ofile_disallow_fclose(smb_ofile_t *);
-smb_ofile_t *smb_ofile_open(smb_request_t *, smb_node_t *,
- smb_arg_open_t *, uint16_t, uint32_t, smb_error_t *);
+smb_ofile_t *smb_ofile_alloc(smb_request_t *, smb_arg_open_t *, smb_node_t *,
+ uint16_t, uint16_t, uint32_t);
+void smb_ofile_open(smb_request_t *, smb_arg_open_t *, smb_ofile_t *);
void smb_ofile_close(smb_ofile_t *, int32_t);
+void smb_ofile_free(smb_ofile_t *);
uint32_t smb_ofile_access(smb_ofile_t *, cred_t *, uint32_t);
int smb_ofile_seek(smb_ofile_t *, ushort_t, int32_t, uint32_t *);
void smb_ofile_flush(smb_request_t *, smb_ofile_t *);
@@ -640,7 +661,6 @@ boolean_t smb_ofile_hold(smb_ofile_t *);
boolean_t smb_ofile_hold_olbrk(smb_ofile_t *);
void smb_ofile_release(smb_ofile_t *);
void smb_ofile_close_all(smb_tree_t *, uint32_t);
-void smb_ofile_request_complete(smb_ofile_t *);
void smb_ofile_set_flags(smb_ofile_t *, uint32_t);
boolean_t smb_ofile_is_open(smb_ofile_t *);
int smb_ofile_enum(smb_ofile_t *, smb_svcenum_t *);
@@ -649,7 +669,7 @@ uint32_t smb_ofile_rename_check(smb_ofile_t *);
uint32_t smb_ofile_delete_check(smb_ofile_t *);
boolean_t smb_ofile_share_check(smb_ofile_t *);
cred_t *smb_ofile_getcred(smb_ofile_t *);
-void smb_ofile_set_delete_on_close(smb_ofile_t *);
+void smb_ofile_set_delete_on_close(smb_request_t *, smb_ofile_t *);
void smb_delayed_write_timer(smb_llist_t *);
void smb_ofile_set_quota_resume(smb_ofile_t *, char *);
void smb_ofile_get_quota_resume(smb_ofile_t *, char *, int);
diff --git a/usr/src/uts/common/smbsrv/smb_ktypes.h b/usr/src/uts/common/smbsrv/smb_ktypes.h
index 2bd7206762..ac9a01e642 100644
--- a/usr/src/uts/common/smbsrv/smb_ktypes.h
+++ b/usr/src/uts/common/smbsrv/smb_ktypes.h
@@ -576,67 +576,58 @@ int MBC_SHADOW_CHAIN(struct mbuf_chain *SUBMBC, struct mbuf_chain *MBC,
#define MBC_ROOM_FOR(b, n) (((b)->chain_offset + (n)) <= (b)->max_bytes)
-#define OPLOCK_MIN_TIMEOUT (5 * 1000)
-#define OPLOCK_STD_TIMEOUT (30 * 1000)
-
-/*
- * Oplock break flags:
- * SMB_OPLOCK_BREAK_EXCLUSIVE - only break exclusive oplock
- * (type SMB_OPLOCK_EXCLUSIVE or SMB_OPLOCK_BATCH)
- * SMB_OPLOCK_BREAK_BATCH - only break exclusive BATCH oplock
- * SMB_OPLOCK_BREAK_NOWAIT - do not wait for oplock break ack
- */
-#define SMB_OPLOCK_NO_BREAK 0x00
-#define SMB_OPLOCK_BREAK_TO_NONE 0x01
-#define SMB_OPLOCK_BREAK_TO_LEVEL_II 0x02
-#define SMB_OPLOCK_BREAK_EXCLUSIVE 0x04
-#define SMB_OPLOCK_BREAK_BATCH 0x08
-#define SMB_OPLOCK_BREAK_NOWAIT 0x10
-
/*
- * Oplocks levels are defined to match the levels in the SMB
- * protocol (nt_create_andx / nt_transact_create) and should
- * not be changed
+ * Per smb_node oplock state
*/
-#define SMB_OPLOCK_NONE 0
-#define SMB_OPLOCK_EXCLUSIVE 1
-#define SMB_OPLOCK_BATCH 2
-#define SMB_OPLOCK_LEVEL_II 3
-
typedef struct smb_oplock {
kmutex_t ol_mutex;
- kcondvar_t ol_cv;
- kthread_t *ol_xthread;
boolean_t ol_fem; /* fem monitor installed? */
- uint8_t ol_brk_pending;
- uint8_t ol_break;
- uint32_t ol_count; /* number of grants */
- list_t ol_grants; /* list of smb_oplock_grant_t */
+ struct smb_ofile *excl_open;
+ uint32_t ol_state;
+ int32_t cnt_II;
+ int32_t cnt_R;
+ int32_t cnt_RH;
+ int32_t cnt_RHBQ;
+ int32_t waiters;
+ kcondvar_t WaitingOpenCV;
} smb_oplock_t;
-#define SMB_OPLOCK_GRANT_MAGIC 0x4F4C4B47 /* OLKG */
-#define SMB_OPLOCK_GRANT_VALID(p) \
- ASSERT((p)->og_magic == SMB_OPLOCK_GRANT_MAGIC)
-#define SMB_OFILE_OPLOCK_GRANTED(p) \
- ((p)->f_oplock_grant.og_magic == SMB_OPLOCK_GRANT_MAGIC)
+/*
+ * Per smb_ofile oplock state
+ */
typedef struct smb_oplock_grant {
- list_node_t og_lnd;
- uint32_t og_magic;
- uint8_t og_breaking;
- uint8_t og_level;
- uint8_t og_dialect; /* how to send breaks */
- struct smb_ofile *og_ofile;
+ /* smb protocol-level state */
+ uint32_t og_state; /* latest sent to client */
+ uint32_t og_breaking; /* BREAK_TO... flags */
+ uint16_t og_dialect; /* how to send breaks */
+ /* File-system level state */
+ uint8_t onlist_II;
+ uint8_t onlist_R;
+ uint8_t onlist_RH;
+ uint8_t onlist_RHBQ;
+ uint8_t BreakingToRead;
} smb_oplock_grant_t;
-#define SMB_OPLOCK_BREAK_MAGIC 0x4F4C4B42 /* OLKB */
-#define SMB_OPLOCK_BREAK_VALID(p) \
- ASSERT((p)->ob_magic == SMB_OPLOCK_BREAK_MAGIC)
-typedef struct smb_oplock_break {
- list_node_t ob_lnd;
- uint32_t ob_magic;
- struct smb_node *ob_node;
-} smb_oplock_break_t;
+#define SMB_LEASE_KEY_SZ 16
+typedef struct smb_lease {
+ list_node_t ls_lnd; /* sv_lease_ht */
+ kmutex_t ls_mutex;
+ smb_llist_t *ls_bucket;
+ struct smb_node *ls_node;
+ /*
+ * With a lease, just one ofile has the oplock.
+ * This (used only for comparison) identifies which.
+ */
+ void *ls_oplock_ofile;
+ uint32_t ls_refcnt;
+ uint32_t ls_state;
+ uint32_t ls_breaking; /* BREAK_TO... flags */
+ uint16_t ls_epoch;
+ uint16_t ls_version;
+ uint8_t ls_key[SMB_LEASE_KEY_SZ];
+ uint8_t ls_clnt[SMB_LEASE_KEY_SZ];
+} smb_lease_t;
#define SMB_VFS_MAGIC 0x534D4256 /* 'SMBV' */
@@ -1091,6 +1082,8 @@ typedef struct smb_user {
#define SMB_TREE_DFSROOT 0x00020000
#define SMB_TREE_SPARSE 0x00040000
#define SMB_TREE_TRAVERSE_MOUNTS 0x00080000
+#define SMB_TREE_FORCE_L2_OPLOCK 0x00100000
+/* Note: SMB_TREE_... in the mdb module too. */
/*
* See the long "Tree State Machine" comment in smb_tree.c
@@ -1343,7 +1336,8 @@ typedef enum {
* See the long "Ofile State Machine" comment in smb_ofile.c
*/
typedef enum {
- SMB_OFILE_STATE_OPEN = 0,
+ SMB_OFILE_STATE_ALLOC = 0,
+ SMB_OFILE_STATE_OPEN,
SMB_OFILE_STATE_SAVE_DH,
SMB_OFILE_STATE_SAVING,
SMB_OFILE_STATE_CLOSING,
@@ -1393,7 +1387,11 @@ typedef struct smb_ofile {
pid_t f_pid;
smb_attr_t f_pending_attr;
boolean_t f_written;
- smb_oplock_grant_t f_oplock_grant;
+ smb_oplock_grant_t f_oplock;
+ uint8_t TargetOplockKey[SMB_LEASE_KEY_SZ];
+ uint8_t ParentOplockKey[SMB_LEASE_KEY_SZ];
+ struct smb_lease *f_lease;
+
smb_notify_t f_notify;
smb_dh_vers_t dh_vers;
@@ -1598,8 +1596,16 @@ typedef struct open_param {
smb_opipe_t *pipe; /* for smb_opipe_open */
struct smb_sd *sd; /* for NTTransactCreate */
void *create_ctx;
+
uint8_t op_oplock_level; /* requested/granted level */
- boolean_t op_oplock_levelII; /* TRUE if levelII supported */
+ uint32_t op_oplock_state; /* internal type+level */
+ uint32_t lease_state; /* SMB2_LEASE_... */
+ uint32_t lease_flags;
+ uint16_t lease_epoch;
+ uint16_t lease_version; /* 1 or 2 */
+ uint8_t lease_key[SMB_LEASE_KEY_SZ]; /* from client */
+ uint8_t parent_lease_key[SMB_LEASE_KEY_SZ]; /* for V2 */
+
smb_dh_vers_t dh_vers;
smb2fid_t dh_fileid; /* for durable reconnect */
uint8_t create_guid[16];
@@ -1613,6 +1619,11 @@ typedef struct smb_arg_lock {
uint32_t lseq;
} smb_arg_lock_t;
+typedef struct smb_arg_olbrk {
+ uint32_t NewLevel;
+ boolean_t AckRequired;
+} smb_arg_olbrk_t;
+
/*
* SMB Request State Machine
* -------------------------
@@ -1764,6 +1775,9 @@ typedef struct smb_request {
void (*cancel_method)(struct smb_request *);
void *cancel_arg2;
+ /* Queue used by smb_request_append_postwork. */
+ struct smb_request *sr_postwork;
+
list_node_t sr_waiters; /* smb_notify.c */
/* Info from session service header */
@@ -1859,8 +1873,8 @@ typedef struct smb_request {
smb_arg_dirop_t dirop;
smb_arg_open_t open;
smb_arg_lock_t lock;
+ smb_arg_olbrk_t olbrk; /* for async oplock break */
smb_rw_param_t *rw;
- smb_oplock_grant_t olbrk; /* for async oplock break */
int32_t timestamp;
} arg;
} smb_request_t;
@@ -2053,6 +2067,7 @@ typedef struct smb_server {
smb_session_t *sv_session;
smb_llist_t sv_session_list;
smb_hash_t *sv_persistid_ht;
+ smb_hash_t *sv_lease_ht;
struct smb_export sv_export;
struct __door_handle *sv_lmshrd;
diff --git a/usr/src/uts/common/smbsrv/smb_oplock.h b/usr/src/uts/common/smbsrv/smb_oplock.h
new file mode 100644
index 0000000000..7cc0693417
--- /dev/null
+++ b/usr/src/uts/common/smbsrv/smb_oplock.h
@@ -0,0 +1,252 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#ifndef _SMBSRV_SMB_OPLOCK_H
+#define _SMBSRV_SMB_OPLOCK_H
+
+#include <smbsrv/ntifs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * 2.1.1.10 Per Oplock
+ *
+ *
+ * ExclusiveOpen: The Open used to request the opportunistic lock.
+ *
+ * IIOplocks: A list of zero or more Opens used to request a LEVEL_TWO
+ * opportunistic lock, as specified in section 2.1.5.17.1.
+ *
+ * ROplocks: A list of zero or more Opens used to request a LEVEL_GRANULAR
+ * (RequestedOplockLevel: READ_CACHING) opportunistic lock, as specified in
+ * section 2.1.5.17.1.
+ *
+ * RHOplocks: A list of zero or more Opens used to request a LEVEL_GRANULAR
+ * (RequestedOplockLevel: (READ_CACHING|HANDLE_CACHING)) opportunistic lock,
+ * as specified in section 2.1.5.17.1.
+ *
+ * RHBreakQueue: A list of zero or more RHOpContext objects. This queue is
+ * used to track (READ_CACHING|HANDLE_CACHING) oplocks as they are breaking.
+ *
+ * WaitList: A list of zero or more Opens belonging to operations that are
+ * waiting for an oplock to break, as specified in section 2.1.4.12.
+ *
+ * State: The current state of the oplock, expressed as a combination of
+ * one or more flags. Valid flags are:
+ * [ As follows; Re-ordered a bit from the spec. ]
+ */
+
+/*
+ * READ_CACHING - Indicates that this Oplock represents an oplock
+ * that provides caching of reads; this provides the SMB 2.1 read
+ * caching lease, as described in [MS-SMB2] section 2.2.13.2.8.
+ */
+#define READ_CACHING OPLOCK_LEVEL_CACHE_READ /* 1 */
+
+/*
+ * HANDLE_CACHING - Indicates that this Oplock represents an oplock
+ * that provides caching of handles; this provides the SMB 2.1 handle
+ * caching lease, as described in [MS-SMB2] section 2.2.13.2.8.
+ */
+#define HANDLE_CACHING OPLOCK_LEVEL_CACHE_HANDLE /* 2 */
+
+/*
+ * WRITE_CACHING - Indicates that this Oplock represents an oplock
+ * that provides caching of writes; this provides the SMB 2.1 write
+ * caching lease, as described in [MS-SMB2] section 2.2.13.2.8.
+ */
+#define WRITE_CACHING OPLOCK_LEVEL_CACHE_WRITE /* 4 */
+
+/*
+ * EXCLUSIVE - Indicates that this Oplock represents an oplock that
+ * can be held by exactly one client at a time. This flag always appears
+ * in combination with other flags that indicate the actual oplock level.
+ * For example, (READ_CACHING|WRITE_CACHING|EXCLUSIVE) represents a
+ * read caching and write caching oplock, which can be held by only
+ * one client at a time.
+ */
+#define EXCLUSIVE 0x00000010
+
+/*
+ * MIXED_R_AND_RH - Always appears together with READ_CACHING and
+ * HANDLE_CACHING. Indicates that this Oplock represents an oplock
+ * on which at least one client has been granted a read caching oplock,
+ * and at least one other client has been granted a read caching and
+ * handle caching oplock.
+ */
+#define MIXED_R_AND_RH 0x00000020
+
+/*
+ * LEVEL_TWO_OPLOCK - Indicates that this Oplock represents a
+ * Level 2 (also called Shared) oplock.
+ * Corresponds to SMB2_OPLOCK_LEVEL_II
+ */
+#define LEVEL_TWO_OPLOCK OPLOCK_LEVEL_TWO /* 0x100 */
+
+/*
+ * LEVEL_ONE_OPLOCK - Indicates that this Oplock represents a
+ * Level 1 (also called Exclusive) oplock.
+ * Corresponds to SMB2_OPLOCK_LEVEL_EXCLUSIVE
+ */
+#define LEVEL_ONE_OPLOCK OPLOCK_LEVEL_ONE /* 0x200 */
+
+/*
+ * BATCH_OPLOCK - Indicates that this Oplock represents a Batch oplock.
+ * Corresponds to SMB2_OPLOCK_LEVEL_BATCH
+ */
+#define BATCH_OPLOCK OPLOCK_LEVEL_BATCH /* 0x400 */
+
+/* Note: ntifs.h OPLOCK_LEVEL_GRANULAR 0x800 */
+
+/*
+ * Note that the oplock leasing implementation uses this shift
+ * to convert (i.e.) CACHE_READ to BREAK_TO_READ_CACHING etc.
+ * This relationship is checked in smb_srv_oplock.c
+ */
+#define BREAK_SHIFT 16
+
+/*
+ * BREAK_TO_READ_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to an oplock that provides
+ * caching of reads; the oplock has broken but the break has not yet
+ * been acknowledged.
+ */
+#define BREAK_TO_READ_CACHING 0x00010000
+
+/*
+ * BREAK_TO_HANDLE_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to an oplock that provides
+ * caching of handles; the oplock has broken but the break has not yet
+ * been acknowledged. Note: == (CACHE_HANDLE << BREAK_SHIFT)
+ */
+#define BREAK_TO_HANDLE_CACHING 0x00020000
+
+/*
+ * BREAK_TO_WRITE_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to an oplock that provides
+ * caching of writes; the oplock has broken but the break has
+ * not yet been acknowledged.
+ */
+#define BREAK_TO_WRITE_CACHING 0x00040000
+
+/*
+ * BREAK_TO_NO_CACHING - Indicates that this Oplock represents an
+ * oplock that is currently breaking to None (that is, no oplock);
+ * the oplock has broken but the break has not yet been acknowledged.
+ */
+#define BREAK_TO_NO_CACHING 0x00080000
+
+/*
+ * BREAK_TO_TWO - Indicates that this Oplock represents an oplock
+ * that is currently breaking from either Level 1 or Batch to Level 2;
+ * the oplock has broken but the break has not yet been acknowledged.
+ */
+#define BREAK_TO_TWO 0x00100000
+
+/*
+ * BREAK_TO_NONE - Indicates that this Oplock represents an oplock
+ * that is currently breaking from either Level 1 or Batch to None
+ * (that is, no oplock); the oplock has broken but the break has
+ * not yet been acknowledged.
+ */
+#define BREAK_TO_NONE 0x00200000
+
+/*
+ * BREAK_TO_TWO_TO_NONE - Indicates that this Oplock represents an
+ * oplock that is currently breaking from either Level 1 or Batch to
+ * None (that is, no oplock), and was previously breaking from Level 1
+ * or Batch to Level 2; the oplock has broken but the break has
+ * not yet been acknowledged.
+ */
+#define BREAK_TO_TWO_TO_NONE 0x00400000
+
+/*
+ * NO_OPLOCK - Indicates that this Oplock does not represent a
+ * currently granted or breaking oplock. This is semantically
+ * equivalent to the Oplock object being entirely absent from a
+ * Stream. This flag always appears alone.
+ * Note we also have OPLOCK_LEVEL_NONE == 0 from ntifs.h
+ */
+#define NO_OPLOCK 0x10000000
+
+/*
+ * An internal flag, non-overlapping wth other oplock flags,
+ * used only in smb_cmn_oplock.c (and here only to make clear
+ * that it does not overlap with an other flags above).
+ */
+#define PARENT_OBJECT 0x40000000
+
+/*
+ * Also not in the spec, but convenient
+ */
+#define BREAK_LEVEL_MASK (\
+ BREAK_TO_READ_CACHING |\
+ BREAK_TO_WRITE_CACHING |\
+ BREAK_TO_HANDLE_CACHING |\
+ BREAK_TO_NO_CACHING)
+
+#define BREAK_ANY (\
+ BREAK_LEVEL_MASK |\
+ BREAK_TO_TWO |\
+ BREAK_TO_NONE |\
+ BREAK_TO_TWO_TO_NONE)
+
+
+/*
+ * Convenience macro to walk ofiles on a give node.
+ * Used as follows:
+ * FOREACH_NODE_OFILE(node, o) { muck_with(o); }
+ */
+#define FOREACH_NODE_OFILE(node, o) for \
+ (o = smb_llist_head(&node->n_ofile_list); \
+ o != NULL; \
+ o = smb_llist_next(&node->n_ofile_list, o))
+
+/*
+ * Some short-hand names used in the oplock code.
+ */
+
+#define STATUS_NEW_HANDLE NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE
+#define STATUS_CANT_GRANT NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK
+
+typedef enum oplock_type {
+ LEVEL_NONE = OPLOCK_LEVEL_NONE,
+ LEVEL_TWO = OPLOCK_LEVEL_TWO,
+ LEVEL_ONE = OPLOCK_LEVEL_ONE,
+ LEVEL_BATCH = OPLOCK_LEVEL_BATCH,
+ LEVEL_GRANULAR = OPLOCK_LEVEL_GRANULAR
+} oplock_type_t;
+
+typedef enum oplock_cache_level {
+ CACHE_R = READ_CACHING,
+
+ CACHE_RH = READ_CACHING |
+ HANDLE_CACHING,
+
+ CACHE_RW = READ_CACHING |
+ WRITE_CACHING,
+
+ CACHE_RWH = READ_CACHING |
+ WRITE_CACHING |
+ HANDLE_CACHING,
+} oplock_cache_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SMBSRV_SMB_OPLOCK_H */
diff --git a/usr/src/uts/common/smbsrv/smb_share.h b/usr/src/uts/common/smbsrv/smb_share.h
index d8bb0d6519..9df3e9e8e8 100644
--- a/usr/src/uts/common/smbsrv/smb_share.h
+++ b/usr/src/uts/common/smbsrv/smb_share.h
@@ -21,7 +21,7 @@
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2016 by Delphix. All rights reserved.
*/
@@ -94,6 +94,7 @@ extern "C" {
#define SHOPT_DFSROOT "dfsroot"
#define SHOPT_DESCRIPTION "description"
#define SHOPT_QUOTAS "quotas"
+#define SHOPT_FSO "fso" /* Force Shared Oplocks */
#define SHOPT_AUTOHOME "Autohome"
#define SMB_DEFAULT_SHARE_GROUP "smb"
@@ -174,7 +175,8 @@ extern "C" {
#define SMB_SHRF_ACC_RW 0x0400
#define SMB_SHRF_ACC_ALL 0x0F00
-#define SMB_SHRF_QUOTAS 0x1000
+#define SMB_SHRF_QUOTAS 0x1000 /* Enable SMB Quotas */
+#define SMB_SHRF_FSO 0x2000 /* Force Shared Oplocks */
/*
* Runtime flags