diff options
138 files changed, 13537 insertions, 3194 deletions
diff --git a/exception_lists/check_rtime b/exception_lists/check_rtime index c31e6822b2..d7932c5df8 100644 --- a/exception_lists/check_rtime +++ b/exception_lists/check_rtime @@ -231,6 +231,7 @@ FORBIDDEN_DEP usr/lib/MACH(smbfs)/libfksmbfs.so.1 FORBIDDEN_DEP usr/lib/MACH(smbsrv)/libfksmbsrv.so.1 FORBIDDEN_DEP usr/lib/smbsrv/fksmbd FORBIDDEN_DEP usr/lib/smbsrv/test-msgbuf +FORBIDDEN_DEP usr/lib/smbsrv/testoplock FORBIDDEN_DEP usr/sbin/amd64/zdb FORBIDDEN_DEP usr/sbin/i86/zdb FORBIDDEN_DEP usr/sbin/sparcv7/zdb diff --git a/exception_lists/copyright b/exception_lists/copyright index 59e919dcfc..5ad95417ed 100644 --- a/exception_lists/copyright +++ b/exception_lists/copyright @@ -106,6 +106,7 @@ usr/src/cmd/krb5/ldap_util/kdb5_ldap_realm.h usr/src/cmd/krb5/ldap_util/kdb5_ldap_services.h usr/src/cmd/localedef/data/manual-input.UTF-8 usr/src/cmd/smbsrv/smbd/eventlog.dll +usr/src/cmd/smbsrv/testoplock/case* usr/src/cmd/terminfo/termcap.src usr/src/cmd/terminfo/terminfo.src usr/src/common/bzip2/LICENSE diff --git a/exception_lists/packaging b/exception_lists/packaging index e7ab7b63a0..9916b48e59 100644 --- a/exception_lists/packaging +++ b/exception_lists/packaging @@ -604,6 +604,7 @@ usr/lib/smbsrv/libmlsvc.so usr/lib/smbsrv/libsmb.so usr/lib/smbsrv/libsmbns.so usr/lib/smbsrv/test-msgbuf +usr/lib/smbsrv/testoplock # # # Private/Internal 64-bit libraries of smbsrv. Do not ship. diff --git a/exception_lists/wscheck b/exception_lists/wscheck index aae67df495..3e4b8d18fb 100644 --- a/exception_lists/wscheck +++ b/exception_lists/wscheck @@ -101,3 +101,4 @@ usr/src/tools/smatch/src/* usr/src/data/hwdata/pci.ids usr/src/data/hwdata/usb.ids usr/src/data/perfmon/readme.txt +usr/src/cmd/smbsrv/testoplock/case*.ref diff --git a/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c index 9e1402a5dc..fe0f6556c7 100644 --- a/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c +++ b/usr/src/cmd/mdb/common/modules/smbsrv/smbsrv.c @@ -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. */ #include <mdb/mdb_modapi.h> @@ -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 *); @@ -571,16 +576,38 @@ smblist_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) typedef struct mdb_smb_server { smb_server_state_t sv_state; zoneid_t sv_zid; + smb_hash_t *sv_persistid_ht; } mdb_smb_server_t; static int +smb_server_exp_off_sv_list(void) +{ + int svl_off, ll_off; + + /* OFFSETOF(smb_server_t, sv_session_list.ll_list); */ + GET_OFFSET(svl_off, smb_server_t, sv_session_list); + GET_OFFSET(ll_off, smb_llist_t, ll_list); + return (svl_off + ll_off); +} + +static int smb_server_exp_off_nbt_list(void) { int svd_off, lds_off, ll_off; /* OFFSETOF(smb_server_t, sv_nbt_daemon.ld_session_list.ll_list); */ GET_OFFSET(svd_off, smb_server_t, sv_nbt_daemon); - GET_OFFSET(lds_off, smb_listener_daemon_t, ld_session_list); + /* + * We can't do OFFSETOF() because the member doesn't exist, + * but we want backwards compatibility to old cores + */ + lds_off = mdb_ctf_offsetof_by_name("smb_listener_daemon_t", + "ld_session_list"); + if (lds_off < 0) { + mdb_warn("cannot lookup: " + "smb_listener_daemon_t .ld_session_list"); + return (-1); + } GET_OFFSET(ll_off, smb_llist_t, ll_list); return (svd_off + lds_off + ll_off); } @@ -592,7 +619,17 @@ smb_server_exp_off_tcp_list(void) /* OFFSETOF(smb_server_t, sv_tcp_daemon.ld_session_list.ll_list); */ GET_OFFSET(svd_off, smb_server_t, sv_tcp_daemon); - GET_OFFSET(lds_off, smb_listener_daemon_t, ld_session_list); + /* + * We can't do OFFSETOF() because the member doesn't exist, + * but we want backwards compatibility to old cores + */ + lds_off = mdb_ctf_offsetof_by_name("smb_listener_daemon_t", + "ld_session_list"); + if (lds_off < 0) { + mdb_warn("cannot lookup: " + "smb_listener_daemon_t .ld_session_list"); + return (-1); + } GET_OFFSET(ll_off, smb_llist_t, ll_list); return (svd_off + lds_off + ll_off); } @@ -603,6 +640,15 @@ smb_server_exp_off_tcp_list(void) static const smb_exp_t smb_server_exp[] = { { SMB_OPT_ALL_OBJ, + smb_server_exp_off_sv_list, + "smbsess", "smb_session"}, + { 0, 0, NULL, NULL } +}; + +/* for backwards compatibility only */ +static const smb_exp_t smb_server_exp_old[] = +{ + { SMB_OPT_ALL_OBJ, smb_server_exp_off_nbt_list, "smbsess", "smb_session"}, { SMB_OPT_ALL_OBJ, @@ -622,6 +668,9 @@ smbsrv_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint_t opts; ulong_t indent = 0; + const smb_exp_t *sv_exp; + mdb_ctf_id_t id; + ulong_t off; if (smb_dcmd_getopt(&opts, argc, argv)) return (DCMD_USAGE); @@ -668,7 +717,18 @@ smbsrv_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) addr, sv->sv_zid, state); } } - if (smb_obj_expand(addr, opts, smb_server_exp, indent)) + + /* if we can't look up the type name, just error out */ + if (mdb_ctf_lookup_by_name("smb_server_t", &id) == -1) + return (DCMD_ERR); + + if (mdb_ctf_offsetof(id, "sv_session_list", &off) == -1) + /* sv_session_list doesn't exist; old core */ + sv_exp = smb_server_exp_old; + else + sv_exp = smb_server_exp; + + if (smb_obj_expand(addr, opts, sv_exp, indent)) return (DCMD_ERR); return (DCMD_OK); } @@ -721,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 @@ -875,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); } @@ -892,8 +953,6 @@ typedef struct mdb_smb_request { unsigned char first_smb_com; unsigned char smb_com; - uint16_t smb2_cmd_code; - uint64_t smb2_messageid; uint16_t smb_tid; uint32_t smb_pid; @@ -901,6 +960,10 @@ typedef struct mdb_smb_request { uint16_t smb_mid; uint16_t smb_fid; + uint16_t smb2_cmd_code; + uint64_t smb2_messageid; + uint64_t smb2_ssnid; + struct smb_tree *tid_tree; struct smb_ofile *fid_ofile; smb_user_t *uid_user; @@ -990,15 +1053,21 @@ smbreq_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) "state: %u (%s)\n", sr->sr_state, state); + if (sr->smb2_ssnid != 0) { + mdb_printf( + "SSNID(user): 0x%llx (%p)\n", + sr->smb2_ssnid, sr->uid_user); + } else { + mdb_printf( + "UID(user): %u (%p)\n", + sr->smb_uid, sr->uid_user); + } + mdb_printf( "TID(tree): %u (%p)\n", sr->smb_tid, sr->tid_tree); mdb_printf( - "UID(user): %u (%p)\n", - sr->smb_uid, sr->uid_user); - - mdb_printf( "FID(file): %u (%p)\n", sr->smb_fid, sr->fid_ofile); @@ -1284,6 +1353,7 @@ typedef struct mdb_smb_user { cred_t *u_cred; cred_t *u_privcred; + uint64_t u_ssnid; uint32_t u_refcnt; uint32_t u_flags; uint32_t u_privileges; @@ -1387,6 +1457,7 @@ smbuser_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) mdb_printf("%<b>%<u>SMB user information (%p):" "%</u>%</b>\n", addr); mdb_printf("UID: %u\n", user->u_uid); + mdb_printf("SSNID: %llx\n", user->u_ssnid); mdb_printf("State: %d (%s)\n", user->u_state, state); mdb_printf("Flags: 0x%08x <%b>\n", user->u_flags, user->u_flags, user_flag_bits); @@ -1401,11 +1472,12 @@ smbuser_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) mdb_printf( "%<b>%<u>%?-s " "%-5s " + "%-16s " "%-32s%</u>%</b>\n", - "USER", "UID", "ACCOUNT"); + "USER", "UID", "SSNID", "ACCOUNT"); - mdb_printf("%-?p %-5u %-32s\n", addr, user->u_uid, - account); + mdb_printf("%-?p %-5u %-16llx %-32s\n", + addr, user->u_uid, user->u_ssnid, account); } } return (DCMD_OK); @@ -1530,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 } }; @@ -1717,13 +1792,13 @@ 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; static const mdb_bitmask_t ofile_flag_bits[] = { - { "RO", - SMB_OFLAGS_READONLY, - SMB_OFLAGS_READONLY }, + { "RO", 1, 1 }, /* old SMB_OFLAGS_READONLY */ { "EXEC", SMB_OFLAGS_EXECONLY, SMB_OFLAGS_EXECONLY }, @@ -1777,16 +1852,24 @@ smbofile_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) } if (opts & SMB_OPT_VERBOSE) { char state[40]; + char durable[40]; get_enum(state, sizeof (state), "smb_ofile_state_t", of->f_state, "SMB_OFILE_STATE_"); + get_enum(durable, sizeof (durable), + "smb_dh_vers_t", of->dh_vers, + "SMB2_"); + mdb_printf( "%<b>%<u>SMB ofile information (%p):%</u>%</b>\n\n", addr); mdb_printf("FID: %u\n", of->f_fid); 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, @@ -1809,13 +1892,293 @@ smbofile_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) "%<b>%<u>%-?s " "%-5s " "%-?s " + "%-?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); +} + +static int +smbdurable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + mdb_smb_server_t *sv; + + if (!(flags & DCMD_ADDRSPEC)) { + mdb_printf("require address of an smb_server_t\n"); + return (WALK_ERR); + } + + sv = mdb_zalloc(sizeof (*sv), UM_SLEEP | UM_GC); + if (mdb_ctf_vread(sv, SMBSRV_SCOPE "smb_server_t", + "mdb_smb_server_t", addr, 0) < 0) { + mdb_warn("failed to read smb_server at %p", addr); + return (DCMD_ERR); + } + + if (mdb_pwalk_dcmd("smb_hash_walker", "smbofile", + argc, argv, (uintptr_t)sv->sv_persistid_ht) == -1) { + mdb_warn("failed to walk 'smb_ofile'"); + return (DCMD_ERR); + } + return (DCMD_OK); +} + +static int +smb_hash_walk_init(mdb_walk_state_t *wsp) +{ + smb_hash_t hash; + int ll_off, sll_off, i; + uintptr_t addr = wsp->walk_addr; + + if (addr == NULL) { + mdb_printf("require address of an smb_hash_t\n"); + return (WALK_ERR); + } + + GET_OFFSET(sll_off, smb_bucket_t, b_list); + GET_OFFSET(ll_off, smb_llist_t, ll_list); + + if (mdb_vread(&hash, sizeof (hash), addr) == -1) { + mdb_warn("failed to read smb_hash_t at %p", addr); + return (WALK_ERR); + } + + for (i = 0; i < hash.num_buckets; i++) { + wsp->walk_addr = (uintptr_t)hash.buckets + + (i * sizeof (smb_bucket_t)) + sll_off + ll_off; + if (mdb_layered_walk("list", wsp) == -1) { + mdb_warn("failed to walk 'list'"); + return (WALK_ERR); + } + } + + return (WALK_NEXT); +} + +static int +smb_hash_walk_step(mdb_walk_state_t *wsp) +{ + return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer, + wsp->walk_cbdata)); +} + +static int +smbhashstat_cb(uintptr_t addr, const void *data, void *varg) +{ + _NOTE(ARGUNUSED(varg)) + const smb_bucket_t *bucket = data; + + mdb_printf("%-?p ", addr); /* smb_bucket_t */ + mdb_printf("%-6u ", bucket->b_list.ll_count); + mdb_printf("%-16u", bucket->b_max_seen); + mdb_printf("%-u\n", (bucket->b_list.ll_wrop + + bucket->b_list.ll_count) / 2); + return (WALK_NEXT); +} + +static int +smbhashstat_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + _NOTE(ARGUNUSED(argc, argv)) + if (!(flags & DCMD_ADDRSPEC)) { + mdb_printf("require address of an smb_hash_t\n"); + return (DCMD_USAGE); + } + + if (DCMD_HDRSPEC(flags)) { + mdb_printf( + "%<b>%<u>" + "%-?s " + "%-6s " + "%-16s" + "%-s" + "%</u>%</b>\n", + "smb_bucket_t", "count", "largest seen", "inserts"); + } + + if (mdb_pwalk("smb_hashstat_walker", smbhashstat_cb, + NULL, addr) == -1) { + mdb_warn("failed to walk 'smb_ofile'"); + return (DCMD_ERR); + } + return (DCMD_OK); +} + +typedef struct smb_hash_wd { + smb_bucket_t *bucket; + smb_bucket_t *end; +} smb_hash_wd_t; + +static int +smb_hashstat_walk_init(mdb_walk_state_t *wsp) +{ + int sll_off, ll_off; + smb_hash_t hash; + smb_bucket_t *buckets; + uintptr_t addr = wsp->walk_addr; + uint32_t arr_sz; + smb_hash_wd_t *wd; + + if (addr == NULL) { + mdb_printf("require address of an smb_hash_t\n"); + return (WALK_ERR); + } + + GET_OFFSET(sll_off, smb_bucket_t, b_list); + GET_OFFSET(ll_off, smb_llist_t, ll_list); + + if (mdb_vread(&hash, sizeof (hash), addr) == -1) { + mdb_warn("failed to read smb_hash_t at %p", addr); + return (WALK_ERR); + } + + arr_sz = hash.num_buckets * sizeof (smb_bucket_t); + buckets = mdb_alloc(arr_sz, UM_SLEEP | UM_GC); + if (mdb_vread(buckets, arr_sz, (uintptr_t)hash.buckets) == -1) { + mdb_warn("failed to read smb_bucket_t array at %p", + hash.buckets); + return (WALK_ERR); + } + + wd = mdb_alloc(sizeof (*wd), UM_SLEEP | UM_GC); + wd->bucket = buckets; + wd->end = buckets + hash.num_buckets; + + wsp->walk_addr = (uintptr_t)hash.buckets; + wsp->walk_data = wd; + + return (WALK_NEXT); +} + +static int +smb_hashstat_walk_step(mdb_walk_state_t *wsp) +{ + int rc; + smb_hash_wd_t *wd = wsp->walk_data; + + if (wd->bucket >= wd->end) + return (WALK_DONE); + + rc = wsp->walk_callback(wsp->walk_addr, wd->bucket++, + wsp->walk_cbdata); + + wsp->walk_addr += sizeof (smb_bucket_t); + return (rc); +} + +/* + * 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", - "OFILE", "FID", "SMB NODE", "CRED"); + "LEASE", "SMB NODE", "KEY"); - mdb_printf("%?p %-5u %-p %p\n", addr, - of->f_fid, of->f_node, of->f_cr); + 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); } @@ -2097,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]; @@ -2115,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]; @@ -2153,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, @@ -2209,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); */ @@ -2226,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) { @@ -2251,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( @@ -2281,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); @@ -2518,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); } @@ -2575,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); @@ -3170,6 +3715,13 @@ smb_obj_expand(uintptr_t addr, uint_t opts, const smb_exp_t *x, ulong_t indent) while (x->ex_dcmd) { if (x->ex_mask & opts) { ex_off = (x->ex_offset)(); + if (ex_off < 0) { + mdb_warn("failed to get the list offset for %s", + x->ex_name); + rc = ex_off; + break; + } + rc = mdb_pwalk_dcmd("list", x->ex_dcmd, argc, argv, addr + ex_off); @@ -3339,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 }, @@ -3363,6 +3923,15 @@ static const mdb_dcmd_t dcmds[] = { { "smb_mbuf_dump", ":[max_len]", "print mbuf_t data", smb_mbuf_dump_dcmd }, + { "smbdurable", + "[-v]", + "list ofiles on sv->sv_persistid_ht", + smbdurable_dcmd }, + { "smbhashstat", + "[-v]", + "list stats from an smb_hash_t structure", + smbhashstat_dcmd }, + { NULL } }; @@ -3397,6 +3966,19 @@ static const mdb_walker_t walkers[] = { smb_mbuf_walk_step, NULL, NULL }, + { "smb_hash_walker", + "walk an smb_hash_t structure", + smb_hash_walk_init, + smb_hash_walk_step, + NULL, + NULL }, + { "smb_hashstat_walker", + "walk the buckets from an smb_hash_t structure", + smb_hashstat_walk_init, + smb_hashstat_walk_step, + NULL, + NULL }, + { NULL } }; diff --git a/usr/src/cmd/ptools/Makefile.bld b/usr/src/cmd/ptools/Makefile.bld index a48afc3125..8607a5b3f8 100644 --- a/usr/src/cmd/ptools/Makefile.bld +++ b/usr/src/cmd/ptools/Makefile.bld @@ -169,7 +169,7 @@ install: all $(ROOTISAPROG) $(ROOTISALN) $(ROOTBINLN) -$(LN) $(ISAEXEC) $(ROOTBINPROG) -$(INSTALL_$(PTOOL_TYPE)) -$(ROOTBINLN): +$(ROOTBINLN): $(ROOTISAPROG) -$(RM) $@ -$(LN) $(ISAEXEC) $@ 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/fksmbd/Makefile b/usr/src/cmd/smbsrv/fksmbd/Makefile index 9a28a3c8eb..61b8758011 100644 --- a/usr/src/cmd/smbsrv/fksmbd/Makefile +++ b/usr/src/cmd/smbsrv/fksmbd/Makefile @@ -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 2015 Nexenta Systems, Inc. All rights reserved. # @@ -76,11 +76,11 @@ CPPFLAGS += -Dsyslog=smb_syslog CPPFLAGS += -D_LARGEFILE64_SOURCE=1 CPPFLAGS += -DFKSMBD # Always debug here -CPPFLAGS += -DDEBUG +CPPFLAGS += -DDEBUG CPPFLAGS += $(INCS) LDFLAGS += $(ZNOLAZYLOAD) -LDFLAGS += -R/usr/lib/smbsrv +LDFLAGS += '-R$$ORIGIN' '-R$$ORIGIN/..' LDLIBS += -L$(ROOT)/usr/lib/smbsrv LDLIBS += -lfksmbsrv -lfakekernel # prefer to keep libs ordered by dependence diff --git a/usr/src/cmd/smbsrv/fksmbd/Run.sh b/usr/src/cmd/smbsrv/fksmbd/Run.sh index 0e42825dab..8d94211d99 100755 --- a/usr/src/cmd/smbsrv/fksmbd/Run.sh +++ b/usr/src/cmd/smbsrv/fksmbd/Run.sh @@ -12,7 +12,7 @@ # # -# Copyright 2014 Nexenta Systems, Inc. All rights reserved. +# Copyright 2015 Nexenta Systems, Inc. All rights reserved. # # Helper program to run fksmbd (user-space smbd for debugging) @@ -55,6 +55,10 @@ export SMB_SHARE_DNAME="/tmp/fksmbshare_door" LD_LIBRARY_PATH=$ROOT/usr/lib/smbsrv:$ROOT/usr/lib:$ROOT/lib export LD_LIBRARY_PATH +# Enable everything, for debugging +export SMB_MAX_PROTOCOL=3 +export SMB_SIGNING=require + # normally runs with cwd=/ but this is more careful cd /var/smb diff --git a/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c b/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c index 4e4b17fcf1..2bebe761f4 100644 --- a/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c +++ b/usr/src/cmd/smbsrv/fksmbd/fksmbd_kmod.c @@ -69,6 +69,24 @@ fksmbd_adjust_config(smb_ioc_header_t *ioc_hdr) smbd_report("maxconnections=%d, maxworkers=%d", ioc->maxconnections, ioc->maxworkers); + if ((s = getenv("SMB_MAX_PROTOCOL")) != NULL) { + switch (s[0]) { + case '1': + ioc->max_protocol = SMB_VERS_1; + break; + case '2': + ioc->max_protocol = SMB_VERS_2_1; + break; + case '3': + ioc->max_protocol = SMB_VERS_3_0; + break; + default: + smbd_report("env SMB_MAX_PROTOCOL invalid"); + break; + } + } + smbd_report("max_protocol=0x%x", ioc->max_protocol); + if ((s = getenv("SMB_SIGNING")) != NULL) { ioc->signing_enable = 0; ioc->signing_required = 0; diff --git a/usr/src/cmd/smbsrv/smbd/server.xml b/usr/src/cmd/smbsrv/smbd/server.xml index 875d6d3bc0..7eea0b877b 100644 --- a/usr/src/cmd/smbsrv/smbd/server.xml +++ b/usr/src/cmd/smbsrv/smbd/server.xml @@ -184,7 +184,7 @@ file. <propval name='max_connections' type='integer' value='100000' override='true'/> <propval name='keep_alive' type='integer' - value='5400' override='true'/> + value='0' override='true'/> <propval name='restrict_anonymous' type='boolean' value='false' override='true'/> <propval name='signing_enabled' type='boolean' 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/common/smbsrv/smb_xdr.c b/usr/src/common/smbsrv/smb_xdr.c index 0905e48528..d2653a728b 100644 --- a/usr/src/common/smbsrv/smb_xdr.c +++ b/usr/src/common/smbsrv/smb_xdr.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright 2014 Nexenta Systems, Inc. All rights reserved. + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ #include <sys/sunddi.h> @@ -238,8 +238,6 @@ smb_netuserinfo_xdr(XDR *xdrs, smb_netuserinfo_t *objp) { if (!xdr_uint64_t(xdrs, &objp->ui_session_id)) return (FALSE); - if (!xdr_uint16_t(xdrs, &objp->ui_smb_uid)) - return (FALSE); if (!xdr_uint16_t(xdrs, &objp->ui_domain_len)) return (FALSE); if (!xdr_string(xdrs, &objp->ui_domain, ~0)) diff --git a/usr/src/lib/libdtrace/common/smb.d b/usr/src/lib/libdtrace/common/smb.d index 718f7dfa01..c58cb4bf1c 100644 --- a/usr/src/lib/libdtrace/common/smb.d +++ b/usr/src/lib/libdtrace/common/smb.d @@ -145,7 +145,7 @@ translator smb2opinfo_t < struct smb_request *P > { soi_sid = P->session->s_kid; soi_mid = P->smb2_messageid; soi_asyncid = P->smb2_async_id; - soi_uid = P->smb_uid; + soi_uid = P->smb2_ssnid; soi_tid = P->smb_tid; soi_status = P->smb2_status; soi_flags = P->smb2_hdr_flags; diff --git a/usr/src/lib/libfakekernel/common/kmisc.c b/usr/src/lib/libfakekernel/common/kmisc.c index 5feaa66a28..15730d6539 100644 --- a/usr/src/lib/libfakekernel/common/kmisc.c +++ b/usr/src/lib/libfakekernel/common/kmisc.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2014 Nexenta Systems, Inc. All rights reserved. + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 RackTop Systems. */ @@ -19,13 +19,13 @@ #include <sys/thread.h> #include <sys/proc.h> #include <sys/zone.h> - #include <sys/poll.h> #include <time.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> +#include <string.h> #include <fakekernel.h> @@ -118,6 +118,12 @@ delay(clock_t ticks) (void) poll(0, 0, msec); } +int +highbit(ulong_t i) +{ + return (fls(i)); +} + /* ARGSUSED */ int issig(int why) diff --git a/usr/src/lib/libfakekernel/common/mapfile-vers b/usr/src/lib/libfakekernel/common/mapfile-vers index 51067103f7..3950ccd4b5 100644 --- a/usr/src/lib/libfakekernel/common/mapfile-vers +++ b/usr/src/lib/libfakekernel/common/mapfile-vers @@ -10,8 +10,7 @@ # # -# Copyright 2015 Nexenta Systems, Inc. All rights reserved. -# Copyright (c) 2017, Joyent, Inc. +# Copyright 2016 Nexenta Systems, Inc. All rights reserved. # Copyright 2017 RackTop Systems. # Copyright 2019 Joyent, Inc. # @@ -53,6 +52,7 @@ SYMBOL_VERSION SUNWprivate_1.1 { cyclic_reprogram; crfree; + crgetsid; crgetuid; crgetruid; crgetgid; @@ -97,10 +97,11 @@ SYMBOL_VERSION SUNWprivate_1.1 { gethrestime_sec; gethrtime_unscaled; - hz; - + highbit; highbit64; + hz; + issig; kcred; 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/libfakekernel/common/sys/cred.h b/usr/src/lib/libfakekernel/common/sys/cred.h index a338214843..7374dd3df2 100644 --- a/usr/src/lib/libfakekernel/common/sys/cred.h +++ b/usr/src/lib/libfakekernel/common/sys/cred.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2013 Nexenta Systems, Inc. All rights reserved. + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 RackTop Systems. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. @@ -54,6 +54,7 @@ typedef struct cred cred_t; cred_t *_curcred(void); #define CRED() (_curcred()) /* current cred_t pointer */ +struct ksid; extern int ngroups_max; @@ -79,6 +80,7 @@ extern gid_t crgetsgid(const cred_t *); extern zoneid_t crgetzoneid(const cred_t *); extern struct zone *crgetzone(const cred_t *); extern projid_t crgetprojid(const cred_t *); +extern struct ksid *crgetsid(const cred_t *, int); extern const gid_t *crgetgroups(const cred_t *); extern int crgetngroups(const cred_t *); 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 6809bde3c3..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 \ @@ -123,6 +125,7 @@ OBJS_FS_SMBSRV = \ \ smb2_aapl.o \ smb2_dispatch.o \ + smb2_durable.o \ smb2_cancel.o \ smb2_change_notify.o \ smb2_close.o \ @@ -130,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/fake_vop.c b/usr/src/lib/smbsrv/libfksmbsrv/common/fake_vop.c index ca29c8c32c..3d6ec6e52e 100644 --- a/usr/src/lib/smbsrv/libfksmbsrv/common/fake_vop.c +++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fake_vop.c @@ -1128,7 +1128,7 @@ fop_pathconf( break; case _PC_ACL_ENABLED: - val = 0; + val = _ACL_ACE_ENABLED; break; case _PC_CASE_BEHAVIOR: diff --git a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_cred.c b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_cred.c index 0ddd6f51bc..7b2bb93581 100644 --- a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_cred.c +++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_cred.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 2016 Nexenta Systems, Inc. All rights reserved. */ #include <sys/types.h> @@ -38,11 +38,18 @@ * we don't bother with real credential. Everything here uses * the ordinary credentials of the process running this. */ + +/* + * This library does not implement real credentials. All contexts + * use an opaque cred_t object, and all activity happens in the + * context of the user who runs the program. + */ cred_t * smb_cred_create(smb_token_t *token) { + _NOTE(ARGUNUSED(token)) cred_t *cr; - cr = (cred_t *)token; /* hack */ + cr = CRED(); return (cr); } 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/fksmb_sign_pkcs.c b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c index ebafd8cd5a..07e1529434 100644 --- a/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c +++ b/usr/src/lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* @@ -28,17 +28,40 @@ #include <security/pkcs11.h> /* + * Common function to see if a mech is available. + */ +static int +find_mech(smb_sign_mech_t *mech, ulong_t mid) +{ + CK_SESSION_HANDLE hdl; + CK_RV rv; + + rv = SUNW_C_GetMechSession(mid, &hdl); + if (rv != CKR_OK) { + cmn_err(CE_NOTE, "PKCS#11: no mech 0x%x", + (unsigned int)mid); + return (-1); + } + (void) C_CloseSession(hdl); + + mech->mechanism = mid; + mech->pParameter = NULL; + mech->ulParameterLen = 0; + return (0); +} + +/* * SMB1 signing helpers: * (getmech, init, update, final) */ +/* + * Find out if we have this mech. + */ int smb_md5_getmech(smb_sign_mech_t *mech) { - mech->mechanism = CKM_MD5; - mech->pParameter = NULL; - mech->ulParameterLen = 0; - return (0); + return (find_mech(mech, CKM_MD5)); } /* @@ -93,13 +116,13 @@ smb_md5_final(smb_sign_ctx_t ctx, uint8_t *digest16) * (getmech, init, update, final) */ +/* + * Find out if we have this mech. + */ int smb2_hmac_getmech(smb_sign_mech_t *mech) { - mech->mechanism = CKM_SHA256_HMAC; - mech->pParameter = NULL; - mech->ulParameterLen = 0; - return (0); + return (find_mech(mech, CKM_SHA256_HMAC)); } /* @@ -161,3 +184,73 @@ smb2_hmac_final(smb_sign_ctx_t ctx, uint8_t *digest16) return (rv == CKR_OK ? 0 : -1); } + +/* + * SMB3 signing helpers: + * (getmech, init, update, final) + */ + +/* + * Find out if we have this mech. + */ +int +smb3_cmac_getmech(smb_sign_mech_t *mech) +{ + return (find_mech(mech, CKM_AES_CMAC)); +} + +/* + * Start PKCS#11 session, load the key. + */ +int +smb3_cmac_init(smb_sign_ctx_t *ctxp, smb_sign_mech_t *mech, + uint8_t *key, size_t key_len) +{ + CK_OBJECT_HANDLE hkey = 0; + CK_RV rv; + + rv = SUNW_C_GetMechSession(mech->mechanism, ctxp); + if (rv != CKR_OK) + return (-1); + + rv = SUNW_C_KeyToObject(*ctxp, mech->mechanism, + key, key_len, &hkey); + if (rv != CKR_OK) + return (-1); + + rv = C_SignInit(*ctxp, mech, hkey); + (void) C_DestroyObject(*ctxp, hkey); + + return (rv == CKR_OK ? 0 : -1); +} + +/* + * Digest one segment + */ +int +smb3_cmac_update(smb_sign_ctx_t ctx, uint8_t *in, size_t len) +{ + CK_RV rv; + + rv = C_SignUpdate(ctx, in, len); + if (rv != CKR_OK) + (void) C_CloseSession(ctx); + + return (rv == CKR_OK ? 0 : -1); +} + +/* + * Note, the SMB2 signature is just the AES CMAC digest. + * (both are 16 bytes long) + */ +int +smb3_cmac_final(smb_sign_ctx_t ctx, uint8_t *digest) +{ + CK_ULONG len = SMB2_SIG_SIZE; + CK_RV rv; + + rv = C_SignFinal(ctx, digest, &len); + (void) C_CloseSession(ctx); + + return (rv == CKR_OK ? 0 : -1); +} 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/libmlsvc.h b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h index 0506704d71..1992857bdc 100644 --- a/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h +++ b/usr/src/lib/smbsrv/libmlsvc/common/libmlsvc.h @@ -50,6 +50,16 @@ #include <smbsrv/smb_dfs.h> #include <smbsrv/libsmb.h> +/* + * XXX: Some temporary left-overs from the old ntstatus.h + * Should eliminate uses of these macros when convenient. + */ +/* This used to OR in the severity bits. */ +#define NT_SC_ERROR(S) (S) +/* This used to mask off the severity bits. */ +#define NT_SC_VALUE(S) (S) +/* XXX end of temporary left-overs. */ + #ifdef __cplusplus extern "C" { #endif 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 b7073a0f65..5fbc3fca3c 100644 --- a/usr/src/man/man4/smb.4 +++ b/usr/src/man/man4/smb.4 @@ -117,7 +117,7 @@ are \fBtrue\fR and \fBfalse\fR. The default value is \fBfalse\fR. .RS 4n Specifies the number of seconds before an idle SMB connection is dropped by the Solaris CIFS server. If set to 0, idle connections are not dropped. Valid -values are 0 and from 20 seconds and above. The default value is 5400 seconds. +values are 0 and from 20 seconds and above. The default value is 0. .RE .sp @@ -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 f69c7e42cd..5c69fd02d1 100644 --- a/usr/src/uts/common/Makefile.files +++ b/usr/src/uts/common/Makefile.files @@ -1152,6 +1152,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 \ @@ -1212,6 +1213,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 \ @@ -1227,6 +1229,7 @@ SMBSRV_OBJS += $(SMBSRV_SHARED_OBJS) \ \ smb2_aapl.o \ smb2_dispatch.o \ + smb2_durable.o \ smb2_cancel.o \ smb2_change_notify.o \ smb2_close.o \ @@ -1234,6 +1237,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_aapl.c b/usr/src/uts/common/fs/smbsrv/smb2_aapl.c index 0989fdaf6e..1c2e3bfdf9 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_aapl.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_aapl.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* @@ -40,6 +40,8 @@ uint64_t smb2_aapl_server_caps = * modes only when we have a trivial ACL. */ +uint64_t smb2_aapl_volume_caps = kAAPL_SUPPORTS_FULL_SYNC; + /* * Normally suppress file IDs for MacOS because it * requires them to be unique per share, and ours @@ -130,7 +132,7 @@ smb2_aapl_srv_query(smb_request_t *sr, (void) smb_mbc_encodef(mbcout, "q", server_caps); } if ((server_bitmap & kAAPL_VOLUME_CAPS) != 0) { - (void) smb_mbc_encodef(mbcout, "q", 0); + (void) smb_mbc_encodef(mbcout, "q", smb2_aapl_volume_caps); } /* Pad2, null model string. */ diff --git a/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c b/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c index 135d3b4d4e..911e3544f6 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c @@ -21,7 +21,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. - * Copyright 2017 Nexenta Systems, Inc. All rights reserved. + * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* @@ -33,8 +33,6 @@ /* For the output DataOffset fields in here. */ #define DATA_OFF (SMB2_HDR_SIZE + 8) -static smb_sdrc_t smb2_change_notify_async(smb_request_t *); - smb_sdrc_t smb2_change_notify(smb_request_t *sr) { @@ -68,6 +66,16 @@ smb2_change_notify(smb_request_t *sr) if (status != 0) goto errout; /* Bad FID */ + /* + * Only deal with change notify last in a compound, + * because it blocks indefinitely. This status gets + * "sticky" handling in smb2sr_work(). + */ + if (sr->smb2_next_command != 0) { + status = NT_STATUS_INSUFFICIENT_RESOURCES; + goto errout; + } + CompletionFilter &= FILE_NOTIFY_VALID_MASK; if (iFlags & SMB2_WATCH_TREE) CompletionFilter |= FILE_NOTIFY_CHANGE_EV_SUBDIR; @@ -79,18 +87,23 @@ smb2_change_notify(smb_request_t *sr) * Check for events and consume, non-blocking. * Special return STATUS_PENDING means: * No events; caller must call "act2" next. - * SMB2 does that in the "async" handler. + * SMB2 does that in "async mode". */ status = smb_notify_act1(sr, oBufLength, CompletionFilter); if (status == NT_STATUS_PENDING) { - status = smb2sr_go_async(sr, smb2_change_notify_async); + status = smb2sr_go_async(sr); + if (status != 0) + goto errout; + status = smb_notify_act2(sr); + if (status == NT_STATUS_PENDING) { + /* See next: smb2_change_notify_finish */ + return (SDRC_SR_KEPT); + } } errout: sr->smb2_status = status; - if (status != NT_STATUS_PENDING) { - DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr); - } + DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr); if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) { oBufLength = sr->raw_data.chain_offset; @@ -108,30 +121,6 @@ errout: } /* - * This is called when the dispatch loop has made it to the end of a - * compound request, and we had a notify that will require blocking. - */ -static smb_sdrc_t -smb2_change_notify_async(smb_request_t *sr) -{ - uint32_t status; - - status = smb_notify_act2(sr); - if (status == NT_STATUS_PENDING) { - /* See next: smb2_change_notify_finish */ - return (SDRC_SR_KEPT); - } - - /* Note: Never NT_STATUS_NOTIFY_ENUM_DIR here. */ - ASSERT(status != NT_STATUS_NOTIFY_ENUM_DIR); - - if (status != 0) - smb2sr_put_error(sr, status); - - return (SDRC_SUCCESS); -} - -/* * This is called via taskq_dispatch in smb_notify.c * to finish up an NT transact notify change request. * Build an SMB2 Change Notify reply and send it. @@ -139,7 +128,8 @@ smb2_change_notify_async(smb_request_t *sr) void smb2_change_notify_finish(void *arg) { - smb_request_t *sr = arg; + smb_request_t *sr = arg; + smb_disp_stats_t *sds; uint32_t status; uint32_t oBufLength; @@ -150,6 +140,10 @@ smb2_change_notify_finish(void *arg) */ status = smb_notify_act3(sr); + /* + * The prior thread returned SDRC_SR_KEPT and skiped + * the dtrace DONE probe, so fire that here. + */ sr->smb2_status = status; DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr); @@ -165,5 +159,24 @@ smb2_change_notify_finish(void *arg) smb2sr_put_error(sr, status); } - smb2sr_finish_async(sr); + /* + * Record some statistics: (just tx bytes here) + */ + sds = &sr->session->s_server->sv_disp_stats2[SMB2_CHANGE_NOTIFY]; + atomic_add_64(&sds->sdt_txb, (int64_t)(sr->reply.chain_offset)); + + /* + * Put (overwrite) the final SMB2 header, + * sign, send. + */ + (void) smb2_encode_header(sr, B_TRUE); + if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) + smb2_sign_reply(sr); + smb2_send_reply(sr); + + mutex_enter(&sr->sr_mutex); + sr->sr_state = SMB_REQ_STATE_COMPLETED; + mutex_exit(&sr->sr_mutex); + + smb_request_free(sr); } diff --git a/usr/src/uts/common/fs/smbsrv/smb2_create.c b/usr/src/uts/common/fs/smbsrv/smb2_create.c index fb8a7f8823..6aab3c5127 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_create.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_create.c @@ -21,6 +21,25 @@ #include <smbsrv/smb2_kproto.h> #include <smbsrv/smb_fsops.h> +#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. @@ -35,6 +54,8 @@ #define CCTX_QUERY_ON_DISK_ID 0x80 #define CCTX_REQUEST_LEASE 0x100 #define CCTX_AAPL_EXT 0x200 +#define CCTX_DH_REQUEST_V2 0x400 +#define CCTX_DH_RECONNECT_V2 0x800 typedef struct smb2_create_ctx_elem { uint32_t cce_len; @@ -54,10 +75,15 @@ typedef struct smb2_create_ctx { smb2_create_ctx_elem_t cc_in_time_warp; smb2_create_ctx_elem_t cc_in_req_lease; smb2_create_ctx_elem_t cc_in_aapl; + smb2_create_ctx_elem_t cc_in_dh_request_v2; + smb2_create_ctx_elem_t cc_in_dh_reconnect_v2; /* Elements we my place in the response */ 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; static uint32_t smb2_decode_create_ctx( @@ -68,6 +94,8 @@ static int smb2_encode_create_ctx_elem( mbuf_chain_t *, smb2_create_ctx_elem_t *, uint32_t); static void smb2_free_create_ctx(smb2_create_ctx_t *); +int smb2_enable_dh = 1; + smb_sdrc_t smb2_create(smb_request_t *sr) { @@ -78,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; @@ -86,8 +113,9 @@ smb2_create(smb_request_t *sr) uint16_t NameLength; uint32_t CreateCtxOffset; uint32_t CreateCtxLength; - smb2fid_t smb2fid; + smb2fid_t smb2fid = { 0, 0 }; uint32_t status; + int dh_flags; int skip; int rc = 0; @@ -99,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; } @@ -213,29 +240,145 @@ smb2_create(smb_request_t *sr) */ /* + * Only disk trees get durable handles. + */ + if (smb2_enable_dh == 0 || + (sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) { + cctx.cc_in_flags &= + ~(CCTX_DH_REQUEST | CCTX_DH_REQUEST_V2 | + CCTX_DH_RECONNECT | CCTX_DH_RECONNECT_V2); + } + + /* + * DH v2 is only valid in SMB3.0 and later. + * If seen in earlier dialects, ignore. + */ + if (sr->session->dialect < SMB_VERS_3_0) { + cctx.cc_in_flags &= + ~(CCTX_DH_REQUEST_V2|CCTX_DH_RECONNECT_V2); + } + + /* + * It is an error to specify more than one Durable Handle + * operation in a single create, except when only the v1 + * REQUEST and RECONNECT operations are specified. In that + * case, the v1 REQUEST is ignored. + */ + dh_flags = cctx.cc_in_flags & + (CCTX_DH_REQUEST | CCTX_DH_REQUEST_V2 | + CCTX_DH_RECONNECT | CCTX_DH_RECONNECT_V2); + if ((dh_flags & (dh_flags - 1)) != 0 && + dh_flags != (CCTX_DH_REQUEST|CCTX_DH_RECONNECT)) { + status = NT_STATUS_INVALID_PARAMETER; + goto cmd_done; + } + + /* + * Reconnect is special in MANY ways, including the + * somewhat surprising (specified) behavior that + * most other creat parameters are ignored, and + * many create context types are ignored too. + */ + op->dh_vers = SMB2_NOT_DURABLE; + op->dh_v2_flags = 0; + if ((cctx.cc_in_flags & + (CCTX_DH_RECONNECT|CCTX_DH_RECONNECT_V2)) != 0) { + + if ((cctx.cc_in_flags & CCTX_DH_RECONNECT_V2) != 0) + op->dh_vers = SMB2_DURABLE_V2; + else + op->dh_vers = SMB2_DURABLE_V1; + + /* Ignore these create contexts. */ + cctx.cc_in_flags &= + ~(CCTX_DH_REQUEST | + CCTX_DH_REQUEST_V2 | + CCTX_EA_BUFFER | + CCTX_SD_BUFFER | + CCTX_ALLOCATION_SIZE | + 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, + * but need (reclaimed) oplock state in *op. + */ + of = sr->fid_ofile; + smb2_oplock_reconnect(sr); + goto reconnect_done; + } + + /* + * Real create (of a new handle, not reconnect) + */ + + /* * 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. @@ -245,6 +388,14 @@ smb2_create(smb_request_t *sr) cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE; } + if ((cctx.cc_in_flags & + (CCTX_DH_REQUEST|CCTX_DH_REQUEST_V2)) != 0) { + if ((cctx.cc_in_flags & CCTX_DH_REQUEST_V2) != 0) + op->dh_vers = SMB2_DURABLE_V2; + else + op->dh_vers = SMB2_DURABLE_V1; + } + if (cctx.cc_in_flags & CCTX_EA_BUFFER) { status = NT_STATUS_EAS_NOT_SUPPORTED; goto cmd_done; @@ -285,6 +436,85 @@ smb2_create(smb_request_t *sr) of = sr->fid_ofile; /* + * Set the "persistent" part of the file ID + * (only for DISK shares). Need this even for + * non-durable handles in case we get the ioctl + * to set "resiliency" on this handle. + */ + 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); + } + + /* + * Make this a durable open, but only if: + * (durable handle requested and...) + * + * 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, 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 == 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" + */ + if (op->dh_vers == SMB2_DURABLE_V2) { + (void) memcpy(of->dh_create_guid, + op->create_guid, UUID_LEN); + + /* no persistent handles yet */ + of->dh_persist = B_FALSE; + } + if (op->dh_vers != SMB2_NOT_DURABLE) { + uint32_t msto; + + of->dh_vers = op->dh_vers; + of->dh_expire_time = 0; + + /* + * Client may provide timeout=0 to request + * the default timeout (in mSec.) + */ + msto = op->dh_timeout; + if (msto == 0) + msto = smb2_dh_def_timeout; + if (msto > smb2_dh_max_timeout) + msto = smb2_dh_max_timeout; + op->dh_timeout = msto; + of->dh_timeout_offset = MSEC2NSEC(msto); + } + } else { + op->dh_vers = SMB2_NOT_DURABLE; + } + + /* * NB: after the above smb_common_open() success, * we have a handle allocated (sr->fid_ofile). * If we don't return success, we must close it. @@ -293,14 +523,15 @@ smb2_create(smb_request_t *sr) * though it could later be something larger, * (16 bytes) similar to an NFSv4 open handle. */ - smb2fid.persistent = 0; +reconnect_done: + smb2fid.persistent = of->f_persistid; smb2fid.temporal = sr->smb_fid; switch (sr->tid_tree->t_res_type & STYPE_MASK) { 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; } @@ -344,6 +575,25 @@ smb2_create(smb_request_t *sr) } /* + * 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; + } + if ((cctx.cc_in_flags & CCTX_DH_REQUEST_V2) != 0 && + of->dh_vers == SMB2_DURABLE_V2) { + cctx.cc_out_flags |= CCTX_DH_REQUEST_V2; + } + + /* * This marks the end of the "body" section and the * beginning of the "encode" section. Any errors * encoding the response should use: goto errout @@ -366,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; @@ -393,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 */ @@ -554,6 +784,18 @@ smb2_decode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc) cc->cc_in_flags |= CCTX_AAPL_EXT; cce = &cc->cc_in_aapl; break; + case SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2: /* ("DH2Q") */ + cc->cc_in_flags |= CCTX_DH_REQUEST_V2; + cce = &cc->cc_in_dh_request_v2; + break; + case SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2: /* ("DH2C") */ + cc->cc_in_flags |= CCTX_DH_RECONNECT_V2; + cce = &cc->cc_in_dh_reconnect_v2; + break; + case 0x9ccbcf9e: /* SVHDX_OPEN_DEVICE_CONTEXT */ + /* 9ccbcf9e 04c1e643 980e158d a1f6ec83 */ + /* silently ignore */ + break; default: /* * Unknown create context values are normal, and @@ -613,6 +855,81 @@ 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 */ + &op->dh_fileid.temporal, /* q */ + UUID_LEN, /* # */ + op->create_guid, /* c */ + &op->dh_v2_flags); /* l */ + if (rc != 0) + goto errout; + break; + + case SMB2_CREATE_DURABLE_HANDLE_RECONNECT: /* ("DHnC") */ + rc = smb_mbc_decodef(&cce->cce_mbc, "qq", + &op->dh_fileid.persistent, /* q */ + &op->dh_fileid.temporal); /* q */ + if (rc != 0) + goto errout; + bzero(op->create_guid, UUID_LEN); + op->dh_v2_flags = 0; + break; + + case SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2: /* ("DH2Q") */ + rc = smb_mbc_decodef(&cce->cce_mbc, + "ll8.#c", + &op->dh_timeout, /* l */ + &op->dh_v2_flags, /* l */ + /* reserved */ /* 8. */ + UUID_LEN, /* # */ + op->create_guid); /* c */ + if (rc != 0) + goto errout; + break; + + case SMB2_CREATE_DURABLE_HANDLE_REQUEST: /* ("DHnQ") */ + rc = smb_mbc_decodef(&cce->cce_mbc, + "16."); /* reserved */ + if (rc != 0) + goto errout; + op->dh_timeout = 0; /* default */ + op->dh_v2_flags = 0; + break; } next_cc: @@ -708,6 +1025,65 @@ 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; + + cce->cce_mbc.max_bytes = cce->cce_len = 8; + (void) smb_mbc_encodef(&cce->cce_mbc, "q", 0LL); + + last_top = mbc->chain_offset; + rc = smb2_encode_create_ctx_elem(mbc, cce, + SMB2_CREATE_DURABLE_HANDLE_REQUEST); + 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_V2) { + cce = &cc->cc_out_dh_request_v2; + + cce->cce_mbc.max_bytes = cce->cce_len = 8; + (void) smb_mbc_encodef(&cce->cce_mbc, "ll", + op->dh_timeout, op->dh_v2_flags); + + last_top = mbc->chain_offset; + rc = smb2_encode_create_ctx_elem(mbc, cce, + SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2); + if (rc) + return (NT_STATUS_INTERNAL_ERROR); + (void) smb_mbc_poke(mbc, last_top, "l", + mbc->chain_offset - last_top); + } + if (last_top >= 0) (void) smb_mbc_poke(mbc, last_top, "l", 0); @@ -736,6 +1112,7 @@ smb2_encode_create_ctx_elem(mbuf_chain_t *out_mbc, * a: this header (16 bytes) * b: the name (4 bytes, 4 pad) * c: the payload (variable) + * d: padding (to align 8) * * Note that "Next elem." is filled in later. */ @@ -759,6 +1136,8 @@ smb2_encode_create_ctx_elem(mbuf_chain_t *out_mbc, cce->cce_len, /* # */ &cce->cce_mbc); /* C */ + (void) smb_mbc_put_align(out_mbc, 8); + return (rc); } @@ -779,4 +1158,16 @@ 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); + } + if (cc->cc_out_flags & CCTX_DH_REQUEST_V2) { + cce = &cc->cc_out_dh_request_v2; + 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 39161f7c30..7c0fcea968 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_dispatch.c @@ -18,55 +18,11 @@ #include <smbsrv/smb_kstat.h> #include <smbsrv/smb2.h> -/* - * Saved state for a command that "goes async". When a compound request - * contains a command that may block indefinitely, the compound reply is - * composed with an "interim response" for that command, and information - * needed to actually dispatch that command is saved on a list of "async" - * commands for this compound request. After the compound reply is sent, - * the list of async commands is processed, and those may block as long - * as they need to without affecting the initial compound request. - * - * Now interestingly, this "async" mechanism is not used with the full - * range of asynchrony that one might imagine. The design of async - * request processing can be drastically simplified if we can assume - * that there's no need to run more than one async command at a time. - * With that simplifying assumption, we can continue using the current - * "one worker thread per request message" model, which has very simple - * locking rules etc. The same worker thread that handles the initial - * compound request can handle the list of async requests. - * - * As it turns out, SMB2 clients do not try to use more than one "async" - * command in a compound. If they were to do so, the [MS-SMB2] spec. - * allows us to decline additional async requests with an error. - * - * smb_async_req_t is the struct used to save an "async" request on - * the list of requests that had an interim reply in the initial - * compound reply. This includes everything needed to restart - * processing at the async command. - */ +#define SMB2_ASYNCID(sr) (sr->smb2_messageid ^ (1ULL << 62)) -typedef struct smb2_async_req { - - smb_sdrc_t (*ar_func)(smb_request_t *); - - int ar_cmd_hdr; /* smb2_cmd_hdr offset */ - int ar_cmd_len; /* length from hdr */ - - /* - * SMB2 header fields. - */ - uint16_t ar_cmd_code; - uint16_t ar_uid; - uint16_t ar_tid; - uint32_t ar_pid; - uint32_t ar_hdr_flags; - uint64_t ar_messageid; -} smb2_async_req_t; - -void smb2sr_do_async(smb_request_t *); 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] = { @@ -288,6 +244,135 @@ smb2_tq_work(void *arg) } /* + * SMB2 credits determine how many simultaneous commands the + * client may issue, and bounds the range of message IDs those + * commands may use. With multi-credit support, commands may + * use ranges of message IDs, where the credits used by each + * command are proportional to their data transfer size. + * + * Every command may request an increase or decrease of + * the currently granted credits, based on the difference + * between the credit request and the credit charge. + * [MS-SMB2] 3.3.1.2 Algorithm for the Granting of Credits + * + * Most commands have credit_request=1, credit_charge=1, + * which keeps the credit grant unchanged. + * + * All we're really doing here (for now) is reducing the + * credit_response if the client requests a credit increase + * that would take their credit over the maximum, and + * limiting the decrease so they don't run out of credits. + * + * Later, this could do something dynamic based on load. + * + * One other non-obvious bit about credits: We keep the + * session s_max_credits low until the 1st authentication, + * at which point we'll set the normal maximum_credits. + * Some clients ask for more credits with session setup, + * and we need to handle that requested increase _after_ + * the command-specific handler returns so it won't be + * restricted to the lower (pre-auth) limit. + */ +static inline void +smb2_credit_decrease(smb_request_t *sr) +{ + smb_session_t *session = sr->session; + uint16_t cur, d; + + mutex_enter(&session->s_credits_mutex); + cur = session->s_cur_credits; + + /* Handle credit decrease. */ + d = sr->smb2_credit_charge - sr->smb2_credit_request; + cur -= d; + if (cur & 0x8000) { + /* + * underflow (bad credit charge or request) + * leave credits unchanged (response=charge) + */ + cur = session->s_cur_credits; + sr->smb2_credit_response = sr->smb2_credit_charge; + DTRACE_PROBE1(smb2__credit__neg, smb_request_t *, sr); + } + + /* + * The server MUST ensure that the number of credits + * held by the client is never reduced to zero. + * [MS-SMB2] 3.3.1.2 + */ + if (cur == 0) { + cur = 1; + sr->smb2_credit_response += 1; + DTRACE_PROBE1(smb2__credit__min, smb_request_t *, sr); + } + + DTRACE_PROBE3(smb2__credit__decrease, + smb_request_t *, sr, int, (int)cur, + int, (int)session->s_cur_credits); + + session->s_cur_credits = cur; + mutex_exit(&session->s_credits_mutex); +} + +/* + * Second half of SMB2 credit handling (increases) + */ +static inline void +smb2_credit_increase(smb_request_t *sr) +{ + smb_session_t *session = sr->session; + uint16_t cur, d; + + mutex_enter(&session->s_credits_mutex); + cur = session->s_cur_credits; + + /* Handle credit increase. */ + d = sr->smb2_credit_request - sr->smb2_credit_charge; + cur += d; + + /* + * If new credits would be above max, + * reduce the credit grant. + */ + if (cur > session->s_max_credits) { + d = cur - session->s_max_credits; + cur = session->s_max_credits; + sr->smb2_credit_response -= d; + DTRACE_PROBE1(smb2__credit__max, smb_request_t, sr); + } + + DTRACE_PROBE3(smb2__credit__increase, + smb_request_t *, sr, int, (int)cur, + int, (int)session->s_cur_credits); + + session->s_cur_credits = cur; + mutex_exit(&session->s_credits_mutex); +} + +/* + * Record some statistics: latency, rx bytes, tx bytes + * per: server, session & kshare. + */ +static inline void +smb2_record_stats(smb_request_t *sr, smb_disp_stats_t *sds, boolean_t tx_only) +{ + hrtime_t dt; + int64_t rxb; + int64_t txb; + + dt = gethrtime() - sr->sr_time_start; + rxb = (int64_t)(sr->command.chain_offset - sr->smb2_cmd_hdr); + txb = (int64_t)(sr->reply.chain_offset - sr->smb2_reply_hdr); + + if (!tx_only) { + smb_server_inc_req(sr->sr_server); + smb_latency_add_sample(&sds->sdt_lat, dt); + atomic_add_64(&sds->sdt_rxb, rxb); + } + atomic_add_64(&sds->sdt_txb, txb); +} + +/* * smb2sr_work * * This function processes each SMB command in the current request @@ -325,6 +410,7 @@ smb2sr_work(struct smb_request *sr) session = sr->session; + ASSERT(sr->smb2_async == B_FALSE); ASSERT(sr->tid_tree == 0); ASSERT(sr->uid_user == 0); ASSERT(sr->fid_ofile == 0); @@ -376,6 +462,15 @@ cmd_start: goto cleanup; } related = (sr->smb2_hdr_flags & SMB2_FLAGS_RELATED_OPERATIONS); + sr->smb2_hdr_flags |= SMB2_FLAGS_SERVER_TO_REDIR; + if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) { + /* Probably an async cancel. */ + DTRACE_PROBE1(smb2__dispatch__async, smb_request_t *, sr); + } else if (sr->smb2_async) { + /* Previous command in compound went async. */ + sr->smb2_hdr_flags |= SMB2_FLAGS_ASYNC_COMMAND; + sr->smb2_async_id = SMB2_ASYNCID(sr); + } /* * In case we bail out with an error before we get to the @@ -388,11 +483,17 @@ cmd_start: sr->smb2_credit_response = sr->smb2_credit_charge; /* - * Reserve space for the reply header, and save the offset. - * The reply header will be overwritten later. If we have - * already exhausted the output space, then this client is - * trying something funny. Log it and kill 'em. + * Write a tentative reply header. + * + * We could just leave this blank, but if we're using the + * mdb module feature that extracts packets, it's useful + * to have the header mostly correct here. + * + * If we have already exhausted the output space, then the + * client is trying something funny. Log it and kill 'em. */ + sr->smb2_next_reply = 0; + ASSERT((sr->reply.chain_offset & 7) == 0); sr->smb2_reply_hdr = sr->reply.chain_offset; if ((rc = smb2_encode_header(sr, B_FALSE)) != 0) { cmn_err(CE_WARN, "clnt %s excessive reply", @@ -469,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; } @@ -503,15 +603,15 @@ cmd_start: NT_STATUS_INVALID_PARAMETER); goto cmd_done; } - sr->smb_uid = sr->uid_user->u_uid; + sr->smb2_ssnid = sr->uid_user->u_ssnid; } else { /* * Lookup the UID * [MS-SMB2] 3.3.5.2 Verifying the Session */ ASSERT(sr->uid_user == NULL); - sr->uid_user = smb_session_lookup_uid(session, - sr->smb_uid); + sr->uid_user = smb_session_lookup_ssnid(session, + sr->smb2_ssnid); if (sr->uid_user == NULL) { smb2sr_put_error(sr, NT_STATUS_USER_SESSION_DELETED); @@ -587,7 +687,7 @@ cmd_start: } rc = smb2_sign_check_request(sr); if (rc != 0) { - DTRACE_PROBE1(smb2__sign__check, smb_request_t, sr); + DTRACE_PROBE1(smb2__sign__check, smb_request_t *, sr); smb2sr_put_error(sr, NT_STATUS_ACCESS_DENIED); goto cmd_done; } @@ -603,72 +703,16 @@ cmd_start: sr->smb_data.chain_offset = sr->smb2_cmd_hdr + SMB2_HDR_SIZE; /* - * SMB2 credits determine how many simultaneous commands the - * client may issue, and bounds the range of message IDs those - * commands may use. With multi-credit support, commands may - * use ranges of message IDs, where the credits used by each - * command are proportional to their data transfer size. - * - * Every command may request an increase or decrease of - * the currently granted credits, based on the difference - * between the credit request and the credit charge. - * [MS-SMB2] 3.3.1.2 Algorithm for the Granting of Credits - * - * Most commands have credit_request=1, credit_charge=1, - * which keeps the credit grant unchanged. + * Credit adjustments (decrease) * - * All we're really doing here (for now) is reducing the - * credit_response if the client requests a credit increase - * that would take their credit over the maximum, and - * limiting the decrease so they don't run out of credits. - * - * Later, this could do something dynamic based on load. - * - * One other non-obvious bit about credits: We keep the - * session s_max_credits low until the 1st authentication, - * at which point we'll set the normal maximum_credits. - * Some clients ask for more credits with session setup, - * and we need to handle that requested increase _after_ - * the command-specific handler returns so it won't be - * restricted to the lower (pre-auth) limit. + * If we've gone async, credit adjustments were done + * when we sent the interim reply. */ - sr->smb2_credit_response = sr->smb2_credit_request; - if (sr->smb2_credit_request < sr->smb2_credit_charge) { - uint16_t cur, d; - - mutex_enter(&session->s_credits_mutex); - cur = session->s_cur_credits; - - /* Handle credit decrease. */ - d = sr->smb2_credit_charge - sr->smb2_credit_request; - cur -= d; - if (cur & 0x8000) { - /* - * underflow (bad credit charge or request) - * leave credits unchanged (response=charge) - */ - cur = session->s_cur_credits; - sr->smb2_credit_response = sr->smb2_credit_charge; - DTRACE_PROBE1(smb2__credit__neg, smb_request_t, sr); + if (!sr->smb2_async) { + sr->smb2_credit_response = sr->smb2_credit_request; + if (sr->smb2_credit_request < sr->smb2_credit_charge) { + smb2_credit_decrease(sr); } - - /* - * The server MUST ensure that the number of credits - * held by the client is never reduced to zero. - * [MS-SMB2] 3.3.1.2 - */ - if (cur == 0) { - cur = 1; - sr->smb2_credit_response += 1; - DTRACE_PROBE1(smb2__credit__min, smb_request_t, sr); - } - - DTRACE_PROBE3(smb2__credit__decrease, - smb_request_t, sr, int, (int)cur, - int, (int)session->s_cur_credits); - - session->s_cur_credits = cur; - mutex_exit(&session->s_credits_mutex); } /* @@ -685,61 +729,26 @@ cmd_start: smb2sr_put_error(sr, sr->smb2_status); } - MBC_FLUSH(&sr->raw_data); - /* - * Second half of SMB2 credit handling (increases) + * When the sdt_function returns SDRC_SR_KEPT, it means + * this SR may have been passed to another thread so we + * MUST NOT touch it anymore. */ - if (sr->smb2_credit_request > sr->smb2_credit_charge) { - uint16_t cur, d; - - mutex_enter(&session->s_credits_mutex); - cur = session->s_cur_credits; - - /* Handle credit increase. */ - d = sr->smb2_credit_request - sr->smb2_credit_charge; - cur += d; - - /* - * If new credits would be above max, - * reduce the credit grant. - */ - if (cur > session->s_max_credits) { - d = cur - session->s_max_credits; - cur = session->s_max_credits; - sr->smb2_credit_response -= d; - DTRACE_PROBE1(smb2__credit__max, smb_request_t, sr); - } - - DTRACE_PROBE3(smb2__credit__increase, - smb_request_t, sr, int, (int)cur, - int, (int)session->s_cur_credits); + if (rc == SDRC_SR_KEPT) + return; - session->s_cur_credits = cur; - mutex_exit(&session->s_credits_mutex); - } + MBC_FLUSH(&sr->raw_data); -cmd_done: /* - * Pad the reply to align(8) if necessary. + * Credit adjustments (increase) */ - if (sr->reply.chain_offset & 7) { - int padsz = 8 - (sr->reply.chain_offset & 7); - (void) smb_mbc_encodef(&sr->reply, "#.", padsz); + if (!sr->smb2_async) { + if (sr->smb2_credit_request > sr->smb2_credit_charge) { + smb2_credit_increase(sr); + } } - ASSERT((sr->reply.chain_offset & 7) == 0); - - /* - * Record some statistics: latency, rx bytes, tx bytes. - */ - smb_server_inc_req(sr->sr_server); - smb_latency_add_sample(&sds->sdt_lat, - gethrtime() - sr->sr_time_start); - atomic_add_64(&sds->sdt_rxb, - (int64_t)(sr->command.chain_offset - sr->smb2_cmd_hdr)); - atomic_add_64(&sds->sdt_txb, - (int64_t)(sr->reply.chain_offset - sr->smb2_reply_hdr)); +cmd_done: switch (rc) { case SDRC_SUCCESS: break; @@ -777,11 +786,27 @@ cmd_done: } /* + * Pad the reply to align(8) if there will be another. + * (We don't compound async replies.) + */ + if (!sr->smb2_async && sr->smb2_next_command != 0) + (void) smb_mbc_put_align(&sr->reply, 8); + + /* + * Record some statistics. Uses: + * rxb = command.chain_offset - smb2_cmd_hdr; + * txb = reply.chain_offset - smb2_reply_hdr; + * which at this point represent the current cmd/reply. + * + * Note: If async, this does txb only, and + * skips the smb_latency_add_sample() calls. + */ + smb2_record_stats(sr, sds, sr->smb2_async); + + /* * If there's a next command, figure out where it starts, - * and fill in the next command offset for the reply. - * Note: We sanity checked smb2_next_command above - * (the offset to the next command). Similarly set - * smb2_next_reply as the offset to the next reply. + * and fill in the next header offset for the reply. + * Note: We sanity checked smb2_next_command above. */ if (sr->smb2_next_command != 0) { sr->command.chain_offset = @@ -789,52 +814,36 @@ cmd_done: sr->smb2_next_reply = sr->reply.chain_offset - sr->smb2_reply_hdr; } else { - sr->smb2_next_reply = 0; + ASSERT(sr->smb2_next_reply == 0); } /* - * Overwrite the SMB2 header for the response of - * this command (possibly part of a compound). - * encode_header adds: SMB2_FLAGS_SERVER_TO_REDIR + * Overwrite the (now final) SMB2 header for this response. */ (void) smb2_encode_header(sr, B_TRUE); if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) smb2_sign_reply(sr); - if (sr->smb2_next_command != 0) - goto cmd_start; - /* - * We've done all the commands in this compound. - * Send it out. + * Non-async runs the whole compound before send. + * When we've gone async, send each individually. */ + if (!sr->smb2_async && sr->smb2_next_command != 0) + goto cmd_start; smb2_send_reply(sr); - - /* - * If any of the requests "went async", process those now. - * The async. function "keeps" this sr, changing its state - * to completed and calling smb_request_free(). - */ - if (sr->sr_async_req != NULL) { - smb2sr_do_async(sr); - return; + if (sr->smb2_async && sr->smb2_next_command != 0) { + MBC_FLUSH(&sr->reply); /* New reply buffer. */ + ASSERT(sr->reply.max_bytes == sr->session->reply_max_bytes); + goto cmd_start; } cleanup: - if (disconnect) { - smb_rwx_rwenter(&session->s_lock, RW_WRITER); - switch (session->s_state) { - case SMB_SESSION_STATE_DISCONNECTED: - case SMB_SESSION_STATE_TERMINATED: - break; - default: - smb_soshutdown(session->sock); - session->s_state = SMB_SESSION_STATE_DISCONNECTED; - break; - } - smb_rwx_rwexit(&session->s_lock); - } + 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; @@ -844,226 +853,296 @@ cleanup: } /* - * Dispatch an async request using saved information. - * See smb2sr_save_async and [MS-SMB2] 3.3.4.2 + * Build interim responses for the current and all following + * requests in this compound, then send the compound response, + * leaving the SR state so that smb2sr_work() can continue its + * processing of this compound in "async mode". + * + * If we agree to "go async", this should return STATUS_SUCCESS. + * Otherwise return STATUS_INSUFFICIENT_RESOURCES for this and + * all requests following this request. (See the comments re. + * "sticky" smb2_status values in smb2sr_work). * - * This is sort of a "lite" version of smb2sr_work. Initialize the - * command and reply areas as they were when the command-speicific - * handler started (in case it needs to decode anything again). - * Call the async function, which builds the command-specific part - * of the response. Finally, send the response and free the sr. + * Note: the Async ID we assign here is arbitrary, and need only + * be unique among pending async responses on this connection, so + * this just uses a modified messageID, which is already unique. + * + * Credits: All credit changes should happen via the interim + * responses, so we have to manage credits here. After this + * returns to smb2sr_work, the final replies for all these + * commands will have smb2_credit_response = smb2_credit_charge + * (meaning no further changes to the clients' credits). */ -void -smb2sr_do_async(smb_request_t *sr) +uint32_t +smb2sr_go_async(smb_request_t *sr) { - const smb_disp_entry_t *sdd; - smb2_async_req_t *ar; - smb_sdrc_t (*ar_func)(smb_request_t *); - int sdrc; + smb_session_t *session; + smb_disp_stats_t *sds; + uint16_t cmd_idx; + int32_t saved_com_offset; + uint32_t saved_cmd_hdr; + uint16_t saved_cred_resp; + uint32_t saved_hdr_flags; + uint32_t saved_reply_hdr; + uint32_t msg_len; + boolean_t disconnect = B_FALSE; + + if (sr->smb2_async) { + /* already went async in some previous cmd. */ + return (NT_STATUS_SUCCESS); + } + sr->smb2_async = B_TRUE; + + /* The "server" session always runs async. */ + session = sr->session; + if (session->sock == NULL) + return (NT_STATUS_SUCCESS); + + sds = NULL; + saved_com_offset = sr->command.chain_offset; + saved_cmd_hdr = sr->smb2_cmd_hdr; + saved_cred_resp = sr->smb2_credit_response; + saved_hdr_flags = sr->smb2_hdr_flags; + saved_reply_hdr = sr->smb2_reply_hdr; /* - * Restore what smb2_decode_header found. - * (In lieu of decoding it again.) + * The command-specific handler should not yet have put any + * data in the reply except for the (place holder) header. */ - ar = sr->sr_async_req; - sr->smb2_cmd_hdr = ar->ar_cmd_hdr; - sr->smb2_cmd_code = ar->ar_cmd_code; - sr->smb2_hdr_flags = ar->ar_hdr_flags; - sr->smb2_async_id = (uintptr_t)ar; - sr->smb2_messageid = ar->ar_messageid; - sr->smb_pid = ar->ar_pid; - sr->smb_tid = ar->ar_tid; - sr->smb_uid = ar->ar_uid; - sr->smb2_status = 0; + if (sr->reply.chain_offset != sr->smb2_reply_hdr + SMB2_HDR_SIZE) { + ASSERT3U(sr->reply.chain_offset, ==, + sr->smb2_reply_hdr + SMB2_HDR_SIZE); + return (NT_STATUS_INTERNAL_ERROR); + } /* - * Async requests don't grant credits, because any credits - * should have gone out with the interim reply. - * An async reply goes alone (no next reply). + * Rewind to the start of the current header in both the + * command and reply bufers, so the loop below can just + * decode/encode just in every pass. This means the + * current command header is decoded again, but that + * avoids having to special-case the first loop pass. */ - sr->smb2_credit_response = 0; - sr->smb2_next_reply = 0; + sr->command.chain_offset = sr->smb2_cmd_hdr; + sr->reply.chain_offset = sr->smb2_reply_hdr; /* - * Setup input mbuf_chain + * This command processing loop is a simplified version of + * smb2sr_work() that just puts an "interim response" for + * every command in the compound (NT_STATUS_PENDING). */ - ASSERT(ar->ar_cmd_len >= SMB2_HDR_SIZE); - (void) MBC_SHADOW_CHAIN(&sr->smb_data, &sr->command, - sr->smb2_cmd_hdr + SMB2_HDR_SIZE, - ar->ar_cmd_len - SMB2_HDR_SIZE); +cmd_start: + sr->smb2_status = NT_STATUS_PENDING; /* - * Done with sr_async_req + * Decode the request header */ - ar_func = ar->ar_func; - kmem_free(ar, sizeof (*ar)); - sr->sr_async_req = ar = NULL; + sr->smb2_cmd_hdr = sr->command.chain_offset; + if ((smb2_decode_header(sr)) != 0) { + cmn_err(CE_WARN, "clnt %s bad SMB2 header", + session->ip_addr_str); + disconnect = B_TRUE; + goto cleanup; + } + sr->smb2_hdr_flags |= (SMB2_FLAGS_SERVER_TO_REDIR | + SMB2_FLAGS_ASYNC_COMMAND); + sr->smb2_async_id = SMB2_ASYNCID(sr); /* - * Setup output mbuf_chain + * In case we bail out... */ - MBC_FLUSH(&sr->reply); - sr->smb2_reply_hdr = sr->reply.chain_offset; - (void) smb2_encode_header(sr, B_FALSE); - - VERIFY3U(sr->smb2_cmd_code, <, SMB2_INVALID_CMD); - sdd = &smb2_disp_table[sr->smb2_cmd_code]; + if (sr->smb2_credit_charge == 0) + sr->smb2_credit_charge = 1; + sr->smb2_credit_response = sr->smb2_credit_charge; /* - * Keep the UID, TID, ofile we have. + * Write a tentative reply header. */ - if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0 && - sr->uid_user == NULL) { - smb2sr_put_error(sr, NT_STATUS_USER_SESSION_DELETED); - goto cmd_done; - } - if ((sdd->sdt_flags & SDDF_SUPPRESS_TID) == 0 && - sr->tid_tree == NULL) { - smb2sr_put_error(sr, NT_STATUS_NETWORK_NAME_DELETED); - goto cmd_done; + sr->smb2_next_reply = 0; + ASSERT((sr->reply.chain_offset & 7) == 0); + sr->smb2_reply_hdr = sr->reply.chain_offset; + if ((smb2_encode_header(sr, B_FALSE)) != 0) { + cmn_err(CE_WARN, "clnt %s excessive reply", + session->ip_addr_str); + disconnect = B_TRUE; + goto cleanup; } /* - * Signature already verified - * Credits handled... - * - * Just call the async handler function. + * Figure out the length of data... */ - sdrc = ar_func(sr); - switch (sdrc) { - case SDRC_SUCCESS: - break; - case SDRC_ERROR: - if (sr->smb2_status == 0) - sr->smb2_status = NT_STATUS_INTERNAL_ERROR; - break; - case SDRC_SR_KEPT: - /* This SR will be completed later. */ - return; + if (sr->smb2_next_command != 0) { + /* [MS-SMB2] says this is 8-byte aligned */ + msg_len = sr->smb2_next_command; + if ((msg_len & 7) != 0 || (msg_len < SMB2_HDR_SIZE) || + ((sr->smb2_cmd_hdr + msg_len) > sr->command.max_bytes)) { + cmn_err(CE_WARN, "clnt %s bad SMB2 next cmd", + session->ip_addr_str); + disconnect = B_TRUE; + goto cleanup; + } + } else { + msg_len = sr->command.max_bytes - sr->smb2_cmd_hdr; } -cmd_done: - smb2sr_finish_async(sr); -} + /* + * We just skip any data, so no shadow chain etc. + */ + sr->command.chain_offset = sr->smb2_cmd_hdr + msg_len; + ASSERT(sr->command.chain_offset <= sr->command.max_bytes); -void -smb2sr_finish_async(smb_request_t *sr) -{ - smb_disp_stats_t *sds; + /* + * Validate the commmand code... + */ + if (sr->smb2_cmd_code < SMB2_INVALID_CMD) + cmd_idx = sr->smb2_cmd_code; + else + cmd_idx = SMB2_INVALID_CMD; + sds = &session->s_server->sv_disp_stats2[cmd_idx]; /* - * Pad the reply to align(8) if necessary. + * Don't change (user, tree, file) because we want them + * exactly as they were when we entered. That also means + * we may not have the right user in sr->uid_user for + * signature checks, so leave that until smb2sr_work + * runs these commands "for real". Therefore, here + * we behave as if: (sr->uid_user == NULL) */ - if (sr->reply.chain_offset & 7) { - int padsz = 8 - (sr->reply.chain_offset & 7); - (void) smb_mbc_encodef(&sr->reply, "#.", padsz); - } - ASSERT((sr->reply.chain_offset & 7) == 0); + sr->smb2_hdr_flags &= ~SMB2_FLAGS_SIGNED; /* - * Record some statistics: (just tx bytes here) + * Credit adjustments (decrease) + * + * NOTE: interim responses are not signed. + * Any attacker can modify the credit grant + * in the response. Because of this property, + * it is no worse to assume the credit charge and grant + * are sane without verifying the signature, + * and that saves us a whole lot of work. + * If the credits WERE modified, we'll find out + * when we verify the signature later, + * which nullifies any changes caused here. + * + * Skip this on the first command, because the + * credit decrease was done by the caller. */ - sds = &sr->session->s_server->sv_disp_stats2[sr->smb2_cmd_code]; - atomic_add_64(&sds->sdt_txb, (int64_t)(sr->reply.chain_offset)); + if (sr->smb2_cmd_hdr != saved_cmd_hdr) { + sr->smb2_credit_response = sr->smb2_credit_request; + if (sr->smb2_credit_request < sr->smb2_credit_charge) { + smb2_credit_decrease(sr); + } + } /* - * Put (overwrite) the final SMB2 header. - * The call adds: SMB2_FLAGS_SERVER_TO_REDIR + * The real work: ... (would be here) */ - (void) smb2_encode_header(sr, B_TRUE); + smb2sr_put_error(sr, sr->smb2_status); - if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED) - smb2_sign_reply(sr); + /* + * Credit adjustments (increase) + */ + if (sr->smb2_credit_request > sr->smb2_credit_charge) { + smb2_credit_increase(sr); + } - smb2_send_reply(sr); + /* cmd_done: label */ /* - * Done. Unlink and free. + * Pad the reply to align(8) if there will be another. + * This (interim) reply uses compounding. */ + if (sr->smb2_next_command != 0) + (void) smb_mbc_put_align(&sr->reply, 8); - mutex_enter(&sr->sr_mutex); - sr->sr_state = SMB_REQ_STATE_COMPLETED; - mutex_exit(&sr->sr_mutex); + /* + * Record some statistics. Uses: + * rxb = command.chain_offset - smb2_cmd_hdr; + * txb = reply.chain_offset - smb2_reply_hdr; + * which at this point represent the current cmd/reply. + * + * Note: We're doing smb_latency_add_sample() for all + * remaining commands NOW, which means we won't include + * the async part of their work in latency statistics. + * That's intentional, as the async part of a command + * would otherwise skew our latency statistics. + */ + smb2_record_stats(sr, sds, B_FALSE); - smb_request_free(sr); -} + /* + * If there's a next command, figure out where it starts, + * and fill in the next header offset for the reply. + * Note: We sanity checked smb2_next_command above. + */ + if (sr->smb2_next_command != 0) { + sr->command.chain_offset = + sr->smb2_cmd_hdr + sr->smb2_next_command; + sr->smb2_next_reply = + sr->reply.chain_offset - sr->smb2_reply_hdr; + } else { + ASSERT(sr->smb2_next_reply == 0); + } -/* - * In preparation for sending an "interim response", save - * all the state we'll need to run an async command later, - * and assign an "async id" for this (now async) command. - * See [MS-SMB2] 3.3.4.2 - * - * If more than one request in a compound request tries to - * "go async", we can "say no". See [MS-SMB2] 3.3.4.2 - * If an operation would require asynchronous processing - * but resources are constrained, the server MAY choose to - * fail that operation with STATUS_INSUFFICIENT_RESOURCES. - * - * For simplicity, we further restrict the cases where we're - * willing to "go async", and only allow the last command in a - * compound to "go async". It happens that this is the only - * case where we're actually asked to go async anyway. This - * simplification also means there can be at most one command - * in a compound that "goes async" (the last one). - * - * If we agree to "go async", this should return STATUS_PENDING. - * Otherwise return STATUS_INSUFFICIENT_RESOURCES for this and - * all requests following this request. (See the comments re. - * "sticky" smb2_status values in smb2sr_work). - * - * Note: the Async ID we assign here is arbitrary, and need only - * be unique among pending async responses on this connection, so - * this just uses an object address as the Async ID. - * - * Also, the assigned worker is the ONLY thread using this - * async request object (sr_async_req) so no locking. - */ -uint32_t -smb2sr_go_async(smb_request_t *sr, - smb_sdrc_t (*async_func)(smb_request_t *)) -{ - smb2_async_req_t *ar; + /* + * Overwrite the (now final) SMB2 header for this response. + */ + (void) smb2_encode_header(sr, B_TRUE); + /* + * Process whole compound before sending. + */ if (sr->smb2_next_command != 0) - return (NT_STATUS_INSUFFICIENT_RESOURCES); + goto cmd_start; + smb2_send_reply(sr); - ASSERT(sr->sr_async_req == NULL); - ar = kmem_zalloc(sizeof (*ar), KM_SLEEP); + ASSERT(!disconnect); +cleanup: /* - * Place an interim response in the compound reply. - * - * Turn on the "async" flag for both the (synchronous) - * interim response and the (later) async response, - * by storing that in flags before coping into ar. + * Restore caller's command processing state. */ - sr->smb2_hdr_flags |= SMB2_FLAGS_ASYNC_COMMAND; - sr->smb2_async_id = (uintptr_t)ar; - - ar->ar_func = async_func; - ar->ar_cmd_hdr = sr->smb2_cmd_hdr; - ar->ar_cmd_len = sr->smb_data.max_bytes - sr->smb2_cmd_hdr; + sr->smb2_cmd_hdr = saved_cmd_hdr; + sr->command.chain_offset = saved_cmd_hdr; + (void) smb2_decode_header(sr); + sr->command.chain_offset = saved_com_offset; - ar->ar_cmd_code = sr->smb2_cmd_code; - ar->ar_hdr_flags = sr->smb2_hdr_flags; - ar->ar_messageid = sr->smb2_messageid; - ar->ar_pid = sr->smb_pid; - ar->ar_tid = sr->smb_tid; - ar->ar_uid = sr->smb_uid; + sr->smb2_credit_response = saved_cred_resp; + sr->smb2_hdr_flags = saved_hdr_flags; + sr->smb2_status = NT_STATUS_SUCCESS; - sr->sr_async_req = ar; + /* + * In here, the "disconnect" flag just means we had an + * error decoding or encoding something. Rather than + * actually disconnect here, let's assume whatever + * problem we encountered will be seen by the caller + * as they continue processing the compound, and just + * restore everything and return an error. + */ + if (disconnect) { + sr->smb2_async = B_FALSE; + sr->smb2_reply_hdr = saved_reply_hdr; + sr->reply.chain_offset = sr->smb2_reply_hdr; + (void) smb2_encode_header(sr, B_FALSE); + return (NT_STATUS_INVALID_PARAMETER); + } - /* Interim responses are NOT signed. */ - sr->smb2_hdr_flags &= ~SMB2_FLAGS_SIGNED; + /* + * The compound reply buffer we sent is now gone. + * Setup a new reply buffer for the caller. + */ + sr->smb2_hdr_flags |= SMB2_FLAGS_ASYNC_COMMAND; + sr->smb2_async_id = SMB2_ASYNCID(sr); + sr->smb2_next_reply = 0; + MBC_FLUSH(&sr->reply); + ASSERT(sr->reply.max_bytes == sr->session->reply_max_bytes); + ASSERT(sr->reply.chain_offset == 0); + sr->smb2_reply_hdr = 0; + (void) smb2_encode_header(sr, B_FALSE); - return (NT_STATUS_PENDING); + return (NT_STATUS_SUCCESS); } int smb2_decode_header(smb_request_t *sr) { - uint64_t ssnid; uint32_t pid, tid; uint16_t hdr_len; int rc; @@ -1081,7 +1160,7 @@ smb2_decode_header(smb_request_t *sr) &sr->smb2_messageid, /* q */ &pid, /* l */ &tid, /* l */ - &ssnid, /* q */ + &sr->smb2_ssnid, /* q */ sr->smb2_sig); /* 16c */ if (rc) return (rc); @@ -1089,12 +1168,13 @@ smb2_decode_header(smb_request_t *sr) if (hdr_len != SMB2_HDR_SIZE) return (-1); - sr->smb_uid = (uint16_t)ssnid; /* XXX wide UIDs */ - if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) { sr->smb2_async_id = pid | ((uint64_t)tid) << 32; + sr->smb_pid = 0; + sr->smb_tid = 0; } else { + sr->smb2_async_id = 0; sr->smb_pid = pid; sr->smb_tid = (uint16_t)tid; /* XXX wide TIDs */ } @@ -1105,9 +1185,7 @@ smb2_decode_header(smb_request_t *sr) int smb2_encode_header(smb_request_t *sr, boolean_t overwrite) { - uint64_t ssnid = sr->smb_uid; uint64_t pid_tid_aid; /* pid+tid, or async id */ - uint32_t reply_hdr_flags; int rc; if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) { @@ -1116,7 +1194,6 @@ smb2_encode_header(smb_request_t *sr, boolean_t overwrite) pid_tid_aid = sr->smb_pid | ((uint64_t)sr->smb_tid) << 32; } - reply_hdr_flags = sr->smb2_hdr_flags | SMB2_FLAGS_SERVER_TO_REDIR; if (overwrite) { rc = smb_mbc_poke(&sr->reply, @@ -1127,11 +1204,11 @@ smb2_encode_header(smb_request_t *sr, boolean_t overwrite) sr->smb2_status, /* l */ sr->smb2_cmd_code, /* w */ sr->smb2_credit_response, /* w */ - reply_hdr_flags, /* l */ + sr->smb2_hdr_flags, /* l */ sr->smb2_next_reply, /* l */ sr->smb2_messageid, /* q */ pid_tid_aid, /* q */ - ssnid, /* q */ + sr->smb2_ssnid, /* q */ sr->smb2_sig); /* 16c */ } else { rc = smb_mbc_encodef(&sr->reply, @@ -1141,11 +1218,11 @@ smb2_encode_header(smb_request_t *sr, boolean_t overwrite) sr->smb2_status, /* l */ sr->smb2_cmd_code, /* w */ sr->smb2_credit_response, /* w */ - reply_hdr_flags, /* l */ + sr->smb2_hdr_flags, /* l */ sr->smb2_next_reply, /* l */ sr->smb2_messageid, /* q */ pid_tid_aid, /* q */ - ssnid, /* q */ + sr->smb2_ssnid, /* q */ sr->smb2_sig); /* 16c */ } @@ -1265,7 +1342,8 @@ smb2sr_lookup_fid(smb_request_t *sr, smb2fid_t *fid) sr->smb_fid = (uint16_t)fid->temporal; sr->fid_ofile = smb_ofile_lookup_by_fid(sr, sr->smb_fid); } - if (sr->fid_ofile == NULL) + if (sr->fid_ofile == NULL || + sr->fid_ofile->f_persistid != fid->persistent) return (NT_STATUS_FILE_CLOSED); return (0); @@ -1341,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 new file mode 100644 index 0000000000..981b09a28e --- /dev/null +++ b/usr/src/uts/common/fs/smbsrv/smb2_durable.c @@ -0,0 +1,478 @@ +/* + * 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. + */ + +/* + * SMB2 Durable Handle support + */ + +#include <sys/types.h> +#include <sys/cmn_err.h> +#include <sys/fcntl.h> +#include <sys/nbmlock.h> +#include <smbsrv/string.h> +#include <smbsrv/smb_kproto.h> +#include <smbsrv/smb_fsops.h> +#include <smbsrv/smbinfo.h> +#include <smbsrv/smb2_kproto.h> + +/* Windows default values from [MS-SMB2] */ +/* + * (times in seconds) + * resilient: + * MaxTimeout = 300 (win7+) + * if timeout > MaxTimeout, ERROR + * if timeout != 0, timeout = req.timeout + * if timeout == 0, timeout = (infinity) (Win7/w2k8r2) + * if timeout == 0, timeout = 120 (Win8+) + * v2: + * if timeout != 0, timeout = MIN(timeout, 300) (spec) + * if timeout != 0, timeout = timeout (win8/2k12) + * if timeout == 0, timeout = Share.CATimeout. \ + * if Share.CATimeout == 0, timeout = 60 (win8/w2k12) + * if timeout == 0, timeout = 180 (win8.1/w2k12r2) + * open.timeout = 60 (win8/w2k12r2) (i.e. we ignore the request) + * v1: + * open.timeout = 16 minutes + */ + +uint32_t smb2_dh_def_timeout = 60 * MILLISEC; /* mSec. */ +uint32_t smb2_dh_max_timeout = 300 * MILLISEC; /* mSec. */ + +uint32_t smb2_res_def_timeout = 120 * MILLISEC; /* mSec. */ +uint32_t smb2_res_max_timeout = 300 * MILLISEC; /* mSec. */ + +/* + * smb_dh_should_save + * + * During session tear-down, decide whether to keep a durable handle. + * + * There are two cases where we save durable handles: + * 1. An SMB2 LOGOFF request was received + * 2. An unexpected disconnect from the client + * Note: Specifying a PrevSessionID in session setup + * is considered a disconnect (we just haven't learned about it yet) + * In every other case, we close durable handles. + * + * [MS-SMB2] 3.3.5.6 SMB2_LOGOFF + * [MS-SMB2] 3.3.7.1 Handling Loss of a Connection + * + * If any of the following are true, preserve for reconnect: + * + * - Open.IsResilient is TRUE. + * + * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_BATCH and + * Open.OplockState == Held, and Open.IsDurable is TRUE. + * + * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_LEASE, + * Lease.LeaseState SMB2_LEASE_HANDLE_CACHING, + * Open.OplockState == Held, and Open.IsDurable is TRUE. + * + * - Open.IsPersistent is TRUE. + */ +boolean_t +smb_dh_should_save(smb_ofile_t *of) +{ + ASSERT(MUTEX_HELD(&of->f_mutex)); + ASSERT(of->dh_vers != SMB2_NOT_DURABLE); + + if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_NONE) + return (B_FALSE); + + if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_ALL) + return (B_TRUE); + + switch (of->dh_vers) { + case SMB2_RESILIENT: + 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); +} + +/* + * Requirements for ofile found during reconnect (MS-SMB2 3.3.5.9.7): + * - security descriptor must match provided descriptor + * + * If file is leased: + * - lease must be requested + * - client guid must match session guid + * - file name must match given name + * - lease key must match provided lease key + * If file is not leased: + * - Lease must not be requested + * + * dh_v2 only: + * - SMB2_DHANDLE_FLAG_PERSISTENT must be set if dh_persist is true + * - SMB2_DHANDLE_FLAG_PERSISTENT must not be set if dh_persist is false + * - desired access, share access, and create_options must be ignored + * - createguid must match + */ +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 = + ((op->dh_v2_flags & SMB2_DHANDLE_FLAG_PERSISTENT) != 0); + if (of->dh_persist != op_persist) + return (NT_STATUS_OBJECT_NAME_NOT_FOUND); + if (memcmp(op->create_guid, of->dh_create_guid, UUID_LEN)) + return (NT_STATUS_OBJECT_NAME_NOT_FOUND); + } + + if (!smb_is_same_user(sr->user_cr, of->f_cr)) + return (NT_STATUS_ACCESS_DENIED); + + return (NT_STATUS_SUCCESS); +} + +/* + * [MS-SMB2] 3.3.5.9.7 and 3.3.5.9.12 (durable reconnect v1/v2) + * + * Looks up an ofile on the server's sv_dh_list by the persistid. + * If found, it validates the request. + * (see smb2_dh_reconnect_checks() for details) + * If the checks are passed, add it onto the new tree's list. + * + * Note that the oplock break code path can get to an ofile via the node + * ofile list. It starts with a ref taken in smb_ofile_hold_olbrk, which + * waits if the ofile is found in state RECONNECT. That wait happens with + * the node ofile list lock held as reader, and the oplock mutex held. + * Implications of that are: While we're in state RECONNECT, we shoud NOT + * block (at least, not for long) and must not try to enter any of the + * node ofile list lock or oplock mutex. Thankfully, we don't need to + * enter those while reclaiming an orphaned ofile. + */ +uint32_t +smb2_dh_reconnect(smb_request_t *sr) +{ + smb_arg_open_t *op = &sr->sr_open; + smb_tree_t *tree = sr->tid_tree; + smb_ofile_t *of; + cred_t *old_cr; + uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + uint16_t fid = 0; + + if (smb_idpool_alloc(&tree->t_fid_pool, &fid)) + return (NT_STATUS_TOO_MANY_OPENED_FILES); + + /* Find orphaned handle. */ + of = smb_ofile_lookup_by_persistid(sr, op->dh_fileid.persistent); + if (of == NULL) + goto errout; + + mutex_enter(&of->f_mutex); + if (of->f_state != SMB_OFILE_STATE_ORPHANED) { + mutex_exit(&of->f_mutex); + goto errout; + } + + status = smb2_dh_reconnect_checks(sr, of); + if (status != NT_STATUS_SUCCESS) { + mutex_exit(&of->f_mutex); + goto errout; + } + + /* + * Note: cv_broadcast(&of->f_cv) when we're + * done messing around in this state. + * See: smb_ofile_hold_olbrk() + */ + of->f_state = SMB_OFILE_STATE_RECONNECT; + mutex_exit(&of->f_mutex); + + /* + * At this point, we should be the only thread with a ref on the + * ofile, and the RECONNECT state should prevent new refs from + * being granted, or other durable threads from observing or + * reclaiming it. Put this ofile in the new tree, similar to + * the last part of smb_ofile_open. + */ + + old_cr = of->f_cr; + of->f_cr = sr->user_cr; + crhold(of->f_cr); + crfree(old_cr); + + of->f_session = sr->session; /* hold is via user and tree */ + smb_user_hold_internal(sr->uid_user); + of->f_user = sr->uid_user; + smb_tree_hold_internal(tree); + of->f_tree = tree; + of->f_fid = fid; + + 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(&sr->session->s_file_cnt); + + /* + * The ofile is now in the caller's session & tree. + * + * In case smb_ofile_hold or smb_oplock_send_brk() are + * waiting for state RECONNECT to complete, wakeup. + */ + mutex_enter(&of->f_mutex); + of->dh_expire_time = 0; + of->f_state = SMB_OFILE_STATE_OPEN; + cv_broadcast(&of->f_cv); + mutex_exit(&of->f_mutex); + + /* + * The ofile is now visible in the new session. + * From here, this is similar to the last part of + * smb_common_open(). + */ + op->fqi.fq_fattr.sa_mask = SMB_AT_ALL; + (void) smb_node_getattr(sr, of->f_node, zone_kcred(), of, + &op->fqi.fq_fattr); + + /* + * Set up the fileid and dosattr in open_param for response + */ + op->fileid = op->fqi.fq_fattr.sa_vattr.va_nodeid; + op->dattr = op->fqi.fq_fattr.sa_dosattr; + + /* + * Set up the file type in open_param for the response + * The ref. from ofile lookup is "given" to fid_ofile. + */ + op->ftype = SMB_FTYPE_DISK; + sr->smb_fid = of->f_fid; + sr->fid_ofile = of; + + if (smb_node_is_file(of->f_node)) { + op->dsize = op->fqi.fq_fattr.sa_vattr.va_size; + } else { + /* directory or symlink */ + op->dsize = 0; + } + + op->create_options = 0; /* no more modifications wanted */ + op->action_taken = SMB_OACT_OPENED; + return (NT_STATUS_SUCCESS); + +errout: + if (of != NULL) + smb_ofile_release(of); + if (fid != 0) + smb_idpool_free(&tree->t_fid_pool, fid); + + return (status); +} + +/* + * Durable handle expiration + * ofile state is _EXPIRED + */ +static void +smb2_dh_expire(void *arg) +{ + smb_ofile_t *of = (smb_ofile_t *)arg; + + smb_ofile_close(of, 0); + smb_ofile_release(of); +} + +void +smb2_durable_timers(smb_server_t *sv) +{ + smb_hash_t *hash; + smb_llist_t *bucket; + smb_ofile_t *of; + hrtime_t now; + int i; + + hash = sv->sv_persistid_ht; + now = gethrtime(); + + for (i = 0; i < hash->num_buckets; i++) { + bucket = &hash->buckets[i].b_list; + smb_llist_enter(bucket, RW_READER); + for (of = smb_llist_head(bucket); + of != NULL; + of = smb_llist_next(bucket, of)) { + SMB_OFILE_VALID(of); + + /* + * Check outside the mutex first to avoid some + * mutex_enter work in this loop. If the state + * changes under foot, the worst that happens + * is we either enter the mutex when we might + * not have needed to, or we miss some DH in + * this pass and get it on the next. + */ + if (of->f_state != SMB_OFILE_STATE_ORPHANED) + continue; + + mutex_enter(&of->f_mutex); + /* STATE_ORPHANED implies dh_expire_time != 0 */ + if (of->f_state == SMB_OFILE_STATE_ORPHANED && + of->dh_expire_time <= now) { + of->f_state = SMB_OFILE_STATE_EXPIRED; + /* inline smb_ofile_hold_internal() */ + of->f_refcnt++; + smb_llist_post(bucket, of, smb2_dh_expire); + } + mutex_exit(&of->f_mutex); + } + smb_llist_exit(bucket); + } +} + +/* + * Clean out durable handles during shutdown. + * Like, smb2_durable_timers but expire all, + * and make sure the hash buckets are empty. + */ +void +smb2_dh_shutdown(smb_server_t *sv) +{ + smb_hash_t *hash; + smb_llist_t *bucket; + smb_ofile_t *of; + int i; + + hash = sv->sv_persistid_ht; + + for (i = 0; i < hash->num_buckets; i++) { + bucket = &hash->buckets[i].b_list; + smb_llist_enter(bucket, RW_READER); + of = smb_llist_head(bucket); + while (of != NULL) { + SMB_OFILE_VALID(of); + mutex_enter(&of->f_mutex); + + switch (of->f_state) { + case SMB_OFILE_STATE_ORPHANED: + of->f_state = SMB_OFILE_STATE_EXPIRED; + /* inline smb_ofile_hold_internal() */ + of->f_refcnt++; + smb_llist_post(bucket, of, smb2_dh_expire); + break; + default: + break; + } + mutex_exit(&of->f_mutex); + of = smb_llist_next(bucket, of); + } + smb_llist_exit(bucket); + } + +#ifdef DEBUG + for (i = 0; i < hash->num_buckets; i++) { + bucket = &hash->buckets[i].b_list; + smb_llist_enter(bucket, RW_READER); + of = smb_llist_head(bucket); + while (of != NULL) { + SMB_OFILE_VALID(of); + cmn_err(CE_NOTE, "dh_shutdown leaked of=%p", + (void *)of); + of = smb_llist_next(bucket, of); + } + smb_llist_exit(bucket); + } +#endif // DEBUG +} + +uint32_t +smb2_fsctl_resiliency(smb_request_t *sr, smb_fsctl_t *fsctl) +{ + uint32_t timeout; + smb_ofile_t *of = sr->fid_ofile; + + /* + * Note: The spec does not explicitly prohibit resilient directories + * the same way it prohibits durable directories. We prohibit them + * anyway as a simplifying assumption, as there doesn't seem to be + * much use for it. (HYPER-V only seems to use it on files anyway) + */ + if (fsctl->InputCount < 8 || !smb_node_is_file(of->f_node)) + return (NT_STATUS_INVALID_PARAMETER); + + (void) smb_mbc_decodef(fsctl->in_mbc, "l4.", + &timeout); /* milliseconds */ + + if (smb2_enable_dh == 0) + return (NT_STATUS_NOT_SUPPORTED); + + /* + * The spec wants us to return INVALID_PARAMETER if the timeout + * is too large, but we have no way of informing the client + * what an appropriate timeout is, so just set the timeout to + * our max and return SUCCESS. + */ + if (timeout == 0) + timeout = smb2_res_def_timeout; + if (timeout > smb2_res_max_timeout) + timeout = smb2_res_max_timeout; + + mutex_enter(&of->f_mutex); + of->dh_vers = SMB2_RESILIENT; + of->dh_timeout_offset = MSEC2NSEC(timeout); + mutex_exit(&of->f_mutex); + + return (NT_STATUS_SUCCESS); +} diff --git a/usr/src/uts/common/fs/smbsrv/smb2_ioctl.c b/usr/src/uts/common/fs/smbsrv/smb2_ioctl.c index 95bb5a68b9..df0881f31b 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_ioctl.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_ioctl.c @@ -275,8 +275,7 @@ smb2_ioc_tbl[] = { { FSCTL_SRV_COPYCHUNK_WRITE, 0, smb2_fsctl_notsup }, { FSCTL_SRV_READ_HASH, 0, smb2_fsctl_notsup }, - { FSCTL_LMR_REQUEST_RESILIENCY, - ITF_NO_FID, smb2_fsctl_notsup }, + { FSCTL_LMR_REQUEST_RESILIENCY, 0, smb2_fsctl_resiliency }, { FSCTL_QUERY_NETWORK_INTERFACE_INFO, ITF_NO_FID, smb2_fsctl_notsup }, { FSCTL_VALIDATE_NEGOTIATE_INFO, 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_lock.c b/usr/src/uts/common/fs/smbsrv/smb2_lock.c index 91f57dade9..c6e8236cce 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_lock.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_lock.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* @@ -19,6 +19,12 @@ #include <smbsrv/smb2_kproto.h> +/* + * [MS-SMB2] 2.2.26 LockSequenceIndex, LockSequenceNumber. + */ +#define SMB2_LSN_SHIFT 4 +#define SMB2_LSN_MASK 0xf + typedef struct SMB2_LOCK_ELEMENT { uint64_t Offset; uint64_t Length; @@ -28,7 +34,10 @@ typedef struct SMB2_LOCK_ELEMENT { static uint32_t smb2_unlock(smb_request_t *); static uint32_t smb2_locks(smb_request_t *); -static smb_sdrc_t smb2_lock_async(smb_request_t *); +static uint32_t smb2_lock_blocking(smb_request_t *); + +static boolean_t smb2_lock_chk_lockseq(smb_ofile_t *, uint32_t); +static void smb2_lock_set_lockseq(smb_ofile_t *, uint32_t); /* * This is a somewhat arbitrary sanity limit on the length of the @@ -79,6 +88,26 @@ smb2_lock(smb_request_t *sr) } /* + * Check the LockSequence to determine whether a previous + * lock request succeeded, but the client disconnected + * (retaining a durable or resilient handle). If so, this + * is a lock "replay". We'll find the lock sequence here + * and return success without processing the lock again. + */ + if (sr->session->dialect < SMB_VERS_2_1) + LockSequence = 0; + if ((sr->session->dialect == SMB_VERS_2_1) && + sr->fid_ofile->dh_vers != SMB2_RESILIENT) + LockSequence = 0; + /* dialect 3.0 or later can always use LockSequence */ + + if (LockSequence != 0 && + smb2_lock_chk_lockseq(sr->fid_ofile, LockSequence)) { + status = NT_STATUS_SUCCESS; + goto errout; + } + + /* * Parse the array of SMB2_LOCK_ELEMENT structs. * This array is free'd in smb_srm_fini. */ @@ -115,9 +144,7 @@ smb2_lock(smb_request_t *sr) errout: sr->smb2_status = status; - if (status != NT_STATUS_PENDING) { - DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr); - } + DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr); if (status) { smb2sr_put_error(sr, status); @@ -125,7 +152,7 @@ errout: } /* - * Encode SMB2 Lock reply (sync) + * Encode SMB2 Lock reply */ (void) smb_mbc_encodef( &sr->reply, "w..", @@ -160,7 +187,9 @@ smb2_unlock(smb_request_t *sr) if (status != 0) break; } - (void) LockSequence; /* todo */ + if (status == 0 && LockSequence != 0) { + smb2_lock_set_lockseq(sr->fid_ofile, LockSequence); + } return (status); } @@ -174,6 +203,7 @@ smb2_locks(smb_request_t *sr) lock_elem_t *lk; lock_elem_t *lvec = sr->arg.lock.lvec; uint32_t LockCount = sr->arg.lock.lcnt; + uint32_t LockSequence = sr->arg.lock.lseq; uint32_t i; uint32_t ltype; uint32_t pid = 0; /* SMB2 ignores lock PIDs */ @@ -193,7 +223,7 @@ smb2_locks(smb_request_t *sr) * invalid parameter. */ if (i == 0 && LockCount == 1) { - status = smb2sr_go_async(sr, smb2_lock_async); + status = smb2_lock_blocking(sr); return (status); } /* FALLTHROUGH */ @@ -236,19 +266,22 @@ end_loop: lk->Offset, lk->Length, pid); } } + if (status == 0 && LockSequence != 0) + smb2_lock_set_lockseq(sr->fid_ofile, LockSequence); return (status); } /* - * Async handler for blocking lock requests. + * Handler for blocking lock requests, which may "go async". * Always exactly one lock request here. */ -static smb_sdrc_t -smb2_lock_async(smb_request_t *sr) +static uint32_t +smb2_lock_blocking(smb_request_t *sr) { lock_elem_t *lk = sr->arg.lock.lvec; uint32_t LockCount = sr->arg.lock.lcnt; + uint32_t LockSequence = sr->arg.lock.lseq; uint32_t status; uint32_t ltype; uint32_t pid = 0; /* SMB2 ignores lock PIDs */ @@ -268,28 +301,106 @@ smb2_lock_async(smb_request_t *sr) default: ASSERT(0); - status = NT_STATUS_INTERNAL_ERROR; - goto errout; + return (NT_STATUS_INTERNAL_ERROR); } - status = smb_lock_range(sr, lk->Offset, lk->Length, pid, - ltype, timeout); + /* + * Try the lock first with timeout=0 as we can often + * get a lock without going async and avoid an extra + * round trip with the client. Also, only go async + * for status returns that mean we will block. + */ + status = smb_lock_range(sr, lk->Offset, lk->Length, pid, ltype, 0); + if (status == NT_STATUS_LOCK_NOT_GRANTED || + status == NT_STATUS_FILE_LOCK_CONFLICT) { + status = smb2sr_go_async(sr); + if (status != 0) + return (status); + status = smb_lock_range(sr, lk->Offset, lk->Length, + pid, ltype, timeout); + } -errout: - sr->smb2_status = status; - DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr); + if (status == 0 && LockSequence != 0) + smb2_lock_set_lockseq(sr->fid_ofile, LockSequence); - if (status != 0) { - smb2sr_put_error(sr, status); - return (SDRC_SUCCESS); + return (status); +} + +/* + * Check whether we've stored a given LockSequence + * + * [MS-SMB2] 3.3.5.14 + * + * The server verifies the LockSequence by performing the following steps: + * + * 1. The server MUST use LockSequenceIndex as an index into the + * Open.LockSequenceArray in order to locate the sequence number entry. + * If the index exceeds the maximum extent of the Open.LockSequenceArray, + * or LockSequenceIndex is 0, or if the sequence number entry is empty, + * the server MUST skip step 2 and continue lock/unlock processing. + * + * 2. The server MUST compare LockSequenceNumber to the SequenceNumber of + * the entry located in step 1. If the sequence numbers are equal, the + * server MUST complete the lock/unlock request with success. Otherwise, + * the server MUST reset the entry value to empty and continue lock/unlock + * processing. + */ +boolean_t +smb2_lock_chk_lockseq(smb_ofile_t *ofile, uint32_t lockseq) +{ + uint32_t lsi; + uint8_t lsn; + boolean_t rv; + + /* + * LockSequenceNumber is the low four bits. + * LockSequenceIndex is the remaining 28 bits. + * valid range is 1..64, which we convert to an + * array index in the range 0..63 + */ + lsn = lockseq & SMB2_LSN_MASK; + lsi = (lockseq >> SMB2_LSN_SHIFT); + if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX) + return (B_FALSE); + --lsi; + + mutex_enter(&ofile->f_mutex); + + if (ofile->f_lock_seq[lsi] == lsn) { + rv = B_TRUE; + } else { + ofile->f_lock_seq[lsi] = (uint8_t)-1; /* "Empty" */ + rv = B_FALSE; } + mutex_exit(&ofile->f_mutex); + + return (rv); +} + +static void +smb2_lock_set_lockseq(smb_ofile_t *ofile, uint32_t lockseq) +{ + uint32_t lsi; + uint8_t lsn; + /* - * SMB2 Lock reply (async) + * LockSequenceNumber is the low four bits. + * LockSequenceIndex is the remaining 28 bits. + * valid range is 1..64, which we convert to an + * array index in the range 0..63 */ - (void) smb_mbc_encodef( - &sr->reply, "w..", - 4); /* StructSize w */ - /* reserved .. */ - return (SDRC_SUCCESS); + lsn = lockseq & SMB2_LSN_MASK; + lsi = (lockseq >> SMB2_LSN_SHIFT); + if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX) { + cmn_err(CE_NOTE, "smb2_lock_set_lockseq, index=%u", lsi); + return; + } + --lsi; + + mutex_enter(&ofile->f_mutex); + + ofile->f_lock_seq[lsi] = lsn; + + mutex_exit(&ofile->f_mutex); } diff --git a/usr/src/uts/common/fs/smbsrv/smb2_logoff.c b/usr/src/uts/common/fs/smbsrv/smb2_logoff.c index 772c8f6e46..497e950126 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_logoff.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_logoff.c @@ -43,6 +43,7 @@ smb2_logoff(smb_request_t *sr) DTRACE_SMB2_START(op__Logoff, smb_request_t *, sr); + sr->uid_user->preserve_opens = SMB2_DH_PRESERVE_ALL; smb_user_logoff(sr->uid_user); DTRACE_SMB2_DONE(op__Logoff, smb_request_t *, sr); diff --git a/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c b/usr/src/uts/common/fs/smbsrv/smb2_negotiate.c index a0f9b33cc1..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; /* @@ -67,6 +68,7 @@ uint32_t smb2_old_rwsize = (1<<16); /* 64KB */ static uint16_t smb2_versions[] = { 0x202, /* SMB 2.002 */ 0x210, /* SMB 2.1 */ + 0x300, /* SMB 3.0 */ }; static uint16_t smb2_nversions = sizeof (smb2_versions) / sizeof (smb2_versions[0]); @@ -326,6 +328,13 @@ smb2_negotiate_common(smb_request_t *sr, uint16_t version) } /* + * If the version is 0x2FF, we haven't completed negotiate. + * Don't initialize until we have our final request. + */ + if (version != 0x2FF) + smb2_sign_init_mech(s); + + /* * See notes above smb2_max_rwsize, smb2_old_rwsize */ if (s->capabilities & 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 ef7ad73d8f..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; } @@ -135,9 +192,9 @@ smb2_oplock_break_notification(smb_request_t *sr, uint8_t brk) */ sr->smb2_cmd_code = SMB2_OPLOCK_BREAK; sr->smb2_hdr_flags = SMB2_FLAGS_SERVER_TO_REDIR; - sr->smb_tid = ofile->f_tree->t_tid; + sr->smb_tid = 0; sr->smb_pid = 0; - sr->smb_uid = 0; + sr->smb2_ssnid = 0; sr->smb2_messageid = UINT64_MAX; (void) smb2_encode_header(sr, B_FALSE); @@ -145,7 +202,7 @@ smb2_oplock_break_notification(smb_request_t *sr, uint8_t brk) * SMB2 Oplock Break, variable part */ StructSize = 24; - smb2fid.persistent = 0; + smb2fid.persistent = ofile->f_persistid; smb2fid.temporal = ofile->f_fid; (void) smb_mbc_encodef( &sr->reply, "wb5.qq", @@ -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 11f3515504..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; @@ -437,7 +437,7 @@ smb2_find_mbc_encode(smb_request_t *sr, smb2_find_args_t *args) smb_macinfo_t *macinfo = &args->fa_mi; uint8_t buf83[26]; smb_msgbuf_t mb; - int namelen, padsz; + int namelen; int shortlen = 0; int rc, starting_offset; uint32_t next_entry_offset; @@ -647,11 +647,8 @@ smb2_find_mbc_encode(smb_request_t *sr, smb2_find_args_t *args) return (NT_STATUS_BUFFER_OVERFLOW); /* Next entry needs to be 8-byte aligned. */ - padsz = sr->raw_data.chain_offset & 7; - if (padsz) { - padsz = 8 - padsz; - (void) smb_mbc_encodef(&sr->raw_data, "#.", padsz); - } + (void) smb_mbc_put_align(&sr->raw_data, 8); + next_entry_offset = sr->raw_data.chain_offset - starting_offset; (void) smb_mbc_poke(&sr->raw_data, starting_offset, "l", next_entry_offset); diff --git a/usr/src/uts/common/fs/smbsrv/smb2_session_setup.c b/usr/src/uts/common/fs/smbsrv/smb2_session_setup.c index ff064994de..4de814f378 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_session_setup.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_session_setup.c @@ -37,7 +37,7 @@ smb2_session_setup(smb_request_t *sr) uint32_t Channel; uint16_t SecBufOffset; uint16_t SecBufLength; - uint64_t PrevSessionId; + uint64_t PrevSsnId; uint16_t SessionFlags; uint32_t status; int skip; @@ -55,11 +55,24 @@ smb2_session_setup(smb_request_t *sr) &Channel, /* l */ &SecBufOffset, /* w */ &SecBufLength, /* w */ - &PrevSessionId); /* q */ + &PrevSsnId); /* q */ if (rc) return (SDRC_ERROR); /* + * SMB3 multi-channel features are not supported. + * Once they are, this will check the dialect and + * whether multi-channel was negotiated, i.e. + * if (sr->session->dialect < SMB_VERS_3_0 || + * s->IsMultiChannelCapable == False) + * return (error...) + */ + if (Flags & SMB2_SESSION_FLAG_BINDING) { + status = NT_STATUS_REQUEST_NOT_ACCEPTED; + goto errout; + } + + /* * We're normally positioned at the security buffer now, * but there could be some padding before it. */ @@ -103,6 +116,19 @@ smb2_session_setup(smb_request_t *sr) if (sr->uid_user->u_flags & SMB_USER_FLAG_ANON) SessionFlags |= SMB2_SESSION_FLAG_IS_NULL; smb2_ss_adjust_credits(sr); + + /* + * PrevSsnId is a session that the client is reporting as + * having gone away, and for which we might not yet have seen + * a disconnect. We need to log off the previous session so + * any durable handles in that session will become orphans + * that can be reclaimed in this new session. Note that + * either zero or the _current_ session ID means there is + * no previous session to logoff. + */ + if (PrevSsnId != 0 && + PrevSsnId != sr->smb2_ssnid) + smb_server_logoff_ssnid(sr, PrevSsnId); break; /* @@ -115,6 +141,7 @@ smb2_session_setup(smb_request_t *sr) break; default: +errout: SecBufLength = 0; sr->smb2_status = status; break; diff --git a/usr/src/uts/common/fs/smbsrv/smb2_signing.c b/usr/src/uts/common/fs/smbsrv/smb2_signing.c index 25caed6ad2..ed19e6950e 100644 --- a/usr/src/uts/common/fs/smbsrv/smb2_signing.c +++ b/usr/src/uts/common/fs/smbsrv/smb2_signing.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2007, 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. */ /* * These routines provide the SMB MAC signing for the SMB2 server. @@ -48,6 +48,41 @@ #define SMB2_SIG_OFFS 48 #define SMB2_SIG_SIZE 16 +typedef struct mac_ops { + int (*mac_init)(smb_sign_ctx_t *, smb_sign_mech_t *, + uint8_t *, size_t); + int (*mac_update)(smb_sign_ctx_t, uint8_t *, size_t); + int (*mac_final)(smb_sign_ctx_t, uint8_t *); +} mac_ops_t; + +static int smb2_sign_calc_common(smb_request_t *, struct mbuf_chain *, + uint8_t *, mac_ops_t *); + +static int smb3_do_kdf(void *, void *, size_t, uint8_t *, uint32_t); + +/* + * SMB2 wrapper functions + */ + +static mac_ops_t +smb2_sign_ops = { + smb2_hmac_init, + smb2_hmac_update, + smb2_hmac_final +}; + +static int +smb2_sign_calc(smb_request_t *sr, + struct mbuf_chain *mbc, + uint8_t *digest16) +{ + int rv; + + rv = smb2_sign_calc_common(sr, mbc, digest16, &smb2_sign_ops); + + return (rv); +} + /* * Called during session destroy. */ @@ -63,20 +98,84 @@ smb2_sign_fini(smb_session_t *s) } /* + * SMB3 wrapper functions + */ + +static struct mac_ops +smb3_sign_ops = { + smb3_cmac_init, + smb3_cmac_update, + smb3_cmac_final +}; + +static int +smb3_sign_calc(smb_request_t *sr, + struct mbuf_chain *mbc, + uint8_t *digest16) +{ + int rv; + + rv = smb2_sign_calc_common(sr, mbc, digest16, &smb3_sign_ops); + + return (rv); +} + +/* + * Input to KDF for SigningKey. + * See comment for smb3_do_kdf for content. + */ +static uint8_t sign_kdf_input[29] = { + 0, 0, 0, 1, 'S', 'M', 'B', '2', + 'A', 'E', 'S', 'C', 'M', 'A', 'C', 0, + 0, 'S', 'm', 'b', 'S', 'i', 'g', 'n', + 0, 0, 0, 0, 0x80 }; + +void +smb2_sign_init_mech(smb_session_t *s) +{ + smb_sign_mech_t *mech; + int (*get_mech)(smb_sign_mech_t *); + int (*sign_calc)(smb_request_t *, struct mbuf_chain *, uint8_t *); + int rc; + + if (s->sign_mech != NULL) + return; + + if (s->dialect >= SMB_VERS_3_0) { + get_mech = smb3_cmac_getmech; + sign_calc = smb3_sign_calc; + } else { + get_mech = smb2_hmac_getmech; + sign_calc = smb2_sign_calc; + } + + mech = kmem_zalloc(sizeof (*mech), KM_SLEEP); + rc = get_mech(mech); + if (rc != 0) { + kmem_free(mech, sizeof (*mech)); + return; + } + s->sign_mech = mech; + s->sign_calc = sign_calc; + s->sign_fini = smb2_sign_fini; +} + +/* * smb2_sign_begin + * Handles both SMB2 & SMB3 * * Get the mechanism info. * Intializes MAC key based on the user session key and store it in * the signing structure. This begins signing on this session. */ -int +void smb2_sign_begin(smb_request_t *sr, smb_token_t *token) { smb_session_t *s = sr->session; smb_user_t *u = sr->uid_user; struct smb_key *sign_key = &u->u_sign_key; - smb_sign_mech_t *mech; - int rc; + + sign_key->len = 0; /* * We should normally have a session key here because @@ -84,41 +183,36 @@ smb2_sign_begin(smb_request_t *sr, smb_token_t *token) * However, buggy clients could get us here without a * session key, in which case we'll fail later when a * request that requires signing can't be checked. + * Also, don't bother initializing if we don't have a mechanism. */ - if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0) - return (0); - - /* - * Session-level initialization (once per session) - * Get mech handle, sign_fini function. - */ - smb_rwx_rwenter(&s->s_lock, RW_WRITER); - if (s->sign_mech == NULL) { - mech = kmem_zalloc(sizeof (*mech), KM_SLEEP); - rc = smb2_hmac_getmech(mech); - if (rc != 0) { - kmem_free(mech, sizeof (*mech)); - smb_rwx_rwexit(&s->s_lock); - return (rc); - } - s->sign_mech = mech; - s->sign_fini = smb2_sign_fini; - } - smb_rwx_rwexit(&s->s_lock); + if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 || + s->sign_mech == NULL) + return; /* * Compute and store the signing key, which lives in * the user structure. */ - sign_key->len = SMB2_SIG_SIZE; - - /* - * For SMB2, the signing key is just the first 16 bytes - * of the session key (truncated or padded with zeros). - * [MS-SMB2] 3.2.5.3.1 - */ - bcopy(token->tkn_ssnkey.val, sign_key->key, - MIN(token->tkn_ssnkey.len, sign_key->len)); + if (s->dialect >= SMB_VERS_3_0) { + /* + * For SMB3, the signing key is a "KDF" hash of the + * session key. + */ + if (smb3_do_kdf(sign_key->key, sign_kdf_input, + sizeof (sign_kdf_input), token->tkn_ssnkey.val, + token->tkn_ssnkey.len) != 0) + return; + sign_key->len = SMB3_KEYLEN; + } else { + /* + * For SMB2, the signing key is just the first 16 bytes + * of the session key (truncated or padded with zeros). + * [MS-SMB2] 3.2.5.3.1 + */ + sign_key->len = SMB2_SIG_SIZE; + bcopy(token->tkn_ssnkey.val, sign_key->key, + MIN(token->tkn_ssnkey.len, sign_key->len)); + } mutex_enter(&u->u_mutex); if (s->secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) @@ -136,25 +230,23 @@ smb2_sign_begin(smb_request_t *sr, smb_token_t *token) */ if (u->u_sign_flags & SMB_SIGNING_ENABLED) sr->smb2_hdr_flags |= SMB2_FLAGS_SIGNED; - - return (0); } /* - * smb2_sign_calc + * smb2_sign_calc_common * * Calculates MAC signature for the given buffer and returns * it in the mac_sign parameter. * - * The signature is in the last 16 bytes of the SMB2 header. - * The signature algorighm is to compute HMAC SHA256 over the - * entire command, with the signature field set to zeros. + * The signature algorithm is to compute HMAC SHA256 or AES_CMAC + * over the entire command, with the signature field set to zeros. * * Return 0 if success else -1 */ + static int -smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc, - uint8_t *digest) +smb2_sign_calc_common(smb_request_t *sr, struct mbuf_chain *mbc, + uint8_t *digest, mac_ops_t *ops) { uint8_t tmp_hdr[SMB2_HDR_SIZE]; smb_sign_ctx_t ctx = 0; @@ -167,7 +259,8 @@ smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc, if (s->sign_mech == NULL || sign_key->len == 0) return (-1); - rc = smb2_hmac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len); + /* smb2_hmac_init or smb3_cmac_init */ + rc = ops->mac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len); if (rc != 0) return (rc); @@ -182,7 +275,8 @@ smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc, if (smb_mbc_peek(mbc, offset, "#c", tlen, tmp_hdr) != 0) return (-1); bzero(tmp_hdr + SMB2_SIG_OFFS, SMB2_SIG_SIZE); - if ((rc = smb2_hmac_update(ctx, tmp_hdr, tlen)) != 0) + /* smb2_hmac_update or smb3_cmac_update */ + if ((rc = ops->mac_update(ctx, tmp_hdr, tlen)) != 0) return (rc); offset += tlen; resid -= tlen; @@ -210,7 +304,8 @@ smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc, tlen = mbuf->m_len - offset; if (tlen > resid) tlen = resid; - rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen); + /* smb2_hmac_update or smb3_cmac_update */ + rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen); if (rc != 0) return (rc); resid -= tlen; @@ -225,17 +320,20 @@ smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc, tlen = mbuf->m_len; if (tlen > resid) tlen = resid; - rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data, tlen); + rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data, tlen); if (rc != 0) return (rc); resid -= tlen; } /* + * smb2_hmac_final or smb3_cmac_final * Note: digest is _always_ SMB2_SIG_SIZE, * even if the mech uses a longer one. + * + * smb2_hmac_update or smb3_cmac_update */ - if ((rc = smb2_hmac_final(ctx, digest)) != 0) + if ((rc = ops->mac_final(ctx, digest)) != 0) return (rc); return (0); @@ -260,6 +358,7 @@ smb2_sign_check_request(smb_request_t *sr) uint8_t req_sig[SMB2_SIG_SIZE]; uint8_t vfy_sig[SMB2_SIG_SIZE]; struct mbuf_chain *mbc = &sr->smb_data; + smb_session_t *s = sr->session; smb_user_t *u = sr->uid_user; int sig_off; @@ -267,9 +366,13 @@ smb2_sign_check_request(smb_request_t *sr) * Don't check commands with a zero session ID. * [MS-SMB2] 3.3.4.1.1 */ - if (sr->smb_uid == 0 || u == NULL) + if (sr->smb2_ssnid == 0 || u == NULL) return (0); + /* In case _sign_begin failed. */ + if (s->sign_calc == NULL) + return (-1); + /* Get the request signature. */ sig_off = sr->smb2_cmd_hdr + SMB2_SIG_OFFS; if (smb_mbc_peek(mbc, sig_off, "#c", SMB2_SIG_SIZE, req_sig) != 0) @@ -277,8 +380,9 @@ smb2_sign_check_request(smb_request_t *sr) /* * Compute the correct signature and compare. + * smb2_sign_calc() or smb3_sign_calc() */ - if (smb2_sign_calc(sr, mbc, vfy_sig) != 0) + if (s->sign_calc(sr, mbc, vfy_sig) != 0) return (-1); if (memcmp(vfy_sig, req_sig, SMB2_SIG_SIZE) != 0) { cmn_err(CE_NOTE, "smb2_sign_check_request: bad signature"); @@ -300,11 +404,14 @@ smb2_sign_reply(smb_request_t *sr) { uint8_t reply_sig[SMB2_SIG_SIZE]; struct mbuf_chain tmp_mbc; + smb_session_t *s = sr->session; smb_user_t *u = sr->uid_user; int hdr_off, msg_len; if (u == NULL) return; + if (s->sign_calc == NULL) + return; msg_len = sr->reply.chain_offset - sr->smb2_reply_hdr; (void) MBC_SHADOW_CHAIN(&tmp_mbc, &sr->reply, @@ -312,8 +419,9 @@ smb2_sign_reply(smb_request_t *sr) /* * Calculate the MAC signature for this reply. + * smb2_sign_calc() or smb3_sign_calc() */ - if (smb2_sign_calc(sr, &tmp_mbc, reply_sig) != 0) + if (s->sign_calc(sr, &tmp_mbc, reply_sig) != 0) return; /* @@ -323,3 +431,54 @@ smb2_sign_reply(smb_request_t *sr) (void) smb_mbc_poke(&sr->reply, hdr_off, "#c", SMB2_SIG_SIZE, reply_sig); } + +/* + * Derive SMB3 key as described in [MS-SMB2] 3.1.4.2 + * and [NIST SP800-108] + * + * r = 32, L = 128, PRF = HMAC-SHA256, key = (session key) + * + * Note that these describe pre-3.1.1 inputs. + * + * Session.SigningKey for binding a session: + * - Session.SessionKey as K1 + * - label = SMB2AESCMAC (size 12) + * - context = SmbSign (size 8) + * Channel.SigningKey for for all other requests + * - if SMB2_SESSION_FLAG_BINDING, GSS key (in Session.SessionKey?) as K1; + * - otherwise, Session.SessionKey as K1 + * - label = SMB2AESCMAC (size 12) + * - context = SmbSign (size 8) + * Session.ApplicationKey for ... (not sure what yet) + * - Session.SessionKey as K1 + * - label = SMB2APP (size 8) + * - context = SmbRpc (size 7) + */ +static int +smb3_do_kdf(void *outbuf, void *input, size_t input_len, + uint8_t *key, uint32_t key_len) +{ + uint8_t digest32[SHA256_DIGEST_LENGTH]; + smb_sign_mech_t mech; + smb_sign_ctx_t hctx = 0; + int rc; + + bzero(&mech, sizeof (mech)); + if ((rc = smb2_hmac_getmech(&mech)) != 0) + return (rc); + + /* Limit the SessionKey input to its maximum size (16 bytes) */ + rc = smb2_hmac_init(&hctx, &mech, key, MIN(key_len, SMB2_KEYLEN)); + if (rc != 0) + return (rc); + + if ((rc = smb2_hmac_update(hctx, input, input_len)) != 0) + return (rc); + + if ((rc = smb2_hmac_final(hctx, digest32)) != 0) + return (rc); + + /* Output is first 16 bytes of digest. */ + bcopy(digest32, outbuf, SMB3_KEYLEN); + return (0); +} 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_authenticate.c b/usr/src/uts/common/fs/smbsrv/smb_authenticate.c index 29fcf0d26b..72d06d4d33 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_authenticate.c +++ b/usr/src/uts/common/fs/smbsrv/smb_authenticate.c @@ -51,6 +51,7 @@ static uint32_t smb_priv_xlate(smb_token_t *); /* * Handle old-style session setup (non-extended security) + * Note: Used only by SMB1 * * The user information is passed to smbd for authentication. * If smbd can authenticate the user an access token is returned and we @@ -69,6 +70,7 @@ smb_authenticate_old(smb_request_t *sr) /* user cleanup in smb_request_free */ sr->uid_user = user; sr->smb_uid = user->u_uid; + sr->smb2_ssnid = 0; /* * Open a connection to the local logon service. @@ -231,20 +233,42 @@ smb_authenticate_ext(smb_request_t *sr) ASSERT(sr->uid_user == NULL); /* - * On the first request (UID==0) create a USER object. - * On subsequent requests (UID!=0) find the USER object. + * Paranoid: While finding/creating the user object, make sure + * SMB2 ignores smb_uid, and SMB1 ignores smb2_ssnid. The + * logic below assumes the "other" one is always zero; both + * the "first request" tests and smb_session_lookup_uid_st. + */ + if (sr->session->dialect >= SMB_VERS_2_BASE) { + /* SMB2+ ignores smb_uid */ + ASSERT(sr->smb_uid == 0); + sr->smb_uid = 0; + } else { + /* SMB1 ignores smb2_ssnid */ + ASSERT(sr->smb2_ssnid == 0); + sr->smb2_ssnid = 0; + } + + /* + * On the first request (UID/ssnid==0) create a USER object. + * On subsequent requests (UID/ssnid!=0) find the USER object. * Either way, sr->uid_user is set, so our ref. on the * user object is dropped during normal cleanup work * for the smb_request (sr). Ditto u_authsock. */ - if (sr->smb_uid == 0) { + if (sr->smb2_ssnid == 0 && sr->smb_uid == 0) { user = smb_user_new(sr->session); if (user == NULL) return (NT_STATUS_TOO_MANY_SESSIONS); /* user cleanup in smb_request_free */ sr->uid_user = user; - sr->smb_uid = user->u_uid; + if (sr->session->dialect >= SMB_VERS_2_BASE) { + /* Intentionally leave smb_uid=0 for SMB2 */ + sr->smb2_ssnid = user->u_ssnid; + } else { + /* Intentionally leave smb2_ssnid=0 for SMB1 */ + sr->smb_uid = user->u_uid; + } /* * Open a connection to the local logon service. @@ -263,7 +287,7 @@ smb_authenticate_ext(smb_request_t *sr) msg_hdr.lmh_msgtype = LSA_MTYPE_ESFIRST; } else { user = smb_session_lookup_uid_st(sr->session, - sr->smb_uid, SMB_USER_STATE_LOGGING_ON); + sr->smb2_ssnid, sr->smb_uid, SMB_USER_STATE_LOGGING_ON); if (user == NULL) return (NT_STATUS_USER_SESSION_DELETED); @@ -388,7 +412,6 @@ smb_auth_get_token(smb_request_t *sr) uint32_t rlen = 0; uint32_t privileges; uint32_t status; - int rc; bool_t ok; msg_hdr.lmh_msgtype = LSA_MTYPE_GETTOK; @@ -450,13 +473,9 @@ smb_auth_get_token(smb_request_t *sr) */ if ((token->tkn_flags & (SMB_ATF_GUEST | SMB_ATF_ANON)) == 0) { if (sr->session->dialect >= SMB_VERS_2_BASE) { - rc = smb2_sign_begin(sr, token); + smb2_sign_begin(sr, token); } else { - rc = smb_sign_begin(sr, token); - } - if (rc != 0) { - status = NT_STATUS_INTERNAL_ERROR; - goto errout; + smb_sign_begin(sr, token); } } 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 bca7530c6e..161f2790f6 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_common_open.c +++ b/usr/src/uts/common/fs/smbsrv/smb_common_open.c @@ -34,7 +34,7 @@ #include <sys/fcntl.h> #include <sys/nbmlock.h> #include <smbsrv/string.h> -#include <smbsrv/smb_kproto.h> +#include <smbsrv/smb2_kproto.h> #include <smbsrv/smb_fsops.h> #include <smbsrv/smbinfo.h> @@ -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, @@ -244,18 +207,14 @@ smb_common_open(smb_request_t *sr) * 1. The creator of a readonly file can write to/modify the size of the file * using the original create fid, even though the file will appear as readonly * to all other fids and via a CIFS getattr call. - * The readonly bit therefore cannot be set in the filesystem until the file - * is closed (smb_ofile_close). It is accounted for via ofile and node flags. * * 2. A setinfo operation (using either an open fid or a path) to set/unset * readonly will be successful regardless of whether a creator of a readonly - * file has an open fid (and has the special privilege mentioned in #1, - * above). I.e., the creator of a readonly fid holding that fid will no longer - * have a special privilege. + * file has an open fid. * * 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 * @@ -289,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); @@ -348,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; @@ -372,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 ? @@ -411,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 @@ -430,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; } /* @@ -479,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; } } @@ -498,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; } } @@ -543,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; } } @@ -562,58 +553,207 @@ 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 * may cause other clients to close the file which would - * affect the sharing checks. This may block, so set the - * file opening count before oplock stuff. + * affect the sharing checks, and may delete the file due to + * 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 + + smb_node_inc_opening_count(fnode); + opening_incr = B_TRUE; - smb_node_wrlock(node); + /* + * 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 + * 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. */ - status = smb_fsop_shrlock(sr->user_cr, node, uniq_fid, + 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); - 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); + 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. + * 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 + */ + DTRACE_PROBE1(node_deleted, smb_node_t, fnode); + smb_ofile_free(of); + of = NULL; + last_comp_found = B_FALSE; + + /* + * 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); + + 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; } /* @@ -624,10 +764,10 @@ smb_open_subr(smb_request_t *sr) case FILE_OVERWRITE_IF: case FILE_OVERWRITE: op->dattr |= FILE_ATTRIBUTE_ARCHIVE; - /* Don't apply readonly bit until smb_ofile_close */ + /* Don't apply readonly until smb_set_open_attributes */ if (op->dattr & FILE_ATTRIBUTE_READONLY) { - op->created_readonly = B_TRUE; op->dattr &= ~FILE_ATTRIBUTE_READONLY; + op->created_readonly = B_TRUE; } /* @@ -640,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; @@ -684,6 +814,7 @@ smb_open_subr(smb_request_t *sr) break; } } else { +create: /* Last component was not found. */ dnode = op->fqi.fq_dnode; @@ -692,39 +823,43 @@ smb_open_subr(smb_request_t *sr) 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. + * 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 + * or that would prevent this open. Note that op->dattr + * needs to be what smb_set_open_attributes will use, + * except for the readonly bit. */ - smb_node_wrlock(dnode); - - /* Don't apply readonly bit until smb_ofile_close */ + bzero(&new_attr, sizeof (new_attr)); + new_attr.sa_mask = SMB_AT_DOSATTR | SMB_AT_TYPE | SMB_AT_MODE; if (op->dattr & FILE_ATTRIBUTE_READONLY) { op->dattr &= ~FILE_ATTRIBUTE_READONLY; op->created_readonly = B_TRUE; } - bzero(&new_attr, sizeof (new_attr)); + /* + * SMB create can specify the create time. + */ if ((op->crtime.tv_sec != 0) && (op->crtime.tv_sec != UINT_MAX)) { - new_attr.sa_mask |= SMB_AT_CRTIME; new_attr.sa_crtime = op->crtime; } @@ -733,11 +868,12 @@ smb_open_subr(smb_request_t *sr) op->dattr |= FILE_ATTRIBUTE_ARCHIVE; new_attr.sa_dosattr = op->dattr; new_attr.sa_vattr.va_type = VREG; - new_attr.sa_vattr.va_mode = is_stream ? S_IRUSR : - S_IRUSR | S_IRGRP | S_IROTH | - S_IWUSR | S_IWGRP | S_IWOTH; - new_attr.sa_mask |= - SMB_AT_DOSATTR | SMB_AT_TYPE | SMB_AT_MODE; + if (is_stream) + new_attr.sa_vattr.va_mode = S_IRUSR | S_IWUSR; + else + new_attr.sa_vattr.va_mode = + S_IRUSR | S_IRGRP | S_IROTH | + S_IWUSR | S_IWGRP | S_IWOTH; /* * We set alloc_size = op->dsize later, @@ -754,55 +890,43 @@ smb_open_subr(smb_request_t *sr) 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; new_attr.sa_vattr.va_type = VDIR; new_attr.sa_vattr.va_mode = 0777; - new_attr.sa_mask |= - SMB_AT_DOSATTR | SMB_AT_TYPE | SMB_AT_MODE; 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; } /* @@ -812,83 +936,80 @@ smb_open_subr(smb_request_t *sr) * 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; } /* - * This MUST be done after ofile creation, so that explicitly - * set timestamps can be remembered on the ofile, and the - * readonly flag will be stored "pending" on the node. + * Moved this up from smb_ofile_open() */ - if (status == NT_STATUS_SUCCESS) { - if ((rc = smb_set_open_attributes(sr, of)) != 0) { - status = smb_errno2status(rc); - } + if ((rc = smb_fsop_open(fnode, of->f_mode, of->f_cr)) != 0) { + status = smb_errno2status(rc); + goto errout; } - 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; - } - } + /* + * Complete this open (add to ofile lists) + */ + smb_ofile_open(sr, op, of); + did_open = B_TRUE; /* - * smb_fsop_unshrlock is a no-op if node is a directory - * smb_fsop_unshrlock is done in smb_ofile_close + * 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 (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); + if ((rc = smb_set_open_attributes(sr, of)) != 0) { + status = smb_errno2status(rc); + goto errout; } /* + * 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; + (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 @@ -903,83 +1024,62 @@ smb_open_subr(smb_request_t *sr) 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; +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; - (void) smb_oplock_break(sr, node, flags); - } else if (!smb_open_attr_only(op)) { - flags |= SMB_OPLOCK_BREAK_TO_LEVEL_II; - (void) smb_oplock_break(sr, node, flags); + if (created) { + /* Try to roll-back create. */ + smb_delete_new_object(sr); } -} -/* - * 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 (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); -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); + if (tree_fid != 0) + smb_idpool_free(&tree->t_fid_pool, tree_fid); + + return (status); } /* @@ -994,8 +1094,6 @@ smb_open_overwrite(smb_arg_open_t *op) * - If we created_readonly, we now store the real DOS attributes * (including the readonly bit) so subsequent opens will see it. * - * Both are stored "pending" rather than in the file system. - * * Returns: errno */ static int @@ -1034,7 +1132,7 @@ smb_set_open_attributes(smb_request_t *sr, smb_ofile_t *of) * However, keep track of the fact that we modified * the file via this handle, so we can do the evil, * gratuitious mtime update on close that Windows - * clients appear to expect. + * clients expect. */ if (op->action_taken == SMB_OACT_TRUNCATED) of->f_written = B_TRUE; diff --git a/usr/src/uts/common/fs/smbsrv/smb_create.c b/usr/src/uts/common/fs/smbsrv/smb_create.c index a5a320e37b..cd25aa53f7 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_create.c +++ b/usr/src/uts/common/fs/smbsrv/smb_create.c @@ -162,7 +162,7 @@ smb_com_create_temporary(smb_request_t *sr) if (smb_common_create(sr) != NT_STATUS_SUCCESS) return (SDRC_ERROR); - if (smbsr_encode_result(sr, 1, VAR_BCC, "bww%S", 1, sr->smb_fid, + if (smbsr_encode_result(sr, 1, VAR_BCC, "bww%s", 1, sr->smb_fid, VAR_BCC, sr, name)) return (SDRC_ERROR); @@ -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 95c2063bca..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); } @@ -544,18 +538,12 @@ smb_delete_remove_file(smb_request_t *sr, smb_error_t *err) rc = smb_fsop_remove(sr, sr->user_cr, node->n_dnode, node->od_name, flags); if (rc != 0) { - if (rc == ENOENT) - smb_delete_error(err, NT_STATUS_OBJECT_NAME_NOT_FOUND, - ERRDOS, ERROR_FILE_NOT_FOUND); - else - smbsr_map_errno(rc, err); - - smb_node_end_crit(node); - return (-1); + smbsr_map_errno(rc, err); + rc = -1; } smb_node_end_crit(node); - return (0); + return (rc); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_directory.c b/usr/src/uts/common/fs/smbsrv/smb_directory.c index 007f34fb45..f009c32580 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_directory.c +++ b/usr/src/uts/common/fs/smbsrv/smb_directory.c @@ -250,11 +250,7 @@ smb_com_delete_directory(smb_request_t *sr) rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS, tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode); if (rc != 0) { - if (rc == ENOENT) - smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND, - ERRDOS, ERROR_FILE_NOT_FOUND); - else - smbsr_errno(sr, rc); + smbsr_errno(sr, rc); smb_node_release(fqi->fq_dnode); return (SDRC_ERROR); } @@ -414,11 +410,7 @@ smb_com_check_directory(smb_request_t *sr) tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode); smb_node_release(fqi->fq_dnode); if (rc != 0) { - if (rc == ENOENT) - smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND, - ERRDOS, ERROR_PATH_NOT_FOUND); - else - smbsr_errno(sr, rc); + smbsr_errno(sr, rc); return (SDRC_ERROR); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_dispatch.c b/usr/src/uts/common/fs/smbsrv/smb_dispatch.c index add4a009d9..a1e9e4c163 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_dispatch.c +++ b/usr/src/uts/common/fs/smbsrv/smb_dispatch.c @@ -836,9 +836,9 @@ andx_more: sr->sr_time_start = gethrtime(); if ((sdrc = (*sdd->sdt_pre_op)(sr)) == SDRC_SUCCESS) sdrc = (*sdd->sdt_function)(sr); - (*sdd->sdt_post_op)(sr); if (sdrc != SDRC_SR_KEPT) { + (*sdd->sdt_post_op)(sr); smbsr_cleanup(sr); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_errno.c b/usr/src/uts/common/fs/smbsrv/smb_errno.c index 533d099b4a..fe0ea8c7c6 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_errno.c +++ b/usr/src/uts/common/fs/smbsrv/smb_errno.c @@ -21,7 +21,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. */ /* @@ -47,9 +47,9 @@ struct errno2status { static const struct errno2status smb_errno2status_map[] = { { EPERM, NT_STATUS_ACCESS_DENIED }, - { ENOENT, NT_STATUS_NO_SUCH_FILE }, - /* NB: ESRCH is used to represent stream lookup failures. */ - { ESRCH, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { ENOENT, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + /* NB: ESRCH is used in rename and stream ops. */ + { ESRCH, NT_STATUS_NO_SUCH_FILE }, { EINTR, NT_STATUS_CANCELLED }, { EIO, NT_STATUS_IO_DEVICE_ERROR }, { ENXIO, NT_STATUS_BAD_DEVICE_TYPE }, @@ -60,9 +60,8 @@ smb_errno2status_map[] = { { EACCES, NT_STATUS_ACCESS_DENIED }, /* EFAULT, ENOTBLK, EBUSY */ { EEXIST, NT_STATUS_OBJECT_NAME_COLLISION }, - { EXDEV, NT_STATUS_NOT_SAME_DEVICE }, + { EXDEV, NT_STATUS_NOT_SAME_DEVICE }, { ENODEV, NT_STATUS_NO_SUCH_DEVICE }, - /* ENOTDIR should be: NT_STATUS_NOT_A_DIRECTORY, but not yet */ { ENOTDIR, NT_STATUS_OBJECT_PATH_NOT_FOUND }, { EISDIR, NT_STATUS_FILE_IS_A_DIRECTORY }, { EINVAL, NT_STATUS_INVALID_PARAMETER }, @@ -81,7 +80,7 @@ smb_errno2status_map[] = { /* ENOMSG, EIDRM, ... */ { ENOTSUP, NT_STATUS_NOT_SUPPORTED }, { EDQUOT, NT_STATUS_DISK_FULL }, - { EREMOTE, NT_STATUS_PATH_NOT_COVERED}, + { EREMOTE, NT_STATUS_PATH_NOT_COVERED}, { ENAMETOOLONG, NT_STATUS_OBJECT_NAME_INVALID }, { EILSEQ, NT_STATUS_OBJECT_NAME_INVALID }, { ENOTEMPTY, NT_STATUS_DIRECTORY_NOT_EMPTY }, 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_find.c b/usr/src/uts/common/fs/smbsrv/smb_find.c index a8774f72e2..9dc3689652 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_find.c +++ b/usr/src/uts/common/fs/smbsrv/smb_find.c @@ -300,6 +300,7 @@ smb_com_search(smb_request_t *sr) ERRDOS, ERROR_NO_MORE_FILES); return (SDRC_ERROR); } + odid = od->d_odid; } else { if (smb_mbc_decodef(&sr->smb_data, "b12.wwl", &resume_char, &index, &odid, &client_key) != 0) { @@ -455,6 +456,7 @@ smb_com_find(smb_request_t *sr) smbsr_error(sr, status, 0, 0); return (SDRC_ERROR); } + odid = od->d_odid; } else { if (smb_mbc_decodef(&sr->smb_data, "b12.wwl", &resume_char, &index, &odid, &client_key) != 0) { @@ -691,8 +693,8 @@ smb_com_find_unique(struct smb_request *sr) smb_name83(fileinfo.fi_shortname, name83, SMB_SHORTNAMELEN); (void) smb_mbc_encodef(&sr->reply, "b11c.wwlbYl13c", - resume_char, name83, index, od->d_odid, - client_key, fileinfo.fi_dosattr & 0xff, + resume_char, name83, index, od->d_odid, client_key, + fileinfo.fi_dosattr & 0xff, smb_time_gmt_to_local(sr, fileinfo.fi_mtime.tv_sec), (int32_t)fileinfo.fi_size, fileinfo.fi_shortname); diff --git a/usr/src/uts/common/fs/smbsrv/smb_fsops.c b/usr/src/uts/common/fs/smbsrv/smb_fsops.c index e94e7e7d99..abe69a9017 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_fsops.c +++ b/usr/src/uts/common/fs/smbsrv/smb_fsops.c @@ -820,6 +820,7 @@ smb_fsop_remove_streams(smb_request_t *sr, cred_t *cr, smb_node_t *fnode) switch (status) { case 0: break; + case NT_STATUS_OBJECT_NAME_NOT_FOUND: case NT_STATUS_NO_SUCH_FILE: case NT_STATUS_NOT_SUPPORTED: /* No streams to remove. */ @@ -1243,25 +1244,6 @@ smb_fsop_setattr( return (EACCES); /* - * The file system cannot detect pending READDONLY - * (i.e. if the file has been opened readonly but - * not yet closed) so we need to test READONLY here. - * - * Note that file handle that were opened before the - * READONLY flag was set in the node (or the FS) are - * immune to that change, and remain writable. - */ - if (sr && (set_attr->sa_mask & SMB_AT_SIZE)) { - if (sr->fid_ofile) { - if (SMB_OFILE_IS_READONLY(sr->fid_ofile)) - return (EACCES); - } else { - if (SMB_PATHFILE_IS_READONLY(sr, snode)) - return (EACCES); - } - } - - /* * SMB checks access on open and retains an access granted * mask for use while the file is open. ACL changes should * not affect access to an open file. @@ -1351,23 +1333,6 @@ smb_fsop_set_data_length( return (EACCES); /* - * The file system cannot detect pending READDONLY - * (i.e. if the file has been opened readonly but - * not yet closed) so we need to test READONLY here. - * - * Note that file handle that were opened before the - * READONLY flag was set in the node (or the FS) are - * immune to that change, and remain writable. - */ - if (sr->fid_ofile) { - if (SMB_OFILE_IS_READONLY(sr->fid_ofile)) - return (EACCES); - } else { - /* This requires an open file. */ - return (EACCES); - } - - /* * SMB checks access on open and retains an access granted * mask for use while the file is open. ACL changes should * not affect access to an open file. @@ -1502,8 +1467,7 @@ smb_fsop_write( if (SMB_TREE_IS_READONLY(sr)) return (EROFS); - if (SMB_OFILE_IS_READONLY(of) || - SMB_TREE_HAS_ACCESS(sr, ACE_WRITE_DATA | ACE_APPEND_DATA) == 0) + if (SMB_TREE_HAS_ACCESS(sr, ACE_WRITE_DATA | ACE_APPEND_DATA) == 0) return (EACCES); rc = smb_ofile_access(of, cr, FILE_WRITE_DATA); @@ -2578,6 +2542,15 @@ smb_fsop_eaccess(smb_request_t *sr, cred_t *cr, smb_node_t *snode, if (access & VWRITE) *eaccess |= FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_DELETE_CHILD; + + if (access & (VREAD | VWRITE)) + *eaccess |= SYNCHRONIZE; + +#ifdef _FAKE_KERNEL + /* Should be: if (we are the owner)... */ + if (access & VWRITE) + *eaccess |= DELETE | WRITE_DAC | WRITE_OWNER; +#endif } /* 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 b344f7a944..f89a8106ee 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_kshare.c +++ b/usr/src/uts/common/fs/smbsrv/smb_kshare.c @@ -892,6 +892,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_kutil.c b/usr/src/uts/common/fs/smbsrv/smb_kutil.c index 5c74e4f8cd..c5be4710d8 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_kutil.c +++ b/usr/src/uts/common/fs/smbsrv/smb_kutil.c @@ -21,7 +21,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ #include <sys/param.h> @@ -40,6 +40,7 @@ #include <sys/sid.h> #include <sys/priv_names.h> +#include <sys/bitmap.h> static kmem_cache_t *smb_dtor_cache = NULL; @@ -332,8 +333,22 @@ smb_idpool_alloc( pool->id_pool[pool->id_idx] |= bit; *id = (uint16_t)(pool->id_idx * 8 + (uint32_t)bit_idx); pool->id_free_counter--; - pool->id_bit = bit; - pool->id_bit_idx = bit_idx; + /* + * Leave position at next bit to allocate, + * so we don't keep re-using the last in an + * alloc/free/alloc/free sequence. Doing + * that can confuse some SMB clients. + */ + if (bit & 0x80) { + pool->id_bit = 1; + pool->id_bit_idx = 0; + pool->id_idx++; + pool->id_idx &= pool->id_idx_msk; + } else { + pool->id_bit = (bit << 1); + pool->id_bit_idx = bit_idx + 1; + /* keep id_idx */ + } mutex_exit(&pool->id_mutex); return (0); } @@ -445,6 +460,16 @@ smb_llist_destructor( } /* + * smb_llist_enter + * Not a macro so dtrace smbsrv:* can see it. + */ +void +smb_llist_enter(smb_llist_t *ll, krw_t mode) +{ + rw_enter(&ll->ll_lock, mode); +} + +/* * Post an object to the delete queue. The delete queue will be processed * during list exit or list destruction. Objects are often posted for * deletion during list iteration (while the list is locked) but that is @@ -636,6 +661,16 @@ smb_slist_destructor( } /* + * smb_slist_enter + * Not a macro so dtrace smbsrv:* can see it. + */ +void +smb_slist_enter(smb_slist_t *sl) +{ + mutex_enter(&(sl)->sl_mutex); +} + +/* * smb_slist_insert_head * * This function inserts the object passed a the beginning of the list. @@ -799,90 +834,46 @@ smb_rwx_destroy( } /* - * smb_rwx_rwexit + * smb_rwx_rwenter */ void -smb_rwx_rwexit( - smb_rwx_t *rwx) +smb_rwx_rwenter(smb_rwx_t *rwx, krw_t mode) { - if (rw_write_held(&rwx->rwx_lock)) { - ASSERT(rw_owner(&rwx->rwx_lock) == curthread); - mutex_enter(&rwx->rwx_mutex); - if (rwx->rwx_waiting) { - rwx->rwx_waiting = B_FALSE; - cv_broadcast(&rwx->rwx_cv); - } - mutex_exit(&rwx->rwx_mutex); - } - rw_exit(&rwx->rwx_lock); + rw_enter(&rwx->rwx_lock, mode); } /* - * smb_rwx_rwupgrade + * smb_rwx_rwexit */ -krw_t -smb_rwx_rwupgrade( +void +smb_rwx_rwexit( smb_rwx_t *rwx) { - if (rw_write_held(&rwx->rwx_lock)) { - ASSERT(rw_owner(&rwx->rwx_lock) == curthread); - return (RW_WRITER); - } - if (!rw_tryupgrade(&rwx->rwx_lock)) { - rw_exit(&rwx->rwx_lock); - rw_enter(&rwx->rwx_lock, RW_WRITER); - } - return (RW_READER); + rw_exit(&rwx->rwx_lock); } -/* - * smb_rwx_rwrestore - */ -void -smb_rwx_rwdowngrade( - smb_rwx_t *rwx, - krw_t mode) -{ - ASSERT(rw_write_held(&rwx->rwx_lock)); - ASSERT(rw_owner(&rwx->rwx_lock) == curthread); - - if (mode == RW_WRITER) { - return; - } - ASSERT(mode == RW_READER); - mutex_enter(&rwx->rwx_mutex); - if (rwx->rwx_waiting) { - rwx->rwx_waiting = B_FALSE; - cv_broadcast(&rwx->rwx_cv); - } - mutex_exit(&rwx->rwx_mutex); - rw_downgrade(&rwx->rwx_lock); -} /* - * smb_rwx_wait + * smb_rwx_cvwait * - * This function assumes the smb_rwx lock was enter in RW_READER or RW_WRITER + * Wait on rwx->rw_cv, dropping the rw lock and retake after wakeup. + * Assumes the smb_rwx lock was entered in RW_READER or RW_WRITER * mode. It will: * * 1) release the lock and save its current mode. - * 2) wait until the condition variable is signaled. This can happen for - * 2 reasons: When a writer releases the lock or when the time out (if - * provided) expires. + * 2) wait until the condition variable is signaled. * 3) re-acquire the lock in the mode saved in (1). + * + * Lock order: rwlock, mutex */ int -smb_rwx_rwwait( +smb_rwx_cvwait( smb_rwx_t *rwx, clock_t timeout) { krw_t mode; int rc = 1; - mutex_enter(&rwx->rwx_mutex); - rwx->rwx_waiting = B_TRUE; - mutex_exit(&rwx->rwx_mutex); - if (rw_write_held(&rwx->rwx_lock)) { ASSERT(rw_owner(&rwx->rwx_lock) == curthread); mode = RW_WRITER; @@ -890,16 +881,16 @@ smb_rwx_rwwait( ASSERT(rw_read_held(&rwx->rwx_lock)); mode = RW_READER; } - rw_exit(&rwx->rwx_lock); mutex_enter(&rwx->rwx_mutex); - if (rwx->rwx_waiting) { - if (timeout == -1) { - cv_wait(&rwx->rwx_cv, &rwx->rwx_mutex); - } else { - rc = cv_reltimedwait(&rwx->rwx_cv, &rwx->rwx_mutex, - timeout, TR_CLOCK_TICK); - } + rw_exit(&rwx->rwx_lock); + + rwx->rwx_waiting = B_TRUE; + if (timeout == -1) { + cv_wait(&rwx->rwx_cv, &rwx->rwx_mutex); + } else { + rc = cv_reltimedwait(&rwx->rwx_cv, &rwx->rwx_mutex, + timeout, TR_CLOCK_TICK); } mutex_exit(&rwx->rwx_mutex); @@ -907,6 +898,24 @@ smb_rwx_rwwait( return (rc); } +/* + * smb_rwx_cvbcast + * + * Wake up threads waiting on rx_cv + * The rw lock may or may not be held. + * The mutex MUST NOT be held. + */ +void +smb_rwx_cvbcast(smb_rwx_t *rwx) +{ + mutex_enter(&rwx->rwx_mutex); + if (rwx->rwx_waiting) { + rwx->rwx_waiting = B_FALSE; + cv_broadcast(&rwx->rwx_cv); + } + mutex_exit(&rwx->rwx_mutex); +} + /* smb_idmap_... moved to smb_idmap.c */ uint64_t @@ -1269,8 +1278,8 @@ smb_avl_destroy(smb_avl_t *avl) * * Returns: * - * ENOTACTIVE AVL is not in READY state - * EEXIST The item is already in AVL + * ENOTACTIVE AVL is not in READY state + * EEXIST The item is already in AVL */ int smb_avl_add(smb_avl_t *avl, void *item) @@ -1737,3 +1746,51 @@ smb_threshold_wake_all(smb_cmd_threshold_t *ct) cv_broadcast(&ct->ct_cond); mutex_exit(&ct->ct_mutex); } + +/* taken from mod_hash_byptr */ +uint_t +smb_hash_uint64(smb_hash_t *hash, uint64_t val) +{ + uint64_t k = val >> hash->rshift; + uint_t idx = ((uint_t)k) & (hash->num_buckets - 1); + + return (idx); +} + +boolean_t +smb_is_pow2(size_t n) +{ + return ((n & (n - 1)) == 0); +} + +smb_hash_t * +smb_hash_create(size_t elemsz, size_t link_offset, + uint32_t num_buckets) +{ + smb_hash_t *hash = kmem_alloc(sizeof (*hash), KM_SLEEP); + int i; + + if (!smb_is_pow2(num_buckets)) + num_buckets = 1 << highbit(num_buckets); + + hash->rshift = highbit(elemsz); + hash->num_buckets = num_buckets; + hash->buckets = kmem_zalloc(num_buckets * sizeof (smb_bucket_t), + KM_SLEEP); + for (i = 0; i < num_buckets; i++) + smb_llist_constructor(&hash->buckets[i].b_list, elemsz, + link_offset); + return (hash); +} + +void +smb_hash_destroy(smb_hash_t *hash) +{ + int i; + + for (i = 0; i < hash->num_buckets; i++) + smb_llist_destructor(&hash->buckets[i].b_list); + + kmem_free(hash->buckets, hash->num_buckets * sizeof (smb_bucket_t)); + kmem_free(hash, sizeof (*hash)); +} diff --git a/usr/src/uts/common/fs/smbsrv/smb_lock.c b/usr/src/uts/common/fs/smbsrv/smb_lock.c index 6afe9396a9..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( @@ -415,6 +417,7 @@ smb_lock_range_access( void smb_node_destroy_lock_by_ofile(smb_node_t *node, smb_ofile_t *file) { + cred_t *kcr = zone_kcred(); smb_lock_t *lock; smb_lock_t *nxtl; list_t destroy_list; @@ -457,7 +460,7 @@ smb_node_destroy_lock_by_ofile(smb_node_t *node, smb_ofile_t *file) nxtl = smb_llist_next(&node->n_lock_list, lock); if (lock->l_file == file) { smb_llist_remove(&node->n_lock_list, lock); - smb_lock_posix_unlock(node, lock, file->f_user->u_cred); + smb_lock_posix_unlock(node, lock, kcr); list_insert_tail(&destroy_list, lock); } lock = nxtl; 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_mangle_name.c b/usr/src/uts/common/fs/smbsrv/smb_mangle_name.c index 7555441f01..b504911f8d 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_mangle_name.c +++ b/usr/src/uts/common/fs/smbsrv/smb_mangle_name.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> @@ -351,11 +351,8 @@ smb_mangle(const char *name, ino64_t fid, char *buf, size_t buflen) * smb_unmangle should only be called on names for which * smb_maybe_mangled() is true * - * File systems which support VFSFT_EDIRENT_FLAGS will return the - * directory entries as a buffer of edirent_t structure. Others will - * return a buffer of dirent64_t structures. A union is used for the - * the pointer into the buffer (bufptr, edp and dp). - * The ed_name/d_name is NULL terminated by the file system. + * The flags arg is no longer used, but retained just to avoid + * changing the many callers of this function. * * Returns: * 0 - SUCCESS. Unmangled name is returned in namebuf. @@ -368,21 +365,17 @@ int smb_unmangle(smb_node_t *dnode, char *name, char *namebuf, int buflen, uint32_t flags) { - int err, eof, bufsize, reclen; + _NOTE(ARGUNUSED(flags)) // avoid changing all callers + int err, eof, bufsize; uint64_t offset; ino64_t ino; - boolean_t is_edp; char *namep, *buf; char shortname[SMB_SHORTNAMELEN]; vnode_t *vp; - union { - char *u_bufptr; - edirent_t *u_edp; - dirent64_t *u_dp; - } u; -#define bufptr u.u_bufptr -#define edp u.u_edp -#define dp u.u_dp + char *bufptr; + dirent64_t *dp; + cred_t *cr = zone_kcred(); + int rc = ENOENT; if (dnode == NULL || name == NULL || namebuf == NULL || buflen == 0) return (EINVAL); @@ -394,57 +387,62 @@ smb_unmangle(smb_node_t *dnode, char *name, char *namebuf, vp = dnode->vp; *namebuf = '\0'; - is_edp = vfs_has_feature(vp->v_vfsp, VFSFT_DIRENTFLAGS); buf = kmem_alloc(SMB_UNMANGLE_BUFSIZE, KM_SLEEP); - bufsize = SMB_UNMANGLE_BUFSIZE; - offset = 0; - - while ((err = smb_vop_readdir(vp, offset, buf, &bufsize, - &eof, flags, zone_kcred())) == 0) { - if (bufsize == 0) { - err = ENOENT; - break; - } - - bufptr = buf; - reclen = 0; - - while ((bufptr += reclen) < buf + bufsize) { - if (is_edp) { - reclen = edp->ed_reclen; - offset = edp->ed_off; - ino = edp->ed_ino; - namep = edp->ed_name; - } else { - reclen = dp->d_reclen; - offset = dp->d_off; - ino = dp->d_ino; - namep = dp->d_name; + bufptr = buf; + bufsize = 0; + offset = 0; // next entry offset + eof = B_FALSE; + + for (;;) { + /* + * Read some entries, if buffer empty or + * we've scanned all of it. Flags zero + * (no edirent, no ABE wanted here) + */ + if (bufsize <= 0) { + bufsize = SMB_UNMANGLE_BUFSIZE; + rc = smb_vop_readdir(vp, offset, buf, + &bufsize, &eof, 0, cr); + if (rc != 0) + break; /* error */ + if (bufsize == 0) { + eof = B_TRUE; + rc = ENOENT; + break; } + bufptr = buf; + } + /* LINTED pointer alignment */ + dp = (dirent64_t *)bufptr; + + /* + * Partial records are not supposed to happen, + * but let's be defensive. If this happens, + * restart at the current offset. + */ + bufptr += dp->d_reclen; + bufsize -= dp->d_reclen; + if (bufsize < 0) + continue; - /* skip non utf8 filename */ - if (u8_validate(namep, strlen(namep), NULL, - U8_VALIDATE_ENTIRE, &err) < 0) - continue; - - smb_mangle(namep, ino, shortname, SMB_SHORTNAMELEN); + offset = dp->d_off; + ino = dp->d_ino; + namep = dp->d_name; - if (smb_strcasecmp(name, shortname, 0) == 0) { - (void) strlcpy(namebuf, namep, buflen); - kmem_free(buf, SMB_UNMANGLE_BUFSIZE); - return (0); - } - } + /* skip non utf8 filename */ + if (u8_validate(namep, strlen(namep), NULL, + U8_VALIDATE_ENTIRE, &err) < 0) + continue; - if (eof) { - err = ENOENT; + smb_mangle(namep, ino, shortname, SMB_SHORTNAMELEN); + if (smb_strcasecmp(name, shortname, 0) == 0) { + (void) strlcpy(namebuf, namep, buflen); + rc = 0; break; } - - bufsize = SMB_UNMANGLE_BUFSIZE; } kmem_free(buf, SMB_UNMANGLE_BUFSIZE); - return (err); + return (rc); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c b/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c index f9b629b24f..132820a147 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c +++ b/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c @@ -916,6 +916,23 @@ smb_mbc_put_mem(mbuf_chain_t *mbc, void *vmem, int mem_len) } /* + * Put padding sufficient to align to A, where + * A is some power of 2 greater than zero. + */ +int +smb_mbc_put_align(mbuf_chain_t *mbc, int align) +{ + int mask = align - 1; + int padsz; + + ASSERT(align > 0 && (align & mask) == 0); + if ((mbc->chain_offset & mask) == 0) + return (0); + padsz = align - (mbc->chain_offset & mask); + return (smb_mbc_encodef(mbc, "#.", padsz)); +} + +/* * Put data into mbuf chain allocating as needed. * Adds room to end of mbuf chain if needed. */ diff --git a/usr/src/uts/common/fs/smbsrv/smb_node.c b/usr/src/uts/common/fs/smbsrv/smb_node.c index eb34b04038..63756f9037 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_node.c +++ b/usr/src/uts/common/fs/smbsrv/smb_node.c @@ -99,7 +99,6 @@ #include <fs/fs_reparse.h> uint32_t smb_is_executable(char *); -static void smb_node_delete_on_close(smb_node_t *); static void smb_node_create_audit_buf(smb_node_t *, int); static void smb_node_destroy_audit_buf(smb_node_t *); static void smb_node_audit(smb_node_t *); @@ -190,6 +189,7 @@ smb_node_fini(void) #ifdef DEBUG for (i = 0; i <= SMBND_HASH_MASK; i++) { + smb_llist_t *bucket; smb_node_t *node; /* @@ -205,8 +205,13 @@ smb_node_fini(void) * smb_node_lookup() and smb_node_release(). You must track that * down. */ - node = smb_llist_head(&smb_node_hash_table[i]); - ASSERT(node == NULL); + bucket = &smb_node_hash_table[i]; + node = smb_llist_head(bucket); + while (node != NULL) { + cmn_err(CE_NOTE, "leaked node: 0x%p %s", + (void *)node, node->od_name); + node = smb_llist_next(bucket, node); + } } #endif @@ -482,7 +487,9 @@ smb_node_release(smb_node_t *node) /* * Check if the file was deleted */ - smb_node_delete_on_close(node); + if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) { + smb_node_delete_on_close(node); + } if (node->n_dnode) { ASSERT(node->n_dnode->n_magic == @@ -507,7 +514,7 @@ smb_node_release(smb_node_t *node) mutex_exit(&node->n_mutex); } -static void +void smb_node_delete_on_close(smb_node_t *node) { smb_node_t *d_snode; @@ -515,19 +522,23 @@ smb_node_delete_on_close(smb_node_t *node) uint32_t flags = 0; d_snode = node->n_dnode; - if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) { - node->flags &= ~NODE_FLAGS_DELETE_ON_CLOSE; - flags = node->n_delete_on_close_flags; - ASSERT(node->od_name != NULL); - - if (smb_node_is_dir(node)) - rc = smb_fsop_rmdir(0, node->delete_on_close_cred, - d_snode, node->od_name, flags); - else - rc = smb_fsop_remove(0, node->delete_on_close_cred, - d_snode, node->od_name, flags); - crfree(node->delete_on_close_cred); - } + + ASSERT((node->flags & NODE_FLAGS_DELETE_ON_CLOSE) != 0); + + node->flags &= ~NODE_FLAGS_DELETE_ON_CLOSE; + node->flags |= NODE_FLAGS_DELETE_COMMITTED; + flags = node->n_delete_on_close_flags; + ASSERT(node->od_name != NULL); + + if (smb_node_is_dir(node)) + rc = smb_fsop_rmdir(0, node->delete_on_close_cred, + d_snode, node->od_name, flags); + else + rc = smb_fsop_remove(0, node->delete_on_close_cred, + d_snode, node->od_name, flags); + crfree(node->delete_on_close_cred); + node->delete_on_close_cred = NULL; + if (rc != 0) cmn_err(CE_WARN, "File %s could not be removed, rc=%d\n", node->od_name, rc); @@ -600,40 +611,47 @@ smb_node_root_init(smb_server_t *sv, smb_node_t **svrootp) * and check for anything other than "." or ".." in the readdir buf. */ static uint32_t -smb_rmdir_possible(smb_node_t *n, uint32_t flags) +smb_rmdir_possible(smb_node_t *n) { ASSERT(n->vp->v_type == VDIR); - char buf[512]; /* Only large enough to see if the dir is empty. */ - int eof, bsize = sizeof (buf), reclen = 0; - char *name; - boolean_t edp = vfs_has_feature(n->vp->v_vfsp, VFSFT_DIRENTFLAGS); - - union { - char *u_bufptr; - struct edirent *u_edp; - struct dirent64 *u_dp; - } u; -#define bufptr u.u_bufptr -#define extdp u.u_edp -#define dp u.u_dp - - if (smb_vop_readdir(n->vp, 0, buf, &bsize, &eof, flags, zone_kcred())) - return (NT_STATUS_INTERNAL_ERROR); - if (bsize == 0) - return (0); /* empty dir */ + char *buf; + char *bufptr; + struct dirent64 *dp; + uint32_t status = NT_STATUS_SUCCESS; + int bsize = SMB_ODIR_BUFSIZE; + int eof = 0; + + buf = kmem_alloc(SMB_ODIR_BUFSIZE, KM_SLEEP); + + /* Flags zero: no edirent, no ABE wanted here */ + if (smb_vop_readdir(n->vp, 0, buf, &bsize, &eof, 0, zone_kcred())) { + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + bufptr = buf; - while ((bufptr += reclen) < buf + bsize) { - if (edp) { - reclen = extdp->ed_reclen; - name = extdp->ed_name; - } else { - reclen = dp->d_reclen; - name = dp->d_name; + while (bsize > 0) { + /* LINTED pointer alignment */ + dp = (struct dirent64 *)bufptr; + + bufptr += dp->d_reclen; + bsize -= dp->d_reclen; + if (bsize < 0) { + /* partial record */ + status = NT_STATUS_DIRECTORY_NOT_EMPTY; + break; + } + + if (strcmp(dp->d_name, ".") != 0 && + strcmp(dp->d_name, "..") != 0) { + status = NT_STATUS_DIRECTORY_NOT_EMPTY; + break; } - if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) - return (NT_STATUS_DIRECTORY_NOT_EMPTY); } - return (0); + +out: + kmem_free(buf, SMB_ODIR_BUFSIZE); + return (status); } /* @@ -661,7 +679,7 @@ smb_node_set_delete_on_close(smb_node_t *node, cred_t *cr, uint32_t flags) * "File System Behavior Overview" doc section 4.3.2 */ if (smb_node_is_dir(node)) { - status = smb_rmdir_possible(node, flags); + status = smb_rmdir_possible(node); if (status != 0) { return (status); } @@ -733,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); } @@ -765,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); } @@ -802,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); } @@ -1160,7 +1184,6 @@ smb_node_alloc( node->n_refcnt = 1; node->n_hash_bucket = bucket; node->n_hashkey = hashkey; - node->n_pending_dosattr = 0; node->n_open_count = 0; node->n_allocsz = 0; node->n_dnode = NULL; @@ -1168,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) @@ -1201,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)); @@ -1223,15 +1241,13 @@ smb_node_constructor(void *buf, void *un, int kmflags) bzero(node, sizeof (smb_node_t)); smb_llist_constructor(&node->n_ofile_list, sizeof (smb_ofile_t), - offsetof(smb_ofile_t, f_nnd)); + offsetof(smb_ofile_t, f_node_lnd)); smb_llist_constructor(&node->n_lock_list, sizeof (smb_lock_t), 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); @@ -1251,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); } /* @@ -1382,9 +1397,9 @@ smb_node_is_system(smb_node_t *node) * smb_node_file_is_readonly * * Checks if the file (which node represents) is marked readonly - * in the filesystem. No account is taken of any pending readonly - * in the node, which must be handled by the callers. - * (See SMB_OFILE_IS_READONLY and SMB_PATHFILE_IS_READONLY) + * in the filesystem. Note that there may be handles open with + * modify rights, and those continue to allow access even after + * the DOS read-only flag has been set in the file system. */ boolean_t smb_node_file_is_readonly(smb_node_t *node) @@ -1394,9 +1409,6 @@ smb_node_file_is_readonly(smb_node_t *node) if (node == NULL) return (B_FALSE); /* pipes */ - if (node->n_pending_dosattr & FILE_ATTRIBUTE_READONLY) - return (B_TRUE); - bzero(&attr, sizeof (smb_attr_t)); attr.sa_mask = SMB_AT_DOSATTR; (void) smb_fsop_getattr(NULL, zone_kcred(), node, &attr); @@ -1568,40 +1580,18 @@ smb_node_setattr(smb_request_t *sr, smb_node_t *node, */ } - /* - * After this point, tmp_attr is what we will actually - * store in the file system _now_, which may differ - * from the callers attr and f_pending_attr w.r.t. - * the DOS readonly flag etc. - */ - bcopy(attr, &tmp_attr, sizeof (tmp_attr)); - if (attr->sa_mask & (SMB_AT_DOSATTR | SMB_AT_ALLOCSZ)) { + if ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0) { mutex_enter(&node->n_mutex); - if ((attr->sa_mask & SMB_AT_DOSATTR) != 0) { - tmp_attr.sa_dosattr &= smb_vop_dosattr_settable; - if (((tmp_attr.sa_dosattr & - FILE_ATTRIBUTE_READONLY) != 0) && - (node->n_open_count != 0)) { - /* Delay setting readonly */ - node->n_pending_dosattr = - tmp_attr.sa_dosattr; - tmp_attr.sa_dosattr &= - ~FILE_ATTRIBUTE_READONLY; - } else { - node->n_pending_dosattr = 0; - } - } /* * Simulate n_allocsz persistence only while * there are opens. See smb_node_getattr */ - if ((attr->sa_mask & SMB_AT_ALLOCSZ) != 0 && - node->n_open_count != 0) + if (node->n_open_count != 0) node->n_allocsz = attr->sa_allocsz; mutex_exit(&node->n_mutex); } - rc = smb_fsop_setattr(sr, cr, node, &tmp_attr); + rc = smb_fsop_setattr(sr, cr, node, attr); if (rc != 0) return (rc); @@ -1650,21 +1640,7 @@ smb_node_getattr(smb_request_t *sr, smb_node_t *node, cred_t *cr, mutex_enter(&node->n_mutex); - /* - * When there are open handles, and one of them has - * set the DOS readonly flag (in n_pending_dosattr), - * it will not have been stored in the file system. - * In this case use n_pending_dosattr. Note that - * n_pending_dosattr has only the settable bits, - * (setattr masks it with smb_vop_dosattr_settable) - * so we need to keep any non-settable bits we got - * from the file-system above. - */ if (attr->sa_mask & SMB_AT_DOSATTR) { - if (node->n_pending_dosattr) { - attr->sa_dosattr &= ~smb_vop_dosattr_settable; - attr->sa_dosattr |= node->n_pending_dosattr; - } if (attr->sa_dosattr == 0) { attr->sa_dosattr = (isdir) ? FILE_ATTRIBUTE_DIRECTORY: 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_odir.c b/usr/src/uts/common/fs/smbsrv/smb_odir.c index 54fa075dbe..d2f56b47b0 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_odir.c +++ b/usr/src/uts/common/fs/smbsrv/smb_odir.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* @@ -264,6 +264,7 @@ static int smb_odir_next_odirent(smb_odir_t *, smb_odirent_t *); static boolean_t smb_odir_lookup_link(smb_request_t *, smb_odir_t *, char *, smb_node_t **); static boolean_t smb_odir_match_name(smb_odir_t *, smb_odirent_t *); +static void smb_odir_delete(void *); /* @@ -282,7 +283,7 @@ smb_odir_openpath(smb_request_t *sr, char *path, uint16_t sattr, smb_tree_t *tree; smb_node_t *dnode; char pattern[MAXNAMELEN]; - uint16_t odid; + uint16_t odid; cred_t *cr; ASSERT(sr); @@ -445,6 +446,8 @@ smb_odir_hold(smb_odir_t *od) void smb_odir_release(smb_odir_t *od) { + smb_tree_t *tree = od->d_tree; + SMB_ODIR_VALID(od); mutex_enter(&od->d_mutex); @@ -462,7 +465,8 @@ smb_odir_release(smb_odir_t *od) od->d_refcnt--; if (od->d_refcnt == 0) { od->d_state = SMB_ODIR_STATE_CLOSED; - smb_tree_post_odir(od->d_tree, od); + smb_llist_post(&tree->t_odir_list, od, + smb_odir_delete); } break; case SMB_ODIR_STATE_CLOSED: @@ -989,7 +993,7 @@ smb_odir_reopen(smb_odir_t *od, const char *pattern, uint16_t sattr) * Remove the odir from the tree list before freeing resources * associated with the odir. */ -void +static void smb_odir_delete(void *arg) { smb_tree_t *tree; @@ -1007,6 +1011,13 @@ smb_odir_delete(void *arg) atomic_dec_32(&tree->t_session->s_dir_cnt); smb_llist_exit(&tree->t_odir_list); + /* + * This odir is no longer on t_odir_list, however... + * + * This is called via smb_llist_post, which means it may run + * BEFORE smb_odir_release drops d_mutex (if another thread + * flushes the delete queue before we do). Synchronize. + */ mutex_enter(&od->d_mutex); mutex_exit(&od->d_mutex); @@ -1048,12 +1059,17 @@ smb_odir_next_odirent(smb_odir_t *od, smb_odirent_t *odirent) dirent64_t *dp; edirent_t *edp; char *np; - uint32_t abe_flag = 0; + uint32_t rddir_flags = 0; ASSERT(MUTEX_HELD(&od->d_mutex)); bzero(odirent, sizeof (smb_odirent_t)); + if (od->d_flags & SMB_ODIR_FLAG_ABE) + rddir_flags |= SMB_ABE; + if (od->d_flags & SMB_ODIR_FLAG_EDIRENT) + rddir_flags |= SMB_EDIRENT; + if (od->d_bufptr != NULL) { if (od->d_flags & SMB_ODIR_FLAG_EDIRENT) reclen = od->d_edp->ed_reclen; @@ -1075,11 +1091,8 @@ smb_odir_next_odirent(smb_odir_t *od, smb_odirent_t *odirent) od->d_bufsize = sizeof (od->d_buf); - if (od->d_flags & SMB_ODIR_FLAG_ABE) - abe_flag = SMB_ABE; - rc = smb_vop_readdir(od->d_dnode->vp, od->d_offset, - od->d_buf, &od->d_bufsize, &eof, abe_flag, od->d_cred); + od->d_buf, &od->d_bufsize, &eof, rddir_flags, od->d_cred); if ((rc == 0) && (od->d_bufsize == 0)) rc = ENOENT; diff --git a/usr/src/uts/common/fs/smbsrv/smb_ofile.c b/usr/src/uts/common/fs/smbsrv/smb_ofile.c index 25dee633bf..046f550710 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_ofile.c +++ b/usr/src/uts/common/fs/smbsrv/smb_ofile.c @@ -22,7 +22,7 @@ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2016 Syneto S.R.L. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. - * Copyright 2019 Nexenta Systems, Inc. All rights reserved. + * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ /* @@ -71,22 +71,32 @@ * ------------------ * * +-------------------------+ T0 - * | SMB_OFILE_STATE_OPEN |<----------- Creation/Allocation + * | SMB_OFILE_STATE_OPEN |<--+-------- Creation/Allocation + * +-------------------------+ | + * | | | T5 + * | | +---------------------------+ + * | | | SMB_OFILE_STATE_RECONNECT | + * | | +---------------------------+ + * | | ^ + * | v | + * | +---------------+ | + * | | STATE_SAVE_DH | | + * | | STATE_SAVING | | + * | +---------------+ | + * | | | T4 + * | T1 | T3 +--------------------------+ + * | +------>| SMB_OFILE_STATE_ORPHANED | + * v +--------------------------+ + * +-------------------------+ | | + * | SMB_OFILE_STATE_CLOSING |<--+ T6 | T7 + * +-------------------------+ | + * | ^ v + * | T2 | T8 +-------------------------+ + * | +-------| SMB_OFILE_STATE_EXPIRED | + * v +-------------------------+ * +-------------------------+ - * | - * | T1 - * | - * v - * +-------------------------+ - * | SMB_OFILE_STATE_CLOSING | - * +-------------------------+ - * | - * | T2 - * | - * v - * +-------------------------+ T3 * | SMB_OFILE_STATE_CLOSED |----------> Deletion/Free - * +-------------------------+ + * +-------------------------+ T9 * * SMB_OFILE_STATE_OPEN * @@ -94,8 +104,34 @@ * - The ofile is queued in the list of ofiles of its tree. * - References will be given out if the ofile is looked up. * + * SMB_OFILE_STATE_SAVE_DH + * + * Similar to state _CLOSING, but instead of deleting the ofile, + * it leaves the ofile in state _ORPHANED (for later reclaim). + * Will move to _SAVING after last ref, then _ORPHANED. + * + * While in this state: + * - The ofile has been marked for preservation during a + * walk of the tree ofile list to close multiple files. + * - References will not be given out if the ofile is looked up, + * except for oplock break processing. + * - Still affects Sharing Violation rules + * + * SMB_OFILE_STATE_SAVING + * + * Transient state used to keep oplock break processing out + * while the ofile moves to state _ORPHANED. + * + * While in this state: + * - References will not be given out if the ofile is looked up, + * except for oplock break processing. + * - Still affects Sharing Violation rules + * * SMB_OFILE_STATE_CLOSING * + * Close has been requested. Stay in this state until the last + * ref. is gone, then move to state _CLOSED + * * While in this state: * - The ofile is queued in the list of ofiles of its tree. * - References will not be given out if the ofile is looked up. @@ -109,6 +145,38 @@ * - References will not be given out if the ofile is looked up. * - The resources associated with the ofile remain. * + * SMB_OFILE_STATE_ORPHANED + * + * While in this state: + * - The ofile is queued in the list of ofiles of its tree. + * - Can be reclaimed by the original owner + * - References will not be given out if the ofile is looked up. + * - All the tree, user, and session "up" pointers are NULL! + * - Will eventually be "expired" if not reclaimed + * - Can be closed if its oplock is broken + * - Still affects Sharing Violation rules + * + * SMB_OFILE_STATE_EXPIRED + * + * While in this state: + * - The ofile is queued in the list of ofiles of its tree. + * - References will not be given out if the ofile is looked up. + * - The ofile has not been reclaimed and will soon be closed, + * due to, for example, the durable handle timer expiring, or its + * oplock being broken. + * - Cannot be reclaimed at this point + * + * SMB_OFILE_STATE_RECONNECT + * + * Transient state used to keep oplock break processing out + * while the ofile moves from state _ORPHANED to _OPEN. + * + * While in this state: + * - The ofile is being reclaimed; do not touch it. + * - References will not be given out if the ofile is looked up. + * - Still affects Sharing Violation rules + * - see smb2_dh_reconnect() for which members need to be avoided + * * Transition T0 * * This transition occurs in smb_ofile_open(). A new ofile is created and @@ -116,7 +184,9 @@ * * Transition T1 * - * This transition occurs in smb_ofile_close(). + * This transition occurs in smb_ofile_close(). Note that this only happens + * when we determine that an ofile should be closed in spite of its durable + * handle properties. * * Transition T2 * @@ -125,6 +195,50 @@ * transition to occur, the ofile must be in the SMB_OFILE_STATE_CLOSED * state and the reference count be zero. * + * Transition T3 + * + * This transition occurs in smb_ofile_orphan_dh(). It happens during an + * smb2 logoff, or during a session disconnect when certain conditions are + * met. The ofile and structures above it will be kept around until the ofile + * either gets reclaimed, expires after f_timeout_offset nanoseconds, or its + * oplock is broken. + * + * Transition T4 + * + * This transition occurs in smb2_dh_reconnect(). An smb2 create request + * with a DURABLE_HANDLE_RECONNECT(_V2) create context has been + * recieved from the original owner. If leases are supported or it's + * RECONNECT_V2, reconnect is subject to additional conditions. The ofile + * will be unwired from the old, disconnected session, tree, and user, + * and wired up to its new context. + * + * Transition T5 + * + * This transition occurs in smb2_dh_reconnect(). The ofile has been + * successfully reclaimed. + * + * Transition T6 + * + * This transition occurs in smb_ofile_close(). The ofile has been orphaned + * while some thread was blocked, and that thread closes the ofile. Can only + * happen when the ofile is orphaned due to an SMB2 LOGOFF request. + * + * Transition T7 + * + * This transition occurs in smb_session_durable_timers() and + * 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. + * + * Transition T8 + * + * This transition occurs in smb_ofile_close(). + * + * Transition T9 + * + * This transition occurs in smb_ofile_delete(). + * * Comments * -------- * @@ -142,7 +256,8 @@ * Rules of access to a ofile structure: * * 1) In order to avoid deadlocks, when both (mutex and lock of the ofile - * list) have to be entered, the lock must be entered first. + * list) have to be entered, the lock must be entered first. Additionally, + * f_mutex must not be held when removing the ofile from sv_persistid_ht. * * 2) All actions applied to an ofile require a reference count. * @@ -162,40 +277,45 @@ * being queued in that list is NOT registered by incrementing the * reference count. */ -#include <smbsrv/smb_kproto.h> +#include <smbsrv/smb2_kproto.h> #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)) static boolean_t smb_ofile_is_open_locked(smb_ofile_t *); -static smb_ofile_t *smb_ofile_close_and_next(smb_ofile_t *); +static void smb_ofile_delete(void *arg); +static void smb_ofile_save_dh(void *arg); + static int smb_ofile_netinfo_encode(smb_ofile_t *, uint8_t *, size_t, uint32_t *); 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)); @@ -205,9 +325,11 @@ 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_fid = fid; + of->f_ftype = ftype; + of->f_fid = tree_fid; + /* of->f_persistid see smb2_create */ of->f_uniqid = uniqid; of->f_opened_by_pid = sr->smb_pid; of->f_granted_access = op->desired_access; @@ -216,127 +338,115 @@ smb_ofile_open( of->f_cr = (op->create_options & FILE_OPEN_FOR_BACKUP_INTENT) ? smb_user_getprivcred(sr->uid_user) : sr->uid_user->u_cred; crhold(of->f_cr); - of->f_ftype = ftype; of->f_server = tree->t_server; 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 - * released in smb_ofile_delete() + * grab a ref for of->f_user and of->f_tree + * 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); of->f_user = sr->uid_user; 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); - - /* - * 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; - - /* - * 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; - } + return (of); +} - 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; - } - } +/* + * 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; - if (tree->t_flags & SMB_TREE_READONLY) - of->f_flags |= SMB_OFLAGS_READONLY; + ASSERT(of->f_state == SMB_OFILE_STATE_ALLOC); + of->f_state = SMB_OFILE_STATE_OPEN; - /* - * Note that if we created_readonly, that - * will _not_ yet show in attr.sa_dosattr - * so creating a readonly file gives the - * caller a writable handle as it should. - */ - if (attr.sa_dosattr & FILE_ATTRIBUTE_READONLY) - of->f_flags |= SMB_OFLAGS_READONLY; + 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_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); } /* * smb_ofile_close + * + * Incoming states: (where from) + * SMB_OFILE_STATE_OPEN protocol close, smb_ofile_drop + * SMB_OFILE_STATE_EXPIRED called via smb2_dh_expire + * SMB_OFILE_STATE_ORPHANED smb2_dh_shutdown */ void smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) { smb_attr_t *pa; timestruc_t now; - uint32_t flags = 0; SMB_OFILE_VALID(of); mutex_enter(&of->f_mutex); ASSERT(of->f_refcnt); - if (of->f_state != SMB_OFILE_STATE_OPEN) { + + switch (of->f_state) { + case SMB_OFILE_STATE_OPEN: + case SMB_OFILE_STATE_ORPHANED: + case SMB_OFILE_STATE_EXPIRED: + of->f_state = SMB_OFILE_STATE_CLOSING; + mutex_exit(&of->f_mutex); + break; + default: mutex_exit(&of->f_mutex); return; } - of->f_state = SMB_OFILE_STATE_CLOSING; - mutex_exit(&of->f_mutex); switch (of->f_ftype) { case SMB_FTYPE_BYTE_PIPE: @@ -346,7 +456,14 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) break; case SMB_FTYPE_DISK: - case SMB_FTYPE_PRINTER: + 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 * while not holding of->f_mutex. This is OK @@ -374,10 +491,8 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) } if (of->f_flags & SMB_OFLAGS_SET_DELETE_ON_CLOSE) { - if (smb_tree_has_feature(of->f_tree, - SMB_TREE_CATIA)) { - flags |= SMB_CATIA; - } + /* We delete using the on-disk name. */ + uint32_t flags = SMB_CASE_SENSITIVE; (void) smb_node_set_delete_on_close(of->f_node, of->f_cr, flags); } @@ -387,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. @@ -416,17 +530,20 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) } if (smb_node_dec_open_ofiles(of->f_node) == 0) { /* - * Last close. The f_pending_attr has - * only times (atime,ctime,mtime) so - * we can borrow it to commit the - * n_pending_dosattr from the node. + * Last close. If we're not deleting + * the file, apply any pending attrs. + * 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. */ - pa->sa_dosattr = - of->f_node->n_pending_dosattr; - if (pa->sa_dosattr != 0) - pa->sa_mask |= SMB_AT_DOSATTR; - /* Let's leave this zero when not in use. */ + mutex_enter(&of->f_node->n_mutex); + if (of->f_node->flags & NODE_FLAGS_DELETE_ON_CLOSE) { + smb_node_delete_on_close(of->f_node); + pa->sa_mask = 0; + } of->f_node->n_allocsz = 0; + mutex_exit(&of->f_node->n_mutex); } if (pa->sa_mask != 0) { /* @@ -435,9 +552,6 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) * we pass NULL as the ofile to setattr * so it will write to the file system * and not keep anything on the ofile. - * This clears n_pending_dosattr if - * there are no opens, otherwise the - * dosattr will be pending again. */ (void) smb_node_setattr(NULL, of->f_node, of->f_cr, NULL, pa); @@ -446,66 +560,107 @@ smb_ofile_close(smb_ofile_t *of, int32_t mtime_sec) smb_server_dec_files(of->f_server); break; } - atomic_dec_32(&of->f_tree->t_open_files); - mutex_enter(&of->f_mutex); - ASSERT(of->f_refcnt); - ASSERT(of->f_state == SMB_OFILE_STATE_CLOSING); - of->f_state = SMB_OFILE_STATE_CLOSED; - mutex_exit(&of->f_mutex); + /* + * Keep f_state == SMB_OFILE_STATE_CLOSING + * until the last ref. is dropped, in + * smb_ofile_release() + */ } /* - * smb_ofile_close_all + * "Destructor" function for smb_ofile_close_all, and + * smb_ofile_close_all_by_pid, called after the llist lock + * for tree list has been exited. Our job is to either + * close this ofile, or (if durable) set state _SAVE_DH. * + * The next interesting thing happens when the last ref. + * on this ofile calls smb_ofile_release(), where we + * eihter delete the ofile, or (if durable) leave it + * in the persistid hash table for possible reclaim. * + * This is run via smb_llist_post (after smb_llist_exit) + * because smb_ofile_close can block, and we'd rather not + * block while holding the ofile list as reader. */ -void -smb_ofile_close_all( - smb_tree_t *tree) +static void +smb_ofile_drop(void *arg) { - smb_ofile_t *of; + smb_ofile_t *of = arg; - ASSERT(tree); - ASSERT(tree->t_magic == SMB_TREE_MAGIC); + SMB_OFILE_VALID(of); - smb_llist_enter(&tree->t_ofile_list, RW_READER); - of = smb_llist_head(&tree->t_ofile_list); - while (of) { - ASSERT(of->f_magic == SMB_OFILE_MAGIC); - ASSERT(of->f_tree == tree); - of = smb_ofile_close_and_next(of); + mutex_enter(&of->f_mutex); + switch (of->f_state) { + case SMB_OFILE_STATE_OPEN: + /* DH checks under mutex. */ + if (of->f_ftype == SMB_FTYPE_DISK && + of->dh_vers != SMB2_NOT_DURABLE && + smb_dh_should_save(of)) { + /* + * Tell smb_ofile_release() to + * make this an _ORPHANED DH. + */ + of->f_state = SMB_OFILE_STATE_SAVE_DH; + mutex_exit(&of->f_mutex); + break; + } + /* OK close it. */ + mutex_exit(&of->f_mutex); + smb_ofile_close(of, 0); + break; + + default: + /* Something else closed it already. */ + mutex_exit(&of->f_mutex); + break; } - smb_llist_exit(&tree->t_ofile_list); + + /* + * Release the ref acquired during the traversal loop. + * Note that on the last ref, this ofile will be + * removed from the tree list etc. + * See: smb_llist_post, smb_ofile_delete + */ + smb_ofile_release(of); } /* - * smb_ofiles_close_by_pid + * smb_ofile_close_all * * */ void -smb_ofile_close_all_by_pid( +smb_ofile_close_all( smb_tree_t *tree, - uint16_t pid) + uint32_t pid) { smb_ofile_t *of; + smb_llist_t *ll; ASSERT(tree); ASSERT(tree->t_magic == SMB_TREE_MAGIC); - smb_llist_enter(&tree->t_ofile_list, RW_READER); - of = smb_llist_head(&tree->t_ofile_list); - while (of) { + ll = &tree->t_ofile_list; + + smb_llist_enter(ll, RW_READER); + for (of = smb_llist_head(ll); + of != NULL; + of = smb_llist_next(ll, of)) { ASSERT(of->f_magic == SMB_OFILE_MAGIC); ASSERT(of->f_tree == tree); - if (of->f_opened_by_pid == pid) { - of = smb_ofile_close_and_next(of); - } else { - of = smb_llist_next(&tree->t_ofile_list, of); + if (pid != 0 && of->f_opened_by_pid != pid) + continue; + if (smb_ofile_hold(of)) { + smb_llist_post(ll, of, smb_ofile_drop); } } - smb_llist_exit(&tree->t_ofile_list); + + /* + * Drop the lock and process the llist dtor queue. + * Calls smb_ofile_drop on ofiles that were open. + */ + smb_llist_exit(ll); } /* @@ -552,6 +707,53 @@ smb_ofile_enum(smb_ofile_t *of, smb_svcenum_t *svcenum) } /* + * Take a reference on an open file, in any of the states: + * RECONNECT, SAVE_DH, OPEN, ORPHANED. + * Return TRUE if ref taken. Used for oplock breaks. + * + * Note: When the oplock break code calls this, it holds the + * node ofile list lock and node oplock mutex. When we see + * an ofile in states RECONNECT or SAVING, we know the ofile + * is gaining or losing it's tree, and that happens quickly, + * so we just wait for that work to finish. However, the + * waiting for state transitions here means we have to be + * careful not to re-enter the node list lock or otherwise + * block on things that could cause a deadlock. Waiting + * just on of->f_mutex here is OK. + */ +boolean_t +smb_ofile_hold_olbrk(smb_ofile_t *of) +{ + boolean_t ret = B_FALSE; + + ASSERT(of); + ASSERT(of->f_magic == SMB_OFILE_MAGIC); + + mutex_enter(&of->f_mutex); + +again: + switch (of->f_state) { + case SMB_OFILE_STATE_RECONNECT: + case SMB_OFILE_STATE_SAVING: + cv_wait(&of->f_cv, &of->f_mutex); + goto again; + + case SMB_OFILE_STATE_OPEN: + case SMB_OFILE_STATE_ORPHANED: + case SMB_OFILE_STATE_SAVE_DH: + of->f_refcnt++; + ret = B_TRUE; + break; + + default: + break; + } + mutex_exit(&of->f_mutex); + + return (ret); +} + +/* * Take a reference on an open file. */ boolean_t @@ -577,23 +779,50 @@ smb_ofile_hold(smb_ofile_t *of) * zero and the file has been closed, post the object for deletion. * Object deletion is deferred to avoid modifying a list while an * iteration may be in progress. + * + * We're careful to avoid dropping f_session etc. until the last + * reference goes away. The oplock break code depends on that + * not changing while it holds a ref. on an ofile. */ void smb_ofile_release(smb_ofile_t *of) { + smb_tree_t *tree = of->f_tree; + boolean_t delete = B_FALSE; + SMB_OFILE_VALID(of); mutex_enter(&of->f_mutex); - ASSERT(of->f_refcnt); + ASSERT(of->f_refcnt > 0); of->f_refcnt--; + switch (of->f_state) { case SMB_OFILE_STATE_OPEN: - case SMB_OFILE_STATE_CLOSING: + case SMB_OFILE_STATE_ORPHANED: + case SMB_OFILE_STATE_EXPIRED: break; - case SMB_OFILE_STATE_CLOSED: - if (of->f_refcnt == 0) - smb_tree_post_ofile(of->f_tree, of); + case SMB_OFILE_STATE_SAVE_DH: + ASSERT(tree != NULL); + if (of->f_refcnt == 0) { + of->f_state = SMB_OFILE_STATE_SAVING; + smb_llist_post(&tree->t_ofile_list, of, + smb_ofile_save_dh); + } + break; + + case SMB_OFILE_STATE_CLOSING: + /* Note, tree == NULL on _ORPHANED */ + if (of->f_refcnt == 0) { + of->f_state = SMB_OFILE_STATE_CLOSED; + if (tree == NULL) { + /* Skip smb_llist_post */ + delete = B_TRUE; + break; + } + smb_llist_post(&tree->t_ofile_list, of, + smb_ofile_delete); + } break; default: @@ -601,34 +830,14 @@ smb_ofile_release(smb_ofile_t *of) break; } mutex_exit(&of->f_mutex); -} -/* - * 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; + /* + * When we drop the last ref. on an expired DH, it's no longer + * in any tree, so skip the smb_llist_post and just call + * smb_ofile_delete directly. + */ + if (delete) { + smb_ofile_delete(of); } } @@ -673,6 +882,7 @@ smb_ofile_lookup_by_fid( goto out; } + /* inline smb_ofile_hold() */ mutex_enter(&of->f_mutex); if (of->f_state != SMB_OFILE_STATE_OPEN) { mutex_exit(&of->f_mutex); @@ -722,6 +932,92 @@ smb_ofile_lookup_by_uniqid(smb_tree_t *tree, uint32_t uniqid) return (NULL); } +static smb_ofile_t * +smb_ofile_hold_cb(smb_ofile_t *of) +{ + smb_ofile_t *ret = of; + + mutex_enter(&of->f_mutex); + if (of->f_state == SMB_OFILE_STATE_ORPHANED) + /* inline smb_ofile_hold() */ + of->f_refcnt++; + else + ret = NULL; + + mutex_exit(&of->f_mutex); + return (ret); +} + +/* + * Lookup an ofile by persistent ID, and return ONLY if in state ORPHANED + * This is used by SMB2 create "reclaim". + */ +smb_ofile_t * +smb_ofile_lookup_by_persistid(smb_request_t *sr, uint64_t persistid) +{ + smb_hash_t *hash; + smb_bucket_t *bucket; + smb_llist_t *ll; + smb_ofile_t *of; + uint_t idx; + + hash = sr->sr_server->sv_persistid_ht; + idx = smb_hash_uint64(hash, persistid); + bucket = &hash->buckets[idx]; + ll = &bucket->b_list; + + smb_llist_enter(ll, RW_READER); + of = smb_llist_head(ll); + while (of != NULL) { + if (of->f_persistid == persistid) + break; + of = smb_llist_next(ll, of); + } + if (of != NULL) + of = smb_ofile_hold_cb(of); + smb_llist_exit(ll); + + return (of); +} + +/* + * Create a (unique) persistent ID for a new ofile, + * and add this ofile to the persistid hash table. + */ +void +smb_ofile_set_persistid(smb_ofile_t *of) +{ + smb_hash_t *hash = of->f_server->sv_persistid_ht; + smb_bucket_t *bucket; + smb_llist_t *ll; + uint_t idx; + + of->f_persistid = SMB_OFILE_PERSISTID(of); + + idx = smb_hash_uint64(hash, of->f_persistid); + bucket = &hash->buckets[idx]; + ll = &bucket->b_list; + smb_llist_enter(ll, RW_WRITER); + smb_llist_insert_tail(ll, of); + smb_llist_exit(ll); +} + +void +smb_ofile_del_persistid(smb_ofile_t *of) +{ + smb_hash_t *hash = of->f_server->sv_persistid_ht; + smb_bucket_t *bucket; + smb_llist_t *ll; + uint_t idx; + + idx = smb_hash_uint64(hash, of->f_persistid); + bucket = &hash->buckets[idx]; + ll = &bucket->b_list; + smb_llist_enter(ll, RW_WRITER); + smb_llist_remove(ll, of); + smb_llist_exit(ll); +} + /* * Disallow NetFileClose on certain ofiles to avoid side-effects. * Closing a tree root is not allowed: use NetSessionDel or NetShareDel. @@ -895,12 +1191,19 @@ smb_ofile_is_open(smb_ofile_t *of) static boolean_t smb_ofile_is_open_locked(smb_ofile_t *of) { + ASSERT(MUTEX_HELD(&of->f_mutex)); + switch (of->f_state) { case SMB_OFILE_STATE_OPEN: + case SMB_OFILE_STATE_SAVE_DH: + case SMB_OFILE_STATE_SAVING: + case SMB_OFILE_STATE_ORPHANED: + case SMB_OFILE_STATE_RECONNECT: return (B_TRUE); case SMB_OFILE_STATE_CLOSING: case SMB_OFILE_STATE_CLOSED: + case SMB_OFILE_STATE_EXPIRED: return (B_FALSE); default: @@ -910,75 +1213,105 @@ smb_ofile_is_open_locked(smb_ofile_t *of) } /* - * This function closes the file passed in (if appropriate) and returns the - * next open file in the list of open files of the tree of the open file passed - * in. It requires that the list of open files of the tree be entered in - * RW_READER mode before being called. + * smb_ofile_save_dh + * + * Called via smb_llist_post (after smb_llist_exit) when the last ref. + * on this ofile has gone, and this ofile is a "durable handle" (DH) + * that has state we've decided to save. + * + * This does parts of what smb_ofile_delete would do, including: + * remove the ofile from the tree ofile list and related. + * + * We leave the ofile in state ORPHANED, ready for reconnect + * or expiration via smb2_dh_expire (see smb_ofile_delete). */ -static smb_ofile_t * -smb_ofile_close_and_next(smb_ofile_t *of) +static void +smb_ofile_save_dh(void *arg) { - smb_ofile_t *next_of; - smb_tree_t *tree; + smb_ofile_t *of = (smb_ofile_t *)arg; + smb_tree_t *tree = of->f_tree; - ASSERT(of); - ASSERT(of->f_magic == SMB_OFILE_MAGIC); + SMB_OFILE_VALID(of); + ASSERT(of->f_refcnt == 0); + ASSERT(of->f_ftype == SMB_FTYPE_DISK); + ASSERT(of->f_state == SMB_OFILE_STATE_SAVING); + atomic_dec_32(&of->f_session->s_file_cnt); + atomic_dec_32(&of->f_tree->t_open_files); + smb_llist_enter(&tree->t_ofile_list, RW_WRITER); + smb_llist_remove(&tree->t_ofile_list, of); + smb_llist_exit(&tree->t_ofile_list); + + /* + * This ofile is no longer on t_ofile_list, however... + * + * This is called via smb_llist_post, which means it may run + * BEFORE smb_ofile_release drops f_mutex (if another thread + * flushes the delete queue before we do). Synchronize. + */ mutex_enter(&of->f_mutex); - switch (of->f_state) { - case SMB_OFILE_STATE_OPEN: - /* The file is still open. */ - of->f_refcnt++; - ASSERT(of->f_refcnt); - tree = of->f_tree; - mutex_exit(&of->f_mutex); - smb_llist_exit(&of->f_tree->t_ofile_list); - smb_ofile_close(of, 0); - smb_ofile_release(of); - smb_llist_enter(&tree->t_ofile_list, RW_READER); - next_of = smb_llist_head(&tree->t_ofile_list); - break; - case SMB_OFILE_STATE_CLOSING: - case SMB_OFILE_STATE_CLOSED: - /* - * The ofile exists but is closed or - * in the process being closed. - */ - mutex_exit(&of->f_mutex); - next_of = smb_llist_next(&of->f_tree->t_ofile_list, of); - break; - default: - ASSERT(0); - mutex_exit(&of->f_mutex); - next_of = smb_llist_next(&of->f_tree->t_ofile_list, of); - break; - } - return (next_of); + DTRACE_PROBE1(ofile__exit, smb_ofile_t, of); + mutex_exit(&of->f_mutex); + + /* + * Keep f_notify state, lease, and + * keep on node ofile list. + * Keep of->f_cr until reclaim. + */ + + ASSERT(of->f_fid != 0); + smb_idpool_free(&tree->t_fid_pool, of->f_fid); + of->f_fid = 0; + smb_tree_release(of->f_tree); + of->f_tree = NULL; + smb_user_release(of->f_user); + of->f_user = NULL; + of->f_session = NULL; + + /* + * Make it "orphaned" so it can now be reclaimed. + * Note that smb_ofile_hold_olbrk() may have blocked + * for state SMB_OFILE_STATE_SAVING, so wake it. + */ + mutex_enter(&of->f_mutex); + of->dh_expire_time = gethrtime() + of->dh_timeout_offset; + of->f_state = SMB_OFILE_STATE_ORPHANED; + cv_broadcast(&of->f_cv); + mutex_exit(&of->f_mutex); } /* * Delete an ofile. * - * Remove the ofile from the tree list before freeing resources - * associated with the 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. + * + * Normally,this removes the ofile from the tree list and + * then frees resources held on the ofile. However, when + * we're expiring an orphaned durable handle, the linkage + * into the tree lists etc. have already been destroyed. + * This case is distinguished by of->f_tree == NULL. */ -void +static void smb_ofile_delete(void *arg) { - smb_tree_t *tree; smb_ofile_t *of = (smb_ofile_t *)arg; + smb_tree_t *tree = of->f_tree; SMB_OFILE_VALID(of); ASSERT(of->f_refcnt == 0); ASSERT(of->f_state == SMB_OFILE_STATE_CLOSED); - ASSERT(!SMB_OFILE_OPLOCK_GRANTED(of)); - tree = of->f_tree; - smb_llist_enter(&tree->t_ofile_list, RW_WRITER); - smb_llist_remove(&tree->t_ofile_list, of); - smb_idpool_free(&tree->t_fid_pool, of->f_fid); - atomic_dec_32(&tree->t_session->s_file_cnt); - smb_llist_exit(&tree->t_ofile_list); + if (tree != NULL) { + ASSERT(of->f_user != NULL); + ASSERT(of->f_session != NULL); + atomic_dec_32(&of->f_session->s_file_cnt); + atomic_dec_32(&of->f_tree->t_open_files); + smb_llist_enter(&tree->t_ofile_list, RW_WRITER); + smb_llist_remove(&tree->t_ofile_list, of); + smb_llist_exit(&tree->t_ofile_list); + } /* * Remove this ofile from the node's n_ofile_list so it @@ -995,7 +1328,16 @@ smb_ofile_delete(void *arg) smb_node_rem_ofile(of->f_node, of); } + /* + * This ofile is no longer on any lists, however... + * + * This is called via smb_llist_post, which means it may run + * BEFORE smb_ofile_release drops f_mutex (if another thread + * 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) { @@ -1009,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: /* @@ -1022,11 +1368,32 @@ 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); + smb_tree_release(of->f_tree); + smb_user_release(of->f_user); + } + + if (of->f_cr != NULL) + crfree(of->f_cr); + of->f_magic = (uint32_t)~SMB_OFILE_MAGIC; list_destroy(&of->f_notify.nc_waiters); mutex_destroy(&of->f_mutex); - smb_user_release(of->f_user); - crfree(of->f_cr); kmem_cache_free(smb_cache_ofile, of); } @@ -1082,19 +1449,21 @@ uint32_t smb_ofile_open_check(smb_ofile_t *of, uint32_t desired_access, uint32_t share_access) { + uint32_t ret; + ASSERT(of->f_magic == SMB_OFILE_MAGIC); mutex_enter(&of->f_mutex); - if (of->f_state != SMB_OFILE_STATE_OPEN) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_INVALID_HANDLE); + if (!smb_ofile_is_open_locked(of)) { + ret = NT_STATUS_INVALID_HANDLE; + goto out; } /* if it's just meta data */ if ((of->f_granted_access & FILE_DATA_ALL) == 0) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SUCCESS); + ret = NT_STATUS_SUCCESS; + goto out; } /* @@ -1102,42 +1471,44 @@ smb_ofile_open_check(smb_ofile_t *of, uint32_t desired_access, * open granted (desired) access */ if (SMB_DENY_DELETE(share_access) && (of->f_granted_access & DELETE)) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } if (SMB_DENY_READ(share_access) && (of->f_granted_access & (FILE_READ_DATA | FILE_EXECUTE))) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } if (SMB_DENY_WRITE(share_access) && (of->f_granted_access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } /* check requested desired access against the open share access */ if (SMB_DENY_DELETE(of->f_share_access) && (desired_access & DELETE)) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } if (SMB_DENY_READ(of->f_share_access) && (desired_access & (FILE_READ_DATA | FILE_EXECUTE))) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } if (SMB_DENY_WRITE(of->f_share_access) && (desired_access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } + ret = NT_STATUS_SUCCESS; +out: mutex_exit(&of->f_mutex); - return (NT_STATUS_SUCCESS); + return (ret); } /* @@ -1152,27 +1523,31 @@ smb_ofile_open_check(smb_ofile_t *of, uint32_t desired_access, uint32_t smb_ofile_rename_check(smb_ofile_t *of) { + uint32_t ret; + ASSERT(of->f_magic == SMB_OFILE_MAGIC); mutex_enter(&of->f_mutex); - if (of->f_state != SMB_OFILE_STATE_OPEN) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_INVALID_HANDLE); + if (!smb_ofile_is_open_locked(of)) { + ret = NT_STATUS_INVALID_HANDLE; + goto out; } if ((of->f_granted_access & FILE_DATA_ALL) == 0) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SUCCESS); + ret = NT_STATUS_SUCCESS; + goto out; } if ((of->f_share_access & FILE_SHARE_DELETE) == 0) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } + ret = NT_STATUS_SUCCESS; +out: mutex_exit(&of->f_mutex); - return (NT_STATUS_SUCCESS); + return (ret); } /* @@ -1196,24 +1571,28 @@ smb_ofile_rename_check(smb_ofile_t *of) uint32_t smb_ofile_delete_check(smb_ofile_t *of) { + uint32_t ret; + ASSERT(of->f_magic == SMB_OFILE_MAGIC); mutex_enter(&of->f_mutex); - if (of->f_state != SMB_OFILE_STATE_OPEN) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_INVALID_HANDLE); + if (!smb_ofile_is_open_locked(of)) { + ret = NT_STATUS_INVALID_HANDLE; + goto out; } if (of->f_granted_access & (FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_EXECUTE | DELETE)) { - mutex_exit(&of->f_mutex); - return (NT_STATUS_SHARING_VIOLATION); + ret = NT_STATUS_SHARING_VIOLATION; + goto out; } + ret = NT_STATUS_SUCCESS; +out: mutex_exit(&of->f_mutex); - return (NT_STATUS_SUCCESS); + return (ret); } cred_t * @@ -1236,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 7e2d862c77..83b74ee075 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_opipe.c +++ b/usr/src/uts/common/fs/smbsrv/smb_opipe.c @@ -111,9 +111,12 @@ smb_opipe_cancel(smb_request_t *sr) { ksocket_t so; - if (sr->session->s_state == SMB_SESSION_STATE_DISCONNECTED && - (so = sr->cancel_arg2) != NULL) { - (void) ksocket_shutdown(so, SHUT_RDWR, sr->user_cr); + switch (sr->session->s_state) { + case SMB_SESSION_STATE_DISCONNECTED: + case SMB_SESSION_STATE_TERMINATED: + if ((so = sr->cancel_arg2) != NULL) + (void) ksocket_shutdown(so, SHUT_RDWR, sr->user_cr); + break; } } @@ -265,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; @@ -289,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 488a091077..7be36ebf42 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_oplock.c +++ b/usr/src/uts/common/fs/smbsrv/smb_oplock.c @@ -20,778 +20,139 @@ */ /* * 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. */ /* - * 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_exec_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); -} +#define BATCH_OR_EXCL (OPLOCK_LEVEL_BATCH | OPLOCK_LEVEL_ONE) /* - * 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; - -/* - * 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; - tree = SMB_OFILE_GET_TREE(ofile); - session = SMB_OFILE_GET_SESSION(ofile); + 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); - - /* - * 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); - 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; + if (!smb_session_levelII_oplocks(sr->session)) + level2ok = B_FALSE; - mutex_enter(&ol->ol_mutex); - smb_oplock_wait(node); + /* Common code checks file type. */ - 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 ((flags & SMB_OPLOCK_BREAK_TO_LEVEL_II) && - smb_session_levelII_oplocks(ofile->f_session)) { - brk = SMB_OPLOCK_BREAK_TO_LEVEL_II; - } else { - brk = SMB_OPLOCK_BREAK_TO_NONE; - } - - 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; + case SMB_OPLOCK_NONE: + default: + op->op_oplock_level = SMB_OPLOCK_NONE; + return; } - if (flags & SMB_OPLOCK_BREAK_NOWAIT) { - mutex_exit(&ol->ol_mutex); - return (EAGAIN); - } - - if (sr && (sr->uid_user == ofile->f_user)) { - timeout = smb_oplock_min_timeout; - } else { - timeout = smb_oplock_timeout; + /* + * Tree options may force shared oplocks + */ + if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_FORCE_L2_OPLOCK)) { + op->op_oplock_state = OPLOCK_LEVEL_TWO; } - 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; + /* + * 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 { - /* Equivalent of smb_oplock_wait() done. */ - smb_oplock_break_levelII_locked(node); + status = NT_STATUS_OPLOCK_NOT_GRANTED; } - 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); - smb_oplock_remove_grant(node, og); - smb_oplock_clear_grant(og); - } -} - -/* - * Schedule a call to smb_session_oplock_break - * using an smb_request on the owning session. - */ -static void -smb_oplock_sched_async_break(smb_oplock_grant_t *og, uint8_t brk) -{ - smb_request_t *sr; - smb_ofile_t *ofile; - /* - * Make sure we can get a hold on the ofile. If we can't, - * the file is closing, and there's no point scheduling an - * oplock break on it because the close will release the - * oplock very soon. Same for the tree & user holds. - * - * These holds account for the pointers we copy into the - * smb_request fields: fid_ofile, tid_tree, uid_user. - * These holds are released via smb_request_free after - * the oplock break has been sent. + * If exclusive failed (or tree forced shared oplocks) + * and if the caller supports Level II, try shared. */ - ofile = og->og_ofile; /* containing struct */ - if (!smb_ofile_hold(ofile)) - return; - - if ((sr = smb_request_alloc(ofile->f_session, 0)) == NULL) { - smb_ofile_release(ofile); - return; - } - - smb_tree_hold_internal(ofile->f_tree); - smb_user_hold_internal(ofile->f_user); - - sr->sr_state = SMB_REQ_STATE_SUBMITTED; - sr->user_cr = zone_kcred(); - sr->fid_ofile = ofile; - sr->tid_tree = ofile->f_tree; - sr->uid_user = ofile->f_user; - - sr->arg.olbrk = *og; /* struct copy */ - sr->arg.olbrk.og_breaking = brk; - - (void) taskq_dispatch( - sr->sr_server->sv_worker_pool, - smb_oplock_exec_async_break, sr, TQ_SLEEP); -} - -/* - * smb_oplock_exec_async_break - * - * Called via the taskq to handle an asynchronous oplock break. - * We have a hold on the ofile, which keeps the FID here valid. - */ -static void -smb_oplock_exec_async_break(void *arg) -{ - smb_request_t *sr = arg; - smb_oplock_grant_t *og = &sr->arg.olbrk; - - SMB_REQ_VALID(sr); - SMB_OPLOCK_GRANT_VALID(og); - - mutex_enter(&sr->sr_mutex); - sr->sr_worker = curthread; - sr->sr_time_active = gethrtime(); - - switch (sr->sr_state) { - case SMB_REQ_STATE_SUBMITTED: - sr->sr_state = SMB_REQ_STATE_ACTIVE; - mutex_exit(&sr->sr_mutex); - - /* - * This is where we actually do the deferred work - * requested by smb_oplock_sched_async_break(). - */ - smb_session_oplock_break(sr, og->og_breaking); - - mutex_enter(&sr->sr_mutex); - /* FALLTHROUGH */ - - default: /* typically cancelled */ - sr->sr_state = SMB_REQ_STATE_COMPLETED; - mutex_exit(&sr->sr_mutex); + 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); } - 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); - } - } - - 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_query_fileinfo.c b/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c index 2504f524c9..cfa3ab4aca 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c +++ b/usr/src/uts/common/fs/smbsrv/smb_query_fileinfo.c @@ -329,11 +329,7 @@ smb_query_by_path(smb_request_t *sr, smb_xa_t *xa, uint16_t infolev) } if (rc != 0) { - if (rc == ENOENT) - smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND, - ERRDOS, ERROR_FILE_NOT_FOUND); - else - smbsr_errno(sr, rc); + smbsr_errno(sr, rc); kmem_free(qinfo, sizeof (smb_queryinfo_t)); return (-1); @@ -654,9 +650,11 @@ smb_query_stream_info(smb_request_t *sr, mbuf_chain_t *mbc, switch (status) { case 0: break; + case NT_STATUS_OBJECT_NAME_NOT_FOUND: case NT_STATUS_NO_SUCH_FILE: case NT_STATUS_NOT_SUPPORTED: /* No streams. */ + status = 0; done = B_TRUE; break; default: 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 408f321189..b65500ce45 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_server.c +++ b/usr/src/uts/common/fs/smbsrv/smb_server.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2017 by Delphix. All rights reserved. */ @@ -219,11 +219,6 @@ #include <smbsrv/smb_door.h> #include <smbsrv/smb_kstat.h> -typedef struct { - smb_listener_daemon_t *ra_listener; - smb_session_t *ra_session; -} smb_receiver_arg_t; - static void smb_server_kstat_init(smb_server_t *); static void smb_server_kstat_fini(smb_server_t *); static void smb_server_timers(smb_thread_t *, void *); @@ -250,12 +245,25 @@ static void smb_server_listener_stop(smb_listener_daemon_t *); static void smb_server_listener(smb_thread_t *, void *); static void smb_server_receiver(void *); static void smb_server_create_session(smb_listener_daemon_t *, ksocket_t); -static void smb_server_destroy_session(smb_listener_daemon_t *, - smb_session_t *); +static void smb_server_destroy_session(smb_session_t *); static uint16_t smb_spool_get_fid(smb_server_t *); static boolean_t smb_spool_lookup_doc_byfid(smb_server_t *, uint16_t, smb_kspooldoc_t *); +/* + * How many "buckets" should our hash tables use? On a "real" server, + * make them much larger than the number of CPUs we're likely to have. + * On "fksmbd" make it smaller so dtrace logs are shorter. + * These must be powers of two. + */ +#ifdef _KERNEL +#define DEFAULT_HASH_NBUCKETS 256 /* real server */ +#else +#define DEFAULT_HASH_NBUCKETS 16 /* for "fksmbd" */ +#endif +uint32_t SMB_OFILE_HASH_NBUCKETS = DEFAULT_HASH_NBUCKETS; +uint32_t SMB_LEASE_HASH_NBUCKETS = DEFAULT_HASH_NBUCKETS; + int smb_event_debug = 0; static smb_llist_t smb_servers; @@ -307,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); @@ -363,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(); @@ -410,6 +420,15 @@ smb_server_create(void) cv_init(&sv->sv_cv, NULL, CV_DEFAULT, NULL); cv_init(&sv->sp_info.sp_cv, NULL, CV_DEFAULT, NULL); + 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)); + smb_llist_constructor(&sv->sv_event_list, sizeof (smb_event_t), offsetof(smb_event_t, se_lnd)); @@ -510,6 +529,7 @@ smb_server_delete(void) smb_kshare_fini(sv); smb_kdoor_fini(sv); smb_llist_destructor(&sv->sv_event_list); + smb_llist_destructor(&sv->sv_session_list); kmem_free(sv->sv_disp_stats1, SMB_COM_NUM * sizeof (smb_disp_stats_t)); @@ -521,6 +541,8 @@ 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; kmem_free(sv, sizeof (smb_server_t)); @@ -882,17 +904,11 @@ smb_server_enum(smb_ioc_svcenum_t *ioc) switch (svcenum->se_type) { case SMB_SVCENUM_TYPE_USER: - smb_server_enum_users(&sv->sv_nbt_daemon.ld_session_list, - svcenum); - smb_server_enum_users(&sv->sv_tcp_daemon.ld_session_list, - svcenum); + smb_server_enum_users(&sv->sv_session_list, svcenum); break; case SMB_SVCENUM_TYPE_TREE: case SMB_SVCENUM_TYPE_FILE: - smb_server_enum_trees(&sv->sv_nbt_daemon.ld_session_list, - svcenum); - smb_server_enum_trees(&sv->sv_tcp_daemon.ld_session_list, - svcenum); + smb_server_enum_trees(&sv->sv_session_list, svcenum); break; default: rc = EINVAL; @@ -910,22 +926,18 @@ smb_server_session_close(smb_ioc_session_t *ioc) { smb_llist_t *ll; smb_server_t *sv; - int nbt_cnt; - int tcp_cnt; + int cnt; int rc; if ((rc = smb_server_lookup(&sv)) != 0) return (rc); - ll = &sv->sv_nbt_daemon.ld_session_list; - nbt_cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username); - - ll = &sv->sv_tcp_daemon.ld_session_list; - tcp_cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username); + ll = &sv->sv_session_list; + cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username); smb_server_release(sv); - if ((nbt_cnt == 0) && (tcp_cnt == 0)) + if (cnt == 0) return (ENOENT); return (0); } @@ -944,14 +956,9 @@ smb_server_file_close(smb_ioc_fileid_t *ioc) if ((rc = smb_server_lookup(&sv)) != 0) return (rc); - ll = &sv->sv_nbt_daemon.ld_session_list; + ll = &sv->sv_session_list; rc = smb_server_fclose(ll, uniqid); - if (rc == ENOENT) { - ll = &sv->sv_tcp_daemon.ld_session_list; - rc = smb_server_fclose(ll, uniqid); - } - smb_server_release(sv); return (rc); } @@ -965,8 +972,7 @@ smb_server_get_session_count(smb_server_t *sv) { uint32_t counter = 0; - counter = smb_llist_get_count(&sv->sv_nbt_daemon.ld_session_list); - counter += smb_llist_get_count(&sv->sv_tcp_daemon.ld_session_list); + counter = smb_llist_get_count(&sv->sv_session_list); return (counter); } @@ -999,7 +1005,7 @@ smb_server_sharevp(smb_server_t *sv, const char *shr_path, vnode_t **vp) mutex_exit(&sv->sv_mutex); if ((sr = smb_request_alloc(sv->sv_session, 0)) == NULL) { - return (ENOMEM); + return (ENOTCONN); } sr->user_cr = zone_kcred(); @@ -1082,10 +1088,7 @@ smb_server_unshare(const char *sharename) } mutex_exit(&sv->sv_mutex); - ll = &sv->sv_nbt_daemon.ld_session_list; - smb_server_disconnect_share(ll, sharename); - - ll = &sv->sv_tcp_daemon.ld_session_list; + ll = &sv->sv_session_list; smb_server_disconnect_share(ll, sharename); smb_server_release(sv); @@ -1109,12 +1112,13 @@ smb_server_disconnect_share(smb_llist_t *ll, const char *sharename) smb_rwx_rwenter(&session->s_lock, RW_READER); switch (session->s_state) { case SMB_SESSION_STATE_NEGOTIATED: + smb_rwx_rwexit(&session->s_lock); smb_session_disconnect_share(session, sharename); break; default: + smb_rwx_rwexit(&session->s_lock); break; } - smb_rwx_rwexit(&session->s_lock); session = smb_llist_next(ll, session); } @@ -1259,12 +1263,13 @@ smb_server_timers(smb_thread_t *thread, void *arg) ASSERT(sv != NULL); /* - * This just kills old inactive sessions. No urgency. - * The session code expects one call per minute. + * This kills old inactive sessions and expired durable + * handles. The session code expects one call per minute. */ while (smb_thread_continue_timedwait(thread, 60 /* Seconds */)) { - smb_session_timers(&sv->sv_nbt_daemon.ld_session_list); - smb_session_timers(&sv->sv_tcp_daemon.ld_session_list); + if (sv->sv_cfg.skc_keepalive != 0) + smb_session_timers(sv); + smb2_durable_timers(sv); } } @@ -1425,17 +1430,29 @@ smb_server_legacy_kstat_update(kstat_t *ksp, int rw) static void smb_server_shutdown(smb_server_t *sv) { + smb_llist_t *sl = &sv->sv_session_list; + smb_session_t *session; + clock_t time; + SMB_SERVER_VALID(sv); /* * Stop the listeners first, so we don't get any more * new work while we're trying to shut down. - * Also disconnects all sessions under each. */ smb_server_listener_stop(&sv->sv_nbt_daemon); smb_server_listener_stop(&sv->sv_tcp_daemon); smb_thread_stop(&sv->si_thread_timers); + /* Disconnect all of the sessions */ + smb_llist_enter(sl, RW_READER); + session = smb_llist_head(sl); + while (session != NULL) { + smb_session_disconnect(session); + session = smb_llist_next(sl, session); + } + smb_llist_exit(sl); + /* * Wake up any threads we might have blocked. * Must precede kdoor_close etc. because those will @@ -1446,6 +1463,39 @@ smb_server_shutdown(smb_server_t *sv) smb_threshold_wake_all(&sv->sv_tcon_ct); smb_threshold_wake_all(&sv->sv_opipe_ct); + /* + * Wait for the session list to empty. + * (cv_signal in smb_server_destroy_session) + * + * This should not take long, but if there are any leaked + * references to ofiles, trees, or users, there could be a + * session hanging around. If that happens, the ll_count + * never gets to zero and we'll never get the sv_signal. + * Defend against that problem using timed wait, then + * complain if we find sessions left over and continue + * with shutdown in spite of any leaked sessions. + * That's better than a server that won't reboot. + */ + time = SEC_TO_TICK(10) + ddi_get_lbolt(); + mutex_enter(&sv->sv_mutex); + while (sv->sv_session_list.ll_count != 0) { + if (cv_timedwait(&sv->sv_cv, &sv->sv_mutex, time) < 0) + break; + } + mutex_exit(&sv->sv_mutex); +#ifdef DEBUG + if (sv->sv_session_list.ll_count != 0) { + cmn_err(CE_NOTE, "shutdown leaked sessions"); + debug_enter("shutdown leaked sessions"); + } +#endif + + /* + * Clean out any durable handles. After this we should + * have no ofiles remaining (and no more oplock breaks). + */ + smb2_dh_shutdown(sv); + smb_kdoor_close(sv); #ifdef _KERNEL smb_kshare_door_fini(sv->sv_lmshrd); @@ -1453,14 +1503,18 @@ smb_server_shutdown(smb_server_t *sv) sv->sv_lmshrd = NULL; smb_export_stop(sv); + smb_kshare_stop(sv); + /* + * Both kshare and the oplock break sub-systems may have + * taskq jobs on the spcial "server" session, until we've + * closed all ofiles and stopped the kshare exporter. + * Now it's safe to destroy the server session, but first + * wait for any requests on it to finish. Note that for + * normal sessions, this happens in smb_session_cancel, + * but that's not called for the server session. + */ if (sv->sv_session != NULL) { - /* - * smb_kshare_export may have a request on here. - * Normal sessions do this in smb_session_cancel() - * but this is a "fake" session used only for the - * requests used by the kshare thread(s). - */ smb_slist_wait_for_empty(&sv->sv_session->s_req_list); smb_session_delete(sv->sv_session); @@ -1477,7 +1531,6 @@ smb_server_shutdown(smb_server_t *sv) sv->sv_worker_pool = NULL; } - smb_kshare_stop(sv); smb_server_fsop_stop(sv); } @@ -1513,8 +1566,6 @@ smb_server_listener_init( sizeof (ld->ld_sin6.sin6_addr.s6_addr)); } - smb_llist_constructor(&ld->ld_session_list, sizeof (smb_session_t), - offsetof(smb_session_t, s_lnd)); smb_thread_init(&ld->ld_thread, name, smb_server_listener, ld, smbsrv_listen_pri); ld->ld_magic = SMB_LISTENER_MAGIC; @@ -1538,7 +1589,6 @@ smb_server_listener_destroy(smb_listener_daemon_t *ld) SMB_LISTENER_VALID(ld); ASSERT(ld->ld_so == NULL); smb_thread_destroy(&ld->ld_thread); - smb_llist_destructor(&ld->ld_session_list); ld->ld_magic = 0; } @@ -1636,7 +1686,6 @@ smb_server_listener(smb_thread_t *thread, void *arg) { _NOTE(ARGUNUSED(thread)) smb_listener_daemon_t *ld; - smb_session_t *session; ksocket_t s_so; int on; int txbuf_size; @@ -1685,14 +1734,6 @@ smb_server_listener(smb_thread_t *thread, void *arg) smb_server_create_session(ld, s_so); } out: - /* Disconnect all the sessions this listener created. */ - smb_llist_enter(&ld->ld_session_list, RW_READER); - session = smb_llist_head(&ld->ld_session_list); - while (session != NULL) { - smb_session_disconnect(session); - session = smb_llist_next(&ld->ld_session_list, session); - } - smb_llist_exit(&ld->ld_session_list); ksocket_rele(ld->ld_so); } @@ -1700,18 +1741,20 @@ out: * smb_server_receiver * * Entry point of the receiver threads. + * Also does cleanup when socket disconnected. */ static void smb_server_receiver(void *arg) { - smb_listener_daemon_t *ld; - smb_session_t *session; + smb_session_t *session; + + session = (smb_session_t *)arg; - ld = ((smb_receiver_arg_t *)arg)->ra_listener; - session = ((smb_receiver_arg_t *)arg)->ra_session; - smb_mem_free(arg); + /* We stay in here until socket disconnect. */ smb_session_receiver(session); - smb_server_destroy_session(ld, session); + + ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN); + smb_server_destroy_session(session); } /* @@ -1865,48 +1908,37 @@ smb_server_session_disconnect(smb_llist_t *ll, smb_session_t *sn; smb_llist_t *ulist; smb_user_t *user; - boolean_t match; int count = 0; smb_llist_enter(ll, RW_READER); - sn = smb_llist_head(ll); - while (sn != NULL) { + for (sn = smb_llist_head(ll); + sn != NULL; + sn = smb_llist_next(ll, sn)) { SMB_SESSION_VALID(sn); - if ((*client != '\0') && (!smb_session_isclient(sn, client))) { - sn = smb_llist_next(ll, sn); + if (*client != '\0' && !smb_session_isclient(sn, client)) continue; - } ulist = &sn->s_user_list; smb_llist_enter(ulist, RW_READER); - user = smb_llist_head(ulist); - while (user != NULL) { - if (smb_user_hold(user)) { - match = (*name == '\0'); - if (!match) - match = smb_user_namecmp(user, name); - - if (match) { - smb_llist_exit(ulist); - smb_user_logoff(user); - ++count; - smb_user_release(user); - smb_llist_enter(ulist, RW_READER); - user = smb_llist_head(ulist); - continue; - } + for (user = smb_llist_head(ulist); + user != NULL; + user = smb_llist_next(ulist, user)) { + SMB_USER_VALID(user); + if (*name != '\0' && !smb_user_namecmp(user, name)) + continue; + + if (smb_user_hold(user)) { + smb_user_logoff(user); smb_user_release(user); + count++; } - - user = smb_llist_next(ulist, user); } smb_llist_exit(ulist); - sn = smb_llist_next(ll, sn); } smb_llist_exit(ll); @@ -1950,6 +1982,63 @@ smb_server_fclose(smb_llist_t *ll, uint32_t uniqid) return (rc); } +/* + * This is used by SMB2 session setup to logoff a previous session, + * so it can force a logoff that we haven't noticed yet. + * This is not called frequently, so we just walk the list of + * connections searching for the user. + */ +void +smb_server_logoff_ssnid(smb_request_t *sr, uint64_t ssnid) +{ + smb_server_t *sv = sr->sr_server; + smb_llist_t *sess_list; + smb_session_t *sess; + + if (sv->sv_state != SMB_SERVER_STATE_RUNNING) + return; + + sess_list = &sv->sv_session_list; + smb_llist_enter(sess_list, RW_READER); + + for (sess = smb_llist_head(sess_list); + sess != NULL; + sess = smb_llist_next(sess_list, sess)) { + + smb_user_t *user; + + SMB_SESSION_VALID(sess); + + if (sess->dialect < SMB_VERS_2_BASE) + continue; + + if (sess->s_state != SMB_SESSION_STATE_NEGOTIATED) + continue; + + user = smb_session_lookup_ssnid(sess, ssnid); + if (user == NULL) + continue; + + if (!smb_is_same_user(user->u_cred, sr->user_cr)) { + smb_user_release(user); + continue; + } + + /* Treat this as if we lost the connection */ + user->preserve_opens = SMB2_DH_PRESERVE_SOME; + smb_user_logoff(user); + smb_user_release(user); + + /* + * The above may have left work on the delete queues + */ + smb_llist_flush(&sess->s_tree_list); + smb_llist_flush(&sess->s_user_list); + } + + smb_llist_exit(sess_list); +} + /* See also: libsmb smb_kmod_setcfg */ static void smb_server_store_cfg(smb_server_t *sv, smb_ioc_cfg_t *ioc) @@ -1957,11 +2046,6 @@ smb_server_store_cfg(smb_server_t *sv, smb_ioc_cfg_t *ioc) if (ioc->maxconnections == 0) ioc->maxconnections = 0xFFFFFFFF; - smb_session_correct_keep_alive_values( - &sv->sv_nbt_daemon.ld_session_list, ioc->keepalive); - smb_session_correct_keep_alive_values( - &sv->sv_tcp_daemon.ld_session_list, ioc->keepalive); - sv->sv_cfg.skc_maxworkers = ioc->maxworkers; sv->sv_cfg.skc_maxconnections = ioc->maxconnections; sv->sv_cfg.skc_keepalive = ioc->keepalive; @@ -2359,10 +2443,11 @@ static void smb_server_create_session(smb_listener_daemon_t *ld, ksocket_t s_so) { smb_session_t *session; - smb_receiver_arg_t *rarg; taskqid_t tqid; + smb_llist_t *sl; + smb_server_t *sv = ld->ld_sv; - session = smb_session_create(s_so, ld->ld_port, ld->ld_sv, + session = smb_session_create(s_so, ld->ld_port, sv, ld->ld_family); if (session == NULL) { @@ -2372,25 +2457,20 @@ smb_server_create_session(smb_listener_daemon_t *ld, ksocket_t s_so) return; } - smb_llist_enter(&ld->ld_session_list, RW_WRITER); - smb_llist_insert_tail(&ld->ld_session_list, session); - smb_llist_exit(&ld->ld_session_list); - - rarg = (smb_receiver_arg_t *)smb_mem_alloc( - sizeof (smb_receiver_arg_t)); - rarg->ra_listener = ld; - rarg->ra_session = session; + sl = &sv->sv_session_list; + smb_llist_enter(sl, RW_WRITER); + smb_llist_insert_tail(sl, session); + smb_llist_exit(sl); /* * These taskq entries must run independently of one another, * so TQ_NOQUEUE. TQ_SLEEP (==0) just for clarity. */ - tqid = taskq_dispatch(ld->ld_sv->sv_receiver_pool, - smb_server_receiver, rarg, TQ_NOQUEUE | TQ_SLEEP); + tqid = taskq_dispatch(sv->sv_receiver_pool, + smb_server_receiver, session, TQ_NOQUEUE | TQ_SLEEP); if (tqid == TASKQID_INVALID) { - smb_mem_free(rarg); smb_session_disconnect(session); - smb_server_destroy_session(ld, session); + smb_server_destroy_session(session); cmn_err(CE_WARN, "SMB Session: taskq_dispatch failed"); return; } @@ -2399,10 +2479,41 @@ smb_server_create_session(smb_listener_daemon_t *ld, ksocket_t s_so) } static void -smb_server_destroy_session(smb_listener_daemon_t *ld, smb_session_t *session) +smb_server_destroy_session(smb_session_t *session) { - smb_llist_enter(&ld->ld_session_list, RW_WRITER); - smb_llist_remove(&ld->ld_session_list, session); - smb_llist_exit(&ld->ld_session_list); + smb_server_t *sv; + smb_llist_t *ll; + uint32_t count; + + ASSERT(session->s_server != NULL); + sv = session->s_server; + ll = &sv->sv_session_list; + + smb_llist_flush(&session->s_tree_list); + smb_llist_flush(&session->s_user_list); + + /* + * The user and tree lists should be empty now. + */ +#ifdef DEBUG + if (session->s_user_list.ll_count != 0) { + cmn_err(CE_WARN, "user list not empty?"); + debug_enter("s_user_list"); + } + if (session->s_tree_list.ll_count != 0) { + cmn_err(CE_WARN, "tree list not empty?"); + debug_enter("s_tree_list"); + } +#endif + + smb_llist_enter(ll, RW_WRITER); + smb_llist_remove(ll, session); + count = ll->ll_count; + smb_llist_exit(ll); + smb_session_delete(session); + if (count == 0) { + /* See smb_server_shutdown */ + cv_signal(&sv->sv_cv); + } } diff --git a/usr/src/uts/common/fs/smbsrv/smb_session.c b/usr/src/uts/common/fs/smbsrv/smb_session.c index c3ce565688..1ed8563a1b 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_session.c +++ b/usr/src/uts/common/fs/smbsrv/smb_session.c @@ -74,14 +74,29 @@ static int smb_session_xprt_puthdr(smb_session_t *, uint8_t *dst, size_t dstlen); static smb_tree_t *smb_session_get_tree(smb_session_t *, smb_tree_t *); static void smb_session_logoff(smb_session_t *); +static void smb_session_disconnect_trees(smb_session_t *); static void smb_request_init_command_mbuf(smb_request_t *sr); static void smb_session_genkey(smb_session_t *); +/* + * This (legacy) code is in support of an "idle timeout" feature, + * which is apparently incomplete. To complete it, we should: + * when the keep_alive timer expires, check whether the client + * has any open files, and if not then kill their session. + * Right now the timers are there, but nothing happens when + * a timer expires. + * + * Todo: complete logic to kill idle sessions. + * + * Only called when sv_cfg.skc_keepalive != 0 + */ void -smb_session_timers(smb_llist_t *ll) +smb_session_timers(smb_server_t *sv) { smb_session_t *session; + smb_llist_t *ll; + ll = &sv->sv_session_list; smb_llist_enter(ll, RW_READER); session = smb_llist_head(ll); while (session != NULL) { @@ -93,40 +108,8 @@ smb_session_timers(smb_llist_t *ll) if (session->keep_alive && (session->keep_alive != (uint32_t)-1)) session->keep_alive--; - session = smb_llist_next(ll, session); - } - smb_llist_exit(ll); -} - -void -smb_session_correct_keep_alive_values(smb_llist_t *ll, uint32_t new_keep_alive) -{ - smb_session_t *sn; - /* - * Caller specifies seconds, but we track in minutes, so - * convert to minutes (rounded up). - */ - new_keep_alive = (new_keep_alive + 59) / 60; - - if (new_keep_alive == smb_keep_alive) - return; - /* - * keep alive == 0 means do not drop connection if it's idle - */ - smb_keep_alive = (new_keep_alive) ? new_keep_alive : -1; - - /* - * Walk through the table and set each session to the new keep_alive - * value if they have not already timed out. Block clock interrupts. - */ - smb_llist_enter(ll, RW_READER); - sn = smb_llist_head(ll); - while (sn != NULL) { - SMB_SESSION_VALID(sn); - if (sn->keep_alive != 0) - sn->keep_alive = new_keep_alive; - sn = smb_llist_next(ll, sn); + session = smb_llist_next(ll, session); } smb_llist_exit(ll); } @@ -242,9 +225,9 @@ smb_netbios_session_request(struct smb_session *session) int rc; char *calling_name; char *called_name; - char client_name[NETBIOS_NAME_SZ]; - struct mbuf_chain mbc; - char *names = NULL; + char client_name[NETBIOS_NAME_SZ]; + struct mbuf_chain mbc; + char *names = NULL; smb_wchar_t *wbuf = NULL; smb_xprt_t hdr; char *p; @@ -491,6 +474,11 @@ smb_request_cancel(smb_request_t *sr) * smb_session_receiver * * Receives request from the network and dispatches them to a worker. + * + * When we receive a disconnect here, it _could_ be due to the server + * having initiated disconnect, in which case the session state will be + * SMB_SESSION_STATE_TERMINATED and we want to keep that state so later + * tear-down logic will know which side initiated. */ void smb_session_receiver(smb_session_t *session) @@ -505,7 +493,9 @@ smb_session_receiver(smb_session_t *session) rc = smb_netbios_session_request(session); if (rc != 0) { smb_rwx_rwenter(&session->s_lock, RW_WRITER); - session->s_state = SMB_SESSION_STATE_DISCONNECTED; + if (session->s_state != SMB_SESSION_STATE_TERMINATED) + session->s_state = + SMB_SESSION_STATE_DISCONNECTED; smb_rwx_rwexit(&session->s_lock); return; } @@ -518,7 +508,8 @@ smb_session_receiver(smb_session_t *session) (void) smb_session_reader(session); smb_rwx_rwenter(&session->s_lock, RW_WRITER); - session->s_state = SMB_SESSION_STATE_DISCONNECTED; + if (session->s_state != SMB_SESSION_STATE_TERMINATED) + session->s_state = SMB_SESSION_STATE_DISCONNECTED; smb_rwx_rwexit(&session->s_lock); smb_soshutdown(session->sock); @@ -536,7 +527,7 @@ smb_session_receiver(smb_session_t *session) /* * smb_session_disconnect * - * Disconnects the session passed in. + * Server-initiated disconnect (i.e. server shutdown) */ void smb_session_disconnect(smb_session_t *session) @@ -550,8 +541,8 @@ smb_session_disconnect(smb_session_t *session) case SMB_SESSION_STATE_ESTABLISHED: case SMB_SESSION_STATE_NEGOTIATED: smb_soshutdown(session->sock); - session->s_state = SMB_SESSION_STATE_DISCONNECTED; - _NOTE(FALLTHRU) + session->s_state = SMB_SESSION_STATE_TERMINATED; + break; case SMB_SESSION_STATE_DISCONNECTED: case SMB_SESSION_STATE_TERMINATED: break; @@ -622,8 +613,8 @@ smb_session_reader(smb_session_t *session) /* * Allocate a request context, read the whole message. - * If the request alloc fails, we've disconnected and - * won't be able to send the reply anyway, so bail now. + * If the request alloc fails, we've disconnected + * and won't be able to send the reply anyway, so bail now. */ if ((sr = smb_request_alloc(session, hdr.xh_length)) == NULL) break; @@ -733,6 +724,7 @@ smb_session_create(ksocket_t new_so, uint16_t port, smb_server_t *sv, now = ddi_get_lbolt64(); + session->s_server = sv; session->s_kid = SMB_NEW_KID(); session->s_state = SMB_SESSION_STATE_INITIALIZED; session->native_os = NATIVE_OS_UNKNOWN; @@ -802,7 +794,6 @@ smb_session_create(ksocket_t new_so, uint16_t port, smb_server_t *sv, else smb_server_inc_tcp_sess(sv); } - session->s_server = sv; smb_server_get_cfg(sv, &session->s_cfg); session->s_srqueue = &sv->sv_srqueue; @@ -925,13 +916,23 @@ smb_session_cancel_requests( smb_user_t * smb_session_lookup_uid(smb_session_t *session, uint16_t uid) { - return (smb_session_lookup_uid_st(session, uid, + return (smb_session_lookup_uid_st(session, 0, uid, + SMB_USER_STATE_LOGGED_ON)); +} + +/* + * Find a user on the specified session by SMB2 SSNID. + */ +smb_user_t * +smb_session_lookup_ssnid(smb_session_t *session, uint64_t ssnid) +{ + return (smb_session_lookup_uid_st(session, ssnid, 0, SMB_USER_STATE_LOGGED_ON)); } smb_user_t * -smb_session_lookup_uid_st(smb_session_t *session, uint16_t uid, - smb_user_state_t st) +smb_session_lookup_uid_st(smb_session_t *session, uint64_t ssnid, + uint16_t uid, smb_user_state_t st) { smb_user_t *user; smb_llist_t *user_list; @@ -941,34 +942,30 @@ smb_session_lookup_uid_st(smb_session_t *session, uint16_t uid, user_list = &session->s_user_list; smb_llist_enter(user_list, RW_READER); - user = smb_llist_head(user_list); - while (user) { + for (user = smb_llist_head(user_list); + user != NULL; + user = smb_llist_next(user_list, user)) { + SMB_USER_VALID(user); ASSERT(user->u_session == session); - if (user->u_uid == uid && user->u_state == st) { - smb_user_hold_internal(user); + if (user->u_ssnid != ssnid && user->u_uid != uid) + continue; + + mutex_enter(&user->u_mutex); + if (user->u_state == st) { + // smb_user_hold_internal(user); + user->u_refcnt++; + mutex_exit(&user->u_mutex); break; } - - user = smb_llist_next(user_list, user); + mutex_exit(&user->u_mutex); } smb_llist_exit(user_list); return (user); } -void -smb_session_post_user(smb_session_t *session, smb_user_t *user) -{ - SMB_USER_VALID(user); - ASSERT(user->u_refcnt == 0); - ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF); - ASSERT(user->u_session == session); - - smb_llist_post(&session->s_user_list, user, smb_user_delete); -} - /* * Find a tree by tree-id. */ @@ -976,7 +973,6 @@ smb_tree_t * smb_session_lookup_tree( smb_session_t *session, uint16_t tid) - { smb_tree_t *tree; @@ -1148,8 +1144,9 @@ smb_session_disconnect_owned_trees( /* * smb_tree_hold() succeeded, hence we are in state * SMB_TREE_STATE_CONNECTED; schedule this tree - * for asynchronous disconnect, which will fire - * after we drop the llist traversal lock. + * for disconnect after smb_llist_exit because + * the "unmap exec" up-call can block, and we'd + * rather not block with the tree list locked. */ smb_llist_post(tree_list, tree, smb_session_tree_dtor); } @@ -1163,11 +1160,11 @@ smb_session_disconnect_owned_trees( /* * Disconnect all trees that this user has connected. */ -void +static void smb_session_disconnect_trees( smb_session_t *session) { - smb_tree_t *tree; + smb_tree_t *tree, *next_tree; SMB_SESSION_VALID(session); @@ -1176,8 +1173,9 @@ smb_session_disconnect_trees( ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC); ASSERT(tree->t_session == session); smb_tree_disconnect(tree, B_TRUE); + next_tree = smb_session_get_tree(session, tree); smb_tree_release(tree); - tree = smb_session_get_tree(session, NULL); + tree = next_tree; } } @@ -1206,18 +1204,6 @@ smb_session_disconnect_share( } } -void -smb_session_post_tree(smb_session_t *session, smb_tree_t *tree) -{ - SMB_SESSION_VALID(session); - SMB_TREE_VALID(tree); - ASSERT0(tree->t_refcnt); - ASSERT(tree->t_state == SMB_TREE_STATE_DISCONNECTED); - ASSERT(tree->t_session == session); - - smb_llist_post(&session->s_tree_list, tree, smb_tree_dealloc); -} - /* * Get the next connected tree in the list. A reference is taken on * the tree, which can be released later with smb_tree_release(). @@ -1260,44 +1246,110 @@ smb_session_get_tree( /* * Logoff all users associated with the specified session. + * + * This is called for both server-initiated disconnect + * (SMB_SESSION_STATE_TERMINATED) and client-initiated + * disconnect (SMB_SESSION_STATE_DISCONNECTED). + * If client-initiated, save durable handles. */ static void smb_session_logoff(smb_session_t *session) { + smb_llist_t *ulist; smb_user_t *user; SMB_SESSION_VALID(session); - smb_session_disconnect_trees(session); - - smb_llist_enter(&session->s_user_list, RW_READER); +top: + ulist = &session->s_user_list; + smb_llist_enter(ulist, RW_READER); - user = smb_llist_head(&session->s_user_list); + user = smb_llist_head(ulist); while (user) { SMB_USER_VALID(user); ASSERT(user->u_session == session); + mutex_enter(&user->u_mutex); switch (user->u_state) { case SMB_USER_STATE_LOGGING_ON: case SMB_USER_STATE_LOGGED_ON: - smb_user_hold_internal(user); + // smb_user_hold_internal(user); + user->u_refcnt++; + mutex_exit(&user->u_mutex); + if (user->u_session->s_state == + SMB_SESSION_STATE_DISCONNECTED) + user->preserve_opens = SMB2_DH_PRESERVE_ALL; smb_user_logoff(user); smb_user_release(user); break; case SMB_USER_STATE_LOGGED_OFF: case SMB_USER_STATE_LOGGING_OFF: + mutex_exit(&user->u_mutex); break; default: ASSERT(0); + mutex_exit(&user->u_mutex); break; } - user = smb_llist_next(&session->s_user_list, user); + user = smb_llist_next(ulist, user); } - smb_llist_exit(&session->s_user_list); + /* Needed below (Was the list empty?) */ + user = smb_llist_head(ulist); + + smb_llist_exit(ulist); + + /* + * It's possible for user objects to remain due to references + * obtained via smb_server_lookup_ssnid(), when an SMB2 + * session setup is destroying a previous session. + * + * Wait for user objects to clear out (last refs. go away, + * then smb_user_delete takes them out of the list). When + * the last user object is removed, the session state is + * set to SHUTDOWN and s_lock is signaled. + * + * Not all places that call smb_user_release necessarily + * flush the delete queue, so after we wait for the list + * to empty out, go back to the top and recheck the list + * delete queue to make sure smb_user_delete happens. + */ + if (user == NULL) { + /* User list is empty. */ + smb_rwx_rwenter(&session->s_lock, RW_WRITER); + session->s_state = SMB_SESSION_STATE_SHUTDOWN; + smb_rwx_rwexit(&session->s_lock); + } else { + smb_rwx_rwenter(&session->s_lock, RW_READER); + if (session->s_state != SMB_SESSION_STATE_SHUTDOWN) { + (void) smb_rwx_cvwait(&session->s_lock, + MSEC_TO_TICK(200)); + smb_rwx_rwexit(&session->s_lock); + goto top; + } + smb_rwx_rwexit(&session->s_lock); + } + ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN); + + /* + * User list should be empty now. + */ +#ifdef DEBUG + if (ulist->ll_count != 0) { + cmn_err(CE_WARN, "user list not empty?"); + debug_enter("s_user_list"); + } +#endif + + /* + * User logoff happens first so we'll set preserve_opens + * for client-initiated disconnect. When that's done + * there should be no trees left, but check anyway. + */ + smb_session_disconnect_trees(session); } /* @@ -1400,6 +1452,7 @@ smb_request_alloc(smb_session_t *session, int req_length) ASSERT(0); /* FALLTHROUGH */ case SMB_SESSION_STATE_DISCONNECTED: + case SMB_SESSION_STATE_SHUTDOWN: case SMB_SESSION_STATE_TERMINATED: /* Disallow new requests in these states. */ if (sr->sr_request_buf) @@ -1429,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); } @@ -1480,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); @@ -1491,34 +1539,6 @@ smb_session_levelII_oplocks(smb_session_t *session) return (B_FALSE); } -/* - * smb_session_oplock_break - * - * Send an oplock break request to the client, - * recalling some cache delegation. - */ -void -smb_session_oplock_break(smb_request_t *sr, uint8_t brk) -{ - smb_session_t *session = sr->session; - mbuf_chain_t *mbc = &sr->reply; - - SMB_SESSION_VALID(session); - - /* - * Build the break message in sr->reply and then send it. - * The mbc is free'd later, in smb_request_free(). - */ - mbc->max_bytes = MLEN; - if (session->dialect <= NT_LM_0_12) { - smb1_oplock_break_notification(sr, brk); - } else { - smb2_oplock_break_notification(sr, brk); - } - - (void) smb_session_send(session, 0, mbc); -} - static void smb_session_genkey(smb_session_t *session) { 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_set_fileinfo.c b/usr/src/uts/common/fs/smbsrv/smb_set_fileinfo.c index 160459ab7f..32df1912e0 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_set_fileinfo.c +++ b/usr/src/uts/common/fs/smbsrv/smb_set_fileinfo.c @@ -293,12 +293,7 @@ smb_set_by_path(smb_request_t *sr, smb_xa_t *xa, uint16_t infolev) kmem_free(name, MAXNAMELEN); if (rc != 0) { - if (rc == ENOENT) { - smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND, - ERRDOS, ERROR_FILE_NOT_FOUND); - } else { - smbsr_errno(sr, rc); - } + smbsr_errno(sr, rc); return (-1); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_sign_kcf.c b/usr/src/uts/common/fs/smbsrv/smb_sign_kcf.c index fac52cab21..f991eb44b5 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_sign_kcf.c +++ b/usr/src/uts/common/fs/smbsrv/smb_sign_kcf.c @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* @@ -29,23 +29,34 @@ #include <smbsrv/smb_signing.h> /* - * SMB1 signing helpers: - * (getmech, init, update, final) + * Common function to see if a mech is available. */ - -int -smb_md5_getmech(smb_sign_mech_t *mech) +static int +find_mech(smb_sign_mech_t *mech, crypto_mech_name_t name) { crypto_mech_type_t t; - t = crypto_mech2id(SUN_CKM_MD5); - if (t == CRYPTO_MECH_INVALID) + t = crypto_mech2id(name); + if (t == CRYPTO_MECH_INVALID) { + cmn_err(CE_NOTE, "smb: no kcf mech: %s", name); return (-1); + } mech->cm_type = t; return (0); } /* + * SMB1 signing helpers: + * (getmech, init, update, final) + */ + +int +smb_md5_getmech(smb_sign_mech_t *mech) +{ + return (find_mech(mech, SUN_CKM_MD5)); +} + +/* * Start the KCF session, load the key */ int @@ -75,7 +86,12 @@ smb_md5_update(smb_sign_ctx_t ctx, void *buf, size_t len) rv = crypto_digest_update(ctx, &data, 0); - return (rv == CRYPTO_SUCCESS ? 0 : -1); + if (rv != CRYPTO_SUCCESS) { + crypto_cancel_ctx(ctx); + return (-1); + } + + return (0); } /* @@ -106,13 +122,7 @@ smb_md5_final(smb_sign_ctx_t ctx, uint8_t *digest16) int smb2_hmac_getmech(smb_sign_mech_t *mech) { - crypto_mech_type_t t; - - t = crypto_mech2id(SUN_CKM_SHA256_HMAC); - if (t == CRYPTO_MECH_INVALID) - return (-1); - mech->cm_type = t; - return (0); + return (find_mech(mech, SUN_CKM_SHA256_HMAC)); } /* @@ -152,7 +162,12 @@ smb2_hmac_update(smb_sign_ctx_t ctx, uint8_t *in, size_t len) rv = crypto_mac_update(ctx, &data, 0); - return (rv == CRYPTO_SUCCESS ? 0 : -1); + if (rv != CRYPTO_SUCCESS) { + crypto_cancel_ctx(ctx); + return (-1); + } + + return (0); } /* @@ -178,3 +193,80 @@ smb2_hmac_final(smb_sign_ctx_t ctx, uint8_t *digest16) return (rv == CRYPTO_SUCCESS ? 0 : -1); } + +/* + * SMB3 signing helpers: + * (getmech, init, update, final) + */ + +int +smb3_cmac_getmech(smb_sign_mech_t *mech) +{ + return (find_mech(mech, SUN_CKM_AES_CMAC)); +} + +/* + * Start the KCF session, load the key + */ +int +smb3_cmac_init(smb_sign_ctx_t *ctxp, smb_sign_mech_t *mech, + uint8_t *key, size_t key_len) +{ + crypto_key_t ckey; + int rv; + + bzero(&ckey, sizeof (ckey)); + ckey.ck_format = CRYPTO_KEY_RAW; + ckey.ck_data = key; + ckey.ck_length = key_len * 8; /* in bits */ + + rv = crypto_mac_init(mech, &ckey, NULL, ctxp, NULL); + + return (rv == CRYPTO_SUCCESS ? 0 : -1); +} + +/* + * Digest one segment + */ +int +smb3_cmac_update(smb_sign_ctx_t ctx, uint8_t *in, size_t len) +{ + crypto_data_t data; + int rv; + + bzero(&data, sizeof (data)); + data.cd_format = CRYPTO_DATA_RAW; + data.cd_length = len; + data.cd_raw.iov_base = (void *)in; + data.cd_raw.iov_len = len; + + rv = crypto_mac_update(ctx, &data, 0); + + if (rv != CRYPTO_SUCCESS) { + crypto_cancel_ctx(ctx); + return (-1); + } + + return (0); +} + +/* + * Note, the SMB2 signature is just the AES CMAC digest. + * (both are 16 bytes long) + */ +int +smb3_cmac_final(smb_sign_ctx_t ctx, uint8_t *digest16) +{ + crypto_data_t out; + int rv; + + bzero(&out, sizeof (out)); + out.cd_format = CRYPTO_DATA_RAW; + out.cd_length = SMB2_SIG_SIZE; + out.cd_raw.iov_len = SMB2_SIG_SIZE; + out.cd_raw.iov_base = (void *)digest16; + + rv = crypto_mac_final(ctx, &out, 0); + + return (rv == CRYPTO_SUCCESS ? 0 : -1); +} diff --git a/usr/src/uts/common/fs/smbsrv/smb_signing.c b/usr/src/uts/common/fs/smbsrv/smb_signing.c index c6f0212d31..ea3ba9693d 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_signing.c +++ b/usr/src/uts/common/fs/smbsrv/smb_signing.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2007, 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. */ /* * These routines provide the SMB MAC signing for the SMB server. @@ -119,7 +119,7 @@ smb_sign_fini(smb_session_t *s) * NTLM response and store it in the signing structure. * This is what begins SMB signing. */ -int +void smb_sign_begin(smb_request_t *sr, smb_token_t *token) { smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup; @@ -135,7 +135,7 @@ smb_sign_begin(smb_request_t *sr, smb_token_t *token) * session key, in which case: just don't sign. */ if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0) - return (0); + return; /* * Session-level initialization (once per session) @@ -148,7 +148,7 @@ smb_sign_begin(smb_request_t *sr, smb_token_t *token) */ if (sign->mackey != NULL) { smb_rwx_rwexit(&session->s_lock); - return (0); + return; } /* @@ -160,7 +160,7 @@ smb_sign_begin(smb_request_t *sr, smb_token_t *token) if (rc != 0) { kmem_free(mech, sizeof (*mech)); smb_rwx_rwexit(&session->s_lock); - return (rc); + return; } session->sign_mech = mech; session->sign_fini = smb_sign_fini; @@ -194,7 +194,6 @@ smb_sign_begin(smb_request_t *sr, smb_token_t *token) } smb_rwx_rwexit(&session->s_lock); - return (0); } /* 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_trans2_find.c b/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c index ecb7c5c639..d7d1565b2a 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c +++ b/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c @@ -344,7 +344,8 @@ smb_com_trans2_find_first2(smb_request_t *sr, smb_xa_t *xa) if (count == 0) { smb_odir_close(od); smb_odir_release(od); - smbsr_errno(sr, ENOENT); + smbsr_status(sr, NT_STATUS_NO_SUCH_FILE, + ERRDOS, ERROR_FILE_NOT_FOUND); return (SDRC_ERROR); } diff --git a/usr/src/uts/common/fs/smbsrv/smb_tree.c b/usr/src/uts/common/fs/smbsrv/smb_tree.c index 6e86e491d6..da7c2f7416 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_tree.c +++ b/usr/src/uts/common/fs/smbsrv/smb_tree.c @@ -21,7 +21,7 @@ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright 2013 Nexenta Systems, Inc. All rights reserved. + * Copyright 2018 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ @@ -122,10 +122,14 @@ * * Transition T2 * + * This transition occurs in smb_tree_disconnect() + * + * Transition T3 + * * This transition occurs in smb_tree_release(). The resources associated * with the tree are freed as well as the tree structure. For the transition - * to occur, the tree must be in the SMB_TREE_STATE_DISCONNECTED state and - * the reference count be zero. + * to occur, the tree must be in the SMB_TREE_STATE_DISCONNECTED and the + * reference count must be zero. * * Comments * -------- @@ -144,7 +148,11 @@ * Rules of access to a tree structure: * * 1) In order to avoid deadlocks, when both (mutex and lock of the user - * list) have to be entered, the lock must be entered first. + * list) have to be entered, the lock must be entered first. Additionally, + * when both the (mutex and lock of the ofile list) have to be entered, + * the mutex must be entered first. However, the ofile list lock must NOT + * be dropped while the mutex is held in such a way that the ofile deleteq + * is flushed. * * 2) All actions applied to a tree require a reference count. * @@ -178,8 +186,8 @@ uint32_t smb_tree_connect_printq(smb_request_t *, smb_arg_tcon_t *); uint32_t smb_tree_connect_ipc(smb_request_t *, smb_arg_tcon_t *); static smb_tree_t *smb_tree_alloc(smb_request_t *, const smb_kshare_t *, smb_node_t *, uint32_t, uint32_t); +static void smb_tree_dealloc(void *); static boolean_t smb_tree_is_connected_locked(smb_tree_t *); -static boolean_t smb_tree_is_disconnected(smb_tree_t *); static char *smb_tree_get_sharename(char *); static int smb_tree_getattr(const smb_kshare_t *, smb_node_t *, smb_tree_t *); static void smb_tree_get_volname(vfs_t *, smb_tree_t *); @@ -297,7 +305,7 @@ smb_tree_disconnect(smb_tree_t *tree, boolean_t do_exec) /* * The files opened under this tree are closed. */ - smb_ofile_close_all(tree); + smb_ofile_close_all(tree, 0); /* * The directories opened under this tree are closed. */ @@ -371,42 +379,32 @@ smb_tree_release( { SMB_TREE_VALID(tree); - mutex_enter(&tree->t_mutex); - ASSERT(tree->t_refcnt); - tree->t_refcnt--; - /* flush the ofile and odir lists' delete queues */ smb_llist_flush(&tree->t_ofile_list); smb_llist_flush(&tree->t_odir_list); - if (smb_tree_is_disconnected(tree) && (tree->t_refcnt == 0)) - smb_session_post_tree(tree->t_session, tree); - - mutex_exit(&tree->t_mutex); -} - -void -smb_tree_post_ofile(smb_tree_t *tree, smb_ofile_t *of) -{ - SMB_TREE_VALID(tree); - SMB_OFILE_VALID(of); - ASSERT(of->f_refcnt == 0); - ASSERT(of->f_state == SMB_OFILE_STATE_CLOSED); - ASSERT(of->f_tree == tree); - - smb_llist_post(&tree->t_ofile_list, of, smb_ofile_delete); -} + mutex_enter(&tree->t_mutex); + ASSERT(tree->t_refcnt); + tree->t_refcnt--; -void -smb_tree_post_odir(smb_tree_t *tree, smb_odir_t *od) -{ - SMB_TREE_VALID(tree); - SMB_ODIR_VALID(od); - ASSERT(od->d_refcnt == 0); - ASSERT(od->d_state == SMB_ODIR_STATE_CLOSED); - ASSERT(od->d_tree == tree); + switch (tree->t_state) { + case SMB_TREE_STATE_DISCONNECTED: + if (tree->t_refcnt == 0) { + smb_session_t *ssn = tree->t_session; + tree->t_state = SMB_TREE_STATE_DISCONNECTED; + smb_llist_post(&ssn->s_tree_list, tree, + smb_tree_dealloc); + } + break; + case SMB_TREE_STATE_CONNECTED: + case SMB_TREE_STATE_DISCONNECTING: + break; + default: + ASSERT(0); + break; + } - smb_llist_post(&tree->t_odir_list, od, smb_odir_delete); + mutex_exit(&tree->t_mutex); } /* @@ -420,7 +418,7 @@ smb_tree_close_pid( ASSERT(tree); ASSERT(tree->t_magic == SMB_TREE_MAGIC); - smb_ofile_close_all_by_pid(tree, pid); + smb_ofile_close_all(tree, pid); smb_tree_close_odirs(tree, pid); } @@ -484,6 +482,11 @@ smb_tree_fclose(smb_tree_t *tree, uint32_t uniqid) ASSERT(tree); ASSERT(tree->t_magic == SMB_TREE_MAGIC); + /* + * Note that ORPHANED ofiles aren't fclosable, as they have + * no session, user, or tree by which they might be found. + * They will eventually expire. + */ if ((of = smb_ofile_lookup_by_uniqid(tree, uniqid)) == NULL) return (ENOENT); @@ -650,7 +653,7 @@ smb_tree_connect_disk(smb_request_t *sr, smb_arg_tcon_t *tcon) smb_user_t *user = sr->uid_user; smb_node_t *dnode = NULL; smb_node_t *snode = NULL; - smb_kshare_t *si = tcon->si; + smb_kshare_t *si = tcon->si; char *service = tcon->service; char last_component[MAXNAMELEN]; smb_tree_t *tree; @@ -772,7 +775,7 @@ smb_tree_connect_printq(smb_request_t *sr, smb_arg_tcon_t *tcon) smb_user_t *user = sr->uid_user; smb_node_t *dnode = NULL; smb_node_t *snode = NULL; - smb_kshare_t *si = tcon->si; + smb_kshare_t *si = tcon->si; char *service = tcon->service; char last_component[MAXNAMELEN]; smb_tree_t *tree; @@ -922,7 +925,7 @@ smb_tree_alloc(smb_request_t *sr, const smb_kshare_t *si, } smb_llist_constructor(&tree->t_ofile_list, sizeof (smb_ofile_t), - offsetof(smb_ofile_t, f_lnd)); + offsetof(smb_ofile_t, f_tree_lnd)); smb_llist_constructor(&tree->t_odir_list, sizeof (smb_odir_t), offsetof(smb_odir_t, d_lnd)); @@ -968,7 +971,7 @@ smb_tree_alloc(smb_request_t *sr, const smb_kshare_t *si, * Remove the tree from the user's tree list before freeing resources * associated with the tree. */ -void +static void smb_tree_dealloc(void *arg) { smb_session_t *session; @@ -985,6 +988,13 @@ smb_tree_dealloc(void *arg) atomic_dec_32(&session->s_tree_cnt); smb_llist_exit(&session->s_tree_list); + /* + * This tree is no longer on s_tree_list, however... + * + * This is called via smb_llist_post, which means it may run + * BEFORE smb_tree_release drops t_mutex (if another thread + * flushes the delete queue before we do). Synchronize. + */ mutex_enter(&tree->t_mutex); mutex_exit(&tree->t_mutex); @@ -1019,7 +1029,7 @@ smb_tree_is_connected_locked(smb_tree_t *tree) case SMB_TREE_STATE_DISCONNECTING: case SMB_TREE_STATE_DISCONNECTED: /* - * The tree exists but being diconnected or destroyed. + * The tree exists but is being disconnected or destroyed. */ return (B_FALSE); @@ -1030,27 +1040,6 @@ smb_tree_is_connected_locked(smb_tree_t *tree) } /* - * Determine whether or not a tree is disconnected. - * This function must be called with the tree mutex held. - */ -static boolean_t -smb_tree_is_disconnected(smb_tree_t *tree) -{ - switch (tree->t_state) { - case SMB_TREE_STATE_DISCONNECTED: - return (B_TRUE); - - case SMB_TREE_STATE_CONNECTED: - case SMB_TREE_STATE_DISCONNECTING: - return (B_FALSE); - - default: - ASSERT(0); - return (B_FALSE); - } -} - -/* * Return a pointer to the share name within a share resource path. * * The share path may be a Uniform Naming Convention (UNC) string @@ -1172,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_user.c b/usr/src/uts/common/fs/smbsrv/smb_user.c index 046fa00b4d..0bfceb4ff4 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_user.c +++ b/usr/src/uts/common/fs/smbsrv/smb_user.c @@ -181,7 +181,9 @@ * Rules of access to a user structure: * * 1) In order to avoid deadlocks, when both (mutex and lock of the session - * list) have to be entered, the lock must be entered first. + * list) have to be entered, the lock must be entered first. Additionally, + * one may NOT flush the deleteq of either the tree list or the ofile list + * while the user mutex is held. * * 2) All actions applied to a user require a reference count. * @@ -208,23 +210,36 @@ #define ADMINISTRATORS_SID "S-1-5-32-544" +/* Don't leak object addresses */ +#define SMB_USER_SSNID(u) \ + ((uintptr_t)&smb_cache_user ^ (uintptr_t)(u)) + +static void smb_user_delete(void *); static int smb_user_enum_private(smb_user_t *, smb_svcenum_t *); static void smb_user_auth_logoff(smb_user_t *); static void smb_user_logoff_tq(void *); - /* * Create a new user. + * + * For SMB2 and later, session IDs (u_ssnid) need to be unique among all + * current and "recent" sessions. The session ID is derived from the + * address of the smb_user object (obscured by XOR with a constant). + * This adds a 3-bit generation number in the low bits, incremented + * when we allocate an smb_user_t from its kmem cache, so it can't + * be confused with a (recent) previous incarnation of this object. */ smb_user_t * smb_user_new(smb_session_t *session) { smb_user_t *user; + uint_t gen; // generation (low 3 bits of ssnid) ASSERT(session); ASSERT(session->s_magic == SMB_SESSION_MAGIC); user = kmem_cache_alloc(smb_cache_user, KM_SLEEP); + gen = (user->u_ssnid + 1) & 7; bzero(user, sizeof (smb_user_t)); user->u_refcnt = 1; @@ -234,6 +249,7 @@ smb_user_new(smb_session_t *session) if (smb_idpool_alloc(&session->s_uid_pool, &user->u_uid)) goto errout; + user->u_ssnid = SMB_USER_SSNID(user) + gen; mutex_init(&user->u_mutex, NULL, MUTEX_DEFAULT, NULL); user->u_state = SMB_USER_STATE_LOGGING_ON; @@ -317,8 +333,11 @@ smb_user_logon( /* * smb_user_logoff * - * Change the user state and disconnect trees. + * Change the user state to "logging off" and disconnect trees. * The user list must not be entered or modified here. + * + * We remain in state "logging off" until the last ref. is gone, + * then smb_user_release takes us to state "logged off". */ void smb_user_logoff( @@ -337,8 +356,15 @@ smb_user_logoff( user->u_authsock = NULL; tmo = user->u_auth_tmo; user->u_auth_tmo = NULL; - user->u_state = SMB_USER_STATE_LOGGED_OFF; - smb_server_dec_users(user->u_server); + user->u_state = SMB_USER_STATE_LOGGING_OFF; + mutex_exit(&user->u_mutex); + + /* Timeout callback takes u_mutex. See untimeout(9f) */ + if (tmo != NULL) + (void) untimeout(tmo); + /* This close can block, so not under the mutex. */ + if (authsock != NULL) + smb_authsock_close(user, authsock); break; case SMB_USER_STATE_LOGGED_ON: @@ -350,29 +376,18 @@ smb_user_logoff( mutex_exit(&user->u_mutex); smb_session_disconnect_owned_trees(user->u_session, user); smb_user_auth_logoff(user); - mutex_enter(&user->u_mutex); - user->u_state = SMB_USER_STATE_LOGGED_OFF; - smb_server_dec_users(user->u_server); break; case SMB_USER_STATE_LOGGED_OFF: case SMB_USER_STATE_LOGGING_OFF: + mutex_exit(&user->u_mutex); break; default: ASSERT(0); + mutex_exit(&user->u_mutex); break; } - mutex_exit(&user->u_mutex); - - /* Timeout callback takes u_mutex. See untimeout(9f) */ - if (tmo != NULL) - (void) untimeout(tmo); - - /* This close can block, so not under the mutex. */ - if (authsock != NULL) { - smb_authsock_close(user, authsock); - } } /* @@ -419,23 +434,32 @@ void smb_user_release( smb_user_t *user) { - ASSERT(user->u_magic == SMB_USER_MAGIC); + smb_session_t *ssn = user->u_session; + + SMB_USER_VALID(user); + + /* flush the tree list delete queue */ + smb_llist_flush(&ssn->s_tree_list); mutex_enter(&user->u_mutex); ASSERT(user->u_refcnt); user->u_refcnt--; switch (user->u_state) { - case SMB_USER_STATE_LOGGED_OFF: - if (user->u_refcnt == 0) - smb_session_post_user(user->u_session, user); + case SMB_USER_STATE_LOGGING_OFF: + if (user->u_refcnt == 0) { + smb_session_t *ssn = user->u_session; + user->u_state = SMB_USER_STATE_LOGGED_OFF; + smb_llist_post(&ssn->s_user_list, user, + smb_user_delete); + } break; case SMB_USER_STATE_LOGGING_ON: case SMB_USER_STATE_LOGGED_ON: - case SMB_USER_STATE_LOGGING_OFF: break; + case SMB_USER_STATE_LOGGED_OFF: default: ASSERT(0); break; @@ -626,11 +650,12 @@ smb_user_enum(smb_user_t *user, smb_svcenum_t *svcenum) * Remove the user from the session's user list before freeing resources * associated with the user. */ -void +static void smb_user_delete(void *arg) { smb_session_t *session; smb_user_t *user = (smb_user_t *)arg; + uint32_t ucount; SMB_USER_VALID(user); ASSERT(user->u_refcnt == 0); @@ -639,11 +664,28 @@ smb_user_delete(void *arg) ASSERT(user->u_auth_tmo == NULL); session = user->u_session; + + smb_server_dec_users(session->s_server); smb_llist_enter(&session->s_user_list, RW_WRITER); smb_llist_remove(&session->s_user_list, user); smb_idpool_free(&session->s_uid_pool, user->u_uid); + ucount = smb_llist_get_count(&session->s_user_list); smb_llist_exit(&session->s_user_list); + if (ucount == 0) { + smb_rwx_rwenter(&session->s_lock, RW_WRITER); + session->s_state = SMB_SESSION_STATE_SHUTDOWN; + smb_rwx_cvbcast(&session->s_lock); + smb_rwx_rwexit(&session->s_lock); + } + + /* + * This user is no longer on s_user_list, however... + * + * This is called via smb_llist_post, which means it may run + * BEFORE smb_user_release drops u_mutex (if another thread + * flushes the delete queue before we do). Synchronize. + */ mutex_enter(&user->u_mutex); mutex_exit(&user->u_mutex); @@ -777,7 +819,6 @@ smb_user_netinfo_init(smb_user_t *user, smb_netuserinfo_t *info) info->ui_native_os = session->native_os; info->ui_ipaddr = session->ipaddr; info->ui_numopens = session->s_file_cnt; - info->ui_smb_uid = user->u_uid; info->ui_logon_time = user->u_logon_time; info->ui_flags = user->u_flags; info->ui_posix_uid = crgetuid(user->u_cred); @@ -811,11 +852,34 @@ smb_user_netinfo_fini(smb_netuserinfo_t *info) bzero(info, sizeof (smb_netuserinfo_t)); } +/* + * Tell smbd this user is going away so it can clean up their + * audit session, autohome dir, etc. + * + * Note that when we're shutting down, smbd will already have set + * smbd.s_shutting_down and therefore will ignore door calls. + * Skip this during shutdown to reduce upcall noise. + */ static void smb_user_auth_logoff(smb_user_t *user) { - uint32_t audit_sid = user->u_audit_sid; + smb_server_t *sv = user->u_server; + uint32_t audit_sid; + + if (sv->sv_state != SMB_SERVER_STATE_RUNNING) + return; - (void) smb_kdoor_upcall(user->u_server, SMB_DR_USER_AUTH_LOGOFF, + audit_sid = user->u_audit_sid; + (void) smb_kdoor_upcall(sv, SMB_DR_USER_AUTH_LOGOFF, &audit_sid, xdr_uint32_t, NULL, NULL); } + +boolean_t +smb_is_same_user(cred_t *cr1, cred_t *cr2) +{ + ksid_t *ks1 = crgetsid(cr1, KSID_USER); + ksid_t *ks2 = crgetsid(cr2, KSID_USER); + + return (ks1->ks_rid == ks2->ks_rid && + strcmp(ks1->ks_domain->kd_name, ks2->ks_domain->kd_name) == 0); +} diff --git a/usr/src/uts/common/fs/smbsrv/smb_vops.c b/usr/src/uts/common/fs/smbsrv/smb_vops.c index 7a3905d7bb..f90c2d837d 100644 --- a/usr/src/uts/common/fs/smbsrv/smb_vops.c +++ b/usr/src/uts/common/fs/smbsrv/smb_vops.c @@ -1006,7 +1006,8 @@ smb_vop_readdir(vnode_t *vp, uint32_t offset, if (vp->v_type != VDIR) return (ENOTDIR); - if (vfs_has_feature(vp->v_vfsp, VFSFT_DIRENTFLAGS)) { + if ((rddir_flag & SMB_EDIRENT) != 0 && + vfs_has_feature(vp->v_vfsp, VFSFT_DIRENTFLAGS)) { flags |= V_RDDIR_ENTFLAGS; rdirent_size = sizeof (edirent_t); } else { 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 c2ab2186ad..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 @@ -30,9 +30,7 @@ /* * This file defines the list of Win32 status codes. If you need * a status code that is defined in the [MS-ERREF] document but - * is not listed here, please add it to the file. This file is - * compatible with the Windows DDK file inc/ntstatus.h - * Please preserve this compatibility. + * is not listed here, please add it to the file. * * Be careful not to confuse error codes with status codes. The error * codes are listed in nterror.h. Some mappings between NT status @@ -71,16 +69,6 @@ extern "C" { #endif /* - * XXX: Some temporary left-overs from the old ntstatus.h - * Should eliminate uses of these macros when convenient. - */ -/* This used to OR in the severity bits. */ -#define NT_SC_ERROR(S) (S) -/* This used to mask off the severity bits. */ -#define NT_SC_VALUE(S) (S) -/* XXX end of temporary left-overs. */ - -/* * One non-NT macro added for getting the severity value * from a given NT status code. Evaluates to one of the * SEVERITY values defined below. @@ -101,6 +89,10 @@ extern "C" { #define NT_STATUS_SEVERITY_WARNING 2 /* 0x80000000 */ #define NT_STATUS_SEVERITY_ERROR 3 /* 0xC0000000 */ +/* + * Please keep the following defines sorted by number. + */ + #define NT_STATUS_SUCCESS 0x00000000 /* Facility OS (0x..00....) */ @@ -137,6 +129,17 @@ extern "C" { #define NT_STATUS_NOTHING_TO_TERMINATE 0x00000122 #define NT_STATUS_PROCESS_NOT_IN_JOB 0x00000123 #define NT_STATUS_PROCESS_IN_JOB 0x00000124 +#define NT_STATUS_VOLSNAP_HIBERNATE_READY 0x00000125 +#define NT_STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY 0x00000126 +#define NT_STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED 0x00000127 +#define NT_STATUS_INTERRUPT_STILL_CONNECTED 0x00000128 +#define NT_STATUS_PROCESS_CLONED 0x00000129 +#define NT_STATUS_FILE_LOCKED_WITH_ONLY_READERS 0x0000012A +#define NT_STATUS_FILE_LOCKED_WITH_WRITERS 0x0000012B +#define NT_STATUS_RESOURCEMANAGER_READ_ONLY 0x00000202 +#define NT_STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE 0x00000215 +#define NT_STATUS_OPLOCK_HANDLE_CLOSED 0x00000216 +#define NT_STATUS_WAIT_FOR_OPLOCK 0x00000367 /* All severity 1 (informational) */ #define NT_STATUS_OBJECT_NAME_EXISTS 0x40000000 @@ -183,6 +186,21 @@ extern "C" { #define NT_STATUS_MP_PROCESSOR_MISMATCH 0x40000029 #define NT_STATUS_HIBERNATED 0x4000002A #define NT_STATUS_RESUME_HIBERNATION 0x4000002B +#define NT_STATUS_FIRMWARE_UPDATED 0x4000002C +#define NT_STATUS_DRIVERS_LEAKING_LOCKED_PAGES 0x4000002D +#define NT_STATUS_MESSAGE_RETRIEVED 0x4000002E +#define NT_STATUS_SYSTEM_POWERSTATE_TRANSITION 0x4000002F +#define NT_STATUS_ALPC_CHECK_COMPLETION_LIST 0x40000030 +#define NT_STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION 0x40000031 +#define NT_STATUS_ACCESS_AUDIT_BY_POLICY 0x40000032 +#define NT_STATUS_ABANDON_HIBERFILE 0x40000033 +#define NT_STATUS_BIZRULES_NOT_ENABLED 0x40000034 +#define NT_STATUS_WAKE_SYSTEM 0x40000294 +#define NT_STATUS_DS_SHUTTING_DOWN 0x40000370 + +/* Facility RPC Runtime (0x..02....) */ +#define RPC_NT_UUID_LOCAL_ONLY 0x40020056 +#define RPC_NT_SEND_INCOMPLETE 0x400200AF /* All severity 2 (warning) */ #define NT_STATUS_GUARD_PAGE_VIOLATION 0x80000001 @@ -223,8 +241,18 @@ extern "C" { #define NT_STATUS_CLEANER_CARTRIDGE_INSTALLED 0x80000027 #define NT_STATUS_PLUGPLAY_QUERY_VETOED 0x80000028 #define NT_STATUS_UNWIND_CONSOLIDATE 0x80000029 +#define NT_STATUS_REGISTRY_HIVE_RECOVERED 0x8000002A +#define NT_STATUS_DLL_MIGHT_BE_INSECURE 0x8000002B +#define NT_STATUS_DLL_MIGHT_BE_INCOMPATIBLE 0x8000002C +#define NT_STATUS_STOPPED_ON_SYMLINK 0x8000002D +#define NT_STATUS_CANNOT_GRANT_REQUESTED_OPLOCK 0x8000002E +#define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 +#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 +#define NT_STATUS_DATA_LOST_REPAIR 0x80000803 -/* Mostly severity 3 (error) - but NOT all! */ +/* facility cluster (0x8013....) etc, not used */ + +/* All severity 3 (error) */ #define NT_STATUS_UNSUCCESSFUL 0xC0000001 #define NT_STATUS_NOT_IMPLEMENTED 0xC0000002 #define NT_STATUS_INVALID_INFO_CLASS 0xC0000003 @@ -494,14 +522,7 @@ extern "C" { #define NT_STATUS_NO_GUID_TRANSLATION 0xC000010C #define NT_STATUS_CANNOT_IMPERSONATE 0xC000010D #define NT_STATUS_IMAGE_ALREADY_LOADED 0xC000010E -#define NT_STATUS_ABIOS_NOT_PRESENT 0xC000010F -#define NT_STATUS_ABIOS_LID_NOT_EXIST 0xC0000110 -#define NT_STATUS_ABIOS_LID_ALREADY_OWNED 0xC0000111 -#define NT_STATUS_ABIOS_NOT_LID_OWNER 0xC0000112 -#define NT_STATUS_ABIOS_INVALID_COMMAND 0xC0000113 -#define NT_STATUS_ABIOS_INVALID_LID 0xC0000114 -#define NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE 0xC0000115 -#define NT_STATUS_ABIOS_INVALID_SELECTOR 0xC0000116 +/* Old: NT_STATUS_ABIOS_... 0xC000010F - 0xC0000116 */ #define NT_STATUS_NO_LDT 0xC0000117 #define NT_STATUS_INVALID_LDT_SIZE 0xC0000118 #define NT_STATUS_INVALID_LDT_OFFSET 0xC0000119 @@ -632,8 +653,16 @@ extern "C" { #define NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT 0xC000019A #define NT_STATUS_DOMAIN_TRUST_INCONSISTENT 0xC000019B #define NT_STATUS_FS_DRIVER_REQUIRED 0xC000019C +#define NT_STATUS_IMAGE_ALREADY_LOADED_AS_DLL 0xC000019D +/* Was: NT_STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING */ +#define NT_STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_SETTING 0xC000019E #define NT_STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME 0xC000019F +#define NT_STATUS_SECURITY_STREAM_IS_INCONSISTENT 0xC00001A0 #define NT_STATUS_INVALID_LOCK_RANGE 0xC00001A1 +#define NT_STATUS_INVALID_ACE_CONDITION 0xC00001A2 +#define NT_STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT 0xC00001A3 +#define NT_STATUS_NOTIFICATION_GUID_ALREADY_DEFINED 0xC00001A4 +#define NT_STATUS_NETWORK_OPEN_RESTRICTION 0xC0000201 #define NT_STATUS_NO_USER_SESSION_KEY 0xC0000202 #define NT_STATUS_USER_SESSION_DELETED 0xC0000203 #define NT_STATUS_RESOURCE_LANG_NOT_FOUND 0xC0000204 @@ -754,8 +783,8 @@ extern "C" { #define NT_STATUS_ILLEGAL_ELEMENT_ADDRESS 0xC0000285 #define NT_STATUS_MAGAZINE_NOT_PRESENT 0xC0000286 #define NT_STATUS_REINITIALIZATION_NEEDED 0xC0000287 -#define NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 -#define NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 +/* NT_STATUS_DEVICE_REQUIRES_CLEANING 0x80000288 */ +/* NT_STATUS_DEVICE_DOOR_OPEN 0x80000289 */ #define NT_STATUS_ENCRYPTION_FAILED 0xC000028A #define NT_STATUS_DECRYPTION_FAILED 0xC000028B #define NT_STATUS_RANGE_NOT_FOUND 0xC000028C @@ -766,7 +795,7 @@ extern "C" { #define NT_STATUS_FILE_NOT_ENCRYPTED 0xC0000291 #define NT_STATUS_NOT_EXPORT_FORMAT 0xC0000292 #define NT_STATUS_FILE_ENCRYPTED 0xC0000293 -#define NT_STATUS_WAKE_SYSTEM 0x40000294 +/* NT_STATUS_WAKE_SYSTEM 0x40000294 */ #define NT_STATUS_WMI_GUID_NOT_FOUND 0xC0000295 #define NT_STATUS_WMI_INSTANCE_NOT_FOUND 0xC0000296 #define NT_STATUS_WMI_ITEMID_NOT_FOUND 0xC0000297 @@ -904,7 +933,7 @@ extern "C" { #define NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER 0xC0000364 #define NT_STATUS_FAILED_DRIVER_ENTRY 0xC0000365 #define NT_STATUS_DEVICE_ENUMERATION_ERROR 0xC0000366 -#define NT_STATUS_WAIT_FOR_OPLOCK 0x00000367 +/* NT_STATUS_WAIT_FOR_OPLOCK 0x00000367 */ #define NT_STATUS_MOUNT_POINT_NOT_RESOLVED 0xC0000368 #define NT_STATUS_INVALID_DEVICE_OBJECT_PARAMETER 0xC0000369 #define NT_STATUS_MCA_OCCURED 0xC000036A @@ -913,7 +942,11 @@ extern "C" { #define NT_STATUS_DRIVER_DATABASE_ERROR 0xC000036D #define NT_STATUS_SYSTEM_HIVE_TOO_LARGE 0xC000036E #define NT_STATUS_INVALID_IMPORT_OF_NON_DLL 0xC000036F -#define NT_STATUS_DS_SHUTTING_DOWN 0x40000370 +/* NT_STATUS_DS_SHUTTING_DOWN 0x40000370 */ +#define NT_STATUS_NO_SECRETS 0xC0000371 +#define NT_STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY 0xC0000372 +#define NT_STATUS_FAILED_STACK_SWITCH 0xC0000373 +#define NT_STATUS_HEAP_CORRUPTION 0xC0000374 #define NT_STATUS_SMARTCARD_WRONG_PIN 0xC0000380 #define NT_STATUS_SMARTCARD_CARD_BLOCKED 0xC0000381 #define NT_STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED 0xC0000382 @@ -929,8 +962,153 @@ extern "C" { #define NT_STATUS_PKINIT_CLIENT_FAILURE 0xC000038C #define NT_STATUS_SMARTCARD_CERT_EXPIRED 0xC000038D #define NT_STATUS_DRIVER_FAILED_PRIOR_UNLOAD 0xC000038E +#define NT_STATUS_SMARTCARD_SILENT_CONTEXT 0xC000038F +#define NT_STATUS_PER_USER_TRUST_QUOTA_EXCEEDED 0xC0000401 +#define NT_STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED 0xC0000402 +#define NT_STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED 0xC0000403 +#define NT_STATUS_DS_NAME_NOT_UNIQUE 0xC0000404 +#define NT_STATUS_DS_DUPLICATE_ID_FOUND 0xC0000405 +#define NT_STATUS_DS_GROUP_CONVERSION_ERROR 0xC0000406 +#define NT_STATUS_VOLSNAP_PREPARE_HIBERNATE 0xC0000407 +#define NT_STATUS_USER2USER_REQUIRED 0xC0000408 +#define NT_STATUS_STACK_BUFFER_OVERRUN 0xC0000409 +#define NT_STATUS_NO_S4U_PROT_SUPPORT 0xC000040A +#define NT_STATUS_CROSSREALM_DELEGATION_FAILURE 0xC000040B +#define NT_STATUS_REVOCATION_OFFLINE_KDC 0xC000040C +#define NT_STATUS_ISSUING_CA_UNTRUSTED_KDC 0xC000040D +#define NT_STATUS_KDC_CERT_EXPIRED 0xC000040E +#define NT_STATUS_KDC_CERT_REVOKED 0xC000040F +#define NT_STATUS_PARAMETER_QUOTA_EXCEEDED 0xC0000410 +#define NT_STATUS_HIBERNATION_FAILURE 0xC0000411 +#define NT_STATUS_DELAY_LOAD_FAILED 0xC0000412 +#define NT_STATUS_AUTHENTICATION_FIREWALL_FAILED 0xC0000413 +#define NT_STATUS_VDM_DISALLOWED 0xC0000414 +#define NT_STATUS_HUNG_DISPLAY_DRIVER_THREAD 0xC0000415 +/* Was: NT_STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE */ +#define NT_STATUS_INSUFFICIENT_RESOURCE_FOR_SHARED_SECTION_SIZE 0xC0000416 +#define NT_STATUS_INVALID_CRUNTIME_PARAMETER 0xC0000417 +#define NT_STATUS_NTLM_BLOCKED 0xC0000418 +#define NT_STATUS_DS_SRC_SID_EXISTS_IN_FOREST 0xC0000419 +#define NT_STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST 0xC000041A +#define NT_STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST 0xC000041B +#define NT_STATUS_INVALID_USER_PRINCIPAL_NAME 0xC000041C +#define NT_STATUS_ASSERTION_FAILURE 0xC0000420 +#define NT_STATUS_VERIFIER_STOP 0xC0000421 +#define NT_STATUS_CALLBACK_POP_STACK 0xC0000423 +#define NT_STATUS_INCOMPATIBLE_DRIVER_BLOCKED 0xC0000424 +#define NT_STATUS_HIVE_UNLOADED 0xC0000425 +#define NT_STATUS_COMPRESSION_DISABLED 0xC0000426 +#define NT_STATUS_FILE_SYSTEM_LIMITATION 0xC0000427 +#define NT_STATUS_INVALID_IMAGE_HASH 0xC0000428 +#define NT_STATUS_NOT_CAPABLE 0xC0000429 +#define NT_STATUS_REQUEST_OUT_OF_SEQUENCE 0xC000042A +#define NT_STATUS_IMPLEMENTATION_LIMIT 0xC000042B +#define NT_STATUS_ELEVATION_REQUIRED 0xC000042C +#define NT_STATUS_NO_SECURITY_CONTEXT 0xC000042D +#define NT_STATUS_PKU2U_CERT_FAILURE 0xC000042E +#define NT_STATUS_BEYOND_VDL 0xC0000432 +#define NT_STATUS_ENCOUNTERED_WRITE_IN_PROGRESS 0xC0000433 +#define NT_STATUS_PTE_CHANGED 0xC0000434 +#define NT_STATUS_PURGE_FAILED 0xC0000435 +#define NT_STATUS_CRED_REQUIRES_CONFIRMATION 0xC0000440 +#define NT_STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE 0xC0000441 +#define NT_STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER 0xC0000442 +#define NT_STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE 0xC0000443 +#define NT_STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE 0xC0000444 +#define NT_STATUS_CS_ENCRYPTION_FILE_NOT_CSE 0xC0000445 +#define NT_STATUS_INVALID_LABEL 0xC0000446 +#define NT_STATUS_DRIVER_PROCESS_TERMINATED 0xC0000450 +#define NT_STATUS_AMBIGUOUS_SYSTEM_DEVICE 0xC0000451 +#define NT_STATUS_SYSTEM_DEVICE_NOT_FOUND 0xC0000452 +#define NT_STATUS_RESTART_BOOT_APPLICATION 0xC0000453 +#define NT_STATUS_INSUFFICIENT_NVRAM_RESOURCES 0xC0000454 +#define NT_STATUS_NO_RANGES_PROCESSED 0xC0000460 +#define NT_STATUS_DEVICE_FEATURE_NOT_SUPPORTED 0xC0000463 +#define NT_STATUS_DEVICE_UNREACHABLE 0xC0000464 +#define NT_STATUS_INVALID_TOKEN 0xC0000465 +#define NT_STATUS_SERVER_UNAVAILABLE 0xC0000466 +#define NT_STATUS_FILE_NOT_AVAILABLE 0xC0000467 + +#define NT_STATUS_INVALID_TASK_NAME 0xC0000500 +#define NT_STATUS_INVALID_TASK_INDEX 0xC0000501 +#define NT_STATUS_THREAD_ALREADY_IN_TASK 0xC0000502 +#define NT_STATUS_CALLBACK_BYPASS 0xC0000503 +#define NT_STATUS_FAIL_FAST_EXCEPTION 0xC0000602 +#define NT_STATUS_IMAGE_CERT_REVOKED 0xC0000603 +#define NT_STATUS_PORT_CLOSED 0xC0000700 +#define NT_STATUS_MESSAGE_LOST 0xC0000701 +#define NT_STATUS_INVALID_MESSAGE 0xC0000702 +#define NT_STATUS_REQUEST_CANCELED 0xC0000703 +#define NT_STATUS_RECURSIVE_DISPATCH 0xC0000704 +#define NT_STATUS_LPC_RECEIVE_BUFFER_EXPECTED 0xC0000705 +#define NT_STATUS_LPC_INVALID_CONNECTION_USAGE 0xC0000706 +#define NT_STATUS_LPC_REQUESTS_NOT_ALLOWED 0xC0000707 +#define NT_STATUS_RESOURCE_IN_USE 0xC0000708 +#define NT_STATUS_HARDWARE_MEMORY_ERROR 0xC0000709 +#define NT_STATUS_THREADPOOL_HANDLE_EXCEPTION 0xC000070A +#define NT_STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED 0xC000070B +#define NT_STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED 0xC000070C +#define NT_STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED 0xC000070D +#define NT_STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED 0xC000070E +#define NT_STATUS_THREADPOOL_RELEASED_DURING_OPERATION 0xC000070F +#define NT_STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING 0xC0000710 +#define NT_STATUS_APC_RETURNED_WHILE_IMPERSONATING 0xC0000711 +#define NT_STATUS_PROCESS_IS_PROTECTED 0xC0000712 +#define NT_STATUS_MCA_EXCEPTION 0xC0000713 +#define NT_STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE 0xC0000714 +#define NT_STATUS_SYMLINK_CLASS_DISABLED 0xC0000715 +#define NT_STATUS_INVALID_IDN_NORMALIZATION 0xC0000716 +#define NT_STATUS_NO_UNICODE_TRANSLATION 0xC0000717 +#define NT_STATUS_ALREADY_REGISTERED 0xC0000718 +#define NT_STATUS_CONTEXT_MISMATCH 0xC0000719 +#define NT_STATUS_PORT_ALREADY_HAS_COMPLETION_LIST 0xC000071A +#define NT_STATUS_CALLBACK_RETURNED_THREAD_PRIORITY 0xC000071B +#define NT_STATUS_INVALID_THREAD 0xC000071C +#define NT_STATUS_CALLBACK_RETURNED_TRANSACTION 0xC000071D +#define NT_STATUS_CALLBACK_RETURNED_LDR_LOCK 0xC000071E +#define NT_STATUS_CALLBACK_RETURNED_LANG 0xC000071F +#define NT_STATUS_CALLBACK_RETURNED_PRI_BACK 0xC0000720 +#define NT_STATUS_CALLBACK_RETURNED_THREAD_AFFINITY 0xC0000721 +#define NT_STATUS_DISK_REPAIR_DISABLED 0xC0000800 +#define NT_STATUS_DS_DOMAIN_RENAME_IN_PROGRESS 0xC0000801 +#define NT_STATUS_DISK_QUOTA_EXCEEDED 0xC0000802 +#define NT_STATUS_CONTENT_BLOCKED 0xC0000804 +#define NT_STATUS_BAD_CLUSTERS 0xC0000805 +#define NT_STATUS_VOLUME_DIRTY 0xC0000806 +#define NT_STATUS_FILE_CHECKED_OUT 0xC0000901 +#define NT_STATUS_CHECKOUT_REQUIRED 0xC0000902 +#define NT_STATUS_BAD_FILE_TYPE 0xC0000903 +#define NT_STATUS_FILE_TOO_LARGE 0xC0000904 +#define NT_STATUS_FORMS_AUTH_REQUIRED 0xC0000905 +#define NT_STATUS_VIRUS_INFECTED 0xC0000906 +#define NT_STATUS_VIRUS_DELETED 0xC0000907 +#define NT_STATUS_BAD_MCFG_TABLE 0xC0000908 +#define NT_STATUS_CANNOT_BREAK_OPLOCK 0xC0000909 + #define NT_STATUS_WOW_ASSERTION 0xC0009898 +#define NT_STATUS_INVALID_SIGNATURE 0xC000A000 +#define NT_STATUS_HMAC_NOT_SUPPORTED 0xC000A001 +#define NT_STATUS_IPSEC_QUEUE_OVERFLOW 0xC000A010 +#define NT_STATUS_ND_QUEUE_OVERFLOW 0xC000A011 +#define NT_STATUS_HOPLIMIT_EXCEEDED 0xC000A012 +#define NT_STATUS_PROTOCOL_NOT_SUPPORTED 0xC000A013 +#define NT_STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED 0xC000A080 +#define NT_STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR 0xC000A081 +#define NT_STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR 0xC000A082 +#define NT_STATUS_XML_PARSE_ERROR 0xC000A083 +#define NT_STATUS_XMLDSIG_ERROR 0xC000A084 +#define NT_STATUS_WRONG_COMPARTMENT 0xC000A085 +#define NT_STATUS_AUTHIP_FAILURE 0xC000A086 +#define NT_STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS 0xC000A087 +#define NT_STATUS_DS_OID_NOT_FOUND 0xC000A088 +#define NT_STATUS_HASH_NOT_SUPPORTED 0xC000A100 +#define NT_STATUS_HASH_NOT_PRESENT 0xC000A101 +#define NT_STATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED 0xC000A2A1 +#define NT_STATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED 0xC000A2A2 +#define NT_STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED 0xC000A2A3 +#define NT_STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED 0xC000A2A4 + /* Facility Debugger (0x..01....) not used */ /* Facility RPC Runtime (0x..02....) */ @@ -1015,13 +1193,13 @@ extern "C" { #define RPC_NT_UNSUPPORTED_AUTHN_LEVEL 0xC0020053 #define RPC_NT_NO_PRINC_NAME 0xC0020054 #define RPC_NT_NOT_RPC_ERROR 0xC0020055 -#define RPC_NT_UUID_LOCAL_ONLY 0x40020056 +/* RPC_NT_UUID_LOCAL_ONLY 0x40020056 */ #define RPC_NT_SEC_PKG_ERROR 0xC0020057 #define RPC_NT_NOT_CANCELLED 0xC0020058 #define RPC_NT_INVALID_ASYNC_HANDLE 0xC0020062 #define RPC_NT_INVALID_ASYNC_CALL 0xC0020063 #define RPC_NT_PROXY_ACCESS_DENIED 0xC0020064 -#define RPC_NT_SEND_INCOMPLETE 0x400200AF +/* RPC_NT_SEND_INCOMPLETE 0x400200AF */ /* Facility RPC Stubs (0x..03....) */ #define RPC_NT_NO_MORE_ENTRIES 0xC0030001 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 1a16c15cd3..c52fee29b4 100644 --- a/usr/src/uts/common/smbsrv/smb2.h +++ b/usr/src/uts/common/smbsrv/smb2.h @@ -10,7 +10,7 @@ */ /* - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ #ifndef _SMB_SMB2_H @@ -149,6 +149,11 @@ typedef enum { #define SMB2_SESSION_FLAG_ENCRYPT_DATA 0x0004 /* + * Client wants to bind an existing session to a new connection + */ +#define SMB2_SESSION_FLAG_BINDING 0x01 + +/* * SMB2 Tree connect, disconnect */ @@ -185,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 @@ -257,6 +278,22 @@ typedef enum { * on disk. No data is passed to the server by the client. */ +#define SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2 0x44483251 /* ("DH2Q") */ +/* + * The client is requesting the open to be durable. + * This value is only supported for the SMB 3.x dialect family. + */ + +#define SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 0x44483243 /* ("DH2C") */ +/* + * The client is requesting to reconnect to a + * durable open after being disconnected. + * This value is only supported for the SMB 3.x dialect family. + */ + +#define SMB2_DHANDLE_FLAG_PERSISTENT 0x00000002 +/* A persistent handle is requested. */ + #define SMB2_CREATE_REQUEST_LEASE 0x52714c73 /* ("RqLs") */ /* * The client is requesting that the server return a lease. @@ -268,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 @@ -329,7 +357,7 @@ typedef enum { /* * SMB2 Ioctl Request */ -#define SMB2_0_IOCTL_IS_FSCTL 0x00000001 +#define SMB2_0_IOCTL_IS_FSCTL 0x00000001 /* @@ -369,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_aapl.h b/usr/src/uts/common/smbsrv/smb2_aapl.h index c3dcf9c518..69f9043210 100644 --- a/usr/src/uts/common/smbsrv/smb2_aapl.h +++ b/usr/src/uts/common/smbsrv/smb2_aapl.h @@ -22,8 +22,8 @@ */ /* - * This content was published as: smb-759.0/kernel/netsmb/smb_2.h - * in http://opensource.apple.com/source/smb/smb-759.0.tar.gz + * This file contains excerpts of content published under: + * http://opensource.apple.com/source/smb/smb-759.40.1.1 */ #ifndef _SMB2AAPL_H @@ -111,10 +111,22 @@ enum { /* Define Volume Capabilities bitmap */ enum { kAAPL_SUPPORT_RESOLVE_ID = 0x01, - kAAPL_CASE_SENSITIVE = 0x02 + kAAPL_CASE_SENSITIVE = 0x02, + kAAPL_SUPPORTS_FULL_SYNC = 0x04 }; /* + * kAAPL_SUPPORTS_FULL_SYNC - Full Sync Request + * If the volume supports Full Sync, then when a F_FULLSYNC is done on the + * client side, the client will flush its buffers and then a SMB Flush Request + * with Reserved1 (uint16_t) set to 0xFFFF will be sent to the server. The + * server should flush all its buffer for that file and then call the + * filesystem to perform a F_FULLSYNC on that file. + * Refer to "man fsync" and "man fcntl" in OS X for more information on + * F_FULLSYNC + */ + +/* * Resolve ID Request * * uint32_t command_code = kAAPL_RESOLVE_ID; diff --git a/usr/src/uts/common/smbsrv/smb2_kproto.h b/usr/src/uts/common/smbsrv/smb2_kproto.h index bea0141840..00eb2eac5c 100644 --- a/usr/src/uts/common/smbsrv/smb2_kproto.h +++ b/usr/src/uts/common/smbsrv/smb2_kproto.h @@ -28,6 +28,11 @@ extern uint32_t smb2_max_rwsize; extern uint32_t smb2_max_trans; extern int smb2_aapl_use_file_ids; +extern uint32_t smb2_dh_def_timeout; +extern uint32_t smb2_dh_max_timeout; +extern uint32_t smb2_res_def_timeout; +extern uint32_t smb2_res_max_timeout; +extern int smb2_enable_dh; void smb2_dispatch_stats_init(smb_server_t *); void smb2_dispatch_stats_fini(smb_server_t *); @@ -36,6 +41,8 @@ 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); @@ -48,8 +55,10 @@ uint32_t smb2sr_lookup_fid(smb_request_t *, smb2fid_t *); /* SMB2 signing routines - smb2_signing.c */ int smb2_sign_check_request(smb_request_t *); void smb2_sign_reply(smb_request_t *); +void smb2_sign_init_mech(smb_session_t *); uint32_t smb2_fsctl_vneginfo(smb_request_t *, smb_fsctl_t *); +uint32_t smb2_fsctl_resiliency(smb_request_t *, smb_fsctl_t *); smb_sdrc_t smb2_negotiate(smb_request_t *); smb_sdrc_t smb2_session_setup(smb_request_t *); @@ -70,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 *); @@ -92,7 +102,20 @@ 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 smb2sr_finish_async(smb_request_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 *); +boolean_t smb_dh_should_save(smb_ofile_t *); +extern void smb2_dh_shutdown(smb_server_t *); #ifdef __cplusplus } diff --git a/usr/src/uts/common/smbsrv/smb_fsops.h b/usr/src/uts/common/smbsrv/smb_fsops.h index f4421fe733..377dfe257a 100644 --- a/usr/src/uts/common/smbsrv/smb_fsops.h +++ b/usr/src/uts/common/smbsrv/smb_fsops.h @@ -22,7 +22,7 @@ * Copyright 2009 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. */ #ifndef _SMBSRV_SMB_FSOPS_H @@ -123,6 +123,7 @@ int smb_fsop_frlock(smb_node_t *, smb_lock_t *, boolean_t, cred_t *); #define SMB_CATIA 0x00000004 #define SMB_ABE 0x00000008 #define SMB_CASE_SENSITIVE 0x00000010 +#define SMB_EDIRENT 0x00000020 /* * Increased MAXPATHLEN for SMB. Essentially, we want to allow a diff --git a/usr/src/uts/common/smbsrv/smb_kproto.h b/usr/src/uts/common/smbsrv/smb_kproto.h index 835d50bedf..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; @@ -254,23 +252,43 @@ uint32_t smb_make_link(smb_request_t *, smb_fqi_t *, smb_fqi_t *); * Logging functions */ void smb_log_flush(void); -void smb_correct_keep_alive_values(uint32_t new_keep_alive); 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 @@ -310,6 +328,7 @@ int smb_search(smb_request_t *); uint32_t smb_common_create(smb_request_t *); uint32_t smb_common_open(smb_request_t *); + int smb_common_write(smb_request_t *, smb_rw_param_t *); void smb_pathname_init(smb_request_t *, smb_pathname_t *, char *); @@ -325,9 +344,6 @@ uint32_t smb_omode_to_amask(uint32_t desired_access); void sshow_distribution_info(char *); -uint32_t smb2sr_go_async(smb_request_t *sr, - smb_sdrc_t (*async_func)(smb_request_t *)); - void smb_dispatch_stats_init(smb_server_t *); void smb_dispatch_stats_fini(smb_server_t *); void smb_dispatch_stats_update(smb_server_t *, @@ -370,6 +386,7 @@ int smb_mbc_peek(mbuf_chain_t *, int, const char *, ...); int smb_mbc_poke(mbuf_chain_t *, int, const char *, ...); int smb_mbc_put_mem(mbuf_chain_t *, void *, int); int smb_mbc_copy(mbuf_chain_t *, const mbuf_chain_t *, int, int); +int smb_mbc_put_align(mbuf_chain_t *, int); void smbsr_encode_header(smb_request_t *sr, int wct, int bcc, const char *fmt, ...); @@ -396,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 *); @@ -435,6 +452,8 @@ int smb_server_file_close(smb_ioc_fileid_t *); int smb_server_sharevp(smb_server_t *, const char *, vnode_t **); int smb_server_unshare(const char *); +void smb_server_logoff_ssnid(smb_request_t *, uint64_t); + void smb_server_get_cfg(smb_server_t *, smb_kmod_cfg_t *); int smb_server_spooldoc(smb_ioc_spooldoc_t *); @@ -515,6 +534,7 @@ int smb_node_setattr(smb_request_t *, smb_node_t *, cred_t *, smb_ofile_t *, smb_attr_t *); uint32_t smb_node_set_delete_on_close(smb_node_t *, cred_t *, uint32_t); void smb_node_reset_delete_on_close(smb_node_t *); +void smb_node_delete_on_close(smb_node_t *); boolean_t smb_node_file_is_readonly(smb_node_t *); int smb_node_getpath(smb_node_t *, vnode_t *, char *, uint32_t); int smb_node_getmntpath(smb_node_t *, char *, uint32_t); @@ -563,12 +583,12 @@ int smb_try_grow(smb_request_t *sr, int64_t new_size); unsigned short smb_worker_getnum(); /* SMB signing routines smb_signing.c */ -int smb_sign_begin(smb_request_t *, smb_token_t *); +void smb_sign_begin(smb_request_t *, smb_token_t *); int smb_sign_check_request(smb_request_t *); int smb_sign_check_secondary(smb_request_t *, unsigned int); void smb_sign_reply(smb_request_t *, mbuf_chain_t *); /* SMB2, but here because it's called from common code. */ -int smb2_sign_begin(smb_request_t *, smb_token_t *); +void smb2_sign_begin(smb_request_t *, smb_token_t *); boolean_t smb_sattr_check(uint16_t, uint16_t); @@ -586,20 +606,21 @@ void smb_authsock_close(smb_user_t *, ksocket_t); * session functions (file smb_session.c) */ smb_session_t *smb_session_create(ksocket_t, uint16_t, smb_server_t *, int); +smb_session_t *smb_server_find_session_byptr(smb_server_t *, void *); + void smb_session_receiver(smb_session_t *); void smb_session_disconnect(smb_session_t *); -void smb_session_timers(smb_llist_t *); +void smb_session_timers(smb_server_t *); void smb_session_delete(smb_session_t *session); void smb_session_cancel_requests(smb_session_t *, smb_tree_t *, smb_request_t *); void smb_session_config(smb_session_t *session); void smb_session_disconnect_from_share(smb_llist_t *, char *); smb_user_t *smb_session_dup_user(smb_session_t *, char *, char *); +smb_user_t *smb_session_lookup_ssnid(smb_session_t *, uint64_t); smb_user_t *smb_session_lookup_uid(smb_session_t *, uint16_t); -smb_user_t *smb_session_lookup_uid_st(smb_session_t *session, - uint16_t uid, smb_user_state_t st); -void smb_session_post_user(smb_session_t *, smb_user_t *); -void smb_session_post_tree(smb_session_t *, smb_tree_t *); +smb_user_t *smb_session_lookup_uid_st(smb_session_t *, + uint64_t, uint16_t, smb_user_state_t); smb_tree_t *smb_session_lookup_tree(smb_session_t *, uint16_t); smb_tree_t *smb_session_lookup_share(smb_session_t *, const char *, smb_tree_t *); @@ -607,12 +628,10 @@ smb_tree_t *smb_session_lookup_volume(smb_session_t *, const char *, smb_tree_t *); void smb_session_close_pid(smb_session_t *, uint32_t); void smb_session_disconnect_owned_trees(smb_session_t *, smb_user_t *); -void smb_session_disconnect_trees(smb_session_t *); void smb_session_disconnect_share(smb_session_t *, const char *); void smb_session_getclient(smb_session_t *, char *, size_t); boolean_t smb_session_isclient(smb_session_t *, const char *); void smb_session_correct_keep_alive_values(smb_llist_t *, uint32_t); -void smb_session_oplock_break(smb_request_t *, uint8_t); int smb_session_send(smb_session_t *, uint8_t type, mbuf_chain_t *); int smb_session_xprt_gethdr(smb_session_t *, smb_xprt_t *); boolean_t smb_session_oplocks_enable(smb_session_t *); @@ -628,19 +647,20 @@ void smb_request_free(smb_request_t *); */ 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_delete(void *); +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 *); 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_request_complete(smb_ofile_t *); -void smb_ofile_close_all(smb_tree_t *); -void smb_ofile_close_all_by_pid(smb_tree_t *, uint16_t); +void smb_ofile_close_all(smb_tree_t *, uint32_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,10 +669,12 @@ 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); +void smb_ofile_del_persistid(smb_ofile_t *); +void smb_ofile_set_persistid(smb_ofile_t *); #define SMB_OFILE_GET_SESSION(of) ((of)->f_session) #define SMB_OFILE_GET_TREE(of) ((of)->f_tree) @@ -673,7 +695,6 @@ void smb_odir_reopen(smb_odir_t *, const char *, uint16_t); void smb_odir_close(smb_odir_t *); boolean_t smb_odir_hold(smb_odir_t *); void smb_odir_release(smb_odir_t *); -void smb_odir_delete(void *); int smb_odir_read(smb_request_t *, smb_odir_t *, smb_odirent_t *, boolean_t *); @@ -694,7 +715,6 @@ smb_user_t *smb_user_new(smb_session_t *); int smb_user_logon(smb_user_t *, cred_t *, char *, char *, uint32_t, uint32_t, uint32_t); void smb_user_logoff(smb_user_t *); -void smb_user_delete(void *); void smb_user_auth_tmo(void *); boolean_t smb_user_is_admin(smb_user_t *); @@ -711,15 +731,13 @@ int smb_user_netinfo_encode(smb_user_t *, uint8_t *, size_t, uint32_t *); smb_token_t *smb_get_token(smb_session_t *, smb_logon_t *); cred_t *smb_cred_create(smb_token_t *); void smb_user_setcred(smb_user_t *, cred_t *, uint32_t); +boolean_t smb_is_same_user(cred_t *, cred_t *); /* * SMB tree functions (file smb_tree.c) */ uint32_t smb_tree_connect(smb_request_t *); void smb_tree_disconnect(smb_tree_t *, boolean_t); -void smb_tree_dealloc(void *); -void smb_tree_post_ofile(smb_tree_t *, smb_ofile_t *); -void smb_tree_post_odir(smb_tree_t *, smb_odir_t *); void smb_tree_close_pid(smb_tree_t *, uint32_t); boolean_t smb_tree_has_feature(smb_tree_t *, uint_t); int smb_tree_enum(smb_tree_t *, smb_svcenum_t *); @@ -729,7 +747,6 @@ void smb_tree_hold_internal(smb_tree_t *); void smb_tree_release(smb_tree_t *); smb_odir_t *smb_tree_lookup_odir(smb_request_t *, uint16_t); boolean_t smb_tree_is_connected(smb_tree_t *); -#define SMB_TREE_GET_TID(tree) ((tree)->t_tid) smb_xa_t *smb_xa_create(smb_session_t *session, smb_request_t *sr, uint32_t total_parameter_count, uint32_t total_data_count, @@ -773,6 +790,7 @@ 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 *, 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 *); @@ -781,7 +799,6 @@ 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_enter(ll, mode) rw_enter(&(ll)->ll_lock, mode) #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); @@ -795,22 +812,19 @@ void smb_slist_insert_head(smb_slist_t *sl, void *obj); void smb_slist_insert_tail(smb_slist_t *sl, void *obj); void smb_slist_remove(smb_slist_t *sl, void *obj); void smb_slist_wait_for_empty(smb_slist_t *sl); +void smb_slist_enter(smb_slist_t *sl); void smb_slist_exit(smb_slist_t *sl); uint32_t smb_slist_move_tail(list_t *lst, smb_slist_t *sl); void smb_slist_obj_move(smb_slist_t *dst, smb_slist_t *src, void *obj); -#define smb_slist_enter(sl) mutex_enter(&(sl)->sl_mutex) #define smb_slist_head(sl) list_head(&(sl)->sl_list) #define smb_slist_next(sl, obj) list_next(&(sl)->sl_list, obj) void smb_rwx_init(smb_rwx_t *rwx); void smb_rwx_destroy(smb_rwx_t *rwx); -#define smb_rwx_rwenter(rwx, mode) rw_enter(&(rwx)->rwx_lock, mode) +void smb_rwx_rwenter(smb_rwx_t *rwx, krw_t); void smb_rwx_rwexit(smb_rwx_t *rwx); -int smb_rwx_rwwait(smb_rwx_t *rwx, clock_t timeout); -#define smb_rwx_xenter(rwx) mutex_enter(&(rwx)->rwx_mutex) -#define smb_rwx_xexit(rwx) mutex_exit(&(rwx)->rwx_mutex) -krw_t smb_rwx_rwupgrade(smb_rwx_t *rwx); -void smb_rwx_rwdowngrade(smb_rwx_t *rwx, krw_t mode); +int smb_rwx_cvwait(smb_rwx_t *rwx, clock_t timeout); +void smb_rwx_cvbcast(smb_rwx_t *rwx); void smb_thread_init(smb_thread_t *, char *, smb_thread_ep_t, void *, pri_t); @@ -918,6 +932,11 @@ int smb_threshold_enter(smb_cmd_threshold_t *); void smb_threshold_exit(smb_cmd_threshold_t *); void smb_threshold_wake_all(smb_cmd_threshold_t *); +/* SMB hash function prototypes */ +smb_hash_t *smb_hash_create(size_t, size_t, uint32_t num_buckets); +void smb_hash_destroy(smb_hash_t *); +uint_t smb_hash_uint64(smb_hash_t *, uint64_t); + #ifdef __cplusplus } #endif diff --git a/usr/src/uts/common/smbsrv/smb_ktypes.h b/usr/src/uts/common/smbsrv/smb_ktypes.h index f1802522fc..ac9a01e642 100644 --- a/usr/src/uts/common/smbsrv/smb_ktypes.h +++ b/usr/src/uts/common/smbsrv/smb_ktypes.h @@ -391,6 +391,17 @@ typedef struct smb_llist { boolean_t ll_flushing; } smb_llist_t; +typedef struct smb_bucket { + smb_llist_t b_list; + uint32_t b_max_seen; +} smb_bucket_t; + +typedef struct smb_hash { + uint32_t rshift; + uint32_t num_buckets; + smb_bucket_t *buckets; +} smb_hash_t; + typedef struct smb_slist { kmutex_t sl_mutex; kcondvar_t sl_cv; @@ -565,66 +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 + * Per smb_node oplock state */ -#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 - */ -#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; - 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' */ @@ -667,7 +670,6 @@ typedef struct smb_node { /* If entering both, go in order n_lock_list, n_wlock_list */ smb_llist_t n_lock_list; /* active locks */ smb_llist_t n_wlock_list; /* waiting locks */ - uint32_t n_pending_dosattr; volatile int flags; u_offset_t n_allocsz; uint32_t n_fcn_count; @@ -687,6 +689,7 @@ typedef struct smb_node { #define NODE_FLAGS_SYSTEM 0x00008000 #define NODE_FLAGS_WRITE_THROUGH 0x00100000 #define NODE_XATTR_DIR 0x01000000 +#define NODE_FLAGS_DELETE_COMMITTED 0x20000000 #define NODE_FLAGS_DELETE_ON_CLOSE 0x40000000 #define NODE_FLAGS_EXECUTABLE 0x80000000 @@ -812,15 +815,39 @@ struct smb_key { #define SMB_SIGNING_CHECK 2 /* + * Locking notes: + * If you hold the mutex/lock on an object, don't flush the deleteq + * of the objects directly below it in the logical hierarchy + * (i.e. via smb_llist_exit()). I.e. don't drop s_tree_list when + * you hold u_mutex, because deleted trees need u_mutex to + * lower the refcnt. + * + * Note that this also applies to u_mutex and t_ofile_list. + */ + +/* + * The "session" object. + * + * Note that the smb_session_t object here corresponds to what MS-SMB2 + * calls a "connection". Adding to the confusion, what MS calls a + * "session" corresponds to our smb_user_t (below). + */ + +/* * Session State Machine * --------------------- * - * +-----------------------------+ +------------------------------+ - * | SMB_SESSION_STATE_CONNECTED | | SMB_SESSION_STATE_TERMINATED | - * +-----------------------------+ +------------------------------+ - * T0| ^ - * +--------------------+ |T5 - * v |T4 | + * + * +-----------------------------+ +----------------------------+ + * | SMB_SESSION_STATE_CONNECTED | | SMB_SESSION_STATE_SHUTDOWN | + * +-----------------------------+ +----------------------------+ + * | ^ + * | |T6 + * | +------------------------------+ + * | | SMB_SESSION_STATE_TERMINATED | + * T0| +------------------------------+ + * +--------------------+ ^ + * v |T4 |T5 * +-------------------------------+ | +--------------------------------+ * | SMB_SESSION_STATE_ESTABLISHED |---+--->| SMB_SESSION_STATE_DISCONNECTED | * +-------------------------------+ +--------------------------------+ @@ -856,6 +883,10 @@ struct smb_key { * * * + * Transition T6 + * + * + * */ #define SMB_SESSION_MAGIC 0x53455353 /* 'SESS' */ #define SMB_SESSION_VALID(p) \ @@ -870,6 +901,7 @@ typedef enum { SMB_SESSION_STATE_ESTABLISHED, SMB_SESSION_STATE_NEGOTIATED, SMB_SESSION_STATE_TERMINATED, + SMB_SESSION_STATE_SHUTDOWN, SMB_SESSION_STATE_SENTINEL } smb_session_state_t; @@ -909,6 +941,10 @@ typedef struct smb_session { struct smb_sign signing; /* SMB1 */ void *sign_mech; /* mechanism info */ + + /* SMB2/SMB3 signing support */ + int (*sign_calc)(struct smb_request *, + struct mbuf_chain *, uint8_t *); void (*sign_fini)(struct smb_session *); ksocket_t sock; @@ -930,6 +966,7 @@ typedef struct smb_session { uint32_t challenge_len; unsigned char challenge_key[SMB_CHALLENGE_SZ]; int64_t activity_timestamp; + /* * Maximum negotiated buffer sizes between SMB client and server * in SMB_SESSION_SETUP_ANDX @@ -942,10 +979,17 @@ typedef struct smb_session { uint64_t start_time; unsigned char MAC_key[44]; char ip_addr_str[INET6_ADDRSTRLEN]; - char clnt_uuid[16]; + uint8_t clnt_uuid[16]; char workstation[SMB_PI_MAX_HOST]; } smb_session_t; +/* + * The "user" object. + * + * Note that smb_user_t object here corresponds to what MS-SMB2 calls + * a "session". (Our smb_session_t is something else -- see above). + */ + #define SMB_USER_MAGIC 0x55534552 /* 'USER' */ #define SMB_USER_VALID(u) \ ASSERT(((u) != NULL) && ((u)->u_magic == SMB_USER_MAGIC)) @@ -964,7 +1008,9 @@ typedef struct smb_session { #define SMB_USER_PRIV_RESTORE 0x00000004 #define SMB_USER_PRIV_SECURITY 0x00000008 - +/* + * See the long "User State Machine" comment in smb_user.c + */ typedef enum { SMB_USER_STATE_LOGGING_ON = 0, SMB_USER_STATE_LOGGED_ON, @@ -973,6 +1019,12 @@ typedef enum { SMB_USER_STATE_SENTINEL } smb_user_state_t; +typedef enum { + SMB2_DH_PRESERVE_NONE = 0, + SMB2_DH_PRESERVE_SOME, + SMB2_DH_PRESERVE_ALL +} smb_preserve_type_t; + typedef struct smb_user { list_node_t u_lnd; uint32_t u_magic; @@ -991,10 +1043,12 @@ typedef struct smb_user { cred_t *u_cred; cred_t *u_privcred; + uint64_t u_ssnid; /* unique server-wide */ uint32_t u_refcnt; uint32_t u_flags; + smb_preserve_type_t preserve_opens; uint32_t u_privileges; - uint16_t u_uid; + uint16_t u_uid; /* unique per-session */ uint32_t u_audit_sid; uint32_t u_sign_flags; @@ -1028,7 +1082,12 @@ 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 + */ typedef enum { SMB_TREE_STATE_CONNECTED = 0, SMB_TREE_STATE_DISCONNECTING, @@ -1118,16 +1177,6 @@ typedef struct smb_tree { smb_tree_has_feature((sr)->tid_tree, SMB_TREE_TRAVERSE_MOUNTS)) /* - * SMB_OFILE_IS_READONLY reflects whether an ofile is readonly or not. - * The macro takes into account read-only settings in any of: - * the tree, the node (pending) and the file-system object. - * all of this is evaluated in smb_ofile_open() and after that - * we can just test the f_flags & SMB_OFLAGS_READONLY - */ -#define SMB_OFILE_IS_READONLY(of) \ - ((of)->f_flags & SMB_OFLAGS_READONLY) - -/* * SMB_PATHFILE_IS_READONLY indicates whether or not a file is * readonly when the caller has a path rather than an ofile. */ @@ -1260,7 +1309,7 @@ typedef struct smb_opipe { * will be set for the file node upon close. */ -#define SMB_OFLAGS_READONLY 0x0001 +/* 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 @@ -1269,16 +1318,40 @@ typedef struct smb_opipe { #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 { - SMB_OFILE_STATE_OPEN = 0, + 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_lnd; /* t_ofile_list */ - list_node_t f_nnd; /* n_ofile_list */ + 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; @@ -1291,6 +1364,13 @@ typedef struct smb_ofile { smb_odir_t *f_odir; smb_opipe_t *f_pipe; + kcondvar_t f_cv; + /* + * Note: f_persistid == 0 means this ofile has no persistid + * (same interpretation at the protocol level). IFF non-zero, + * this ofile is linked in the sv_persistid_ht hash table. + */ + uint64_t f_persistid; uint32_t f_uniqid; uint32_t f_refcnt; uint64_t f_seek_pos; @@ -1307,9 +1387,20 @@ typedef struct smb_ofile { pid_t f_pid; smb_attr_t f_pending_attr; boolean_t f_written; - char f_quota_resume[SMB_SID_STRSZ]; - 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; + hrtime_t dh_timeout_offset; /* time offset for timeout */ + hrtime_t dh_expire_time; /* time the handle expires */ + boolean_t dh_persist; + uint8_t dh_create_guid[16]; + char f_quota_resume[SMB_SID_STRSZ]; + uint8_t f_lock_seq[SMB_OFILE_LSEQ_MAX]; } smb_ofile_t; typedef struct smb_fileinfo { @@ -1505,8 +1596,21 @@ 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]; + uint32_t dh_v2_flags; + uint32_t dh_timeout; } smb_arg_open_t; typedef struct smb_arg_lock { @@ -1515,7 +1619,10 @@ typedef struct smb_arg_lock { uint32_t lseq; } smb_arg_lock_t; -struct smb_async_req; +typedef struct smb_arg_olbrk { + uint32_t NewLevel; + boolean_t AckRequired; +} smb_arg_olbrk_t; /* * SMB Request State Machine @@ -1668,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 */ @@ -1698,7 +1808,7 @@ typedef struct smb_request { unsigned char smb_sig[8]; /* signiture */ uint16_t smb_tid; /* tree id # */ uint32_t smb_pid; /* caller's process id # */ - uint16_t smb_uid; /* user id # */ + uint16_t smb_uid; /* local (smb1) user id # */ uint16_t smb_mid; /* mutiplex id # */ unsigned char smb_wct; /* count of parameter words */ uint16_t smb_bcc; /* data byte count */ @@ -1728,12 +1838,11 @@ typedef struct smb_request { uint64_t smb2_first_msgid; /* uint32_t smb2_pid; use smb_pid */ /* uint32_t smb2_tid; use smb_tid */ - /* uint64_t smb2_ssnid; use smb_uid */ - unsigned char smb2_sig[16]; /* signiture */ + uint64_t smb2_ssnid; /* See u_ssnid */ + uint8_t smb2_sig[16]; /* signature */ + boolean_t smb2_async; uint64_t smb2_async_id; - struct smb2_async_req *sr_async_req; - /* Parameters */ struct mbuf_chain smb_vwv; /* variable width value */ @@ -1764,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; @@ -1896,7 +2005,6 @@ typedef struct { int ld_family; struct sockaddr_in ld_sin; struct sockaddr_in6 ld_sin6; - smb_llist_t ld_session_list; } smb_listener_daemon_t; #define SMB_SSETUP_CMD "authentication" @@ -1957,6 +2065,9 @@ typedef struct smb_server { krwlock_t sv_cfg_lock; smb_kmod_cfg_t sv_cfg; 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 diff --git a/usr/src/uts/common/smbsrv/smb_signing.h b/usr/src/uts/common/smbsrv/smb_signing.h index 0d6c6bff91..c83e8d1f09 100644 --- a/usr/src/uts/common/smbsrv/smb_signing.h +++ b/usr/src/uts/common/smbsrv/smb_signing.h @@ -37,6 +37,8 @@ extern "C" { #define MD5_DIGEST_LENGTH 16 /* MD5 digest length in bytes */ #define SHA256_DIGEST_LENGTH 32 /* SHA256 digest length in bytes */ #define SMB2_SIG_SIZE 16 +#define SMB2_KEYLEN 16 +#define SMB3_KEYLEN 16 /* AES-128 keys */ #ifdef _KERNEL /* KCF variant */ @@ -51,14 +53,16 @@ typedef CK_SESSION_HANDLE smb_sign_ctx_t; /* * SMB signing routines used in smb_signing.c */ - int smb_md5_getmech(smb_sign_mech_t *); int smb_md5_init(smb_sign_ctx_t *, smb_sign_mech_t *); int smb_md5_update(smb_sign_ctx_t, void *, size_t); int smb_md5_final(smb_sign_ctx_t, uint8_t *); /* - * SMB2 signing routines used in smb2_signing.c + * SMB2/3 signing routines used in smb2_signing.c + * Two implementations of these (kernel/user) in: + * uts/common/fs/smbsrv/smb2_sign_kcf.c + * lib/smbsrv/libfksmbsrv/common/fksmb_sign_pkcs.c */ int smb2_hmac_getmech(smb_sign_mech_t *); @@ -66,7 +70,12 @@ int smb2_hmac_init(smb_sign_ctx_t *, smb_sign_mech_t *, uint8_t *, size_t); int smb2_hmac_update(smb_sign_ctx_t, uint8_t *, size_t); int smb2_hmac_final(smb_sign_ctx_t, uint8_t *); -#ifdef __cplusplus +int smb3_cmac_getmech(smb_sign_mech_t *); +int smb3_cmac_init(smb_sign_ctx_t *, smb_sign_mech_t *, uint8_t *, size_t); +int smb3_cmac_update(smb_sign_ctx_t, uint8_t *, size_t); +int smb3_cmac_final(smb_sign_ctx_t, uint8_t *); + +#ifdef __cplusplus } #endif diff --git a/usr/src/uts/common/smbsrv/smb_xdr.h b/usr/src/uts/common/smbsrv/smb_xdr.h index aaf0ff070f..1ea2a008b8 100644 --- a/usr/src/uts/common/smbsrv/smb_xdr.h +++ b/usr/src/uts/common/smbsrv/smb_xdr.h @@ -127,7 +127,6 @@ typedef struct smb_doorhdr { */ typedef struct smb_netuserinfo { uint64_t ui_session_id; - uint16_t ui_smb_uid; uint16_t ui_domain_len; char *ui_domain; uint16_t ui_account_len; diff --git a/usr/src/uts/common/sys/bitmap.h b/usr/src/uts/common/sys/bitmap.h index 02d54d2bb5..5e6385811f 100644 --- a/usr/src/uts/common/sys/bitmap.h +++ b/usr/src/uts/common/sys/bitmap.h @@ -26,6 +26,7 @@ /* * Copyright (c) 2014 by Delphix. All rights reserved. + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 RackTop Systems. */ diff --git a/usr/src/uts/intel/smbsrv/Makefile b/usr/src/uts/intel/smbsrv/Makefile index bb3739e10a..021dd38404 100644 --- a/usr/src/uts/intel/smbsrv/Makefile +++ b/usr/src/uts/intel/smbsrv/Makefile @@ -22,7 +22,7 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# Copyright 2014 Nexenta Systems, Inc. All rights reserved. +# Copyright 2015 Nexenta Systems, Inc. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. diff --git a/usr/src/uts/sparc/smbsrv/Makefile b/usr/src/uts/sparc/smbsrv/Makefile index c0279c4e73..57de6d9140 100644 --- a/usr/src/uts/sparc/smbsrv/Makefile +++ b/usr/src/uts/sparc/smbsrv/Makefile @@ -22,7 +22,7 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# Copyright 2014 Nexenta Systems, Inc. All rights reserved. +# Copyright 2015 Nexenta Systems, Inc. All rights reserved. # # |