Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/fs/smb/client/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 165 kB image not shown  

Quelle  smb2ops.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 *  SMB2 version specific operations
 *
 *  Copyright (c) 2012, Jeff Layton <jlayton@redhat.com>
 */


#include <linux/pagemap.h>
#include <linux/vfs.h>
#include <linux/falloc.h>
#include <linux/scatterlist.h>
#include <linux/uuid.h>
#include <linux/sort.h>
#include <crypto/aead.h>
#include <linux/fiemap.h>
#include <linux/folio_queue.h>
#include <uapi/linux/magic.h>
#include "cifsfs.h"
#include "cifsglob.h"
#include "smb2pdu.h"
#include "smb2proto.h"
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifs_unicode.h"
#include "../common/smb2status.h"
#include "smb2glob.h"
#include "cifs_ioctl.h"
#include "smbdirect.h"
#include "fscache.h"
#include "fs_context.h"
#include "cached_dir.h"
#include "reparse.h"

/* Change credits for different ops and return the total number of credits */
static int
change_conf(struct TCP_Server_Info *server)
{
 server->credits += server->echo_credits + server->oplock_credits;
 if (server->credits > server->max_credits)
  server->credits = server->max_credits;
 server->oplock_credits = server->echo_credits = 0;
 switch (server->credits) {
 case 0:
  return 0;
 case 1:
  server->echoes = false;
  server->oplocks = false;
  break;
 case 2:
  server->echoes = true;
  server->oplocks = false;
  server->echo_credits = 1;
  break;
 default:
  server->echoes = true;
  if (enable_oplocks) {
   server->oplocks = true;
   server->oplock_credits = 1;
  } else
   server->oplocks = false;

  server->echo_credits = 1;
 }
 server->credits -= server->echo_credits + server->oplock_credits;
 return server->credits + server->echo_credits + server->oplock_credits;
}

static void
smb2_add_credits(struct TCP_Server_Info *server,
   struct cifs_credits *credits, const int optype)
{
 int *val, rc = -1;
 int scredits, in_flight;
 unsigned int add = credits->value;
 unsigned int instance = credits->instance;
 bool reconnect_detected = false;
 bool reconnect_with_invalid_credits = false;

 spin_lock(&server->req_lock);
 val = server->ops->get_credits_field(server, optype);

 /* eg found case where write overlapping reconnect messed up credits */
 if (((optype & CIFS_OP_MASK) == CIFS_NEG_OP) && (*val != 0))
  reconnect_with_invalid_credits = true;

 if ((instance == 0) || (instance == server->reconnect_instance))
  *val += add;
 else
  reconnect_detected = true;

 if (*val > 65000) {
  *val = 65000; /* Don't get near 64K credits, avoid srv bugs */
  pr_warn_once("server overflowed SMB3 credits\n");
  trace_smb3_overflow_credits(server->current_mid,
         server->conn_id, server->hostname, *val,
         add, server->in_flight);
 }
 if (credits->in_flight_check > 1) {
  pr_warn_once("rreq R=%08x[%x] Credits not in flight\n",
        credits->rreq_debug_id, credits->rreq_debug_index);
 } else {
  credits->in_flight_check = 2;
 }
 if (WARN_ON_ONCE(server->in_flight == 0)) {
  pr_warn_once("rreq R=%08x[%x] Zero in_flight\n",
        credits->rreq_debug_id, credits->rreq_debug_index);
  trace_smb3_rw_credits(credits->rreq_debug_id,
          credits->rreq_debug_index,
          credits->value,
          server->credits, server->in_flight, 0,
          cifs_trace_rw_credits_zero_in_flight);
 }
 server->in_flight--;
 if (server->in_flight == 0 &&
    ((optype & CIFS_OP_MASK) != CIFS_NEG_OP) &&
    ((optype & CIFS_OP_MASK) != CIFS_SESS_OP))
  rc = change_conf(server);
 /*
 * Sometimes server returns 0 credits on oplock break ack - we need to
 * rebalance credits in this case.
 */

 else if (server->in_flight > 0 && server->oplock_credits == 0 &&
   server->oplocks) {
  if (server->credits > 1) {
   server->credits--;
   server->oplock_credits++;
  }
 } else if ((server->in_flight > 0) && (server->oplock_credits > 3) &&
     ((optype & CIFS_OP_MASK) == CIFS_OBREAK_OP))
  /* if now have too many oplock credits, rebalance so don't starve normal ops */
  change_conf(server);

 scredits = *val;
 in_flight = server->in_flight;
 spin_unlock(&server->req_lock);
 wake_up(&server->request_q);

 if (reconnect_detected) {
  trace_smb3_reconnect_detected(server->current_mid,
   server->conn_id, server->hostname, scredits, add, in_flight);

  cifs_dbg(FYI, "trying to put %d credits from the old server instance %d\n",
    add, instance);
 }

 if (reconnect_with_invalid_credits) {
  trace_smb3_reconnect_with_invalid_credits(server->current_mid,
   server->conn_id, server->hostname, scredits, add, in_flight);
  cifs_dbg(FYI, "Negotiate operation when server credits is non-zero. Optype: %d, server credits: %d, credits added: %d\n",
    optype, scredits, add);
 }

 spin_lock(&server->srv_lock);
 if (server->tcpStatus == CifsNeedReconnect
     || server->tcpStatus == CifsExiting) {
  spin_unlock(&server->srv_lock);
  return;
 }
 spin_unlock(&server->srv_lock);

 switch (rc) {
 case -1:
  /* change_conf hasn't been executed */
  break;
 case 0:
  cifs_server_dbg(VFS, "Possible client or server bug - zero credits\n");
  break;
 case 1:
  cifs_server_dbg(VFS, "disabling echoes and oplocks\n");
  break;
 case 2:
  cifs_dbg(FYI, "disabling oplocks\n");
  break;
 default:
  /* change_conf rebalanced credits for different types */
  break;
 }

 trace_smb3_add_credits(server->current_mid,
   server->conn_id, server->hostname, scredits, add, in_flight);
 cifs_dbg(FYI, "%s: added %u credits total=%d\n", __func__, add, scredits);
}

static void
smb2_set_credits(struct TCP_Server_Info *server, const int val)
{
 int scredits, in_flight;

 spin_lock(&server->req_lock);
 server->credits = val;
 if (val == 1) {
  server->reconnect_instance++;
  /*
 * ChannelSequence updated for all channels in primary channel so that consistent
 * across SMB3 requests sent on any channel. See MS-SMB2 3.2.4.1 and 3.2.7.1
 */

  if (SERVER_IS_CHAN(server))
   server->primary_server->channel_sequence_num++;
  else
   server->channel_sequence_num++;
 }
 scredits = server->credits;
 in_flight = server->in_flight;
 spin_unlock(&server->req_lock);

 trace_smb3_set_credits(server->current_mid,
   server->conn_id, server->hostname, scredits, val, in_flight);
 cifs_dbg(FYI, "%s: set %u credits\n", __func__, val);

 /* don't log while holding the lock */
 if (val == 1)
  cifs_dbg(FYI, "set credits to 1 due to smb2 reconnect\n");
}

static int *
smb2_get_credits_field(struct TCP_Server_Info *server, const int optype)
{
 switch (optype) {
 case CIFS_ECHO_OP:
  return &server->echo_credits;
 case CIFS_OBREAK_OP:
  return &server->oplock_credits;
 default:
  return &server->credits;
 }
}

static unsigned int
smb2_get_credits(struct mid_q_entry *mid)
{
 return mid->credits_received;
}

static int
smb2_wait_mtu_credits(struct TCP_Server_Info *server, size_t size,
        size_t *num, struct cifs_credits *credits)
{
 int rc = 0;
 unsigned int scredits, in_flight;

 spin_lock(&server->req_lock);
 while (1) {
  spin_unlock(&server->req_lock);

  spin_lock(&server->srv_lock);
  if (server->tcpStatus == CifsExiting) {
   spin_unlock(&server->srv_lock);
   return -ENOENT;
  }
  spin_unlock(&server->srv_lock);

  spin_lock(&server->req_lock);
  if (server->credits <= 0) {
   spin_unlock(&server->req_lock);
   cifs_num_waiters_inc(server);
   rc = wait_event_killable(server->request_q,
    has_credits(server, &server->credits, 1));
   cifs_num_waiters_dec(server);
   if (rc)
    return rc;
   spin_lock(&server->req_lock);
  } else {
   scredits = server->credits;
   /* can deadlock with reopen */
   if (scredits <= 8) {
    *num = SMB2_MAX_BUFFER_SIZE;
    credits->value = 0;
    credits->instance = 0;
    break;
   }

   /* leave some credits for reopen and other ops */
   scredits -= 8;
   *num = min_t(unsigned int, size,
         scredits * SMB2_MAX_BUFFER_SIZE);

   credits->value =
    DIV_ROUND_UP(*num, SMB2_MAX_BUFFER_SIZE);
   credits->instance = server->reconnect_instance;
   server->credits -= credits->value;
   server->in_flight++;
   if (server->in_flight > server->max_in_flight)
    server->max_in_flight = server->in_flight;
   break;
  }
 }
 scredits = server->credits;
 in_flight = server->in_flight;
 spin_unlock(&server->req_lock);

 trace_smb3_wait_credits(server->current_mid,
   server->conn_id, server->hostname, scredits, -(credits->value), in_flight);
 cifs_dbg(FYI, "%s: removed %u credits total=%d\n",
   __func__, credits->value, scredits);

 return rc;
}

