diff options
| author | Gordon Ross <gwr@nexenta.com> | 2017-07-21 16:37:02 -0400 |
|---|---|---|
| committer | Gordon Ross <gwr@nexenta.com> | 2019-06-08 20:48:57 -0400 |
| commit | 94047d49916b669576decf2f622a1ee718646882 (patch) | |
| tree | 6b446f44e97da3deccef4504c5f8bd82f14a35c8 /usr/src | |
| parent | 148d1a4158dc830f7b293a2ceb62ee54c2ebd72f (diff) | |
| download | illumos-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')
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 |
