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

¤ Dauer der Verarbeitung: 0.51 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.