static int
smb2_adjust_credits(struct TCP_Server_Info *server,
      struct cifs_io_subrequest *subreq,
      unsigned int /*enum smb3_rw_credits_trace*/ trace)
{
 struct cifs_credits *credits = &subreq->credits;
 int new_val = DIV_ROUND_UP(subreq->subreq.len - subreq->subreq.transferred,
       SMB2_MAX_BUFFER_SIZE);
 int scredits, in_flight;

 if (!credits->value || credits->value == new_val)
  return 0;

 if (credits->value < new_val) {
  trace_smb3_rw_credits(subreq->rreq->debug_id,
          subreq->subreq.debug_index,
          credits->value,
          server->credits, server->in_flight,
          new_val - credits->value,
          cifs_trace_rw_credits_no_adjust_up);
  trace_smb3_too_many_credits(server->current_mid,
    server->conn_id, server->hostname, 0, credits->value - new_val, 0);
  cifs_server_dbg(VFS, "R=%x[%x] request has less credits (%d) than required (%d)",
    subreq->rreq->debug_id, subreq->subreq.debug_index,
    credits->value, new_val);

  return -EOPNOTSUPP;
 }

 spin_lock(&server->req_lock);

 if (server->reconnect_instance != credits->instance) {
  scredits = server->credits;
  in_flight = server->in_flight;
  spin_unlock(&server->req_lock);

  trace_smb3_rw_credits(subreq->rreq->debug_id,
          subreq->subreq.debug_index,
          credits->value,
          server->credits, server->in_flight,
          new_val - credits->value,
          cifs_trace_rw_credits_old_session);
  trace_smb3_reconnect_detected(server->current_mid,
   server->conn_id, server->hostname, scredits,
   credits->value - new_val, in_flight);
  cifs_server_dbg(VFS, "R=%x[%x] trying to return %d credits to old session\n",
    subreq->rreq->debug_id, subreq->subreq.debug_index,
    credits->value - new_val);
  return -EAGAIN;
 }

 trace_smb3_rw_credits(subreq->rreq->debug_id,
         subreq->subreq.debug_index,
         credits->value,
         server->credits, server->in_flight,
         new_val - credits->value, trace);
 server->credits += credits->value - new_val;
 scredits = server->credits;
 in_flight = server->in_flight;
 spin_unlock(&server->req_lock);
 wake_up(&server->request_q);

 trace_smb3_adj_credits(server->current_mid,
   server->conn_id, server->hostname, scredits,
   credits->value - new_val, in_flight);
 cifs_dbg(FYI, "%s: adjust added %u credits total=%d\n",
   __func__, credits->value - new_val, scredits);

 credits->value = new_val;

 return 0;
}

static __u64
smb2_get_next_mid(struct TCP_Server_Info *server)
{
 __u64 mid;
 /* for SMB2 we need the current value */
 spin_lock(&server->mid_counter_lock);
 mid = server->current_mid++;
 spin_unlock(&server->mid_counter_lock);
 return mid;
}

static void
smb2_revert_current_mid(struct TCP_Server_Info *server, const unsigned int val)
{
 spin_lock(&server->mid_counter_lock);
 if (server->current_mid >= val)
  server->current_mid -= val;
 spin_unlock(&server->mid_counter_lock);
}

static struct mid_q_entry *
__smb2_find_mid(struct TCP_Server_Info *server, char *buf, bool dequeue)
{
 struct mid_q_entry *mid;
 struct smb2_hdr *shdr = (struct smb2_hdr *)buf;
 __u64 wire_mid = le64_to_cpu(shdr->MessageId);

 if (shdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) {
  cifs_server_dbg(VFS, "Encrypted frame parsing not supported yet\n");
  return NULL;
 }

 spin_lock(&server->mid_queue_lock);
 list_for_each_entry(mid, &server->pending_mid_q, qhead) {
  if ((mid->mid == wire_mid) &&
      (mid->mid_state == MID_REQUEST_SUBMITTED) &&
      (mid->command == shdr->Command)) {
   kref_get(&mid->refcount);
   if (dequeue) {
    list_del_init(&mid->qhead);
    mid->deleted_from_q = true;
   }
   spin_unlock(&server->mid_queue_lock);
   return mid;
  }
 }
 spin_unlock(&server->mid_queue_lock);
 return NULL;
}

static struct mid_q_entry *
smb2_find_mid(struct TCP_Server_Info *server, char *buf)
{
 return __smb2_find_mid(server, buf, false);
}

static struct mid_q_entry *
smb2_find_dequeue_mid(struct TCP_Server_Info *server, char *buf)
{
 return __smb2_find_mid(server, buf, true);
}

static void
smb2_dump_detail(void *buf, struct TCP_Server_Info *server)
{
#ifdef CONFIG_CIFS_DEBUG2
 struct smb2_hdr *shdr = (struct smb2_hdr *)buf;

 cifs_server_dbg(VFS, "Cmd: %d Err: 0x%x Flags: 0x%x Mid: %llu Pid: %d\n",
   shdr->Command, shdr->Status, shdr->Flags, shdr->MessageId,
   shdr->Id.SyncId.ProcessId);
 if (!server->ops->check_message(buf, server->total_read, server)) {
  cifs_server_dbg(VFS, "smb buf %p len %u\n", buf,
    server->ops->calc_smb_size(buf));
 }
#endif
}

static bool
smb2_need_neg(struct TCP_Server_Info *server)
{
 return server->max_read == 0;
}

static int
smb2_negotiate(const unsigned int xid,
        struct cifs_ses *ses,
        struct TCP_Server_Info *server)
{
 int rc;

 spin_lock(&server->mid_counter_lock);
 server->current_mid = 0;
 spin_unlock(&server->mid_counter_lock);
 rc = SMB2_negotiate(xid, ses, server);
 return rc;
}

static inline unsigned int
prevent_zero_iosize(unsigned int size, const char *type)
{
 if (size == 0) {
  cifs_dbg(VFS, "SMB: Zero %ssize calculated, using minimum value %u\n",
    type, CIFS_MIN_DEFAULT_IOSIZE);
  return CIFS_MIN_DEFAULT_IOSIZE;
 }
 return size;
}

static unsigned int
smb2_negotiate_wsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
{
 struct TCP_Server_Info *server = tcon->ses->server;
 unsigned int wsize;

 /* start with specified wsize, or default */
 wsize = ctx->got_wsize ? ctx->vol_wsize : CIFS_DEFAULT_IOSIZE;
 wsize = min_t(unsigned int, wsize, server->max_write);
 if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
  wsize = min_t(unsigned int, wsize, SMB2_MAX_BUFFER_SIZE);

 return prevent_zero_iosize(wsize, "w");
}

static unsigned int
smb3_negotiate_wsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
{
 struct TCP_Server_Info *server = tcon->ses->server;
 unsigned int wsize;

 /* start with specified wsize, or default */
 wsize = ctx->got_wsize ? ctx->vol_wsize : SMB3_DEFAULT_IOSIZE;
 wsize = min_t(unsigned int, wsize, server->max_write);
#ifdef CONFIG_CIFS_SMB_DIRECT
 if (server->rdma) {
  struct smbdirect_socket_parameters *sp =
   &server->smbd_conn->socket.parameters;

  if (server->sign)
   /*
 * Account for SMB2 data transfer packet header and
 * possible encryption header
 */

   wsize = min_t(unsigned int,
    wsize,
    sp->max_fragmented_send_size -
     SMB2_READWRITE_PDU_HEADER_SIZE -
     sizeof(struct smb2_transform_hdr));
  else
   wsize = min_t(unsigned int,
    wsize, sp->max_read_write_size);
 }
#endif
 if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
  wsize = min_t(unsigned int, wsize, SMB2_MAX_BUFFER_SIZE);

 return prevent_zero_iosize(wsize, "w");
}

static unsigned int
smb2_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
{
 struct TCP_Server_Info *server = tcon->ses->server;
 unsigned int rsize;

 /* start with specified rsize, or default */
 rsize = ctx->got_rsize ? ctx->vol_rsize : CIFS_DEFAULT_IOSIZE;
 rsize = min_t(unsigned int, rsize, server->max_read);

 if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
  rsize = min_t(unsigned int, rsize, SMB2_MAX_BUFFER_SIZE);

 return prevent_zero_iosize(rsize, "r");
}

static unsigned int
smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
{
 struct TCP_Server_Info *server = tcon->ses->server;
 unsigned int rsize;

 /* start with specified rsize, or default */
 rsize = ctx->got_rsize ? ctx->vol_rsize : SMB3_DEFAULT_IOSIZE;
 rsize = min_t(unsigned int, rsize, server->max_read);
#ifdef CONFIG_CIFS_SMB_DIRECT
 if (server->rdma) {
  struct smbdirect_socket_parameters *sp =
   &server->smbd_conn->socket.parameters;

  if (server->sign)
   /*
 * Account for SMB2 data transfer packet header and
 * possible encryption header
 */

   rsize = min_t(unsigned int,
    rsize,
    sp->max_fragmented_recv_size -
     SMB2_READWRITE_PDU_HEADER_SIZE -
     sizeof(struct smb2_transform_hdr));
  else
   rsize = min_t(unsigned int,
    rsize, sp->max_read_write_size);
 }
#endif

 if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
  rsize = min_t(unsigned int, rsize, SMB2_MAX_BUFFER_SIZE);

 return prevent_zero_iosize(rsize, "r");
}

/*
 * compare two interfaces a and b
 * return 0 if everything matches.
 * return 1 if a is rdma capable, or rss capable, or has higher link speed
 * return -1 otherwise.
 */

static int
iface_cmp(struct cifs_server_iface *a, struct cifs_server_iface *b)
{
 int cmp_ret = 0;

 WARN_ON(!a || !b);
 if (a->rdma_capable == b->rdma_capable) {
  if (a->rss_capable == b->rss_capable) {
   if (a->speed == b->speed) {
    cmp_ret = cifs_ipaddr_cmp((struct sockaddr *) &a->sockaddr,
         (struct sockaddr *) &b->sockaddr);
    if (!cmp_ret)
     return 0;
    else if (cmp_ret > 0)
     return 1;
    else
     return -1;
   } else if (a->speed > b->speed)
    return 1;
   else
    return -1;
  } else if (a->rss_capable > b->rss_capable)
   return 1;
  else
   return -1;
 } else if (a->rdma_capable > b->rdma_capable)
  return 1;
 else
  return -1;
}

static int
parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
   size_t buf_len, struct cifs_ses *ses, bool in_mount)
{
 struct network_interface_info_ioctl_rsp *p;
 struct sockaddr_in *addr4;
 struct sockaddr_in6 *addr6;
 struct iface_info_ipv4 *p4;
 struct iface_info_ipv6 *p6;
 struct cifs_server_iface *info = NULL, *iface = NULL, *niface = NULL;
 struct cifs_server_iface tmp_iface;
 ssize_t bytes_left;
 size_t next = 0;
 int nb_iface = 0;
 int rc = 0, ret = 0;

 bytes_left = buf_len;
 p = buf;

 spin_lock(&ses->iface_lock);
 /* do not query too frequently, this time with lock held */
 if (ses->iface_last_update &&
     time_before(jiffies, ses->iface_last_update +
   (SMB_INTERFACE_POLL_INTERVAL * HZ))) {
  spin_unlock(&ses->iface_lock);
  return 0;
 }

 /*
 * Go through iface_list and mark them as inactive
 */

 list_for_each_entry_safe(iface, niface, &ses->iface_list,
     iface_head)
  iface->is_active = 0;

 spin_unlock(&ses->iface_lock);

 /*
 * Samba server e.g. can return an empty interface list in some cases,
 * which would only be a problem if we were requesting multichannel
 */

 if (bytes_left == 0) {
  /* avoid spamming logs every 10 minutes, so log only in mount */
  if ((ses->chan_max > 1) && in_mount)
   cifs_dbg(VFS,
     "multichannel not available\n"
     "Empty network interface list returned by server %s\n",
     ses->server->hostname);
  rc = -EOPNOTSUPP;
  ses->iface_last_update = jiffies;
  goto out;
 }

 while (bytes_left >= (ssize_t)sizeof(*p)) {
  memset(&tmp_iface, 0, sizeof(tmp_iface));
  /* default to 1Gbps when link speed is unset */
  tmp_iface.speed = le64_to_cpu(p->LinkSpeed) ?: 1000000000;
  tmp_iface.rdma_capable = le32_to_cpu(p->Capability & RDMA_CAPABLE) ? 1 : 0;
  tmp_iface.rss_capable = le32_to_cpu(p->Capability & RSS_CAPABLE) ? 1 : 0;

  switch (p->Family) {
  /*
 * The kernel and wire socket structures have the same
 * layout and use network byte order but make the
 * conversion explicit in case either one changes.
 */

  case INTERNETWORK:
   addr4 = (struct sockaddr_in *)&tmp_iface.sockaddr;
   p4 = (struct iface_info_ipv4 *)p->Buffer;
   addr4->sin_family = AF_INET;
   memcpy(&addr4->sin_addr, &p4->IPv4Address, 4);

   /* [MS-SMB2] 2.2.32.5.1.1 Clients MUST ignore these */
   addr4->sin_port = cpu_to_be16(CIFS_PORT);

   cifs_dbg(FYI, "%s: ipv4 %pI4\n", __func__,
     &addr4->sin_addr);
   break;
  case INTERNETWORKV6:
   addr6 = (struct sockaddr_in6 *)&tmp_iface.sockaddr;
   p6 = (struct iface_info_ipv6 *)p->Buffer;
   addr6->sin6_family = AF_INET6;
   memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16);

   /* [MS-SMB2] 2.2.32.5.1.2 Clients MUST ignore these */
   addr6->sin6_flowinfo = 0;
   addr6->sin6_scope_id = 0;
   addr6->sin6_port = cpu_to_be16(CIFS_PORT);

   cifs_dbg(FYI, "%s: ipv6 %pI6\n", __func__,
     &addr6->sin6_addr);
   break;
  default:
   cifs_dbg(VFS,
     "%s: skipping unsupported socket family\n",
     __func__);
   goto next_iface;
  }

  /*
 * The iface_list is assumed to be sorted by speed.
 * Check if the new interface exists in that list.
 * NEVER change iface. it could be in use.
 * Add a new one instead
 */

  spin_lock(&ses->iface_lock);
  list_for_each_entry_safe(iface, niface, &ses->iface_list,
      iface_head) {
   ret = iface_cmp(iface, &tmp_iface);
   if (!ret) {
    iface->is_active = 1;
    spin_unlock(&ses->iface_lock);
    goto next_iface;
   } else if (ret < 0) {
    /* all remaining ifaces are slower */
    kref_get(&iface->refcount);
    break;
   }
  }
  spin_unlock(&ses->iface_lock);

  /* no match. insert the entry in the list */
  info = kmalloc(sizeof(struct cifs_server_iface),
          GFP_KERNEL);
  if (!info) {
   rc = -ENOMEM;
   goto out;
  }
  memcpy(info, &tmp_iface, sizeof(tmp_iface));

  /* add this new entry to the list */
  kref_init(&info->refcount);
  info->is_active = 1;

  cifs_dbg(FYI, "%s: adding iface %zu\n", __func__, ses->iface_count);
  cifs_dbg(FYI, "%s: speed %zu bps\n", __func__, info->speed);
  cifs_dbg(FYI, "%s: capabilities 0x%08x\n", __func__,
    le32_to_cpu(p->Capability));

  spin_lock(&ses->iface_lock);
  if (!list_entry_is_head(iface, &ses->iface_list, iface_head)) {
   list_add_tail(&info->iface_head, &iface->iface_head);
   kref_put(&iface->refcount, release_iface);
  } else
   list_add_tail(&info->iface_head, &ses->iface_list);

  ses->iface_count++;
  spin_unlock(&ses->iface_lock);
next_iface:
  nb_iface++;
  next = le32_to_cpu(p->Next);
  if (!next) {
   bytes_left -= sizeof(*p);
   break;
  }
  /* Validate that Next doesn't point beyond the buffer */
  if (next > bytes_left) {
   cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n",
     __func__, next, bytes_left);
   rc = -EINVAL;
   goto out;
  }
  p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next);
  bytes_left -= next;
 }

 if (!nb_iface) {
  cifs_dbg(VFS, "%s: malformed interface info\n", __func__);
  rc = -EINVAL;
  goto out;
 }

 /* Azure rounds the buffer size up 8, to a 16 byte boundary */
 if ((bytes_left > 8) ||
     (bytes_left >= offsetof(struct network_interface_info_ioctl_rsp, Next)
      + sizeof(p->Next) && p->Next))
  cifs_dbg(VFS, "%s: incomplete interface info\n", __func__);

 ses->iface_last_update = jiffies;

out:
 /*
 * Go through the list again and put the inactive entries
 */

 spin_lock(&ses->iface_lock);
 list_for_each_entry_safe(iface, niface, &ses->iface_list,
     iface_head) {
  if (!iface->is_active) {
   list_del(&iface->iface_head);
   kref_put(&iface->refcount, release_iface);
   ses->iface_count--;
  }
 }
 spin_unlock(&ses->iface_lock);

 return rc;
}

int
SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon, bool in_mount)
{
 int rc;
 unsigned int ret_data_len = 0;
 struct network_interface_info_ioctl_rsp *out_buf = NULL;
 struct cifs_ses *ses = tcon->ses;
 struct TCP_Server_Info *pserver;

 /* do not query too frequently */
 if (ses->iface_last_update &&
     time_before(jiffies, ses->iface_last_update +
   (SMB_INTERFACE_POLL_INTERVAL * HZ)))
  return 0;

 rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
   FSCTL_QUERY_NETWORK_INTERFACE_INFO,
   NULL /* no data input */, 0 /* no data input */,
   CIFSMaxBufSize, (char **)&out_buf, &ret_data_len);
 if (rc == -EOPNOTSUPP) {
  cifs_dbg(FYI,
    "server does not support query network interfaces\n");
  ret_data_len = 0;
 } else if (rc != 0) {
  cifs_tcon_dbg(VFS, "error %d on ioctl to get interface list\n", rc);
  goto out;
 }

 rc = parse_server_interfaces(out_buf, ret_data_len, ses, in_mount);
 if (rc)
  goto out;

 /* check if iface is still active */
 spin_lock(&ses->chan_lock);
 pserver = ses->chans[0].server;
 if (pserver && !cifs_chan_is_iface_active(ses, pserver)) {
  spin_unlock(&ses->chan_lock);
  cifs_chan_update_iface(ses, pserver);
  spin_lock(&ses->chan_lock);
 }
 spin_unlock(&ses->chan_lock);

out:
 kfree(out_buf);
 return rc;
}

static void
smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon,
       struct cifs_sb_info *cifs_sb)
{
 int rc;
 __le16 srch_path = 0; /* Null - open root of share */
 u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
 struct cifs_open_parms oparms;
 struct cifs_fid fid;
 struct cached_fid *cfid = NULL;

 oparms = (struct cifs_open_parms) {
  .tcon = tcon,
  .path = "",
  .desired_access = FILE_READ_ATTRIBUTES,
  .disposition = FILE_OPEN,
  .create_options = cifs_create_options(cifs_sb, 0),
  .fid = &fid,
 };

 rc = open_cached_dir(xid, tcon, "", cifs_sb, false, &cfid);
 if (rc == 0)
  memcpy(&fid, &cfid->fid, sizeof(struct cifs_fid));
 else
  rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL, NULL,
          NULL, NULL);
 if (rc)
  return;

 SMB3_request_interfaces(xid, tcon, true /* called during  mount */);

 SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid,
   FS_ATTRIBUTE_INFORMATION);
 SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid,
   FS_DEVICE_INFORMATION);
 SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid,
   FS_VOLUME_INFORMATION);
 SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid,
   FS_SECTOR_SIZE_INFORMATION); /* SMB3 specific */
 if (cfid == NULL)
  SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
 else
  close_cached_dir(cfid);
}

static void
smb2_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon,
       struct cifs_sb_info *cifs_sb)
{
 int rc;
 __le16 srch_path = 0; /* Null - open root of share */
 u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
 struct cifs_open_parms oparms;
 struct cifs_fid fid;

 oparms = (struct cifs_open_parms) {
  .tcon = tcon,
  .path = "",
  .desired_access = FILE_READ_ATTRIBUTES,
  .disposition = FILE_OPEN,
  .create_options = cifs_create_options(cifs_sb, 0),
  .fid = &fid,
 };

 rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL, NULL,
         NULL, NULL);
 if (rc)
  return;

 SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid,
   FS_ATTRIBUTE_INFORMATION);
 SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid,
   FS_DEVICE_INFORMATION);
 SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
}

static int
smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
   struct cifs_sb_info *cifs_sb, const char *full_path)
{
 __le16 *utf16_path;
 __u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
 int err_buftype = CIFS_NO_BUFFER;
 struct cifs_open_parms oparms;
 struct kvec err_iov = {};
 struct cifs_fid fid;
 struct cached_fid *cfid;
 bool islink;
 int rc, rc2;

 rc = open_cached_dir(xid, tcon, full_path, cifs_sb, true, &cfid);
 if (!rc) {
  if (cfid->has_lease) {
   close_cached_dir(cfid);
   return 0;
  }
  close_cached_dir(cfid);
 }

 utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb);
 if (!utf16_path)
  return -ENOMEM;

 oparms = (struct cifs_open_parms) {
  .tcon = tcon,
  .path = full_path,
  .desired_access = FILE_READ_ATTRIBUTES,
  .disposition = FILE_OPEN,
  .create_options = cifs_create_options(cifs_sb, 0),
  .fid = &fid,
 };

 rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL,
         &err_iov, &err_buftype);
 if (rc) {
  struct smb2_hdr *hdr = err_iov.iov_base;

  if (unlikely(!hdr || err_buftype == CIFS_NO_BUFFER))
   goto out;

  if (rc != -EREMOTE && hdr->Status == STATUS_OBJECT_NAME_INVALID) {
   rc2 = cifs_inval_name_dfs_link_error(xid, tcon, cifs_sb,
            full_path, &islink);
   if (rc2) {
    rc = rc2;
    goto out;
   }
   if (islink)
    rc = -EREMOTE;
  }
  if (rc == -EREMOTE && IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) &&
      (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS))
   rc = -EOPNOTSUPP;
  goto out;
 }

 rc = SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);

out:
 free_rsp_buf(err_buftype, err_iov.iov_base);
 kfree(utf16_path);
 return rc;
}

static int smb2_get_srv_inum(const unsigned int xid, struct cifs_tcon *tcon,
        struct cifs_sb_info *cifs_sb, const char *full_path,
        u64 *uniqueid, struct cifs_open_info_data *data)
{
 *uniqueid = le64_to_cpu(data->fi.IndexNumber);
 return 0;
}

static int smb2_query_file_info(const unsigned int xid, struct cifs_tcon *tcon,
    struct cifsFileInfo *cfile, struct cifs_open_info_data *data)
{
 struct cifs_fid *fid = &cfile->fid;

 if (cfile->symlink_target) {
  data->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL);
  if (!data->symlink_target)
   return -ENOMEM;
 }
 data->contains_posix_file_info = false;
 return SMB2_query_info(xid, tcon, fid->persistent_fid, fid->volatile_fid, &data->fi);
}

#ifdef CONFIG_CIFS_XATTR
static ssize_t
move_smb2_ea_to_cifs(char *dst, size_t dst_size,
       struct smb2_file_full_ea_info *src, size_t src_size,
       const unsigned char *ea_name)
{
 int rc = 0;
 unsigned int ea_name_len = ea_name ? strlen(ea_name) : 0;
 char *name, *value;
 size_t buf_size = dst_size;
 size_t name_len, value_len, user_name_len;

 while (src_size > 0) {
  name_len = (size_t)src->ea_name_length;
  value_len = (size_t)le16_to_cpu(src->ea_value_length);

  if (name_len == 0)
   break;

  if (src_size < 8 + name_len + 1 + value_len) {
   cifs_dbg(FYI, "EA entry goes beyond length of list\n");
   rc = -EIO;
   goto out;
  }

  name = &src->ea_data[0];
  value = &src->ea_data[src->ea_name_length + 1];

  if (ea_name) {
   if (ea_name_len == name_len &&
       memcmp(ea_name, name, name_len) == 0) {
    rc = value_len;
    if (dst_size == 0)
     goto out;
    if (dst_size < value_len) {
     rc = -ERANGE;
     goto out;
    }
    memcpy(dst, value, value_len);
    goto out;
   }
  } else {
   /* 'user.' plus a terminating null */
   user_name_len = 5 + 1 + name_len;

   if (buf_size == 0) {
    /* skip copy - calc size only */
    rc += user_name_len;
   } else if (dst_size >= user_name_len) {
    dst_size -= user_name_len;
    memcpy(dst, "user.", 5);
    dst += 5;
    memcpy(dst, src->ea_data, name_len);
    dst += name_len;
    *dst = 0;
    ++dst;
    rc += user_name_len;
   } else {
    /* stop before overrun buffer */
    rc = -ERANGE;
    break;
   }
  }

  if (!src->next_entry_offset)
   break;

  if (src_size < le32_to_cpu(src->next_entry_offset)) {
   /* stop before overrun buffer */
   rc = -ERANGE;
   break;
  }
  src_size -= le32_to_cpu(src->next_entry_offset);
  src = (void *)((char *)src +
          le32_to_cpu(src->next_entry_offset));
 }

 /* didn't find the named attribute */
 if (ea_name)
  rc = -ENODATA;

out:
 return (ssize_t)rc;
}

static ssize_t
smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
        const unsigned char *path, const unsigned char *ea_name,
        char *ea_data, size_t buf_size,
        struct cifs_sb_info *cifs_sb)
{
 int rc;
 struct kvec rsp_iov = {NULL, 0};
 int buftype = CIFS_NO_BUFFER;
 struct smb2_query_info_rsp *rsp;
 struct smb2_file_full_ea_info *info = NULL;

 rc = smb2_query_info_compound(xid, tcon, path,
          FILE_READ_EA,
          FILE_FULL_EA_INFORMATION,
          SMB2_O_INFO_FILE,
          CIFSMaxBufSize -
          MAX_SMB2_CREATE_RESPONSE_SIZE -
          MAX_SMB2_CLOSE_RESPONSE_SIZE,
          &rsp_iov, &buftype, cifs_sb);
 if (rc) {
  /*
 * If ea_name is NULL (listxattr) and there are no EAs,
 * return 0 as it's not an error. Otherwise, the specified
 * ea_name was not found.
 */

  if (!ea_name && rc == -ENODATA)
   rc = 0;
  goto qeas_exit;
 }

 rsp = (struct smb2_query_info_rsp *)rsp_iov.iov_base;
 rc = smb2_validate_iov(le16_to_cpu(rsp->OutputBufferOffset),
          le32_to_cpu(rsp->OutputBufferLength),
          &rsp_iov,
          sizeof(struct smb2_file_full_ea_info));
 if (rc)
  goto qeas_exit;

 info = (struct smb2_file_full_ea_info *)(
   le16_to_cpu(rsp->OutputBufferOffset) + (char *)rsp);
 rc = move_smb2_ea_to_cifs(ea_data, buf_size, info,
   le32_to_cpu(rsp->OutputBufferLength), ea_name);

 qeas_exit:
 free_rsp_buf(buftype, rsp_iov.iov_base);
 return rc;
}

static int
smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
     const char *path, const char *ea_name, const void *ea_value,
     const __u16 ea_value_len, const struct nls_table *nls_codepage,
     struct cifs_sb_info *cifs_sb)
{
 struct smb2_compound_vars *vars;
 struct cifs_ses *ses = tcon->ses;
 struct TCP_Server_Info *server;
 struct smb_rqst *rqst;
 struct kvec *rsp_iov;
 __le16 *utf16_path = NULL;
 int ea_name_len = strlen(ea_name);
 int flags = CIFS_CP_CREATE_CLOSE_OP;
 int len;
 int resp_buftype[3];
 struct cifs_open_parms oparms;
 __u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
 struct cifs_fid fid;
 unsigned int size[1];
 void *data[1];
 struct smb2_file_full_ea_info *ea;
 struct smb2_query_info_rsp *rsp;
 int rc, used_len = 0;
 int retries = 0, cur_sleep = 1;

replay_again:
 /* reinitialize for possible replay */
 flags = CIFS_CP_CREATE_CLOSE_OP;
 oplock = SMB2_OPLOCK_LEVEL_NONE;
 server = cifs_pick_channel(ses);

 if (smb3_encryption_required(tcon))
  flags |= CIFS_TRANSFORM_REQ;

 if (ea_name_len > 255)
  return -EINVAL;

 utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
 if (!utf16_path)
  return -ENOMEM;

 ea = NULL;
 resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER;
 vars = kzalloc(sizeof(*vars), GFP_KERNEL);
 if (!vars) {
  rc = -ENOMEM;
  goto out_free_path;
 }
 rqst = vars->rqst;
 rsp_iov = vars->rsp_iov;

 if (ses->server->ops->query_all_EAs) {
  if (!ea_value) {
   rc = ses->server->ops->query_all_EAs(xid, tcon, path,
            ea_name, NULL, 0,
            cifs_sb);
   if (rc == -ENODATA)
    goto sea_exit;
  } else {
   /* If we are adding a attribute we should first check
 * if there will be enough space available to store
 * the new EA. If not we should not add it since we
 * would not be able to even read the EAs back.
 */

   rc = smb2_query_info_compound(xid, tcon, path,
          FILE_READ_EA,
          FILE_FULL_EA_INFORMATION,
          SMB2_O_INFO_FILE,
          CIFSMaxBufSize -
          MAX_SMB2_CREATE_RESPONSE_SIZE -
          MAX_SMB2_CLOSE_RESPONSE_SIZE,
          &rsp_iov[1], &resp_buftype[1], cifs_sb);
   if (rc == 0) {
    rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
    used_len = le32_to_cpu(rsp->OutputBufferLength);
   }
   free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
   resp_buftype[1] = CIFS_NO_BUFFER;
   memset(&rsp_iov[1], 0, sizeof(rsp_iov[1]));
   rc = 0;

   /* Use a fudge factor of 256 bytes in case we collide
 * with a different set_EAs command.
 */

   if (CIFSMaxBufSize - MAX_SMB2_CREATE_RESPONSE_SIZE -
      MAX_SMB2_CLOSE_RESPONSE_SIZE - 256 <
      used_len + ea_name_len + ea_value_len + 1) {
    rc = -ENOSPC;
    goto sea_exit;
   }
  }
 }

 /* Open */
 rqst[0].rq_iov = vars->open_iov;
 rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;

 oparms = (struct cifs_open_parms) {
  .tcon = tcon,
  .path = path,
  .desired_access = FILE_WRITE_EA,
  .disposition = FILE_OPEN,
  .create_options = cifs_create_options(cifs_sb, 0),
  .fid = &fid,
  .replay = !!(retries),
 };

 rc = SMB2_open_init(tcon, server,
       &rqst[0], &oplock, &oparms, utf16_path);
 if (rc)
  goto sea_exit;
 smb2_set_next_command(tcon, &rqst[0]);


 /* Set Info */
 rqst[1].rq_iov = vars->si_iov;
 rqst[1].rq_nvec = 1;

 len = sizeof(*ea) + ea_name_len + ea_value_len + 1;
 ea = kzalloc(len, GFP_KERNEL);
 if (ea == NULL) {
  rc = -ENOMEM;
  goto sea_exit;
 }

 ea->ea_name_length = ea_name_len;
 ea->ea_value_length = cpu_to_le16(ea_value_len);
 memcpy(ea->ea_data, ea_name, ea_name_len + 1);
 memcpy(ea->ea_data + ea_name_len + 1, ea_value, ea_value_len);

 size[0] = len;
 data[0] = ea;

 rc = SMB2_set_info_init(tcon, server,
    &rqst[1], COMPOUND_FID,
    COMPOUND_FID, current->tgid,
    FILE_FULL_EA_INFORMATION,
    SMB2_O_INFO_FILE, 0, data, size);
 if (rc)
  goto sea_exit;
 smb2_set_next_command(tcon, &rqst[1]);
 smb2_set_related(&rqst[1]);

 /* Close */
 rqst[2].rq_iov = &vars->close_iov;
 rqst[2].rq_nvec = 1;
 rc = SMB2_close_init(tcon, server,
        &rqst[2], COMPOUND_FID, COMPOUND_FID, false);
 if (rc)
  goto sea_exit;
 smb2_set_related(&rqst[2]);

 if (retries) {
  smb2_set_replay(server, &rqst[0]);
  smb2_set_replay(server, &rqst[1]);
  smb2_set_replay(server, &rqst[2]);
 }

 rc = compound_send_recv(xid, ses, server,
    flags, 3, rqst,
    resp_buftype, rsp_iov);
 /* no need to bump num_remote_opens because handle immediately closed */

 sea_exit:
 kfree(ea);
 SMB2_open_free(&rqst[0]);
 SMB2_set_info_free(&rqst[1]);
 SMB2_close_free(&rqst[2]);
 free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
 free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
 free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base);
 kfree(vars);
out_free_path:
 kfree(utf16_path);

 if (is_replayable_error(rc) &&
     smb2_should_replay(tcon, &retries, &cur_sleep))
  goto replay_again;

 return rc;
}
#endif

static bool
smb2_can_echo(struct TCP_Server_Info *server)
{
 return server->echoes;
}

static void
smb2_clear_stats(struct cifs_tcon *tcon)
{
 int i;

 for (i = 0; i < NUMBER_OF_SMB2_COMMANDS; i++) {
  atomic_set(&tcon->stats.smb2_stats.smb2_com_sent[i], 0);
  atomic_set(&tcon->stats.smb2_stats.smb2_com_failed[i], 0);
 }
}

static void
smb2_dump_share_caps(struct seq_file *m, struct cifs_tcon *tcon)
{
 seq_puts(m, "\n\tShare Capabilities:");
 if (tcon->capabilities & SMB2_SHARE_CAP_DFS)
  seq_puts(m, " DFS,");
 if (tcon->capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY)
  seq_puts(m, " CONTINUOUS AVAILABILITY,");
 if (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT)
  seq_puts(m, " SCALEOUT,");
 if (tcon->capabilities & SMB2_SHARE_CAP_CLUSTER)
  seq_puts(m, " CLUSTER,");
 if (tcon->capabilities & SMB2_SHARE_CAP_ASYMMETRIC)
  seq_puts(m, " ASYMMETRIC,");
 if (tcon->capabilities == 0)
  seq_puts(m, " None");
 if (tcon->ss_flags & SSINFO_FLAGS_ALIGNED_DEVICE)
  seq_puts(m, " Aligned,");
 if (tcon->ss_flags & SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE)
  seq_puts(m, " Partition Aligned,");
 if (tcon->ss_flags & SSINFO_FLAGS_NO_SEEK_PENALTY)
  seq_puts(m, " SSD,");
 if (tcon->ss_flags & SSINFO_FLAGS_TRIM_ENABLED)
  seq_puts(m, " TRIM-support,");

 seq_printf(m, "\tShare Flags: 0x%x", tcon->share_flags);
 seq_printf(m, "\n\ttid: 0x%x", tcon->tid);
 if (tcon->perf_sector_size)
  seq_printf(m, "\tOptimal sector size: 0x%x",
      tcon->perf_sector_size);
 seq_printf(m, "\tMaximal Access: 0x%x", tcon->maximal_access);
}

static void
smb2_print_stats(struct seq_file *m, struct cifs_tcon *tcon)
{
 atomic_t *sent = tcon->stats.smb2_stats.smb2_com_sent;
 atomic_t *failed = tcon->stats.smb2_stats.smb2_com_failed;

 /*
 *  Can't display SMB2_NEGOTIATE, SESSION_SETUP, LOGOFF, CANCEL and ECHO
 *  totals (requests sent) since those SMBs are per-session not per tcon
 */

 seq_printf(m, "\nBytes read: %llu Bytes written: %llu",
     (long long)(tcon->bytes_read),
     (long long)(tcon->bytes_written));
 seq_printf(m, "\nOpen files: %d total (local), %d open on server",
     atomic_read(&tcon->num_local_opens),
     atomic_read(&tcon->num_remote_opens));
 seq_printf(m, "\nTreeConnects: %d total %d failed",
     atomic_read(&sent[SMB2_TREE_CONNECT_HE]),
     atomic_read(&failed[SMB2_TREE_CONNECT_HE]));
 seq_printf(m, "\nTreeDisconnects: %d total %d failed",
     atomic_read(&sent[SMB2_TREE_DISCONNECT_HE]),
     atomic_read(&failed[SMB2_TREE_DISCONNECT_HE]));
 seq_printf(m, "\nCreates: %d total %d failed",
     atomic_read(&sent[SMB2_CREATE_HE]),
     atomic_read(&failed[SMB2_CREATE_HE]));
 seq_printf(m, "\nCloses: %d total %d failed",
     atomic_read(&sent[SMB2_CLOSE_HE]),
     atomic_read(&failed[SMB2_CLOSE_HE]));
 seq_printf(m, "\nFlushes: %d total %d failed",
     atomic_read(&sent[SMB2_FLUSH_HE]),
     atomic_read(&failed[SMB2_FLUSH_HE]));
 seq_printf(m, "\nReads: %d total %d failed",
     atomic_read(&sent[SMB2_READ_HE]),
     atomic_read(&failed[SMB2_READ_HE]));
 seq_printf(m, "\nWrites: %d total %d failed",
     atomic_read(&sent[SMB2_WRITE_HE]),
     atomic_read(&failed[SMB2_WRITE_HE]));
 seq_printf(m, "\nLocks: %d total %d failed",
     atomic_read(&sent[SMB2_LOCK_HE]),
     atomic_read(&failed[SMB2_LOCK_HE]));
 seq_printf(m, "\nIOCTLs: %d total %d failed",
     atomic_read(&sent[SMB2_IOCTL_HE]),
     atomic_read(&failed[SMB2_IOCTL_HE]));
 seq_printf(m, "\nQueryDirectories: %d total %d failed",
     atomic_read(&sent[SMB2_QUERY_DIRECTORY_HE]),
     atomic_read(&failed[SMB2_QUERY_DIRECTORY_HE]));
 seq_printf(m, "\nChangeNotifies: %d total %d failed",
     atomic_read(&sent[SMB2_CHANGE_NOTIFY_HE]),
     atomic_read(&failed[SMB2_CHANGE_NOTIFY_HE]));
 seq_printf(m, "\nQueryInfos: %d total %d failed",
     atomic_read(&sent[SMB2_QUERY_INFO_HE]),
     atomic_read(&failed[SMB2_QUERY_INFO_HE]));
 seq_printf(m, "\nSetInfos: %d total %d failed",
     atomic_read(&sent[SMB2_SET_INFO_HE]),
     atomic_read(&failed[SMB2_SET_INFO_HE]));
 seq_printf(m, "\nOplockBreaks: %d sent %d failed",
     atomic_read(&sent[SMB2_OPLOCK_BREAK_HE]),
     atomic_read(&failed[SMB2_OPLOCK_BREAK_HE]));
}

static void
smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock)
{
 struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
 struct TCP_Server_Info *server = tlink_tcon(cfile->tlink)->ses->server;

 cfile->fid.persistent_fid = fid->persistent_fid;
 cfile->fid.volatile_fid = fid->volatile_fid;
 cfile->fid.access = fid->access;
#ifdef CONFIG_CIFS_DEBUG2
 cfile->fid.mid = fid->mid;
#endif /* CIFS_DEBUG2 */
 server->ops->set_oplock_level(cinode, oplock, fid->epoch,
          &fid->purge_cache);
 cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode);
 memcpy(cfile->fid.create_guid, fid->create_guid, 16);
}

static int
smb2_close_file(const unsigned int xid, struct cifs_tcon *tcon,
  struct cifs_fid *fid)
{
 return SMB2_close(xid, tcon, fid->persistent_fid, fid->volatile_fid);
}

static int
smb2_close_getattr(const unsigned int xid, struct cifs_tcon *tcon,
     struct cifsFileInfo *cfile)
{
 struct smb2_file_network_open_info file_inf;
 struct inode *inode;
 int rc;

 rc = __SMB2_close(xid, tcon, cfile->fid.persistent_fid,
     cfile->fid.volatile_fid, &file_inf);
 if (rc)
  return rc;

 inode = d_inode(cfile->dentry);

 spin_lock(&inode->i_lock);
 CIFS_I(inode)->time = jiffies;

 /* Creation time should not need to be updated on close */
 if (file_inf.LastWriteTime)
  inode_set_mtime_to_ts(inode,
          cifs_NTtimeToUnix(file_inf.LastWriteTime));
 if (file_inf.ChangeTime)
  inode_set_ctime_to_ts(inode,
          cifs_NTtimeToUnix(file_inf.ChangeTime));
 if (file_inf.LastAccessTime)
  inode_set_atime_to_ts(inode,
          cifs_NTtimeToUnix(file_inf.LastAccessTime));

 /*
 * i_blocks is not related to (i_size / i_blksize),
 * but instead 512 byte (2**9) size is required for
 * calculating num blocks.
 */

 if (le64_to_cpu(file_inf.AllocationSize) > 4096)
  inode->i_blocks =
   (512 - 1 + le64_to_cpu(file_inf.AllocationSize)) >> 9;

 /* End of file and Attributes should not have to be updated on close */
 spin_unlock(&inode->i_lock);
 return rc;
}

static int
SMB2_request_res_key(const unsigned int xid, struct cifs_tcon *tcon,
       u64 persistent_fid, u64 volatile_fid,
       struct copychunk_ioctl *pcchunk)
{
 int rc;
 unsigned int ret_data_len;
 struct resume_key_req *res_key;

 rc = SMB2_ioctl(xid, tcon, persistent_fid, volatile_fid,
   FSCTL_SRV_REQUEST_RESUME_KEY, NULL, 0 /* no input */,
   CIFSMaxBufSize, (char **)&res_key, &ret_data_len);

 if (rc == -EOPNOTSUPP) {
  pr_warn_once("Server share %s does not support copy range\n", tcon->tree_name);
  goto req_res_key_exit;
 } else if (rc) {
  cifs_tcon_dbg(VFS, "refcpy ioctl error %d getting resume key\n", rc);
  goto req_res_key_exit;
 }
 if (ret_data_len < sizeof(struct resume_key_req)) {
  cifs_tcon_dbg(VFS, "Invalid refcopy resume key length\n");
  rc = -EINVAL;
  goto req_res_key_exit;
 }
 memcpy(pcchunk->SourceKey, res_key->ResumeKey, COPY_CHUNK_RES_KEY_SIZE);

req_res_key_exit:
 kfree(res_key);
 return rc;
}

static int
smb2_ioctl_query_info(const unsigned int xid,
        struct cifs_tcon *tcon,
        struct cifs_sb_info *cifs_sb,
        __le16 *path, int is_dir,
        unsigned long p)
{
 struct smb2_compound_vars *vars;
 struct smb_rqst *rqst;
 struct kvec *rsp_iov;
 struct cifs_ses *ses = tcon->ses;
 struct TCP_Server_Info *server;
 char __user *arg = (char __user *)p;
 struct smb_query_info qi;
 struct smb_query_info __user *pqi;
 int rc = 0;
 int flags = CIFS_CP_CREATE_CLOSE_OP;
 struct smb2_query_info_rsp *qi_rsp = NULL;
 struct smb2_ioctl_rsp *io_rsp = NULL;
 void *buffer = NULL;
 int resp_buftype[3];
 struct cifs_open_parms oparms;
 u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
 struct cifs_fid fid;
 unsigned int size[2];
 void *data[2];
 int create_options = is_dir ? CREATE_NOT_FILE : CREATE_NOT_DIR;
 void (*free_req1_func)(struct smb_rqst *r);
 int retries = 0, cur_sleep = 1;

replay_again:
 /* reinitialize for possible replay */
 flags = CIFS_CP_CREATE_CLOSE_OP;
 oplock = SMB2_OPLOCK_LEVEL_NONE;
 server = cifs_pick_channel(ses);

 vars = kzalloc(sizeof(*vars), GFP_ATOMIC);
 if (vars == NULL)
  return -ENOMEM;
 rqst = &vars->rqst[0];
 rsp_iov = &vars->rsp_iov[0];

 resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER;

 if (copy_from_user(&qi, arg, sizeof(struct smb_query_info))) {
  rc = -EFAULT;
  goto free_vars;
 }
 if (qi.output_buffer_length > 1024) {
  rc = -EINVAL;
  goto free_vars;
 }

 if (!ses || !server) {
  rc = -EIO;
  goto free_vars;
 }

 if (smb3_encryption_required(tcon))
  flags |= CIFS_TRANSFORM_REQ;

 if (qi.output_buffer_length) {
  buffer = memdup_user(arg + sizeof(struct smb_query_info), qi.output_buffer_length);
  if (IS_ERR(buffer)) {
   rc = PTR_ERR(buffer);
   goto free_vars;
  }
 }

 /* Open */
 rqst[0].rq_iov = &vars->open_iov[0];
 rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;

 oparms = (struct cifs_open_parms) {
  .tcon = tcon,
  .disposition = FILE_OPEN,
  .create_options = cifs_create_options(cifs_sb, create_options),
  .fid = &fid,
  .replay = !!(retries),
 };

 if (qi.flags & PASSTHRU_FSCTL) {
  switch (qi.info_type & FSCTL_DEVICE_ACCESS_MASK) {
  case FSCTL_DEVICE_ACCESS_FILE_READ_WRITE_ACCESS:
   oparms.desired_access = FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE;
   break;
  case FSCTL_DEVICE_ACCESS_FILE_ANY_ACCESS:
   oparms.desired_access = GENERIC_ALL;
   break;
  case FSCTL_DEVICE_ACCESS_FILE_READ_ACCESS:
   oparms.desired_access = GENERIC_READ;
   break;
  case FSCTL_DEVICE_ACCESS_FILE_WRITE_ACCESS:
   oparms.desired_access = GENERIC_WRITE;
   break;
  }
 } else if (qi.flags & PASSTHRU_SET_INFO) {
  oparms.desired_access = GENERIC_WRITE;
 } else {
  oparms.desired_access = FILE_READ_ATTRIBUTES | READ_CONTROL;
 }

 rc = SMB2_open_init(tcon, server,
       &rqst[0], &oplock, &oparms, path);
 if (rc)
  goto free_output_buffer;
 smb2_set_next_command(tcon, &rqst[0]);

 /* Query */
 if (qi.flags & PASSTHRU_FSCTL) {
  /* Can eventually relax perm check since server enforces too */
  if (!capable(CAP_SYS_ADMIN)) {
   rc = -EPERM;
   goto free_open_req;
  }
  rqst[1].rq_iov = &vars->io_iov[0];
  rqst[1].rq_nvec = SMB2_IOCTL_IOV_SIZE;

  rc = SMB2_ioctl_init(tcon, server, &rqst[1], COMPOUND_FID, COMPOUND_FID,
         qi.info_type, buffer, qi.output_buffer_length,
         CIFSMaxBufSize - MAX_SMB2_CREATE_RESPONSE_SIZE -
         MAX_SMB2_CLOSE_RESPONSE_SIZE);
  free_req1_func = SMB2_ioctl_free;
 } else if (qi.flags == PASSTHRU_SET_INFO) {
  /* Can eventually relax perm check since server enforces too */
  if (!capable(CAP_SYS_ADMIN)) {
   rc = -EPERM;
   goto free_open_req;
  }
  if (qi.output_buffer_length < 8) {
   rc = -EINVAL;
   goto free_open_req;
  }
  rqst[1].rq_iov = vars->si_iov;
  rqst[1].rq_nvec = 1;

  /* MS-FSCC 2.4.13 FileEndOfFileInformation */
  size[0] = 8;
  data[0] = buffer;

  rc = SMB2_set_info_init(tcon, server, &rqst[1], COMPOUND_FID, COMPOUND_FID,
     current->tgid, FILE_END_OF_FILE_INFORMATION,
     SMB2_O_INFO_FILE, 0, data, size);
  free_req1_func = SMB2_set_info_free;
 } else if (qi.flags == PASSTHRU_QUERY_INFO) {
  rqst[1].rq_iov = &vars->qi_iov;
  rqst[1].rq_nvec = 1;

  rc = SMB2_query_info_init(tcon, server,
      &rqst[1], COMPOUND_FID,
      COMPOUND_FID, qi.file_info_class,
      qi.info_type, qi.additional_information,
      qi.input_buffer_length,
      qi.output_buffer_length, buffer);
  free_req1_func = SMB2_query_info_free;
 } else { /* unknown flags */
  cifs_tcon_dbg(VFS, "Invalid passthru query flags: 0x%x\n",
         qi.flags);
  rc = -EINVAL;
 }

 if (rc)
  goto free_open_req;
 smb2_set_next_command(tcon, &rqst[1]);
 smb2_set_related(&rqst[1]);

 /* Close */
 rqst[2].rq_iov = &vars->close_iov;
 rqst[2].rq_nvec = 1;

 rc = SMB2_close_init(tcon, server,
        &rqst[2], COMPOUND_FID, COMPOUND_FID, false);
 if (rc)
  goto free_req_1;
 smb2_set_related(&rqst[2]);

 if (retries) {
  smb2_set_replay(server, &rqst[0]);
  smb2_set_replay(server, &rqst[1]);
  smb2_set_replay(server, &rqst[2]);
 }

 rc = compound_send_recv(xid, ses, server,
    flags, 3, rqst,
    resp_buftype, rsp_iov);
 if (rc)
  goto out;

 /* No need to bump num_remote_opens since handle immediately closed */
 if (qi.flags & PASSTHRU_FSCTL) {
  pqi = (struct smb_query_info __user *)arg;
  io_rsp = (struct smb2_ioctl_rsp *)rsp_iov[1].iov_base;
  if (le32_to_cpu(io_rsp->OutputCount) < qi.input_buffer_length)
   qi.input_buffer_length = le32_to_cpu(io_rsp->OutputCount);
  if (qi.input_buffer_length > 0 &&
      le32_to_cpu(io_rsp->OutputOffset) + qi.input_buffer_length
      > rsp_iov[1].iov_len) {
   rc = -EFAULT;
   goto out;
  }

  if (copy_to_user(&pqi->input_buffer_length,
     &qi.input_buffer_length,
     sizeof(qi.input_buffer_length))) {
   rc = -EFAULT;
   goto out;
  }

  if (copy_to_user((void __user *)pqi + sizeof(struct smb_query_info),
     (const void *)io_rsp + le32_to_cpu(io_rsp->OutputOffset),
     qi.input_buffer_length))
   rc = -EFAULT;
 } else {
  pqi = (struct smb_query_info __user *)arg;
  qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
  if (le32_to_cpu(qi_rsp->OutputBufferLength) < qi.input_buffer_length)
   qi.input_buffer_length = le32_to_cpu(qi_rsp->OutputBufferLength);
  if (copy_to_user(&pqi->input_buffer_length,
     &qi.input_buffer_length,
     sizeof(qi.input_buffer_length))) {
   rc = -EFAULT;
   goto out;
  }

  if (copy_to_user(pqi + 1, qi_rsp->Buffer,
     qi.input_buffer_length))
   rc = -EFAULT;
 }

out:
 free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
 free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
 free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base);
 SMB2_close_free(&rqst[2]);
free_req_1:
 free_req1_func(&rqst[1]);
free_open_req:
 SMB2_open_free(&rqst[0]);
free_output_buffer:
 kfree(buffer);
free_vars:
 kfree(vars);

 if (is_replayable_error(rc) &&
     smb2_should_replay(tcon, &retries, &cur_sleep))
  goto replay_again;

 return rc;
}

static ssize_t
smb2_copychunk_range(const unsigned int xid,
   struct cifsFileInfo *srcfile,
   struct cifsFileInfo *trgtfile, u64 src_off,
   u64 len, u64 dest_off)
{
 int rc;
 unsigned int ret_data_len;
 struct copychunk_ioctl *pcchunk;
 struct copychunk_ioctl_rsp *retbuf = NULL;
 struct cifs_tcon *tcon;
 int chunks_copied = 0;
 bool chunk_sizes_updated = false;
 ssize_t bytes_written, total_bytes_written = 0;

 pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL);
 if (pcchunk == NULL)
  return -ENOMEM;

 cifs_dbg(FYI, "%s: about to call request res key\n", __func__);
 /* Request a key from the server to identify the source of the copy */
 rc = SMB2_request_res_key(xid, tlink_tcon(srcfile->tlink),
    srcfile->fid.persistent_fid,
    srcfile->fid.volatile_fid, pcchunk);

 /* Note: request_res_key sets res_key null only if rc !=0 */
 if (rc)
  goto cchunk_out;

 /* For now array only one chunk long, will make more flexible later */
 pcchunk->ChunkCount = cpu_to_le32(1);
 pcchunk->Reserved = 0;
 pcchunk->Reserved2 = 0;

 tcon = tlink_tcon(trgtfile->tlink);

 trace_smb3_copychunk_enter(xid, srcfile->fid.volatile_fid,
       trgtfile->fid.volatile_fid, tcon->tid,
       tcon->ses->Suid, src_off, dest_off, len);

 while (len > 0) {
  pcchunk->SourceOffset = cpu_to_le64(src_off);
  pcchunk->TargetOffset = cpu_to_le64(dest_off);
  pcchunk->Length =
   cpu_to_le32(min_t(u64, len, tcon->max_bytes_chunk));

  /* Request server copy to target from src identified by key */
  kfree(retbuf);
  retbuf = NULL;
  rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
   trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
   (char *)pcchunk, sizeof(struct copychunk_ioctl),
   CIFSMaxBufSize, (char **)&retbuf, &ret_data_len);
  if (rc == 0) {
   if (ret_data_len !=
     sizeof(struct copychunk_ioctl_rsp)) {
    cifs_tcon_dbg(VFS, "Invalid cchunk response size\n");
    rc = -EIO;
    goto cchunk_out;
   }
   if (retbuf->TotalBytesWritten == 0) {
    cifs_dbg(FYI, "no bytes copied\n");
    rc = -EIO;
    goto cchunk_out;
   }
   /*
 * Check if server claimed to write more than we asked
 */

   if (le32_to_cpu(retbuf->TotalBytesWritten) >
       le32_to_cpu(pcchunk->Length)) {
    cifs_tcon_dbg(VFS, "Invalid copy chunk response\n");
    rc = -EIO;
    goto cchunk_out;
   }
   if (le32_to_cpu(retbuf->ChunksWritten) != 1) {
    cifs_tcon_dbg(VFS, "Invalid num chunks written\n");
    rc = -EIO;
    goto cchunk_out;
   }
   chunks_copied++;

   bytes_written = le32_to_cpu(retbuf->TotalBytesWritten);
   src_off += bytes_written;
   dest_off += bytes_written;
   len -= bytes_written;
   total_bytes_written += bytes_written;

   cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %zu\n",
    le32_to_cpu(retbuf->ChunksWritten),
    le32_to_cpu(retbuf->ChunkBytesWritten),
    bytes_written);
   trace_smb3_copychunk_done(xid, srcfile->fid.volatile_fid,
    trgtfile->fid.volatile_fid, tcon->tid,
    tcon->ses->Suid, src_off, dest_off, len);
  } else if (rc == -EINVAL) {
   if (ret_data_len != sizeof(struct copychunk_ioctl_rsp))
    goto cchunk_out;

   cifs_dbg(FYI, "MaxChunks %d BytesChunk %d MaxCopy %d\n",
    le32_to_cpu(retbuf->ChunksWritten),
    le32_to_cpu(retbuf->ChunkBytesWritten),
    le32_to_cpu(retbuf->TotalBytesWritten));

   /*
 * Check if this is the first request using these sizes,
 * (ie check if copy succeed once with original sizes
 * and check if the server gave us different sizes after
 * we already updated max sizes on previous request).
 * if not then why is the server returning an error now
 */

   if ((chunks_copied != 0) || chunk_sizes_updated)
    goto cchunk_out;

   /* Check that server is not asking us to grow size */
   if (le32_to_cpu(retbuf->ChunkBytesWritten) <
     tcon->max_bytes_chunk)
    tcon->max_bytes_chunk =
     le32_to_cpu(retbuf->ChunkBytesWritten);
   else
    goto cchunk_out; /* server gave us bogus size */

   /* No need to change MaxChunks since already set to 1 */
   chunk_sizes_updated = true;
  } else
   goto cchunk_out;
 }

cchunk_out:
 kfree(pcchunk);
 kfree(retbuf);
 if (rc)
  return rc;
 else
  return total_bytes_written;
}

static int
smb2_flush_file(const unsigned int xid, struct cifs_tcon *tcon,
  struct cifs_fid *fid)
{
 return SMB2_flush(xid, tcon, fid->persistent_fid, fid->volatile_fid);
}

static unsigned int
smb2_read_data_offset(char *buf)
{
 struct smb2_read_rsp *rsp = (struct smb2_read_rsp *)buf;

 return rsp->DataOffset;
}

static unsigned int
smb2_read_data_length(char *buf, bool in_remaining)
{
 struct smb2_read_rsp *rsp = (struct smb2_read_rsp *)buf;

 if (in_remaining)
  return le32_to_cpu(rsp->DataRemaining);

 return le32_to_cpu(rsp->DataLength);
}


static int
smb2_sync_read(const unsigned int xid, struct cifs_fid *pfid,
        struct cifs_io_parms *parms, unsigned int *bytes_read,
        char **buf, int *buf_type)
{
 parms->persistent_fid = pfid->persistent_fid;
 parms->volatile_fid = pfid->volatile_fid;
 return SMB2_read(xid, parms, bytes_read, buf, buf_type);
}

static int
smb2_sync_write(const unsigned int xid, struct cifs_fid *pfid,
  struct cifs_io_parms *parms, unsigned int *written,
  struct kvec *iov, unsigned long nr_segs)
{

 parms->persistent_fid = pfid->persistent_fid;
 parms->volatile_fid = pfid->volatile_fid;
 return SMB2_write(xid, parms, written, iov, nr_segs);
}

/* Set or clear the SPARSE_FILE attribute based on value passed in setsparse */
static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
  struct cifsFileInfo *cfile, struct inode *inode, __u8 setsparse)
{
 struct cifsInodeInfo *cifsi;
 int rc;

 cifsi = CIFS_I(inode);

 /* if file already sparse don't bother setting sparse again */
 if ((cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) && setsparse)
  return true/* already sparse */

 if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) && !setsparse)
  return true/* already not sparse */

 /*
 * Can't check for sparse support on share the usual way via the
 * FS attribute info (FILE_SUPPORTS_SPARSE_FILES) on the share
 * since Samba server doesn't set the flag on the share, yet
 * supports the set sparse FSCTL and returns sparse correctly
 * in the file attributes. If we fail setting sparse though we
 * mark that server does not support sparse files for this share
 * to avoid repeatedly sending the unsupported fsctl to server
 * if the file is repeatedly extended.
 */

 if (tcon->broken_sparse_sup)
  return false;

 rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
   cfile->fid.volatile_fid, FSCTL_SET_SPARSE,
   &setsparse, 1, CIFSMaxBufSize, NULL, NULL);
 if (rc) {
  tcon->broken_sparse_sup = true;
  cifs_dbg(FYI, "set sparse rc = %d\n", rc);
  return false;
 }

 if (setsparse)
  cifsi->cifsAttrs |= FILE_ATTRIBUTE_SPARSE_FILE;
 else
  cifsi->cifsAttrs &= (~FILE_ATTRIBUTE_SPARSE_FILE);

 return true;
}

static int
smb2_set_file_size(const unsigned int xid, struct cifs_tcon *tcon,
     struct cifsFileInfo *cfile, __u64 size, bool set_alloc)
{
 struct inode *inode;

 /*
 * If extending file more than one page make sparse. Many Linux fs
 * make files sparse by default when extending via ftruncate
 */

 inode = d_inode(cfile->dentry);

 if (!set_alloc && (size > inode->i_size + 8192)) {
  __u8 set_sparse = 1;

  /* whether set sparse succeeds or not, extend the file */
  smb2_set_sparse(xid, tcon, cfile, inode, set_sparse);
 }

 return SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
       cfile->fid.volatile_fid, cfile->pid, size);
}

static int
smb2_duplicate_extents(const unsigned int xid,
   struct cifsFileInfo *srcfile,
   struct cifsFileInfo *trgtfile, u64 src_off,
   u64 len, u64 dest_off)
{
 int rc;
 unsigned int ret_data_len;
 struct inode *inode;
 struct duplicate_extents_to_file dup_ext_buf;
 struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink);

 /* server fileays advertise duplicate extent support with this flag */
 if ((le32_to_cpu(tcon->fsAttrInfo.Attributes) &
      FILE_SUPPORTS_BLOCK_REFCOUNTING) == 0)
  return -EOPNOTSUPP;

 dup_ext_buf.VolatileFileHandle = srcfile->fid.volatile_fid;
 dup_ext_buf.PersistentFileHandle = srcfile->fid.persistent_fid;
 dup_ext_buf.SourceFileOffset = cpu_to_le64(src_off);
 dup_ext_buf.TargetFileOffset = cpu_to_le64(dest_off);
 dup_ext_buf.ByteCount = cpu_to_le64(len);
 cifs_dbg(FYI, "Duplicate extents: src off %lld dst off %lld len %lld\n",
  src_off, dest_off, len);
 trace_smb3_clone_enter(xid, srcfile->fid.volatile_fid,
          trgtfile->fid.volatile_fid, tcon->tid,
          tcon->ses->Suid, src_off, dest_off, len);
 inode = d_inode(trgtfile->dentry);
 if (inode->i_size < dest_off + len) {
  rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false);
  if (rc)
   goto duplicate_extents_out;

  /*
 * Although also could set plausible allocation size (i_blocks)
 * here in addition to setting the file size, in reflink
 * it is likely that the target file is sparse. Its allocation
 * size will be queried on next revalidate, but it is important
 * to make sure that file's cached size is updated immediately
 */

  netfs_resize_file(netfs_inode(inode), dest_off + len, true);
  cifs_setsize(inode, dest_off + len);
 }
 rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
   trgtfile->fid.volatile_fid,
   FSCTL_DUPLICATE_EXTENTS_TO_FILE,
   (char *)&dup_ext_buf,
   sizeof(struct duplicate_extents_to_file),
   CIFSMaxBufSize, NULL,
   &ret_data_len);

 if (ret_data_len > 0)
  cifs_dbg(FYI, "Non-zero response length in duplicate extents\n");

duplicate_extents_out:
 if (rc)
  trace_smb3_clone_err(xid, srcfile->fid.volatile_fid,
         trgtfile->fid.volatile_fid,
         tcon->tid, tcon->ses->Suid, src_off,
         dest_off, len, rc);
 else
  trace_smb3_clone_done(xid, srcfile->fid.volatile_fid,
          trgtfile->fid.volatile_fid, tcon->tid,
          tcon->ses->Suid, src_off, dest_off, len);
 return rc;
}

static int
smb2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
     struct cifsFileInfo *cfile)
{
 return SMB2_set_compression(xid, tcon, cfile->fid.persistent_fid,
       cfile->fid.volatile_fid);
}

static int
smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon,
     struct cifsFileInfo *cfile)
{
 struct fsctl_set_integrity_information_req integr_info;
 unsigned int ret_data_len;

 integr_info.ChecksumAlgorithm = cpu_to_le16(CHECKSUM_TYPE_UNCHANGED);
 integr_info.Flags = 0;
 integr_info.Reserved = 0;

 return SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
   cfile->fid.volatile_fid,
   FSCTL_SET_INTEGRITY_INFORMATION,
   (char *)&integr_info,
   sizeof(struct fsctl_set_integrity_information_req),
   CIFSMaxBufSize, NULL,
   &ret_data_len);

}

/* GMT Token is @GMT-YYYY.MM.DD-HH.MM.SS Unicode which is 48 bytes + null */
#define GMT_TOKEN_SIZE 50

#define MIN_SNAPSHOT_ARRAY_SIZE 16 /* See MS-SMB2 section 3.3.5.15.1 */

/*
 * Input buffer contains (empty) struct smb_snapshot array with size filled in
 * For output see struct SRV_SNAPSHOT_ARRAY in MS-SMB2 section 2.2.32.2
 */

static int
smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
     struct cifsFileInfo *cfile, void __user *ioc_buf)
{
 char *retbuf = NULL;
 unsigned int ret_data_len = 0;
 int rc;
 u32 max_response_size;
 struct smb_snapshot_array snapshot_in;

 /*
 * On the first query to enumerate the list of snapshots available
 * for this volume the buffer begins with 0 (number of snapshots
 * which can be returned is zero since at that point we do not know
 * how big the buffer needs to be). On the second query,
 * it (ret_data_len) is set to number of snapshots so we can
 * know to set the maximum response size larger (see below).
 */

 if (get_user(ret_data_len, (unsigned int __user *)ioc_buf))
  return -EFAULT;

 /*
 * Note that for snapshot queries that servers like Azure expect that
 * the first query be minimal size (and just used to get the number/size
 * of previous versions) so response size must be specified as EXACTLY
 * sizeof(struct snapshot_array) which is 16 when rounded up to multiple
 * of eight bytes.
 */

 if (ret_data_len == 0)
  max_response_size = MIN_SNAPSHOT_ARRAY_SIZE;
 else
  max_response_size = CIFSMaxBufSize;

 rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
   cfile->fid.volatile_fid,
   FSCTL_SRV_ENUMERATE_SNAPSHOTS,
   NULL, 0 /* no input data */, max_response_size,
   (char **)&retbuf,
   &ret_data_len);
 cifs_dbg(FYI, "enum snapshots ioctl returned %d and ret buflen is %d\n",
   rc, ret_data_len);
 if (rc)
  return rc;

 if (ret_data_len && (ioc_buf != NULL) && (retbuf != NULL)) {
  /* Fixup buffer */
  if (copy_from_user(&snapshot_in, ioc_buf,
      sizeof(struct smb_snapshot_array))) {
   rc = -EFAULT;
   kfree(retbuf);
   return rc;
  }

  /*
 * Check for min size, ie not large enough to fit even one GMT
 * token (snapshot).  On the first ioctl some users may pass in
 * smaller size (or zero) to simply get the size of the array
 * so the user space caller can allocate sufficient memory
 * and retry the ioctl again with larger array size sufficient
 * to hold all of the snapshot GMT tokens on the second try.
 */

  if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE)
   ret_data_len = sizeof(struct smb_snapshot_array);

  /*
 * We return struct SRV_SNAPSHOT_ARRAY, followed by
 * the snapshot array (of 50 byte GMT tokens) each
 * representing an available previous version of the data
 */

  if (ret_data_len > (snapshot_in.snapshot_array_size +
     sizeof(struct smb_snapshot_array)))
   ret_data_len = snapshot_in.snapshot_array_size +
     sizeof(struct smb_snapshot_array);

  if (copy_to_user(ioc_buf, retbuf, ret_data_len))
   rc = -EFAULT;
 }

 kfree(retbuf);
 return rc;
}



static int
smb3_notify(const unsigned int xid, struct file *pfile,
     void __user *ioc_buf, bool return_changes)
{
 struct smb3_notify_info notify;
 struct smb3_notify_info __user *pnotify_buf;
 struct dentry *dentry = pfile->f_path.dentry;
 struct inode *inode = file_inode(pfile);
 struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
 struct cifs_open_parms oparms;
 struct cifs_fid fid;
 struct cifs_tcon *tcon;
 const unsigned char *path;
 char *returned_ioctl_info = NULL;
 void *page = alloc_dentry_path();
 __le16 *utf16_path = NULL;
 u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
 int rc = 0;
 __u32 ret_len = 0;

 path = build_path_from_dentry(dentry, page);
 if (IS_ERR(path)) {
  rc = PTR_ERR(path);
  goto notify_exit;
 }

 utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);
 if (utf16_path == NULL) {
  rc = -ENOMEM;
  goto notify_exit;
 }

 if (return_changes) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=89 G=92

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.55Angebot  Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können  ¤

*Eine klare Vorstellung vom Zielzustand






Normalansicht

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Diese beiden folgenden Angebotsgruppen bietet das Unternehmen

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.