diff --git a/Documentation/ABI/stable/sysfs-block b/Documentation/ABI/stable/sysfs-block index 1fe9a553c37b..f0025d1c3d5a 100644 --- a/Documentation/ABI/stable/sysfs-block +++ b/Documentation/ABI/stable/sysfs-block @@ -101,6 +101,16 @@ Description: devices that support receiving integrity metadata. +What: /sys/block/<disk>/partscan +Date: May 2024 +Contact: Christoph Hellwig <hch@xxxxxx> +Description: + The /sys/block/<disk>/partscan files reports if partition + scanning is enabled for the disk. It returns "1" if partition + scanning is enabled, or "0" if not. The value type is a 32-bit + unsigned integer, but only "0" and "1" are valid values. + + What: /sys/block/<disk>/<partition>/alignment_offset Date: April 2009 Contact: Martin K. Petersen <martin.petersen@xxxxxxxxxx> diff --git a/Documentation/admin-guide/hw-vuln/core-scheduling.rst b/Documentation/admin-guide/hw-vuln/core-scheduling.rst index cf1eeefdfc32..a92e10ec402e 100644 --- a/Documentation/admin-guide/hw-vuln/core-scheduling.rst +++ b/Documentation/admin-guide/hw-vuln/core-scheduling.rst @@ -67,8 +67,8 @@ arg4: will be performed for all tasks in the task group of ``pid``. arg5: - userspace pointer to an unsigned long for storing the cookie returned by - ``PR_SCHED_CORE_GET`` command. Should be 0 for all other commands. + userspace pointer to an unsigned long long for storing the cookie returned + by ``PR_SCHED_CORE_GET`` command. Should be 0 for all other commands. In order for a process to push a cookie to, or pull a cookie from a process, it is required to have the ptrace access mode: `PTRACE_MODE_READ_REALCREDS` to the diff --git a/Documentation/admin-guide/mm/damon/usage.rst b/Documentation/admin-guide/mm/damon/usage.rst index 8da1b7281827..9285f69f4f73 100644 --- a/Documentation/admin-guide/mm/damon/usage.rst +++ b/Documentation/admin-guide/mm/damon/usage.rst @@ -389,7 +389,7 @@ pages of all memory cgroups except ``/having_care_already``.:: # # further filter out all cgroups except one at '/having_care_already' echo memcg > 1/type echo /having_care_already > 1/memcg_path - echo N > 1/matching + echo Y > 1/matching Note that ``anon`` and ``memcg`` filters are currently supported only when ``paddr`` `implementation <sysfs_contexts>` is being used. diff --git a/Documentation/sphinx/kernel_include.py b/Documentation/sphinx/kernel_include.py index abe768088377..638762442336 100755 --- a/Documentation/sphinx/kernel_include.py +++ b/Documentation/sphinx/kernel_include.py @@ -97,7 +97,6 @@ class KernelInclude(Include): # HINT: this is the only line I had to change / commented out: #path = utils.relative_path(None, path) - path = nodes.reprunicode(path) encoding = self.options.get( 'encoding', self.state.document.settings.input_encoding) e_handler=self.state.document.settings.input_encoding_error_handler diff --git a/Makefile b/Makefile index f66238b5d931..c3b324ed985e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 6 -SUBLEVEL = 31 +SUBLEVEL = 32 EXTRAVERSION = NAME = Hurr durr I'ma ninja sloth diff --git a/block/genhd.c b/block/genhd.c index f9b81be6c761..2ef1e08d70ec 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -345,9 +345,7 @@ int disk_scan_partitions(struct gendisk *disk, blk_mode_t mode) struct block_device *bdev; int ret = 0; - if (disk->flags & (GENHD_FL_NO_PART | GENHD_FL_HIDDEN)) - return -EINVAL; - if (test_bit(GD_SUPPRESS_PART_SCAN, &disk->state)) + if (!disk_has_partscan(disk)) return -EINVAL; if (disk->open_partitions) return -EBUSY; @@ -503,8 +501,7 @@ int __must_check device_add_disk(struct device *parent, struct gendisk *disk, goto out_unregister_bdi; /* Make sure the first partition scan will be proceed */ - if (get_capacity(disk) && !(disk->flags & GENHD_FL_NO_PART) && - !test_bit(GD_SUPPRESS_PART_SCAN, &disk->state)) + if (get_capacity(disk) && disk_has_partscan(disk)) set_bit(GD_NEED_PART_SCAN, &disk->state); bdev_add(disk->part0, ddev->devt); @@ -1040,6 +1037,12 @@ static ssize_t diskseq_show(struct device *dev, return sprintf(buf, "%llu\n", disk->diskseq); } +static ssize_t partscan_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", disk_has_partscan(dev_to_disk(dev))); +} + static DEVICE_ATTR(range, 0444, disk_range_show, NULL); static DEVICE_ATTR(ext_range, 0444, disk_ext_range_show, NULL); static DEVICE_ATTR(removable, 0444, disk_removable_show, NULL); @@ -1053,6 +1056,7 @@ static DEVICE_ATTR(stat, 0444, part_stat_show, NULL); static DEVICE_ATTR(inflight, 0444, part_inflight_show, NULL); static DEVICE_ATTR(badblocks, 0644, disk_badblocks_show, disk_badblocks_store); static DEVICE_ATTR(diskseq, 0444, diskseq_show, NULL); +static DEVICE_ATTR(partscan, 0444, partscan_show, NULL); #ifdef CONFIG_FAIL_MAKE_REQUEST ssize_t part_fail_show(struct device *dev, @@ -1099,6 +1103,7 @@ static struct attribute *disk_attrs[] = { &dev_attr_events_async.attr, &dev_attr_events_poll_msecs.attr, &dev_attr_diskseq.attr, + &dev_attr_partscan.attr, #ifdef CONFIG_FAIL_MAKE_REQUEST &dev_attr_fail.attr, #endif diff --git a/block/partitions/core.c b/block/partitions/core.c index e58c8b50350b..962e4b57d64a 100644 --- a/block/partitions/core.c +++ b/block/partitions/core.c @@ -592,10 +592,7 @@ static int blk_add_partitions(struct gendisk *disk) struct parsed_partitions *state; int ret = -EAGAIN, p; - if (disk->flags & GENHD_FL_NO_PART) - return 0; - - if (test_bit(GD_SUPPRESS_PART_SCAN, &disk->state)) + if (!disk_has_partscan(disk)) return 0; state = check_partition(disk); diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 64f4573656c6..e67a91120385 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -5368,7 +5368,7 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) goto err; break; case BINDER_SET_MAX_THREADS: { - int max_threads; + u32 max_threads; if (copy_from_user(&max_threads, ubuf, sizeof(max_threads))) { diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h index 7270d4d22207..5b7c80b99ae8 100644 --- a/drivers/android/binder_internal.h +++ b/drivers/android/binder_internal.h @@ -421,7 +421,7 @@ struct binder_proc { struct list_head todo; struct binder_stats stats; struct list_head delivered_death; - int max_threads; + u32 max_threads; int requested_threads; int requested_threads_started; int tmp_ref; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 6f6341f70278..67b75ff0f7c3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -1025,6 +1025,9 @@ int amdgpu_ras_query_error_status(struct amdgpu_device *adev, if (!obj) return -EINVAL; + if (!info || info->head.block == AMDGPU_RAS_BLOCK_COUNT) + return -EINVAL; + if (info->head.block == AMDGPU_RAS_BLOCK__UMC) { amdgpu_ras_get_ecc_info(adev, &err_data); } else { diff --git a/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c b/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c index 3966845c7694..230be292ff35 100644 --- a/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c +++ b/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c @@ -1033,7 +1033,12 @@ static bool setup_dsc_config( if (!is_dsc_possible) goto done; - dsc_cfg->num_slices_v = pic_height/slice_height; + if (slice_height > 0) { + dsc_cfg->num_slices_v = pic_height / slice_height; + } else { + is_dsc_possible = false; + goto done; + } if (target_bandwidth_kbps > 0) { is_dsc_possible = decide_dsc_target_bpp_x16( diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 3a927452a650..7e39017e440f 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1819,8 +1819,13 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if (err) goto free_card; - - } else if (!mmc_card_hs400es(card)) { + } else if (mmc_card_hs400es(card)) { + if (host->ops->execute_hs400_tuning) { + err = host->ops->execute_hs400_tuning(host, card); + if (err) + goto free_card; + } + } else { /* Select the desired bus width optionally */ err = mmc_select_bus_width(card); if (err > 0 && mmc_card_hs(card)) { diff --git a/drivers/net/ethernet/intel/ice/ice_virtchnl.c b/drivers/net/ethernet/intel/ice/ice_virtchnl.c index d8509e86214e..6c6f267dcccc 100644 --- a/drivers/net/ethernet/intel/ice/ice_virtchnl.c +++ b/drivers/net/ethernet/intel/ice/ice_virtchnl.c @@ -547,17 +547,15 @@ bool ice_vc_isvalid_vsi_id(struct ice_vf *vf, u16 vsi_id) /** * ice_vc_isvalid_q_id - * @vf: pointer to the VF info - * @vsi_id: VSI ID + * @vsi: VSI to check queue ID against * @qid: VSI relative queue ID * * check for the valid queue ID */ -static bool ice_vc_isvalid_q_id(struct ice_vf *vf, u16 vsi_id, u8 qid) +static bool ice_vc_isvalid_q_id(struct ice_vsi *vsi, u8 qid) { - struct ice_vsi *vsi = ice_find_vsi(vf->pf, vsi_id); /* allocated Tx and Rx queues should be always equal for VF VSI */ - return (vsi && (qid < vsi->alloc_txq)); + return qid < vsi->alloc_txq; } /** @@ -1257,7 +1255,7 @@ static int ice_vc_ena_qs_msg(struct ice_vf *vf, u8 *msg) */ q_map = vqs->rx_queues; for_each_set_bit(vf_q_id, &q_map, ICE_MAX_RSS_QS_PER_VF) { - if (!ice_vc_isvalid_q_id(vf, vqs->vsi_id, vf_q_id)) { + if (!ice_vc_isvalid_q_id(vsi, vf_q_id)) { v_ret = VIRTCHNL_STATUS_ERR_PARAM; goto error_param; } @@ -1279,7 +1277,7 @@ static int ice_vc_ena_qs_msg(struct ice_vf *vf, u8 *msg) q_map = vqs->tx_queues; for_each_set_bit(vf_q_id, &q_map, ICE_MAX_RSS_QS_PER_VF) { - if (!ice_vc_isvalid_q_id(vf, vqs->vsi_id, vf_q_id)) { + if (!ice_vc_isvalid_q_id(vsi, vf_q_id)) { v_ret = VIRTCHNL_STATUS_ERR_PARAM; goto error_param; } @@ -1384,7 +1382,7 @@ static int ice_vc_dis_qs_msg(struct ice_vf *vf, u8 *msg) q_map = vqs->tx_queues; for_each_set_bit(vf_q_id, &q_map, ICE_MAX_RSS_QS_PER_VF) { - if (!ice_vc_isvalid_q_id(vf, vqs->vsi_id, vf_q_id)) { + if (!ice_vc_isvalid_q_id(vsi, vf_q_id)) { v_ret = VIRTCHNL_STATUS_ERR_PARAM; goto error_param; } @@ -1410,7 +1408,7 @@ static int ice_vc_dis_qs_msg(struct ice_vf *vf, u8 *msg) bitmap_zero(vf->rxq_ena, ICE_MAX_RSS_QS_PER_VF); } else if (q_map) { for_each_set_bit(vf_q_id, &q_map, ICE_MAX_RSS_QS_PER_VF) { - if (!ice_vc_isvalid_q_id(vf, vqs->vsi_id, vf_q_id)) { + if (!ice_vc_isvalid_q_id(vsi, vf_q_id)) { v_ret = VIRTCHNL_STATUS_ERR_PARAM; goto error_param; } @@ -1466,7 +1464,7 @@ ice_cfg_interrupt(struct ice_vf *vf, struct ice_vsi *vsi, u16 vector_id, for_each_set_bit(vsi_q_id_idx, &qmap, ICE_MAX_RSS_QS_PER_VF) { vsi_q_id = vsi_q_id_idx; - if (!ice_vc_isvalid_q_id(vf, vsi->vsi_num, vsi_q_id)) + if (!ice_vc_isvalid_q_id(vsi, vsi_q_id)) return VIRTCHNL_STATUS_ERR_PARAM; q_vector->num_ring_rx++; @@ -1480,7 +1478,7 @@ ice_cfg_interrupt(struct ice_vf *vf, struct ice_vsi *vsi, u16 vector_id, for_each_set_bit(vsi_q_id_idx, &qmap, ICE_MAX_RSS_QS_PER_VF) { vsi_q_id = vsi_q_id_idx; - if (!ice_vc_isvalid_q_id(vf, vsi->vsi_num, vsi_q_id)) + if (!ice_vc_isvalid_q_id(vsi, vsi_q_id)) return VIRTCHNL_STATUS_ERR_PARAM; q_vector->num_ring_tx++; @@ -1629,7 +1627,7 @@ static int ice_vc_cfg_qs_msg(struct ice_vf *vf, u8 *msg) qpi->txq.headwb_enabled || !ice_vc_isvalid_ring_len(qpi->txq.ring_len) || !ice_vc_isvalid_ring_len(qpi->rxq.ring_len) || - !ice_vc_isvalid_q_id(vf, qci->vsi_id, qpi->txq.queue_id)) { + !ice_vc_isvalid_q_id(vsi, qpi->txq.queue_id)) { goto error_param; } diff --git a/drivers/net/ethernet/intel/ice/ice_virtchnl_fdir.c b/drivers/net/ethernet/intel/ice/ice_virtchnl_fdir.c index daa6a1e894cf..6fdbd73804d1 100644 --- a/drivers/net/ethernet/intel/ice/ice_virtchnl_fdir.c +++ b/drivers/net/ethernet/intel/ice/ice_virtchnl_fdir.c @@ -107,9 +107,6 @@ ice_vc_fdir_param_check(struct ice_vf *vf, u16 vsi_id) if (!(vf->driver_caps & VIRTCHNL_VF_OFFLOAD_FDIR_PF)) return -EINVAL; - if (vsi_id != vf->lan_vsi_num) - return -EINVAL; - if (!ice_vc_isvalid_vsi_id(vf, vsi_id)) return -EINVAL; diff --git a/drivers/net/ethernet/micrel/ks8851_common.c b/drivers/net/ethernet/micrel/ks8851_common.c index 502518cdb461..6453c92f0fa7 100644 --- a/drivers/net/ethernet/micrel/ks8851_common.c +++ b/drivers/net/ethernet/micrel/ks8851_common.c @@ -328,7 +328,6 @@ static irqreturn_t ks8851_irq(int irq, void *_ks) { struct ks8851_net *ks = _ks; struct sk_buff_head rxq; - unsigned handled = 0; unsigned long flags; unsigned int status; struct sk_buff *skb; @@ -336,24 +335,17 @@ static irqreturn_t ks8851_irq(int irq, void *_ks) ks8851_lock(ks, &flags); status = ks8851_rdreg16(ks, KS_ISR); + ks8851_wrreg16(ks, KS_ISR, status); netif_dbg(ks, intr, ks->netdev, "%s: status 0x%04x\n", __func__, status); - if (status & IRQ_LCI) - handled |= IRQ_LCI; - if (status & IRQ_LDI) { u16 pmecr = ks8851_rdreg16(ks, KS_PMECR); pmecr &= ~PMECR_WKEVT_MASK; ks8851_wrreg16(ks, KS_PMECR, pmecr | PMECR_WKEVT_LINK); - - handled |= IRQ_LDI; } - if (status & IRQ_RXPSI) - handled |= IRQ_RXPSI; - if (status & IRQ_TXI) { unsigned short tx_space = ks8851_rdreg16(ks, KS_TXMIR); @@ -365,20 +357,12 @@ static irqreturn_t ks8851_irq(int irq, void *_ks) if (netif_queue_stopped(ks->netdev)) netif_wake_queue(ks->netdev); spin_unlock(&ks->statelock); - - handled |= IRQ_TXI; } - if (status & IRQ_RXI) - handled |= IRQ_RXI; - if (status & IRQ_SPIBEI) { netdev_err(ks->netdev, "%s: spi bus error\n", __func__); - handled |= IRQ_SPIBEI; } - ks8851_wrreg16(ks, KS_ISR, handled); - if (status & IRQ_RXI) { /* the datasheet says to disable the rx interrupt during * packet read-out, however we're masking the interrupt diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 21b6c4d94a63..6d31061818e9 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -174,6 +174,7 @@ struct ax88179_data { u32 wol_supported; u32 wolopts; u8 disconnecting; + u8 initialized; }; struct ax88179_int_data { @@ -1673,6 +1674,18 @@ static int ax88179_reset(struct usbnet *dev) return 0; } +static int ax88179_net_reset(struct usbnet *dev) +{ + struct ax88179_data *ax179_data = dev->driver_priv; + + if (ax179_data->initialized) + ax88179_reset(dev); + else + ax179_data->initialized = 1; + + return 0; +} + static int ax88179_stop(struct usbnet *dev) { u16 tmp16; @@ -1692,6 +1705,7 @@ static const struct driver_info ax88179_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1704,6 +1718,7 @@ static const struct driver_info ax88178a_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1716,7 +1731,7 @@ static const struct driver_info cypress_GX3_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1729,7 +1744,7 @@ static const struct driver_info dlink_dub1312_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1742,7 +1757,7 @@ static const struct driver_info sitecom_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1755,7 +1770,7 @@ static const struct driver_info samsung_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1768,7 +1783,7 @@ static const struct driver_info lenovo_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1781,7 +1796,7 @@ static const struct driver_info belkin_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1794,7 +1809,7 @@ static const struct driver_info toshiba_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1807,7 +1822,7 @@ static const struct driver_info mct_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1820,7 +1835,7 @@ static const struct driver_info at_umc2000_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1833,7 +1848,7 @@ static const struct driver_info at_umc200_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, @@ -1846,7 +1861,7 @@ static const struct driver_info at_umc2000sp_info = { .unbind = ax88179_unbind, .status = ax88179_status, .link_reset = ax88179_link_reset, - .reset = ax88179_reset, + .reset = ax88179_net_reset, .stop = ax88179_stop, .flags = FLAG_ETHER | FLAG_FRAMING_AX, .rx_fixup = ax88179_rx_fixup, diff --git a/drivers/remoteproc/mtk_scp.c b/drivers/remoteproc/mtk_scp.c index dcc94ee2458d..c4c535b01181 100644 --- a/drivers/remoteproc/mtk_scp.c +++ b/drivers/remoteproc/mtk_scp.c @@ -126,7 +126,7 @@ static int scp_elf_read_ipi_buf_addr(struct mtk_scp *scp, static int scp_ipi_init(struct mtk_scp *scp, const struct firmware *fw) { int ret; - size_t offset; + size_t buf_sz, offset; /* read the ipi buf addr from FW itself first */ ret = scp_elf_read_ipi_buf_addr(scp, fw, &offset); @@ -138,6 +138,14 @@ static int scp_ipi_init(struct mtk_scp *scp, const struct firmware *fw) } dev_info(scp->dev, "IPI buf addr %#010zx\n", offset); + /* Make sure IPI buffer fits in the L2TCM range assigned to this core */ + buf_sz = sizeof(*scp->recv_buf) + sizeof(*scp->send_buf); + + if (scp->sram_size < buf_sz + offset) { + dev_err(scp->dev, "IPI buffer does not fit in SRAM.\n"); + return -EOVERFLOW; + } + scp->recv_buf = (struct mtk_share_obj __iomem *) (scp->sram_base + offset); scp->send_buf = (struct mtk_share_obj __iomem *) diff --git a/drivers/tty/serial/kgdboc.c b/drivers/tty/serial/kgdboc.c index 7ce7bb164005..58ea1e1391ce 100644 --- a/drivers/tty/serial/kgdboc.c +++ b/drivers/tty/serial/kgdboc.c @@ -19,6 +19,7 @@ #include <linux/console.h> #include <linux/vt_kern.h> #include <linux/input.h> +#include <linux/irq_work.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/serial_core.h> @@ -48,6 +49,25 @@ static struct kgdb_io kgdboc_earlycon_io_ops; static int (*earlycon_orig_exit)(struct console *con); #endif /* IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */ +/* + * When we leave the debug trap handler we need to reset the keyboard status + * (since the original keyboard state gets partially clobbered by kdb use of + * the keyboard). + * + * The path to deliver the reset is somewhat circuitous. + * + * To deliver the reset we register an input handler, reset the keyboard and + * then deregister the input handler. However, to get this done right, we do + * have to carefully manage the calling context because we can only register + * input handlers from task context. + * + * In particular we need to trigger the action from the debug trap handler with + * all its NMI and/or NMI-like oddities. To solve this the kgdboc trap exit code + * (the "post_exception" callback) uses irq_work_queue(), which is NMI-safe, to + * schedule a callback from a hardirq context. From there we have to defer the + * work again, this time using schedule_work(), to get a callback using the + * system workqueue, which runs in task context. + */ #ifdef CONFIG_KDB_KEYBOARD static int kgdboc_reset_connect(struct input_handler *handler, struct input_dev *dev, @@ -99,10 +119,17 @@ static void kgdboc_restore_input_helper(struct work_struct *dummy) static DECLARE_WORK(kgdboc_restore_input_work, kgdboc_restore_input_helper); +static void kgdboc_queue_restore_input_helper(struct irq_work *unused) +{ + schedule_work(&kgdboc_restore_input_work); +} + +static DEFINE_IRQ_WORK(kgdboc_restore_input_irq_work, kgdboc_queue_restore_input_helper); + static void kgdboc_restore_input(void) { if (likely(system_state == SYSTEM_RUNNING)) - schedule_work(&kgdboc_restore_input_work); + irq_work_queue(&kgdboc_restore_input_irq_work); } static int kgdboc_register_kbd(char **cptr) @@ -133,6 +160,7 @@ static void kgdboc_unregister_kbd(void) i--; } } + irq_work_sync(&kgdboc_restore_input_irq_work); flush_work(&kgdboc_restore_input_work); } #else /* ! CONFIG_KDB_KEYBOARD */ diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 4062a486b9e6..579d90efc281 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1718,7 +1718,6 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc) */ static int __dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt) { - struct dwc3 *dwc = dep->dwc; struct dwc3_gadget_ep_cmd_params params; u32 cmd; int ret; @@ -1743,8 +1742,7 @@ static int __dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool int dep->resource_index = 0; if (!interrupt) { - if (!DWC3_IP_IS(DWC3) || DWC3_VER_IS_PRIOR(DWC3, 310A)) - mdelay(1); + mdelay(1); dep->flags &= ~DWC3_EP_TRANSFER_STARTED; } else if (!ret) { dep->flags |= DWC3_EP_END_TRANSFER_PENDING; diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 37b56ce75f39..125269f39f83 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -26,6 +26,7 @@ #define TPS_REG_MODE 0x03 #define TPS_REG_CMD1 0x08 #define TPS_REG_DATA1 0x09 +#define TPS_REG_VERSION 0x0F #define TPS_REG_INT_EVENT1 0x14 #define TPS_REG_INT_EVENT2 0x15 #define TPS_REG_INT_MASK1 0x16 @@ -515,49 +516,67 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) static irqreturn_t tps6598x_interrupt(int irq, void *data) { + int intev_len = TPS_65981_2_6_INTEVENT_LEN; struct tps6598x *tps = data; - u64 event1 = 0; - u64 event2 = 0; + u64 event1[2] = { }; + u64 event2[2] = { }; + u32 version; u32 status; int ret; mutex_lock(&tps->lock); - ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event1); - ret |= tps6598x_read64(tps, TPS_REG_INT_EVENT2, &event2); + ret = tps6598x_read32(tps, TPS_REG_VERSION, &version); + if (ret) + dev_warn(tps->dev, "%s: failed to read version (%d)\n", + __func__, ret); + + if (TPS_VERSION_HW_VERSION(version) == TPS_VERSION_HW_65987_8_DH || + TPS_VERSION_HW_VERSION(version) == TPS_VERSION_HW_65987_8_DK) + intev_len = TPS_65987_8_INTEVENT_LEN; + + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event1, intev_len); + + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event1, intev_len); if (ret) { - dev_err(tps->dev, "%s: failed to read events\n", __func__); + dev_err(tps->dev, "%s: failed to read event1\n", __func__); goto err_unlock; } - trace_tps6598x_irq(event1, event2); + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT2, event2, intev_len); + if (ret) { + dev_err(tps->dev, "%s: failed to read event2\n", __func__); + goto err_unlock; + } + trace_tps6598x_irq(event1[0], event2[0]); - if (!(event1 | event2)) + if (!(event1[0] | event1[1] | event2[0] | event2[1])) goto err_unlock; if (!tps6598x_read_status(tps, &status)) goto err_clear_ints; - if ((event1 | event2) & TPS_REG_INT_POWER_STATUS_UPDATE) + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) if (!tps6598x_read_power_status(tps)) goto err_clear_ints; - if ((event1 | event2) & TPS_REG_INT_DATA_STATUS_UPDATE) + if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) if (!tps6598x_read_data_status(tps)) goto err_clear_ints; /* Handle plug insert or removal */ - if ((event1 | event2) & TPS_REG_INT_PLUG_EVENT) + if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) tps6598x_handle_plug_event(tps, status); err_clear_ints: - tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event1); - tps6598x_write64(tps, TPS_REG_INT_CLEAR2, event2); + tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len); + tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len); err_unlock: mutex_unlock(&tps->lock); - if (event1 | event2) + if (event1[0] | event1[1] | event2[0] | event2[1]) return IRQ_HANDLED; + return IRQ_NONE; } diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 527857549d69..1fc3cc8ad199 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -199,4 +199,15 @@ #define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A BIT(2) #define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B (BIT(2) | BIT(1)) +/* Version Register */ +#define TPS_VERSION_HW_VERSION_MASK GENMASK(31, 24) +#define TPS_VERSION_HW_VERSION(x) TPS_FIELD_GET(TPS_VERSION_HW_VERSION_MASK, (x)) +#define TPS_VERSION_HW_65981_2_6 0x00 +#define TPS_VERSION_HW_65987_8_DH 0xF7 +#define TPS_VERSION_HW_65987_8_DK 0xF9 + +/* Int Event Register length */ +#define TPS_65981_2_6_INTEVENT_LEN 8 +#define TPS_65987_8_INTEVENT_LEN 11 + #endif /* __TPS6598X_H__ */ diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 73cd5bf35047..2431febc4615 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -275,8 +275,6 @@ static void ucsi_displayport_work(struct work_struct *work) struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work); int ret; - mutex_lock(&dp->con->lock); - ret = typec_altmode_vdm(dp->alt, dp->header, dp->vdo_data, dp->vdo_size); if (ret) @@ -285,8 +283,6 @@ static void ucsi_displayport_work(struct work_struct *work) dp->vdo_data = NULL; dp->vdo_size = 0; dp->header = 0; - - mutex_unlock(&dp->con->lock); } void ucsi_displayport_remove_partner(struct typec_altmode *alt) diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 1a4fe9f60295..787cc9ff9029 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -82,13 +82,6 @@ struct erofs_dev_context { bool flatdev; }; -struct erofs_fs_context { - struct erofs_mount_opts opt; - struct erofs_dev_context *devs; - char *fsid; - char *domain_id; -}; - /* all filesystem-wide lz4 configurations */ struct erofs_sb_lz4_info { /* # of pages needed for EROFS lz4 rolling decompression */ diff --git a/fs/erofs/super.c b/fs/erofs/super.c index c9f9a43197db..113414e6f35b 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -367,18 +367,18 @@ static int erofs_read_superblock(struct super_block *sb) return ret; } -static void erofs_default_options(struct erofs_fs_context *ctx) +static void erofs_default_options(struct erofs_sb_info *sbi) { #ifdef CONFIG_EROFS_FS_ZIP - ctx->opt.cache_strategy = EROFS_ZIP_CACHE_READAROUND; - ctx->opt.max_sync_decompress_pages = 3; - ctx->opt.sync_decompress = EROFS_SYNC_DECOMPRESS_AUTO; + sbi->opt.cache_strategy = EROFS_ZIP_CACHE_READAROUND; + sbi->opt.max_sync_decompress_pages = 3; + sbi->opt.sync_decompress = EROFS_SYNC_DECOMPRESS_AUTO; #endif #ifdef CONFIG_EROFS_FS_XATTR - set_opt(&ctx->opt, XATTR_USER); + set_opt(&sbi->opt, XATTR_USER); #endif #ifdef CONFIG_EROFS_FS_POSIX_ACL - set_opt(&ctx->opt, POSIX_ACL); + set_opt(&sbi->opt, POSIX_ACL); #endif } @@ -423,17 +423,17 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = { static bool erofs_fc_set_dax_mode(struct fs_context *fc, unsigned int mode) { #ifdef CONFIG_FS_DAX - struct erofs_fs_context *ctx = fc->fs_private; + struct erofs_sb_info *sbi = fc->s_fs_info; switch (mode) { case EROFS_MOUNT_DAX_ALWAYS: warnfc(fc, "DAX enabled. Warning: EXPERIMENTAL, use at your own risk"); - set_opt(&ctx->opt, DAX_ALWAYS); - clear_opt(&ctx->opt, DAX_NEVER); + set_opt(&sbi->opt, DAX_ALWAYS); + clear_opt(&sbi->opt, DAX_NEVER); return true; case EROFS_MOUNT_DAX_NEVER: - set_opt(&ctx->opt, DAX_NEVER); - clear_opt(&ctx->opt, DAX_ALWAYS); + set_opt(&sbi->opt, DAX_NEVER); + clear_opt(&sbi->opt, DAX_ALWAYS); return true; default: DBG_BUGON(1); @@ -448,7 +448,7 @@ static bool erofs_fc_set_dax_mode(struct fs_context *fc, unsigned int mode) static int erofs_fc_parse_param(struct fs_context *fc, struct fs_parameter *param) { - struct erofs_fs_context *ctx = fc->fs_private; + struct erofs_sb_info *sbi = fc->s_fs_info; struct fs_parse_result result; struct erofs_device_info *dif; int opt, ret; @@ -461,9 +461,9 @@ static int erofs_fc_parse_param(struct fs_context *fc, case Opt_user_xattr: #ifdef CONFIG_EROFS_FS_XATTR if (result.boolean) - set_opt(&ctx->opt, XATTR_USER); + set_opt(&sbi->opt, XATTR_USER); else - clear_opt(&ctx->opt, XATTR_USER); + clear_opt(&sbi->opt, XATTR_USER); #else errorfc(fc, "{,no}user_xattr options not supported"); #endif @@ -471,16 +471,16 @@ static int erofs_fc_parse_param(struct fs_context *fc, case Opt_acl: #ifdef CONFIG_EROFS_FS_POSIX_ACL if (result.boolean) - set_opt(&ctx->opt, POSIX_ACL); + set_opt(&sbi->opt, POSIX_ACL); else - clear_opt(&ctx->opt, POSIX_ACL); + clear_opt(&sbi->opt, POSIX_ACL); #else errorfc(fc, "{,no}acl options not supported"); #endif break; case Opt_cache_strategy: #ifdef CONFIG_EROFS_FS_ZIP - ctx->opt.cache_strategy = result.uint_32; + sbi->opt.cache_strategy = result.uint_32; #else errorfc(fc, "compression not supported, cache_strategy ignored"); #endif @@ -502,27 +502,27 @@ static int erofs_fc_parse_param(struct fs_context *fc, kfree(dif); return -ENOMEM; } - down_write(&ctx->devs->rwsem); - ret = idr_alloc(&ctx->devs->tree, dif, 0, 0, GFP_KERNEL); - up_write(&ctx->devs->rwsem); + down_write(&sbi->devs->rwsem); + ret = idr_alloc(&sbi->devs->tree, dif, 0, 0, GFP_KERNEL); + up_write(&sbi->devs->rwsem); if (ret < 0) { kfree(dif->path); kfree(dif); return ret; } - ++ctx->devs->extra_devices; + ++sbi->devs->extra_devices; break; #ifdef CONFIG_EROFS_FS_ONDEMAND case Opt_fsid: - kfree(ctx->fsid); - ctx->fsid = kstrdup(param->string, GFP_KERNEL); - if (!ctx->fsid) + kfree(sbi->fsid); + sbi->fsid = kstrdup(param->string, GFP_KERNEL); + if (!sbi->fsid) return -ENOMEM; break; case Opt_domain_id: - kfree(ctx->domain_id); - ctx->domain_id = kstrdup(param->string, GFP_KERNEL); - if (!ctx->domain_id) + kfree(sbi->domain_id); + sbi->domain_id = kstrdup(param->string, GFP_KERNEL); + if (!sbi->domain_id) return -ENOMEM; break; #else @@ -578,8 +578,7 @@ static const struct export_operations erofs_export_ops = { static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) { struct inode *inode; - struct erofs_sb_info *sbi; - struct erofs_fs_context *ctx = fc->fs_private; + struct erofs_sb_info *sbi = EROFS_SB(sb); int err; sb->s_magic = EROFS_SUPER_MAGIC; @@ -587,19 +586,6 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_op = &erofs_sops; - sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); - if (!sbi) - return -ENOMEM; - - sb->s_fs_info = sbi; - sbi->opt = ctx->opt; - sbi->devs = ctx->devs; - ctx->devs = NULL; - sbi->fsid = ctx->fsid; - ctx->fsid = NULL; - sbi->domain_id = ctx->domain_id; - ctx->domain_id = NULL; - sbi->blkszbits = PAGE_SHIFT; if (erofs_is_fscache_mode(sb)) { sb->s_blocksize = PAGE_SIZE; @@ -703,9 +689,9 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) static int erofs_fc_get_tree(struct fs_context *fc) { - struct erofs_fs_context *ctx = fc->fs_private; + struct erofs_sb_info *sbi = fc->s_fs_info; - if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && ctx->fsid) + if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) return get_tree_nodev(fc, erofs_fc_fill_super); return get_tree_bdev(fc, erofs_fc_fill_super); @@ -715,19 +701,19 @@ static int erofs_fc_reconfigure(struct fs_context *fc) { struct super_block *sb = fc->root->d_sb; struct erofs_sb_info *sbi = EROFS_SB(sb); - struct erofs_fs_context *ctx = fc->fs_private; + struct erofs_sb_info *new_sbi = fc->s_fs_info; DBG_BUGON(!sb_rdonly(sb)); - if (ctx->fsid || ctx->domain_id) + if (new_sbi->fsid || new_sbi->domain_id) erofs_info(sb, "ignoring reconfiguration for fsid|domain_id."); - if (test_opt(&ctx->opt, POSIX_ACL)) + if (test_opt(&new_sbi->opt, POSIX_ACL)) fc->sb_flags |= SB_POSIXACL; else fc->sb_flags &= ~SB_POSIXACL; - sbi->opt = ctx->opt; + sbi->opt = new_sbi->opt; fc->sb_flags |= SB_RDONLY; return 0; @@ -758,12 +744,15 @@ static void erofs_free_dev_context(struct erofs_dev_context *devs) static void erofs_fc_free(struct fs_context *fc) { - struct erofs_fs_context *ctx = fc->fs_private; + struct erofs_sb_info *sbi = fc->s_fs_info; - erofs_free_dev_context(ctx->devs); - kfree(ctx->fsid); - kfree(ctx->domain_id); - kfree(ctx); + if (!sbi) + return; + + erofs_free_dev_context(sbi->devs); + kfree(sbi->fsid); + kfree(sbi->domain_id); + kfree(sbi); } static const struct fs_context_operations erofs_context_ops = { @@ -775,38 +764,35 @@ static const struct fs_context_operations erofs_context_ops = { static int erofs_init_fs_context(struct fs_context *fc) { - struct erofs_fs_context *ctx; + struct erofs_sb_info *sbi; - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); - if (!ctx) + sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); + if (!sbi) return -ENOMEM; - ctx->devs = kzalloc(sizeof(struct erofs_dev_context), GFP_KERNEL); - if (!ctx->devs) { - kfree(ctx); + + sbi->devs = kzalloc(sizeof(struct erofs_dev_context), GFP_KERNEL); + if (!sbi->devs) { + kfree(sbi); return -ENOMEM; } - fc->fs_private = ctx; + fc->s_fs_info = sbi; - idr_init(&ctx->devs->tree); - init_rwsem(&ctx->devs->rwsem); - erofs_default_options(ctx); + idr_init(&sbi->devs->tree); + init_rwsem(&sbi->devs->rwsem); + erofs_default_options(sbi); fc->ops = &erofs_context_ops; return 0; } static void erofs_kill_sb(struct super_block *sb) { - struct erofs_sb_info *sbi; + struct erofs_sb_info *sbi = EROFS_SB(sb); - if (erofs_is_fscache_mode(sb)) + if (IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) kill_anon_super(sb); else kill_block_super(sb); - sbi = EROFS_SB(sb); - if (!sbi) - return; - erofs_free_dev_context(sbi->devs); fs_put_dax(sbi->dax_dev, NULL); erofs_fscache_unregister_fs(sb); diff --git a/fs/smb/client/Makefile b/fs/smb/client/Makefile index 0b07eb94c93b..e11985f2460b 100644 --- a/fs/smb/client/Makefile +++ b/fs/smb/client/Makefile @@ -12,7 +12,7 @@ cifs-y := trace.o cifsfs.o cifs_debug.o connect.o dir.o file.o \ smb2ops.o smb2maperror.o smb2transport.o \ smb2misc.o smb2pdu.o smb2inode.o smb2file.o cifsacl.o fs_context.o \ dns_resolve.o cifs_spnego_negtokeninit.asn1.o asn1.o \ - namespace.o + namespace.o reparse.o $(obj)/asn1.o: $(obj)/cifs_spnego_negtokeninit.asn1.h diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 567f718362c5..0ff2491c311d 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -145,21 +145,27 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, struct cached_fid *cfid; struct cached_fids *cfids; const char *npath; + int retries = 0, cur_sleep = 1; if (tcon == NULL || tcon->cfids == NULL || tcon->nohandlecache || is_smb1_server(tcon->ses->server) || (dir_cache_timeout == 0)) return -EOPNOTSUPP; ses = tcon->ses; - server = cifs_pick_channel(ses); cfids = tcon->cfids; - if (!server->ops->new_lease_key) - return -EIO; - if (cifs_sb->root == NULL) return -ENOENT; +replay_again: + /* reinitialize for possible replay */ + flags = 0; + oplock = SMB2_OPLOCK_LEVEL_II; + server = cifs_pick_channel(ses); + + if (!server->ops->new_lease_key) + return -EIO; + utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); if (!utf16_path) return -ENOMEM; @@ -237,6 +243,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, FILE_READ_EA, .disposition = FILE_OPEN, .fid = pfid, + .replay = !!(retries), }; rc = SMB2_open_init(tcon, server, @@ -269,6 +276,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, */ cfid->has_lease = true; + if (retries) { + smb2_set_replay(server, &rqst[0]); + smb2_set_replay(server, &rqst[1]); + } + rc = compound_send_recv(xid, ses, server, flags, 2, rqst, resp_buftype, rsp_iov); @@ -369,6 +381,10 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, } kfree(utf16_path); + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index c53d516459fc..c71ae5c04306 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -280,6 +280,24 @@ static int cifs_debug_files_proc_show(struct seq_file *m, void *v) return 0; } +static __always_inline const char *compression_alg_str(__le16 alg) +{ + switch (alg) { + case SMB3_COMPRESS_NONE: + return "NONE"; + case SMB3_COMPRESS_LZNT1: + return "LZNT1"; + case SMB3_COMPRESS_LZ77: + return "LZ77"; + case SMB3_COMPRESS_LZ77_HUFF: + return "LZ77-Huffman"; + case SMB3_COMPRESS_PATTERN: + return "Pattern_V1"; + default: + return "invalid"; + } +} + static int cifs_debug_data_proc_show(struct seq_file *m, void *v) { struct mid_q_entry *mid_entry; @@ -425,12 +443,6 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) server->echo_credits, server->oplock_credits, server->dialect); - if (server->compress_algorithm == SMB3_COMPRESS_LZNT1) - seq_printf(m, " COMPRESS_LZNT1"); - else if (server->compress_algorithm == SMB3_COMPRESS_LZ77) - seq_printf(m, " COMPRESS_LZ77"); - else if (server->compress_algorithm == SMB3_COMPRESS_LZ77_HUFF) - seq_printf(m, " COMPRESS_LZ77_HUFF"); if (server->sign) seq_printf(m, " signed"); if (server->posix_ext_supported) @@ -438,6 +450,8 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) if (server->nosharesock) seq_printf(m, " nosharesock"); + seq_printf(m, "\nServer capabilities: 0x%x", server->capabilities); + if (server->rdma) seq_printf(m, "\nRDMA "); seq_printf(m, "\nTCP status: %d Instance: %d" @@ -460,6 +474,14 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) server->leaf_fullpath); } + seq_puts(m, "\nCompression: "); + if (!server->compression.requested) + seq_puts(m, "disabled on mount"); + else if (server->compression.enabled) + seq_printf(m, "enabled (%s)", compression_alg_str(server->compression.alg)); + else + seq_puts(m, "disabled (not supported by this server)"); + seq_printf(m, "\n\n\tSessions: "); i = 0; list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { @@ -777,14 +799,14 @@ static ssize_t name##_write(struct file *file, const char __user *buffer, \ size_t count, loff_t *ppos) \ { \ int rc; \ - rc = kstrtoint_from_user(buffer, count, 10, & name); \ + rc = kstrtoint_from_user(buffer, count, 10, &name); \ if (rc) \ return rc; \ return count; \ } \ static int name##_proc_show(struct seq_file *m, void *v) \ { \ - seq_printf(m, "%d\n", name ); \ + seq_printf(m, "%d\n", name); \ return 0; \ } \ static int name##_open(struct inode *inode, struct file *file) \ diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 44e2cc37a8b6..f1dcb86ab989 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -150,10 +150,6 @@ MODULE_PARM_DESC(disable_legacy_dialects, "To improve security it may be " "vers=1.0 (CIFS/SMB1) and vers=2.0 are weaker" " and less secure. Default: n/N/0"); -extern mempool_t *cifs_sm_req_poolp; -extern mempool_t *cifs_req_poolp; -extern mempool_t *cifs_mid_poolp; - struct workqueue_struct *cifsiod_wq; struct workqueue_struct *decrypt_wq; struct workqueue_struct *fileinfo_put_wq; @@ -674,6 +670,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_printf(s, ",backupgid=%u", from_kgid_munged(&init_user_ns, cifs_sb->ctx->backupgid)); + seq_show_option(s, "reparse", + cifs_reparse_type_str(cifs_sb->ctx->reparse_type)); seq_printf(s, ",rsize=%u", cifs_sb->ctx->rsize); seq_printf(s, ",wsize=%u", cifs_sb->ctx->wsize); @@ -682,6 +680,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) seq_printf(s, ",rasize=%u", cifs_sb->ctx->rasize); if (tcon->ses->server->min_offload) seq_printf(s, ",esize=%u", tcon->ses->server->min_offload); + if (tcon->ses->server->retrans) + seq_printf(s, ",retrans=%u", tcon->ses->server->retrans); seq_printf(s, ",echo_interval=%lu", tcon->ses->server->echo_interval / HZ); @@ -739,6 +739,8 @@ static void cifs_umount_begin(struct super_block *sb) spin_lock(&cifs_tcp_ses_lock); spin_lock(&tcon->tc_lock); + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_umount); if ((tcon->tc_count > 1) || (tcon->status == TID_EXITING)) { /* we have other mounts to same share or we have already tried to umount this and woken up diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index b598c7ed497b..c146f83eba9b 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -49,6 +49,11 @@ */ #define CIFS_DEF_ACTIMEO (1 * HZ) +/* + * max sleep time before retry to server + */ +#define CIFS_MAX_SLEEP 2000 + /* * max attribute cache timeout (jiffies) - 2^30 */ @@ -148,6 +153,24 @@ enum securityEnum { Kerberos, /* Kerberos via SPNEGO */ }; +enum cifs_reparse_type { + CIFS_REPARSE_TYPE_NFS, + CIFS_REPARSE_TYPE_WSL, + CIFS_REPARSE_TYPE_DEFAULT = CIFS_REPARSE_TYPE_NFS, +}; + +static inline const char *cifs_reparse_type_str(enum cifs_reparse_type type) +{ + switch (type) { + case CIFS_REPARSE_TYPE_NFS: + return "nfs"; + case CIFS_REPARSE_TYPE_WSL: + return "wsl"; + default: + return "unknown"; + } +} + struct session_key { unsigned int len; char *response; @@ -192,37 +215,30 @@ struct cifs_open_info_data { bool symlink; }; struct { + /* ioctl response buffer */ + struct { + int buftype; + struct kvec iov; + } io; __u32 tag; union { struct reparse_data_buffer *buf; struct reparse_posix_data *posix; }; } reparse; + struct { + __u8 eas[SMB2_WSL_MAX_QUERY_EA_RESP_SIZE]; + unsigned int eas_len; + } wsl; char *symlink_target; + struct cifs_sid posix_owner; + struct cifs_sid posix_group; union { struct smb2_file_all_info fi; struct smb311_posix_qinfo posix_fi; }; }; -static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) -{ - struct smb2_file_all_info *fi = &data->fi; - u32 attrs = le32_to_cpu(fi->Attributes); - bool ret; - - ret = data->reparse_point || (attrs & ATTR_REPARSE); - if (ret) - attrs |= ATTR_REPARSE; - fi->Attributes = cpu_to_le32(attrs); - return ret; -} - -static inline void cifs_free_open_info(struct cifs_open_info_data *data) -{ - kfree(data->symlink_target); -} - /* ***************************************************************** * Except the CIFS PDUs themselves all the @@ -367,7 +383,8 @@ struct smb_version_operations { struct cifs_open_info_data *data); /* set size by path */ int (*set_path_size)(const unsigned int, struct cifs_tcon *, - const char *, __u64, struct cifs_sb_info *, bool); + const char *, __u64, struct cifs_sb_info *, bool, + struct dentry *); /* set size by file handle */ int (*set_file_size)(const unsigned int, struct cifs_tcon *, struct cifsFileInfo *, __u64, bool); @@ -397,7 +414,7 @@ struct smb_version_operations { struct cifs_sb_info *); /* unlink file */ int (*unlink)(const unsigned int, struct cifs_tcon *, const char *, - struct cifs_sb_info *); + struct cifs_sb_info *, struct dentry *); /* open, rename and delete file */ int (*rename_pending_delete)(const char *, struct dentry *, const unsigned int); @@ -577,6 +594,12 @@ struct smb_version_operations { int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov, struct cifs_open_info_data *data); + int (*create_reparse_symlink)(const unsigned int xid, + struct inode *inode, + struct dentry *dentry, + struct cifs_tcon *tcon, + const char *full_path, + const char *symname); }; struct smb_version_values { @@ -748,7 +771,12 @@ struct TCP_Server_Info { unsigned int max_read; unsigned int max_write; unsigned int min_offload; - __le16 compress_algorithm; + unsigned int retrans; + struct { + bool requested; /* "compress" mount option set*/ + bool enabled; /* actually negotiated with server */ + __le16 alg; /* preferred alg negotiated with server */ + } compression; __u16 signing_algorithm; __le16 cipher_type; /* save initital negprot hash */ @@ -1162,6 +1190,7 @@ struct cifs_fattr { */ struct cifs_tcon { struct list_head tcon_list; + int debug_id; /* Debugging for tracing */ int tc_count; struct list_head rlist; /* reconnect list */ spinlock_t tc_lock; /* protect anything here that is not protected */ @@ -1370,6 +1399,8 @@ struct cifs_open_parms { struct cifs_fid *fid; umode_t mode; bool reconnect:1; + bool replay:1; /* indicates that this open is for a replay */ + struct kvec *ea_cctx; }; struct cifs_fid { @@ -1411,6 +1442,7 @@ struct cifsFileInfo { bool invalidHandle:1; /* file closed via session abend */ bool swapfile:1; bool oplock_break_cancelled:1; + bool status_file_deleted:1; /* file has been deleted */ bool offload:1; /* offload final part of _put to a wq */ unsigned int oplock_epoch; /* epoch from the lease break */ __u32 oplock_level; /* oplock/lease level from the lease break */ @@ -1503,6 +1535,7 @@ struct cifs_writedata { struct smbd_mr *mr; #endif struct cifs_credits credits; + bool replay; }; /* @@ -2080,6 +2113,8 @@ extern struct workqueue_struct *deferredclose_wq; extern struct workqueue_struct *serverclose_wq; extern __u32 cifs_lock_secret; +extern mempool_t *cifs_sm_req_poolp; +extern mempool_t *cifs_req_poolp; extern mempool_t *cifs_mid_poolp; /* Operations for different SMB versions */ @@ -2270,10 +2305,21 @@ static inline void cifs_sg_set_buf(struct sg_table *sgtable, } } +#define CIFS_OPARMS(_cifs_sb, _tcon, _path, _da, _cd, _co, _mode) \ + ((struct cifs_open_parms) { \ + .tcon = _tcon, \ + .path = _path, \ + .desired_access = (_da), \ + .disposition = (_cd), \ + .create_options = cifs_create_options(_cifs_sb, (_co)), \ + .mode = (_mode), \ + .cifs_sb = _cifs_sb, \ + }) + struct smb2_compound_vars { struct cifs_open_parms oparms; - struct kvec rsp_iov[3]; - struct smb_rqst rqst[3]; + struct kvec rsp_iov[MAX_COMPOUND]; + struct smb_rqst rqst[MAX_COMPOUND]; struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; struct kvec qi_iov; struct kvec io_iov[SMB2_IOCTL_IOV_SIZE]; @@ -2281,6 +2327,7 @@ struct smb2_compound_vars { struct kvec close_iov; struct smb2_file_rename_info rename_info; struct smb2_file_link_info link_info; + struct kvec ea_iov; }; static inline bool cifs_ses_exiting(struct cifs_ses *ses) diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 1bdad33580b5..fbc358c09da3 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -210,11 +210,11 @@ extern struct inode *cifs_iget(struct super_block *sb, int cifs_get_inode_info(struct inode **inode, const char *full_path, struct cifs_open_info_data *data, struct super_block *sb, int xid, const struct cifs_fid *fid); -bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, - struct cifs_fattr *fattr, - struct cifs_open_info_data *data); -extern int smb311_posix_get_inode_info(struct inode **pinode, const char *search_path, - struct super_block *sb, unsigned int xid); +extern int smb311_posix_get_inode_info(struct inode **inode, + const char *full_path, + struct cifs_open_info_data *data, + struct super_block *sb, + const unsigned int xid); extern int cifs_get_inode_info_unix(struct inode **pinode, const unsigned char *search_path, struct super_block *sb, unsigned int xid); @@ -294,12 +294,16 @@ extern void cifs_close_all_deferred_files(struct cifs_tcon *cifs_tcon); extern void cifs_close_deferred_file_under_dentry(struct cifs_tcon *cifs_tcon, const char *path); + +extern void cifs_mark_open_handles_for_deleted_file(struct inode *inode, + const char *path); + extern struct TCP_Server_Info * cifs_get_tcp_session(struct smb3_fs_context *ctx, struct TCP_Server_Info *primary_server); extern void cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect); -extern void cifs_put_tcon(struct cifs_tcon *tcon); +extern void cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace); extern void cifs_release_automount_timer(void); @@ -400,7 +404,8 @@ extern int CIFSSMBSetFileDisposition(const unsigned int xid, __u32 pid_of_opener); extern int CIFSSMBSetEOF(const unsigned int xid, struct cifs_tcon *tcon, const char *file_name, __u64 size, - struct cifs_sb_info *cifs_sb, bool set_allocation); + struct cifs_sb_info *cifs_sb, bool set_allocation, + struct dentry *dentry); extern int CIFSSMBSetFileSize(const unsigned int xid, struct cifs_tcon *tcon, struct cifsFileInfo *cfile, __u64 size, bool set_allocation); @@ -436,7 +441,8 @@ extern int CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nls_codepage, int remap_special_chars); extern int CIFSSMBDelFile(const unsigned int xid, struct cifs_tcon *tcon, - const char *name, struct cifs_sb_info *cifs_sb); + const char *name, struct cifs_sb_info *cifs_sb, + struct dentry *dentry); int CIFSSMBRename(const unsigned int xid, struct cifs_tcon *tcon, struct dentry *source_dentry, const char *from_name, const char *to_name, @@ -524,8 +530,9 @@ extern int CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses); extern struct cifs_ses *sesInfoAlloc(void); extern void sesInfoFree(struct cifs_ses *); -extern struct cifs_tcon *tcon_info_alloc(bool dir_leases_enabled); -extern void tconInfoFree(struct cifs_tcon *); +extern struct cifs_tcon *tcon_info_alloc(bool dir_leases_enabled, + enum smb3_tcon_ref_trace trace); +extern void tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace); extern int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server, __u32 *pexpected_response_sequence_number); @@ -673,6 +680,9 @@ char *extract_sharename(const char *unc); int parse_reparse_point(struct reparse_data_buffer *buf, u32 plen, struct cifs_sb_info *cifs_sb, bool unicode, struct cifs_open_info_data *data); +int cifs_sfu_make_node(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev); #ifdef CONFIG_CIFS_DFS_UPCALL static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, @@ -712,8 +722,6 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options) return options; } -struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon); -void cifs_put_tcon_super(struct super_block *sb); int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry); /* Put references of @ses and its children */ @@ -762,4 +770,11 @@ static inline void release_mid(struct mid_q_entry *mid) kref_put(&mid->refcount, __release_mid); } +static inline void cifs_free_open_info(struct cifs_open_info_data *data) +{ + kfree(data->symlink_target); + free_rsp_buf(data->reparse.io.buftype, data->reparse.io.iov.iov_base); + memset(data, 0, sizeof(*data)); +} + #endif /* _CIFSPROTO_H */ diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index e9e33b0b3ac4..301189ee1335 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -738,7 +738,7 @@ CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, int CIFSSMBDelFile(const unsigned int xid, struct cifs_tcon *tcon, const char *name, - struct cifs_sb_info *cifs_sb) + struct cifs_sb_info *cifs_sb, struct dentry *dentry) { DELETE_FILE_REQ *pSMB = NULL; DELETE_FILE_RSP *pSMBr = NULL; @@ -2700,11 +2700,12 @@ int cifs_query_reparse_point(const unsigned int xid, u32 *tag, struct kvec *rsp, int *rsp_buftype) { + struct reparse_data_buffer *buf; struct cifs_open_parms oparms; TRANSACT_IOCTL_REQ *io_req = NULL; TRANSACT_IOCTL_RSP *io_rsp = NULL; struct cifs_fid fid; - __u32 data_offset, data_count; + __u32 data_offset, data_count, len; __u8 *start, *end; int io_rsp_len; int oplock = 0; @@ -2774,7 +2775,16 @@ int cifs_query_reparse_point(const unsigned int xid, goto error; } - *tag = le32_to_cpu(((struct reparse_data_buffer *)start)->ReparseTag); + data_count = le16_to_cpu(io_rsp->ByteCount); + buf = (struct reparse_data_buffer *)start; + len = sizeof(*buf); + if (data_count < len || + data_count < le16_to_cpu(buf->ReparseDataLength) + len) { + rc = -EIO; + goto error; + } + + *tag = le32_to_cpu(buf->ReparseTag); rsp->iov_base = io_rsp; rsp->iov_len = io_rsp_len; *rsp_buftype = CIFS_LARGE_BUFFER; @@ -4983,7 +4993,7 @@ CIFSSMBQFSPosixInfo(const unsigned int xid, struct cifs_tcon *tcon, int CIFSSMBSetEOF(const unsigned int xid, struct cifs_tcon *tcon, const char *file_name, __u64 size, struct cifs_sb_info *cifs_sb, - bool set_allocation) + bool set_allocation, struct dentry *dentry) { struct smb_com_transaction2_spi_req *pSMB = NULL; struct smb_com_transaction2_spi_rsp *pSMBr = NULL; diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index cb3bed8364e0..7a16e12f5da8 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -52,9 +52,6 @@ #include "fs_context.h" #include "cifs_swn.h" -extern mempool_t *cifs_req_poolp; -extern bool disable_legacy_dialects; - /* FIXME: should these be tunable? */ #define TLINK_ERROR_EXPIRE (1 * HZ) #define TLINK_IDLE_EXPIRE (600 * HZ) @@ -501,6 +498,7 @@ static int reconnect_target_unlocked(struct TCP_Server_Info *server, struct dfs_ static int reconnect_dfs_server(struct TCP_Server_Info *server) { struct dfs_cache_tgt_iterator *target_hint = NULL; + DFS_CACHE_TGT_LIST(tl); int num_targets = 0; int rc = 0; @@ -763,6 +761,7 @@ cifs_read_from_socket(struct TCP_Server_Info *server, char *buf, { struct msghdr smb_msg = {}; struct kvec iov = {.iov_base = buf, .iov_len = to_read}; + iov_iter_kvec(&smb_msg.msg_iter, ITER_DEST, &iov, 1, to_read); return cifs_readv_from_socket(server, &smb_msg); @@ -1418,11 +1417,13 @@ cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs) case AF_INET: { struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr; struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs; + return (saddr4->sin_addr.s_addr == vaddr4->sin_addr.s_addr); } case AF_INET6: { struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr; struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)rhs; + return (ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr) && saddr6->sin6_scope_id == vaddr6->sin6_scope_id); } @@ -1588,6 +1589,9 @@ static int match_server(struct TCP_Server_Info *server, if (server->min_offload != ctx->min_offload) return 0; + if (server->retrans != ctx->retrans) + return 0; + return 1; } @@ -1741,7 +1745,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, tcp_ses->channel_sequence_num = 0; /* only tracked for primary channel */ tcp_ses->reconnect_instance = 1; tcp_ses->lstrp = jiffies; - tcp_ses->compress_algorithm = cpu_to_le16(ctx->compression); + tcp_ses->compression.requested = ctx->compress; spin_lock_init(&tcp_ses->req_lock); spin_lock_init(&tcp_ses->srv_lock); spin_lock_init(&tcp_ses->mid_lock); @@ -1812,6 +1816,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, goto out_err_crypto_release; } tcp_ses->min_offload = ctx->min_offload; + tcp_ses->retrans = ctx->retrans; /* * at this point we are the only ones with the pointer * to the struct since the kernel thread not created yet @@ -1938,7 +1943,7 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) } /* no need to setup directory caching on IPC share, so pass in false */ - tcon = tcon_info_alloc(false); + tcon = tcon_info_alloc(false, netfs_trace_tcon_ref_new_ipc); if (tcon == NULL) return -ENOMEM; @@ -1955,7 +1960,7 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) if (rc) { cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc); - tconInfoFree(tcon); + tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail); goto out; } @@ -2038,7 +2043,7 @@ void __cifs_put_smb_ses(struct cifs_ses *ses) * files on session close, as specified in MS-SMB2 3.3.5.6 Receiving an * SMB2 LOGOFF Request. */ - tconInfoFree(tcon); + tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc); if (do_logoff) { xid = get_xid(); rc = server->ops->logoff(xid, ses); @@ -2427,6 +2432,8 @@ cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) continue; } ++tcon->tc_count; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_find); spin_unlock(&tcon->tc_lock); spin_unlock(&cifs_tcp_ses_lock); return tcon; @@ -2436,7 +2443,7 @@ cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) } void -cifs_put_tcon(struct cifs_tcon *tcon) +cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace) { unsigned int xid; struct cifs_ses *ses; @@ -2452,6 +2459,7 @@ cifs_put_tcon(struct cifs_tcon *tcon) cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count); spin_lock(&cifs_tcp_ses_lock); spin_lock(&tcon->tc_lock); + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count - 1, trace); if (--tcon->tc_count > 0) { spin_unlock(&tcon->tc_lock); spin_unlock(&cifs_tcp_ses_lock); @@ -2488,7 +2496,7 @@ cifs_put_tcon(struct cifs_tcon *tcon) _free_xid(xid); cifs_fscache_release_super_cookie(tcon); - tconInfoFree(tcon); + tconInfoFree(tcon, netfs_trace_tcon_ref_free); cifs_put_smb_ses(ses); } @@ -2542,7 +2550,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) nohandlecache = ctx->nohandlecache; else nohandlecache = true; - tcon = tcon_info_alloc(!nohandlecache); + tcon = tcon_info_alloc(!nohandlecache, netfs_trace_tcon_ref_new); if (tcon == NULL) { rc = -ENOMEM; goto out_fail; @@ -2607,8 +2615,8 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) rc = -EOPNOTSUPP; goto out_fail; } else { - cifs_dbg(VFS, "Check vers= mount option. SMB3.11 " - "disabled but required for POSIX extensions\n"); + cifs_dbg(VFS, + "Check vers= mount option. SMB3.11 disabled but required for POSIX extensions\n"); rc = -EOPNOTSUPP; goto out_fail; } @@ -2732,7 +2740,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) return tcon; out_fail: - tconInfoFree(tcon); + tconInfoFree(tcon, netfs_trace_tcon_ref_free_fail); return ERR_PTR(rc); } @@ -2749,9 +2757,8 @@ cifs_put_tlink(struct tcon_link *tlink) } if (!IS_ERR(tlink_tcon(tlink))) - cifs_put_tcon(tlink_tcon(tlink)); + cifs_put_tcon(tlink_tcon(tlink), netfs_trace_tcon_ref_put_tlink); kfree(tlink); - return; } static int @@ -2798,6 +2805,8 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) return 0; if (old->ctx->closetimeo != new->ctx->closetimeo) return 0; + if (old->ctx->reparse_type != new->ctx->reparse_type) + return 0; return 1; } @@ -2892,6 +2901,7 @@ static inline void cifs_reclassify_socket4(struct socket *sock) { struct sock *sk = sock->sk; + BUG_ON(!sock_allow_reclassification(sk)); sock_lock_init_class_and_name(sk, "slock-AF_INET-CIFS", &cifs_slock_key[0], "sk_lock-AF_INET-CIFS", &cifs_key[0]); @@ -2901,6 +2911,7 @@ static inline void cifs_reclassify_socket6(struct socket *sock) { struct sock *sk = sock->sk; + BUG_ON(!sock_allow_reclassification(sk)); sock_lock_init_class_and_name(sk, "slock-AF_INET6-CIFS", &cifs_slock_key[1], "sk_lock-AF_INET6-CIFS", &cifs_key[1]); @@ -2935,15 +2946,18 @@ static int bind_socket(struct TCP_Server_Info *server) { int rc = 0; + if (server->srcaddr.ss_family != AF_UNSPEC) { /* Bind to the specified local IP address */ struct socket *socket = server->ssocket; + rc = kernel_bind(socket, (struct sockaddr *) &server->srcaddr, sizeof(server->srcaddr)); if (rc < 0) { struct sockaddr_in *saddr4; struct sockaddr_in6 *saddr6; + saddr4 = (struct sockaddr_in *)&server->srcaddr; saddr6 = (struct sockaddr_in6 *)&server->srcaddr; if (saddr6->sin6_family == AF_INET6) @@ -3173,6 +3187,7 @@ void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon, if (!CIFSSMBQFSUnixInfo(xid, tcon)) { __u64 cap = le64_to_cpu(tcon->fsUnixInfo.Capability); + cifs_dbg(FYI, "unix caps which server supports %lld\n", cap); /* * check for reconnect case in which we do not @@ -3307,7 +3322,7 @@ void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx) int rc = 0; if (mnt_ctx->tcon) - cifs_put_tcon(mnt_ctx->tcon); + cifs_put_tcon(mnt_ctx->tcon, netfs_trace_tcon_ref_put_mnt_ctx); else if (mnt_ctx->ses) cifs_put_smb_ses(mnt_ctx->ses); else if (mnt_ctx->server) @@ -3698,7 +3713,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, smb_buffer_response = smb_buffer; header_assemble(smb_buffer, SMB_COM_TREE_CONNECT_ANDX, - NULL /*no tid */ , 4 /*wct */ ); + NULL /*no tid */, 4 /*wct */); smb_buffer->Mid = get_next_mid(ses->server); smb_buffer->Uid = ses->Suid; @@ -3717,12 +3732,12 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, if (ses->server->sign) smb_buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE; - if (ses->capabilities & CAP_STATUS32) { + if (ses->capabilities & CAP_STATUS32) smb_buffer->Flags2 |= SMBFLG2_ERR_STATUS; - } - if (ses->capabilities & CAP_DFS) { + + if (ses->capabilities & CAP_DFS) smb_buffer->Flags2 |= SMBFLG2_DFS; - } + if (ses->capabilities & CAP_UNICODE) { smb_buffer->Flags2 |= SMBFLG2_UNICODE; length = diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 855468a32904..864b194dbaa0 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -627,11 +627,18 @@ int cifs_mknod(struct mnt_idmap *idmap, struct inode *inode, goto mknod_out; } + trace_smb3_mknod_enter(xid, tcon->ses->Suid, tcon->tid, full_path); + rc = tcon->ses->server->ops->make_node(xid, inode, direntry, tcon, full_path, mode, device_number); mknod_out: + if (rc) + trace_smb3_mknod_err(xid, tcon->ses->Suid, tcon->tid, rc); + else + trace_smb3_mknod_done(xid, tcon->ses->Suid, tcon->tid); + free_dentry_path(page); free_xid(xid); cifs_put_tlink(tlink); @@ -695,9 +702,10 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, full_path, d_inode(direntry)); again: - if (pTcon->posix_extensions) - rc = smb311_posix_get_inode_info(&newInode, full_path, parent_dir_inode->i_sb, xid); - else if (pTcon->unix_ext) { + if (pTcon->posix_extensions) { + rc = smb311_posix_get_inode_info(&newInode, full_path, NULL, + parent_dir_inode->i_sb, xid); + } else if (pTcon->unix_ext) { rc = cifs_get_inode_info_unix(&newInode, full_path, parent_dir_inode->i_sb, xid); } else { diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 53a8c633221b..7ea8c3cf70f6 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1102,14 +1102,16 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush) if (!is_interrupt_error(rc)) mapping_set_error(inode->i_mapping, rc); - if (tcon->posix_extensions) - rc = smb311_posix_get_inode_info(&inode, full_path, inode->i_sb, xid); - else if (tcon->unix_ext) + if (tcon->posix_extensions) { + rc = smb311_posix_get_inode_info(&inode, full_path, + NULL, inode->i_sb, xid); + } else if (tcon->unix_ext) { rc = cifs_get_inode_info_unix(&inode, full_path, inode->i_sb, xid); - else + } else { rc = cifs_get_inode_info(&inode, full_path, NULL, inode->i_sb, xid, NULL); + } } /* * Else we are writing out data to server already and could deadlock if @@ -1149,6 +1151,19 @@ void smb2_deferred_work_close(struct work_struct *work) _cifsFileInfo_put(cfile, true, false); } +static bool +smb2_can_defer_close(struct inode *inode, struct cifs_deferred_close *dclose) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + struct cifsInodeInfo *cinode = CIFS_I(inode); + + return (cifs_sb->ctx->closetimeo && cinode->lease_granted && dclose && + (cinode->oplock == CIFS_CACHE_RHW_FLG || + cinode->oplock == CIFS_CACHE_RH_FLG) && + !test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags)); + +} + int cifs_close(struct inode *inode, struct file *file) { struct cifsFileInfo *cfile; @@ -1162,10 +1177,8 @@ int cifs_close(struct inode *inode, struct file *file) cfile = file->private_data; file->private_data = NULL; dclose = kmalloc(sizeof(struct cifs_deferred_close), GFP_KERNEL); - if ((cifs_sb->ctx->closetimeo && cinode->oplock == CIFS_CACHE_RHW_FLG) - && cinode->lease_granted && - !test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags) && - dclose) { + if ((cfile->status_file_deleted == false) && + (smb2_can_defer_close(inode, dclose))) { if (test_and_clear_bit(CIFS_INO_MODIFIED_ATTR, &cinode->flags)) { inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); @@ -3202,8 +3215,15 @@ static int cifs_write_end(struct file *file, struct address_space *mapping, if (rc > 0) { spin_lock(&inode->i_lock); if (pos > inode->i_size) { + loff_t additional_blocks = (512 - 1 + copied) >> 9; + i_size_write(inode, pos); - inode->i_blocks = (512 - 1 + pos) >> 9; + /* + * Estimate new allocation size based on the amount written. + * This will be updated from server on close (and on queryinfo) + */ + inode->i_blocks = min_t(blkcnt_t, (512 - 1 + pos) >> 9, + inode->i_blocks + additional_blocks); } spin_unlock(&inode->i_lock); } @@ -3411,6 +3431,7 @@ cifs_resend_wdata(struct cifs_writedata *wdata, struct list_head *wdata_list, if (wdata->cfile->invalidHandle) rc = -EAGAIN; else { + wdata->replay = true; #ifdef CONFIG_CIFS_SMB_DIRECT if (wdata->mr) { wdata->mr->need_invalidate = true; diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index 4d9e57be84db..3bbac925d076 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -139,6 +139,7 @@ const struct fs_parameter_spec smb3_fs_parameters[] = { fsparam_u32("dir_mode", Opt_dirmode), fsparam_u32("port", Opt_port), fsparam_u32("min_enc_offload", Opt_min_enc_offload), + fsparam_u32("retrans", Opt_retrans), fsparam_u32("esize", Opt_min_enc_offload), fsparam_u32("bsize", Opt_blocksize), fsparam_u32("rasize", Opt_rasize), @@ -174,6 +175,7 @@ const struct fs_parameter_spec smb3_fs_parameters[] = { fsparam_string("vers", Opt_vers), fsparam_string("sec", Opt_sec), fsparam_string("cache", Opt_cache), + fsparam_string("reparse", Opt_reparse), /* Arguments that should be ignored */ fsparam_flag("guest", Opt_ignore), @@ -296,6 +298,35 @@ cifs_parse_cache_flavor(struct fs_context *fc, char *value, struct smb3_fs_conte return 0; } +static const match_table_t reparse_flavor_tokens = { + { Opt_reparse_default, "default" }, + { Opt_reparse_nfs, "nfs" }, + { Opt_reparse_wsl, "wsl" }, + { Opt_reparse_err, NULL }, +}; + +static int parse_reparse_flavor(struct fs_context *fc, char *value, + struct smb3_fs_context *ctx) +{ + substring_t args[MAX_OPT_ARGS]; + + switch (match_token(value, reparse_flavor_tokens, args)) { + case Opt_reparse_default: + ctx->reparse_type = CIFS_REPARSE_TYPE_DEFAULT; + break; + case Opt_reparse_nfs: + ctx->reparse_type = CIFS_REPARSE_TYPE_NFS; + break; + case Opt_reparse_wsl: + ctx->reparse_type = CIFS_REPARSE_TYPE_WSL; + break; + default: + cifs_errorf(fc, "bad reparse= option: %s\n", value); + return 1; + } + return 0; +} + #define DUP_CTX_STR(field) \ do { \ if (ctx->field) { \ @@ -947,7 +978,7 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, switch (opt) { case Opt_compress: - ctx->compression = UNKNOWN_TYPE; + ctx->compress = true; cifs_dbg(VFS, "SMB3 compression support is experimental\n"); break; @@ -1098,6 +1129,9 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, case Opt_min_enc_offload: ctx->min_offload = result.uint_32; break; + case Opt_retrans: + ctx->retrans = result.uint_32; + break; case Opt_blocksize: /* * inode blocksize realistically should never need to be @@ -1591,6 +1625,10 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, case Opt_rdma: ctx->rdma = true; break; + case Opt_reparse: + if (parse_reparse_flavor(fc, param->string, ctx)) + goto cifs_parse_mount_err; + break; } /* case Opt_ignore: - is ignored as expected ... */ @@ -1678,6 +1716,9 @@ int smb3_init_fs_context(struct fs_context *fc) ctx->backupuid_specified = false; /* no backup intent for a user */ ctx->backupgid_specified = false; /* no backup intent for a group */ + ctx->retrans = 1; + ctx->reparse_type = CIFS_REPARSE_TYPE_DEFAULT; + /* * short int override_uid = -1; * short int override_gid = -1; diff --git a/fs/smb/client/fs_context.h b/fs/smb/client/fs_context.h index d7c090dbe75d..cf577ec0dd0a 100644 --- a/fs/smb/client/fs_context.h +++ b/fs/smb/client/fs_context.h @@ -41,6 +41,13 @@ enum { Opt_cache_err }; +enum cifs_reparse_parm { + Opt_reparse_default, + Opt_reparse_nfs, + Opt_reparse_wsl, + Opt_reparse_err +}; + enum cifs_sec_param { Opt_sec_krb5, Opt_sec_krb5i, @@ -118,6 +125,7 @@ enum cifs_param { Opt_file_mode, Opt_dirmode, Opt_min_enc_offload, + Opt_retrans, Opt_blocksize, Opt_rasize, Opt_rsize, @@ -148,6 +156,7 @@ enum cifs_param { Opt_vers, Opt_sec, Opt_cache, + Opt_reparse, /* Mount options to be ignored */ Opt_ignore, @@ -249,6 +258,7 @@ struct smb3_fs_context { unsigned int rsize; unsigned int wsize; unsigned int min_offload; + unsigned int retrans; bool sockopt_tcp_nodelay:1; /* attribute cache timemout for files and directories in jiffies */ unsigned long acregmax; @@ -267,12 +277,13 @@ struct smb3_fs_context { unsigned int max_credits; /* smb3 max_credits 10 < credits < 60000 */ unsigned int max_channels; unsigned int max_cached_dirs; - __u16 compression; /* compression algorithm 0xFFFF default 0=disabled */ + bool compress; /* enable SMB2 messages (READ/WRITE) de/compression */ bool rootfs:1; /* if it's a SMB root file system */ bool witness:1; /* use witness protocol */ char *leaf_fullpath; struct cifs_ses *dfs_root_ses; bool dfs_automount:1; /* set for dfs automount only */ + enum cifs_reparse_type reparse_type; }; extern const struct fs_parameter_spec smb3_fs_parameters[]; diff --git a/fs/smb/client/fscache.c b/fs/smb/client/fscache.c index ecabc4b40053..98c5eebdc7b2 100644 --- a/fs/smb/client/fscache.c +++ b/fs/smb/client/fscache.c @@ -94,6 +94,11 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon) } pr_err("Cache volume key already in use (%s)\n", key); vcookie = NULL; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_fscache_collision); + } else { + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_fscache_okay); } tcon->fscache = vcookie; @@ -115,6 +120,8 @@ void cifs_fscache_release_super_cookie(struct cifs_tcon *tcon) cifs_fscache_fill_volume_coherency(tcon, &cd); fscache_relinquish_volume(tcon->fscache, &cd, false); tcon->fscache = NULL; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_fscache_relinq); } void cifs_fscache_get_inode_cookie(struct inode *inode) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index fa6330d586e8..9cdbc3ccc1d1 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -26,6 +26,7 @@ #include "fs_context.h" #include "cifs_ioctl.h" #include "cached_dir.h" +#include "reparse.h" static void cifs_set_ops(struct inode *inode) { @@ -400,7 +401,6 @@ cifs_get_file_info_unix(struct file *filp) cifs_unix_basic_to_fattr(&fattr, &find_data, cifs_sb); } else if (rc == -EREMOTE) { cifs_create_junction_fattr(&fattr, inode->i_sb); - rc = 0; } else goto cifs_gfiunix_out; @@ -666,8 +666,6 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path, /* Fill a cifs_fattr struct with info from POSIX info struct */ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr, struct cifs_open_info_data *data, - struct cifs_sid *owner, - struct cifs_sid *group, struct super_block *sb) { struct smb311_posix_qinfo *info = &data->posix_fi; @@ -693,114 +691,43 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr, fattr->cf_mtime.tv_sec += tcon->ses->server->timeAdj; } + /* + * The srv fs device id is overridden on network mount so setting + * @fattr->cf_rdev isn't needed here. + */ fattr->cf_eof = le64_to_cpu(info->EndOfFile); fattr->cf_bytes = le64_to_cpu(info->AllocationSize); fattr->cf_createtime = le64_to_cpu(info->CreationTime); - fattr->cf_nlink = le32_to_cpu(info->HardLinks); fattr->cf_mode = (umode_t) le32_to_cpu(info->Mode); - /* The srv fs device id is overridden on network mount so setting rdev isn't needed here */ - /* fattr->cf_rdev = le32_to_cpu(info->DeviceId); */ - if (data->symlink) { - fattr->cf_mode |= S_IFLNK; - fattr->cf_dtype = DT_LNK; - fattr->cf_symlink_target = data->symlink_target; - data->symlink_target = NULL; - } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) { + if (cifs_open_data_reparse(data) && + cifs_reparse_point_to_fattr(cifs_sb, fattr, data)) + goto out_reparse; + + fattr->cf_mode &= ~S_IFMT; + if (fattr->cf_cifsattrs & ATTR_DIRECTORY) { fattr->cf_mode |= S_IFDIR; fattr->cf_dtype = DT_DIR; } else { /* file */ fattr->cf_mode |= S_IFREG; fattr->cf_dtype = DT_REG; } - /* else if reparse point ... TODO: add support for FIFO and blk dev; special file types */ - sid_to_id(cifs_sb, owner, fattr, SIDOWNER); - sid_to_id(cifs_sb, group, fattr, SIDGROUP); +out_reparse: + if (S_ISLNK(fattr->cf_mode)) { + if (likely(data->symlink_target)) + fattr->cf_eof = strnlen(data->symlink_target, PATH_MAX); + fattr->cf_symlink_target = data->symlink_target; + data->symlink_target = NULL; + } + sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER); + sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP); cifs_dbg(FYI, "POSIX query info: mode 0x%x uniqueid 0x%llx nlink %d\n", fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink); } -static inline dev_t nfs_mkdev(struct reparse_posix_data *buf) -{ - u64 v = le64_to_cpu(*(__le64 *)buf->DataBuffer); - - return MKDEV(v >> 32, v & 0xffffffff); -} - -bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, - struct cifs_fattr *fattr, - struct cifs_open_info_data *data) -{ - struct reparse_posix_data *buf = data->reparse.posix; - u32 tag = data->reparse.tag; - - if (tag == IO_REPARSE_TAG_NFS && buf) { - switch (le64_to_cpu(buf->InodeType)) { - case NFS_SPECFILE_CHR: - fattr->cf_mode |= S_IFCHR | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_CHR; - fattr->cf_rdev = nfs_mkdev(buf); - break; - case NFS_SPECFILE_BLK: - fattr->cf_mode |= S_IFBLK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_BLK; - fattr->cf_rdev = nfs_mkdev(buf); - break; - case NFS_SPECFILE_FIFO: - fattr->cf_mode |= S_IFIFO | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_FIFO; - break; - case NFS_SPECFILE_SOCK: - fattr->cf_mode |= S_IFSOCK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_SOCK; - break; - case NFS_SPECFILE_LNK: - fattr->cf_mode = S_IFLNK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_LNK; - break; - default: - WARN_ON_ONCE(1); - return false; - } - return true; - } - - switch (tag) { - case IO_REPARSE_TAG_LX_SYMLINK: - fattr->cf_mode |= S_IFLNK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_LNK; - break; - case IO_REPARSE_TAG_LX_FIFO: - fattr->cf_mode |= S_IFIFO | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_FIFO; - break; - case IO_REPARSE_TAG_AF_UNIX: - fattr->cf_mode |= S_IFSOCK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_SOCK; - break; - case IO_REPARSE_TAG_LX_CHR: - fattr->cf_mode |= S_IFCHR | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_CHR; - break; - case IO_REPARSE_TAG_LX_BLK: - fattr->cf_mode |= S_IFBLK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_BLK; - break; - case 0: /* SMB1 symlink */ - case IO_REPARSE_TAG_SYMLINK: - case IO_REPARSE_TAG_NFS: - fattr->cf_mode = S_IFLNK | cifs_sb->ctx->file_mode; - fattr->cf_dtype = DT_LNK; - break; - default: - return false; - } - return true; -} - static void cifs_open_info_to_fattr(struct cifs_fattr *fattr, struct cifs_open_info_data *data, struct super_block *sb) @@ -831,7 +758,10 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr, fattr->cf_bytes = le64_to_cpu(info->AllocationSize); fattr->cf_createtime = le64_to_cpu(info->CreationTime); fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks); + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + fattr->cf_mode = cifs_sb->ctx->file_mode; if (cifs_open_data_reparse(data) && cifs_reparse_point_to_fattr(cifs_sb, fattr, data)) goto out_reparse; @@ -872,9 +802,6 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr, fattr->cf_symlink_target = data->symlink_target; data->symlink_target = NULL; } - - fattr->cf_uid = cifs_sb->ctx->linux_uid; - fattr->cf_gid = cifs_sb->ctx->linux_gid; } static int @@ -888,9 +815,14 @@ cifs_get_file_info(struct file *filp) struct cifsFileInfo *cfile = filp->private_data; struct cifs_tcon *tcon = tlink_tcon(cfile->tlink); struct TCP_Server_Info *server = tcon->ses->server; + struct dentry *dentry = filp->f_path.dentry; + void *page = alloc_dentry_path(); + const unsigned char *path; - if (!server->ops->query_file_info) + if (!server->ops->query_file_info) { + free_dentry_path(page); return -ENOSYS; + } xid = get_xid(); rc = server->ops->query_file_info(xid, tcon, cfile, &data); @@ -902,11 +834,17 @@ cifs_get_file_info(struct file *filp) data.symlink = true; data.reparse.tag = IO_REPARSE_TAG_SYMLINK; } + path = build_path_from_dentry(dentry, page); + if (IS_ERR(path)) { + rc = PTR_ERR(path); + goto cgfi_exit; + } cifs_open_info_to_fattr(&fattr, &data, inode->i_sb); + if (fattr.cf_flags & CIFS_FATTR_DELETE_PENDING) + cifs_mark_open_handles_for_deleted_file(inode, path); break; case -EREMOTE: cifs_create_junction_fattr(&fattr, inode->i_sb); - rc = 0; break; case -EOPNOTSUPP: case -EINVAL: @@ -932,6 +870,7 @@ cifs_get_file_info(struct file *filp) rc = cifs_fattr_to_inode(inode, &fattr, false); cgfi_exit: cifs_free_open_info(&data); + free_dentry_path(page); free_xid(xid); return rc; } @@ -1078,6 +1017,9 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, &rsp_iov, &rsp_buftype); if (!rc) iov = &rsp_iov; + } else if (data->reparse.io.buftype != CIFS_NO_BUFFER && + data->reparse.io.iov.iov_base) { + iov = &data->reparse.io.iov; } rc = -EOPNOTSUPP; @@ -1094,16 +1036,20 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, rc = 0; goto out; default: - if (data->symlink_target) { + /* Check for cached reparse point data */ + if (data->symlink_target || data->reparse.buf) { rc = 0; - } else if (server->ops->parse_reparse_point) { + } else if (iov && server->ops->parse_reparse_point) { rc = server->ops->parse_reparse_point(cifs_sb, iov, data); } break; } - cifs_open_info_to_fattr(fattr, data, sb); + if (tcon->posix_extensions) + smb311_posix_info_to_fattr(fattr, data, sb); + else + cifs_open_info_to_fattr(fattr, data, sb); out: fattr->cf_cifstag = data->reparse.tag; free_rsp_buf(rsp_buftype, rsp_iov.iov_base); @@ -1159,6 +1105,9 @@ static int cifs_get_fattr(struct cifs_open_info_data *data, } else { cifs_open_info_to_fattr(fattr, data, sb); } + if (!rc && *inode && + (fattr->cf_flags & CIFS_FATTR_DELETE_PENDING)) + cifs_mark_open_handles_for_deleted_file(*inode, full_path); break; case -EREMOTE: /* DFS link, no metadata available on this server */ @@ -1293,31 +1242,34 @@ int cifs_get_inode_info(struct inode **inode, return rc; } -static int smb311_posix_get_fattr(struct cifs_fattr *fattr, +static int smb311_posix_get_fattr(struct cifs_open_info_data *data, + struct cifs_fattr *fattr, const char *full_path, struct super_block *sb, const unsigned int xid) { - struct cifs_open_info_data data = {}; + struct cifs_open_info_data tmp_data = {}; + struct TCP_Server_Info *server; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifs_tcon *tcon; struct tcon_link *tlink; - struct cifs_sid owner, group; int tmprc; - int rc; + int rc = 0; tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) return PTR_ERR(tlink); tcon = tlink_tcon(tlink); + server = tcon->ses->server; /* - * 1. Fetch file metadata + * 1. Fetch file metadata if not provided (data) */ - - rc = smb311_posix_query_path_info(xid, tcon, cifs_sb, - full_path, &data, - &owner, &group); + if (!data) { + rc = server->ops->query_path_info(xid, tcon, cifs_sb, + full_path, &tmp_data); + data = &tmp_data; + } /* * 2. Convert it to internal cifs metadata (fattr) @@ -1325,7 +1277,12 @@ static int smb311_posix_get_fattr(struct cifs_fattr *fattr, switch (rc) { case 0: - smb311_posix_info_to_fattr(fattr, &data, &owner, &group, sb); + if (cifs_open_data_reparse(data)) { + rc = reparse_info_to_fattr(data, sb, xid, tcon, + full_path, fattr); + } else { + smb311_posix_info_to_fattr(fattr, data, sb); + } break; case -EREMOTE: /* DFS link, no metadata available on this server */ @@ -1356,12 +1313,15 @@ static int smb311_posix_get_fattr(struct cifs_fattr *fattr, out: cifs_put_tlink(tlink); - cifs_free_open_info(&data); + cifs_free_open_info(data); return rc; } -int smb311_posix_get_inode_info(struct inode **inode, const char *full_path, - struct super_block *sb, const unsigned int xid) +int smb311_posix_get_inode_info(struct inode **inode, + const char *full_path, + struct cifs_open_info_data *data, + struct super_block *sb, + const unsigned int xid) { struct cifs_fattr fattr = {}; int rc; @@ -1371,11 +1331,13 @@ int smb311_posix_get_inode_info(struct inode **inode, const char *full_path, return 0; } - rc = smb311_posix_get_fattr(&fattr, full_path, sb, xid); + rc = smb311_posix_get_fattr(data, &fattr, full_path, sb, xid); if (rc) goto out; rc = update_inode_info(sb, &fattr, inode); + if (!rc && fattr.cf_flags & CIFS_FATTR_DELETE_PENDING) + cifs_mark_open_handles_for_deleted_file(*inode, full_path); out: kfree(fattr.cf_symlink_target); return rc; @@ -1521,7 +1483,7 @@ struct inode *cifs_root_iget(struct super_block *sb) convert_delimiter(path, CIFS_DIR_SEP(cifs_sb)); if (tcon->posix_extensions) - rc = smb311_posix_get_fattr(&fattr, path, sb, xid); + rc = smb311_posix_get_fattr(NULL, &fattr, path, sb, xid); else rc = cifs_get_fattr(NULL, sb, xid, NULL, &fattr, &inode, path); @@ -1539,6 +1501,9 @@ struct inode *cifs_root_iget(struct super_block *sb) goto out; } + if (!rc && fattr.cf_flags & CIFS_FATTR_DELETE_PENDING) + cifs_mark_open_handles_for_deleted_file(inode, path); + if (rc && tcon->pipe) { cifs_dbg(FYI, "ipc connection - fake read inode\n"); spin_lock(&inode->i_lock); @@ -1825,20 +1790,24 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry) goto psx_del_no_retry; } - rc = server->ops->unlink(xid, tcon, full_path, cifs_sb); + rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry); psx_del_no_retry: if (!rc) { - if (inode) + if (inode) { + cifs_mark_open_handles_for_deleted_file(inode, full_path); cifs_drop_nlink(inode); + } } else if (rc == -ENOENT) { d_drop(dentry); } else if (rc == -EBUSY) { if (server->ops->rename_pending_delete) { rc = server->ops->rename_pending_delete(full_path, dentry, xid); - if (rc == 0) + if (rc == 0) { + cifs_mark_open_handles_for_deleted_file(inode, full_path); cifs_drop_nlink(inode); + } } } else if ((rc == -EACCES) && (dosattr == 0) && inode) { attrs = kzalloc(sizeof(*attrs), GFP_KERNEL); @@ -1894,16 +1863,18 @@ cifs_mkdir_qinfo(struct inode *parent, struct dentry *dentry, umode_t mode, int rc = 0; struct inode *inode = NULL; - if (tcon->posix_extensions) - rc = smb311_posix_get_inode_info(&inode, full_path, parent->i_sb, xid); + if (tcon->posix_extensions) { + rc = smb311_posix_get_inode_info(&inode, full_path, + NULL, parent->i_sb, xid); #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY - else if (tcon->unix_ext) + } else if (tcon->unix_ext) { rc = cifs_get_inode_info_unix(&inode, full_path, parent->i_sb, xid); #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ - else + } else { rc = cifs_get_inode_info(&inode, full_path, NULL, parent->i_sb, xid, NULL); + } if (rc) return rc; @@ -2585,13 +2556,15 @@ int cifs_revalidate_dentry_attr(struct dentry *dentry) dentry, cifs_get_time(dentry), jiffies); again: - if (cifs_sb_master_tcon(CIFS_SB(sb))->posix_extensions) - rc = smb311_posix_get_inode_info(&inode, full_path, sb, xid); - else if (cifs_sb_master_tcon(CIFS_SB(sb))->unix_ext) + if (cifs_sb_master_tcon(CIFS_SB(sb))->posix_extensions) { + rc = smb311_posix_get_inode_info(&inode, full_path, + NULL, sb, xid); + } else if (cifs_sb_master_tcon(CIFS_SB(sb))->unix_ext) { rc = cifs_get_inode_info_unix(&inode, full_path, sb, xid); - else + } else { rc = cifs_get_inode_info(&inode, full_path, NULL, sb, xid, NULL); + } if (rc == -EAGAIN && count++ < 10) goto again; out: @@ -2772,7 +2745,7 @@ void cifs_setsize(struct inode *inode, loff_t offset) static int cifs_set_file_size(struct inode *inode, struct iattr *attrs, - unsigned int xid, const char *full_path) + unsigned int xid, const char *full_path, struct dentry *dentry) { int rc; struct cifsFileInfo *open_file; @@ -2823,7 +2796,7 @@ cifs_set_file_size(struct inode *inode, struct iattr *attrs, */ if (server->ops->set_path_size) rc = server->ops->set_path_size(xid, tcon, full_path, - attrs->ia_size, cifs_sb, false); + attrs->ia_size, cifs_sb, false, dentry); else rc = -ENOSYS; cifs_dbg(FYI, "SetEOF by path (setattrs) rc = %d\n", rc); @@ -2913,7 +2886,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) rc = 0; if (attrs->ia_valid & ATTR_SIZE) { - rc = cifs_set_file_size(inode, attrs, xid, full_path); + rc = cifs_set_file_size(inode, attrs, xid, full_path, direntry); if (rc != 0) goto out; } @@ -3079,7 +3052,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) } if (attrs->ia_valid & ATTR_SIZE) { - rc = cifs_set_file_size(inode, attrs, xid, full_path); + rc = cifs_set_file_size(inode, attrs, xid, full_path, direntry); if (rc != 0) goto cifs_setattr_exit; } diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c index 204dd7c47126..855ac5a62edf 100644 --- a/fs/smb/client/ioctl.c +++ b/fs/smb/client/ioctl.c @@ -143,6 +143,7 @@ static long smb_mnt_get_fsinfo(unsigned int xid, struct cifs_tcon *tcon, fsinf->version = 1; fsinf->protocol_id = tcon->ses->server->vals->protocol_id; + fsinf->tcon_flags = tcon->Flags; fsinf->device_characteristics = le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics); fsinf->device_type = le32_to_cpu(tcon->fsDevInfo.DeviceType); @@ -348,6 +349,11 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) xid = get_xid(); cifs_dbg(FYI, "cifs ioctl 0x%x\n", command); + if (pSMBFile == NULL) + trace_smb3_ioctl(xid, 0, command); + else + trace_smb3_ioctl(xid, pSMBFile->fid.persistent_fid, command); + switch (command) { case FS_IOC_GETFLAGS: if (pSMBFile == NULL) diff --git a/fs/smb/client/link.c b/fs/smb/client/link.c index 6c4ae52ddc04..d86da949a919 100644 --- a/fs/smb/client/link.c +++ b/fs/smb/client/link.c @@ -42,23 +42,11 @@ symlink_hash(unsigned int link_len, const char *link_str, u8 *md5_hash) rc = cifs_alloc_hash("md5", &md5); if (rc) - goto symlink_hash_err; + return rc; - rc = crypto_shash_init(md5); - if (rc) { - cifs_dbg(VFS, "%s: Could not init md5 shash\n", __func__); - goto symlink_hash_err; - } - rc = crypto_shash_update(md5, link_str, link_len); - if (rc) { - cifs_dbg(VFS, "%s: Could not update with link_str\n", __func__); - goto symlink_hash_err; - } - rc = crypto_shash_final(md5, md5_hash); + rc = crypto_shash_digest(md5, link_str, link_len, md5_hash); if (rc) cifs_dbg(VFS, "%s: Could not generate md5 hash\n", __func__); - -symlink_hash_err: cifs_free_hash(&md5); return rc; } @@ -581,6 +569,7 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode, int rc = -EOPNOTSUPP; unsigned int xid; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + struct TCP_Server_Info *server; struct tcon_link *tlink; struct cifs_tcon *pTcon; const char *full_path; @@ -602,6 +591,7 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode, goto symlink_exit; } pTcon = tlink_tcon(tlink); + server = cifs_pick_channel(pTcon->ses); full_path = build_path_from_dentry(direntry, page); if (IS_ERR(full_path)) { @@ -613,27 +603,32 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode, cifs_dbg(FYI, "symname is %s\n", symname); /* BB what if DFS and this volume is on different share? BB */ - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) { rc = create_mf_symlink(xid, pTcon, cifs_sb, full_path, symname); #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY - else if (pTcon->unix_ext) + } else if (pTcon->unix_ext) { rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname, cifs_sb->local_nls, cifs_remap(cifs_sb)); #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ - /* else - rc = CIFSCreateReparseSymLink(xid, pTcon, fromName, toName, - cifs_sb_target->local_nls); */ + } else if (server->ops->create_reparse_symlink) { + rc = server->ops->create_reparse_symlink(xid, inode, direntry, + pTcon, full_path, + symname); + goto symlink_exit; + } if (rc == 0) { - if (pTcon->posix_extensions) - rc = smb311_posix_get_inode_info(&newinode, full_path, inode->i_sb, xid); - else if (pTcon->unix_ext) + if (pTcon->posix_extensions) { + rc = smb311_posix_get_inode_info(&newinode, full_path, + NULL, inode->i_sb, xid); + } else if (pTcon->unix_ext) { rc = cifs_get_inode_info_unix(&newinode, full_path, inode->i_sb, xid); - else + } else { rc = cifs_get_inode_info(&newinode, full_path, NULL, inode->i_sb, xid, NULL); + } if (rc != 0) { cifs_dbg(FYI, "Create symlink ok, getinodeinfo fail rc = %d\n", diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index d56959d02e36..07c468ddb88a 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -27,9 +27,6 @@ #include "fs_context.h" #include "cached_dir.h" -extern mempool_t *cifs_sm_req_poolp; -extern mempool_t *cifs_req_poolp; - /* The xid serves as a useful identifier for each incoming vfs request, in a similar way to the mid which is useful to track each sent smb, and CurrentXid can also provide a running counter (although it @@ -114,9 +111,10 @@ sesInfoFree(struct cifs_ses *buf_to_free) } struct cifs_tcon * -tcon_info_alloc(bool dir_leases_enabled) +tcon_info_alloc(bool dir_leases_enabled, enum smb3_tcon_ref_trace trace) { struct cifs_tcon *ret_buf; + static atomic_t tcon_debug_id; ret_buf = kzalloc(sizeof(*ret_buf), GFP_KERNEL); if (!ret_buf) @@ -133,7 +131,8 @@ tcon_info_alloc(bool dir_leases_enabled) atomic_inc(&tconInfoAllocCount); ret_buf->status = TID_NEW; - ++ret_buf->tc_count; + ret_buf->debug_id = atomic_inc_return(&tcon_debug_id); + ret_buf->tc_count = 1; spin_lock_init(&ret_buf->tc_lock); INIT_LIST_HEAD(&ret_buf->openFileList); INIT_LIST_HEAD(&ret_buf->tcon_list); @@ -145,17 +144,19 @@ tcon_info_alloc(bool dir_leases_enabled) #ifdef CONFIG_CIFS_FSCACHE mutex_init(&ret_buf->fscache_lock); #endif + trace_smb3_tcon_ref(ret_buf->debug_id, ret_buf->tc_count, trace); return ret_buf; } void -tconInfoFree(struct cifs_tcon *tcon) +tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace) { if (tcon == NULL) { cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n"); return; } + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, trace); free_cached_dirs(tcon->cfids); atomic_dec(&tconInfoAllocCount); kfree(tcon->nativeFileSystem); @@ -853,6 +854,40 @@ cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon, const char *path) free_dentry_path(page); } +/* + * If a dentry has been deleted, all corresponding open handles should know that + * so that we do not defer close them. + */ +void cifs_mark_open_handles_for_deleted_file(struct inode *inode, + const char *path) +{ + struct cifsFileInfo *cfile; + void *page; + const char *full_path; + struct cifsInodeInfo *cinode = CIFS_I(inode); + + page = alloc_dentry_path(); + spin_lock(&cinode->open_file_lock); + + /* + * note: we need to construct path from dentry and compare only if the + * inode has any hardlinks. When number of hardlinks is 1, we can just + * mark all open handles since they are going to be from the same file. + */ + if (inode->i_nlink > 1) { + list_for_each_entry(cfile, &cinode->openFileList, flist) { + full_path = build_path_from_dentry(cfile->dentry, page); + if (!IS_ERR(full_path) && strcmp(full_path, path) == 0) + cfile->status_file_deleted = true; + } + } else { + list_for_each_entry(cfile, &cinode->openFileList, flist) + cfile->status_file_deleted = true; + } + spin_unlock(&cinode->open_file_lock); + free_dentry_path(page); +} + /* parses DFS referral V3 structure * caller is responsible for freeing target_nodes * returns: diff --git a/fs/smb/client/ntlmssp.h b/fs/smb/client/ntlmssp.h index 2c5dde2ece58..875de43b72de 100644 --- a/fs/smb/client/ntlmssp.h +++ b/fs/smb/client/ntlmssp.h @@ -133,8 +133,8 @@ typedef struct _AUTHENTICATE_MESSAGE { SECURITY_BUFFER WorkstationName; SECURITY_BUFFER SessionKey; __le32 NegotiateFlags; - /* SECURITY_BUFFER for version info not present since we - do not set the version is present flag */ + struct ntlmssp_version Version; + /* SECURITY_BUFFER */ char UserString[]; } __attribute__((packed)) AUTHENTICATE_MESSAGE, *PAUTHENTICATE_MESSAGE; diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 56033e4e4bae..06111d9f3950 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -22,6 +22,7 @@ #include "smb2proto.h" #include "fs_context.h" #include "cached_dir.h" +#include "reparse.h" /* * To be safe - for UCS to UTF-8 with strings loaded with the rare long @@ -55,23 +56,6 @@ static inline void dump_cifs_file_struct(struct file *file, char *label) } #endif /* DEBUG2 */ -/* - * Match a reparse point inode if reparse tag and ctime haven't changed. - * - * Windows Server updates ctime of reparse points when their data have changed. - * The server doesn't allow changing reparse tags from existing reparse points, - * though it's worth checking. - */ -static inline bool reparse_inode_match(struct inode *inode, - struct cifs_fattr *fattr) -{ - struct timespec64 ctime = inode_get_ctime(inode); - - return (CIFS_I(inode)->cifsAttrs & ATTR_REPARSE) && - CIFS_I(inode)->reparse_tag == fattr->cf_cifstag && - timespec64_equal(&ctime, &fattr->cf_ctime); -} - /* * Attempt to preload the dcache with the results from the FIND_FIRST/NEXT * @@ -133,14 +117,16 @@ cifs_prime_dcache(struct dentry *parent, struct qstr *name, * Query dir responses don't provide enough * information about reparse points other than * their reparse tags. Save an invalidation by - * not clobbering the existing mode, size and - * symlink target (if any) when reparse tag and - * ctime haven't changed. + * not clobbering some existing attributes when + * reparse tag and ctime haven't changed. */ rc = 0; if (fattr->cf_cifsattrs & ATTR_REPARSE) { if (likely(reparse_inode_match(inode, fattr))) { fattr->cf_mode = inode->i_mode; + fattr->cf_rdev = inode->i_rdev; + fattr->cf_uid = inode->i_uid; + fattr->cf_gid = inode->i_gid; fattr->cf_eof = CIFS_I(inode)->server_eof; fattr->cf_symlink_target = NULL; } else { @@ -647,10 +633,10 @@ static int cifs_entry_is_dot(struct cifs_dirent *de, bool is_unicode) static int is_dir_changed(struct file *file) { struct inode *inode = file_inode(file); - struct cifsInodeInfo *cifsInfo = CIFS_I(inode); + struct cifsInodeInfo *cifs_inode_info = CIFS_I(inode); - if (cifsInfo->time == 0) - return 1; /* directory was changed, perhaps due to unlink */ + if (cifs_inode_info->time == 0) + return 1; /* directory was changed, e.g. unlink or new file */ else return 0; diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c new file mode 100644 index 000000000000..a0ffbda90733 --- /dev/null +++ b/fs/smb/client/reparse.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Paulo Alcantara <pc@xxxxxxxxxxxxx> + */ + +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/slab.h> +#include "cifsglob.h" +#include "smb2proto.h" +#include "cifsproto.h" +#include "cifs_unicode.h" +#include "cifs_debug.h" +#include "fs_context.h" +#include "reparse.h" + +int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, const char *symname) +{ + struct reparse_symlink_data_buffer *buf = NULL; + struct cifs_open_info_data data; + struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + struct inode *new; + struct kvec iov; + __le16 *path; + char *sym, sep = CIFS_DIR_SEP(cifs_sb); + u16 len, plen; + int rc = 0; + + sym = kstrdup(symname, GFP_KERNEL); + if (!sym) + return -ENOMEM; + + data = (struct cifs_open_info_data) { + .reparse_point = true, + .reparse = { .tag = IO_REPARSE_TAG_SYMLINK, }, + .symlink_target = sym, + }; + + convert_delimiter(sym, sep); + path = cifs_convert_path_to_utf16(sym, cifs_sb); + if (!path) { + rc = -ENOMEM; + goto out; + } + + plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX); + len = sizeof(*buf) + plen * 2; + buf = kzalloc(len, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto out; + } + + buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_SYMLINK); + buf->ReparseDataLength = cpu_to_le16(len - sizeof(struct reparse_data_buffer)); + buf->SubstituteNameOffset = cpu_to_le16(plen); + buf->SubstituteNameLength = cpu_to_le16(plen); + memcpy(&buf->PathBuffer[plen], path, plen); + buf->PrintNameOffset = 0; + buf->PrintNameLength = cpu_to_le16(plen); + memcpy(buf->PathBuffer, path, plen); + buf->Flags = cpu_to_le32(*symname != '/' ? SYMLINK_FLAG_RELATIVE : 0); + if (*sym != sep) + buf->Flags = cpu_to_le32(SYMLINK_FLAG_RELATIVE); + + convert_delimiter(sym, '/'); + iov.iov_base = buf; + iov.iov_len = len; + new = smb2_get_reparse_inode(&data, inode->i_sb, xid, + tcon, full_path, &iov, NULL); + if (!IS_ERR(new)) + d_instantiate(dentry, new); + else + rc = PTR_ERR(new); +out: + kfree(path); + cifs_free_open_info(&data); + kfree(buf); + return rc; +} + +static int nfs_set_reparse_buf(struct reparse_posix_data *buf, + mode_t mode, dev_t dev, + struct kvec *iov) +{ + u64 type; + u16 len, dlen; + + len = sizeof(*buf); + + switch ((type = reparse_mode_nfs_type(mode))) { + case NFS_SPECFILE_BLK: + case NFS_SPECFILE_CHR: + dlen = sizeof(__le64); + break; + case NFS_SPECFILE_FIFO: + case NFS_SPECFILE_SOCK: + dlen = 0; + break; + default: + return -EOPNOTSUPP; + } + + buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_NFS); + buf->Reserved = 0; + buf->InodeType = cpu_to_le64(type); + buf->ReparseDataLength = cpu_to_le16(len + dlen - + sizeof(struct reparse_data_buffer)); + *(__le64 *)buf->DataBuffer = cpu_to_le64(((u64)MAJOR(dev) << 32) | + MINOR(dev)); + iov->iov_base = buf; + iov->iov_len = len + dlen; + return 0; +} + +static int mknod_nfs(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev) +{ + struct cifs_open_info_data data; + struct reparse_posix_data *p; + struct inode *new; + struct kvec iov; + __u8 buf[sizeof(*p) + sizeof(__le64)]; + int rc; + + p = (struct reparse_posix_data *)buf; + rc = nfs_set_reparse_buf(p, mode, dev, &iov); + if (rc) + return rc; + + data = (struct cifs_open_info_data) { + .reparse_point = true, + .reparse = { .tag = IO_REPARSE_TAG_NFS, .posix = p, }, + }; + + new = smb2_get_reparse_inode(&data, inode->i_sb, xid, + tcon, full_path, &iov, NULL); + if (!IS_ERR(new)) + d_instantiate(dentry, new); + else + rc = PTR_ERR(new); + cifs_free_open_info(&data); + return rc; +} + +static int wsl_set_reparse_buf(struct reparse_data_buffer *buf, + mode_t mode, struct kvec *iov) +{ + u32 tag; + + switch ((tag = reparse_mode_wsl_tag(mode))) { + case IO_REPARSE_TAG_LX_BLK: + case IO_REPARSE_TAG_LX_CHR: + case IO_REPARSE_TAG_LX_FIFO: + case IO_REPARSE_TAG_AF_UNIX: + break; + default: + return -EOPNOTSUPP; + } + + buf->ReparseTag = cpu_to_le32(tag); + buf->Reserved = 0; + buf->ReparseDataLength = 0; + iov->iov_base = buf; + iov->iov_len = sizeof(*buf); + return 0; +} + +static struct smb2_create_ea_ctx *ea_create_context(u32 dlen, size_t *cc_len) +{ + struct smb2_create_ea_ctx *cc; + + *cc_len = round_up(sizeof(*cc) + dlen, 8); + cc = kzalloc(*cc_len, GFP_KERNEL); + if (!cc) + return ERR_PTR(-ENOMEM); + + cc->ctx.NameOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx, + name)); + cc->ctx.NameLength = cpu_to_le16(4); + memcpy(cc->name, SMB2_CREATE_EA_BUFFER, strlen(SMB2_CREATE_EA_BUFFER)); + cc->ctx.DataOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx, ea)); + cc->ctx.DataLength = cpu_to_le32(dlen); + return cc; +} + +struct wsl_xattr { + const char *name; + __le64 value; + u16 size; + u32 next; +}; + +static int wsl_set_xattrs(struct inode *inode, umode_t _mode, + dev_t _dev, struct kvec *iov) +{ + struct smb2_file_full_ea_info *ea; + struct smb2_create_ea_ctx *cc; + struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx; + __le64 uid = cpu_to_le64(from_kuid(current_user_ns(), ctx->linux_uid)); + __le64 gid = cpu_to_le64(from_kgid(current_user_ns(), ctx->linux_gid)); + __le64 dev = cpu_to_le64(((u64)MINOR(_dev) << 32) | MAJOR(_dev)); + __le64 mode = cpu_to_le64(_mode); + struct wsl_xattr xattrs[] = { + { .name = SMB2_WSL_XATTR_UID, .value = uid, .size = SMB2_WSL_XATTR_UID_SIZE, }, + { .name = SMB2_WSL_XATTR_GID, .value = gid, .size = SMB2_WSL_XATTR_GID_SIZE, }, + { .name = SMB2_WSL_XATTR_MODE, .value = mode, .size = SMB2_WSL_XATTR_MODE_SIZE, }, + { .name = SMB2_WSL_XATTR_DEV, .value = dev, .size = SMB2_WSL_XATTR_DEV_SIZE, }, + }; + size_t cc_len; + u32 dlen = 0, next = 0; + int i, num_xattrs; + u8 name_size = SMB2_WSL_XATTR_NAME_LEN + 1; + + memset(iov, 0, sizeof(*iov)); + + /* Exclude $LXDEV xattr for sockets and fifos */ + if (S_ISSOCK(_mode) || S_ISFIFO(_mode)) + num_xattrs = ARRAY_SIZE(xattrs) - 1; + else + num_xattrs = ARRAY_SIZE(xattrs); + + for (i = 0; i < num_xattrs; i++) { + xattrs[i].next = ALIGN(sizeof(*ea) + name_size + + xattrs[i].size, 4); + dlen += xattrs[i].next; + } + + cc = ea_create_context(dlen, &cc_len); + if (IS_ERR(cc)) + return PTR_ERR(cc); + + ea = &cc->ea; + for (i = 0; i < num_xattrs; i++) { + ea = (void *)((u8 *)ea + next); + next = xattrs[i].next; + ea->next_entry_offset = cpu_to_le32(next); + + ea->ea_name_length = name_size - 1; + ea->ea_value_length = cpu_to_le16(xattrs[i].size); + memcpy(ea->ea_data, xattrs[i].name, name_size); + memcpy(&ea->ea_data[name_size], + &xattrs[i].value, xattrs[i].size); + } + ea->next_entry_offset = 0; + + iov->iov_base = cc; + iov->iov_len = cc_len; + return 0; +} + +static int mknod_wsl(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev) +{ + struct cifs_open_info_data data; + struct reparse_data_buffer buf; + struct smb2_create_ea_ctx *cc; + struct inode *new; + unsigned int len; + struct kvec reparse_iov, xattr_iov; + int rc; + + rc = wsl_set_reparse_buf(&buf, mode, &reparse_iov); + if (rc) + return rc; + + rc = wsl_set_xattrs(inode, mode, dev, &xattr_iov); + if (rc) + return rc; + + data = (struct cifs_open_info_data) { + .reparse_point = true, + .reparse = { .tag = le32_to_cpu(buf.ReparseTag), .buf = &buf, }, + }; + + cc = xattr_iov.iov_base; + len = le32_to_cpu(cc->ctx.DataLength); + memcpy(data.wsl.eas, &cc->ea, len); + data.wsl.eas_len = len; + + new = smb2_get_reparse_inode(&data, inode->i_sb, + xid, tcon, full_path, + &reparse_iov, &xattr_iov); + if (!IS_ERR(new)) + d_instantiate(dentry, new); + else + rc = PTR_ERR(new); + cifs_free_open_info(&data); + kfree(xattr_iov.iov_base); + return rc; +} + +int smb2_mknod_reparse(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev) +{ + struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx; + int rc = -EOPNOTSUPP; + + switch (ctx->reparse_type) { + case CIFS_REPARSE_TYPE_NFS: + rc = mknod_nfs(xid, inode, dentry, tcon, full_path, mode, dev); + break; + case CIFS_REPARSE_TYPE_WSL: + rc = mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev); + break; + } + return rc; +} + +/* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */ +static int parse_reparse_posix(struct reparse_posix_data *buf, + struct cifs_sb_info *cifs_sb, + struct cifs_open_info_data *data) +{ + unsigned int len; + u64 type; + + switch ((type = le64_to_cpu(buf->InodeType))) { + case NFS_SPECFILE_LNK: + len = le16_to_cpu(buf->ReparseDataLength); + data->symlink_target = cifs_strndup_from_utf16(buf->DataBuffer, + len, true, + cifs_sb->local_nls); + if (!data->symlink_target) + return -ENOMEM; + convert_delimiter(data->symlink_target, '/'); + cifs_dbg(FYI, "%s: target path: %s\n", + __func__, data->symlink_target); + break; + case NFS_SPECFILE_CHR: + case NFS_SPECFILE_BLK: + case NFS_SPECFILE_FIFO: + case NFS_SPECFILE_SOCK: + break; + default: + cifs_dbg(VFS, "%s: unhandled inode type: 0x%llx\n", + __func__, type); + return -EOPNOTSUPP; + } + return 0; +} + +static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, + u32 plen, bool unicode, + struct cifs_sb_info *cifs_sb, + struct cifs_open_info_data *data) +{ + unsigned int len; + unsigned int offs; + + /* We handle Symbolic Link reparse tag here. See: MS-FSCC 2.1.2.4 */ + + offs = le16_to_cpu(sym->SubstituteNameOffset); + len = le16_to_cpu(sym->SubstituteNameLength); + if (offs + 20 > plen || offs + len + 20 > plen) { + cifs_dbg(VFS, "srv returned malformed symlink buffer\n"); + return -EIO; + } + + data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs, + len, unicode, + cifs_sb->local_nls); + if (!data->symlink_target) + return -ENOMEM; + + convert_delimiter(data->symlink_target, '/'); + cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target); + + return 0; +} + +int parse_reparse_point(struct reparse_data_buffer *buf, + u32 plen, struct cifs_sb_info *cifs_sb, + bool unicode, struct cifs_open_info_data *data) +{ + data->reparse.buf = buf; + + /* See MS-FSCC 2.1.2 */ + switch (le32_to_cpu(buf->ReparseTag)) { + case IO_REPARSE_TAG_NFS: + return parse_reparse_posix((struct reparse_posix_data *)buf, + cifs_sb, data); + case IO_REPARSE_TAG_SYMLINK: + return parse_reparse_symlink( + (struct reparse_symlink_data_buffer *)buf, + plen, unicode, cifs_sb, data); + case IO_REPARSE_TAG_LX_SYMLINK: + case IO_REPARSE_TAG_AF_UNIX: + case IO_REPARSE_TAG_LX_FIFO: + case IO_REPARSE_TAG_LX_CHR: + case IO_REPARSE_TAG_LX_BLK: + return 0; + default: + cifs_dbg(VFS, "%s: unhandled reparse tag: 0x%08x\n", + __func__, le32_to_cpu(buf->ReparseTag)); + return -EOPNOTSUPP; + } +} + +int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, + struct kvec *rsp_iov, + struct cifs_open_info_data *data) +{ + struct reparse_data_buffer *buf; + struct smb2_ioctl_rsp *io = rsp_iov->iov_base; + u32 plen = le32_to_cpu(io->OutputCount); + + buf = (struct reparse_data_buffer *)((u8 *)io + + le32_to_cpu(io->OutputOffset)); + return parse_reparse_point(buf, plen, cifs_sb, true, data); +} + +static void wsl_to_fattr(struct cifs_open_info_data *data, + struct cifs_sb_info *cifs_sb, + u32 tag, struct cifs_fattr *fattr) +{ + struct smb2_file_full_ea_info *ea; + u32 next = 0; + + switch (tag) { + case IO_REPARSE_TAG_LX_SYMLINK: + fattr->cf_mode |= S_IFLNK; + break; + case IO_REPARSE_TAG_LX_FIFO: + fattr->cf_mode |= S_IFIFO; + break; + case IO_REPARSE_TAG_AF_UNIX: + fattr->cf_mode |= S_IFSOCK; + break; + case IO_REPARSE_TAG_LX_CHR: + fattr->cf_mode |= S_IFCHR; + break; + case IO_REPARSE_TAG_LX_BLK: + fattr->cf_mode |= S_IFBLK; + break; + } + + if (!data->wsl.eas_len) + goto out; + + ea = (struct smb2_file_full_ea_info *)data->wsl.eas; + do { + const char *name; + void *v; + u8 nlen; + + ea = (void *)((u8 *)ea + next); + next = le32_to_cpu(ea->next_entry_offset); + if (!le16_to_cpu(ea->ea_value_length)) + continue; + + name = ea->ea_data; + nlen = ea->ea_name_length; + v = (void *)((u8 *)ea->ea_data + ea->ea_name_length + 1); + + if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) + fattr->cf_uid = wsl_make_kuid(cifs_sb, v); + else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) + fattr->cf_gid = wsl_make_kgid(cifs_sb, v); + else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) + fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v); + else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) + fattr->cf_rdev = wsl_mkdev(v); + } while (next); +out: + fattr->cf_dtype = S_DT(fattr->cf_mode); +} + +bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, + struct cifs_fattr *fattr, + struct cifs_open_info_data *data) +{ + struct reparse_posix_data *buf = data->reparse.posix; + u32 tag = data->reparse.tag; + + if (tag == IO_REPARSE_TAG_NFS && buf) { + switch (le64_to_cpu(buf->InodeType)) { + case NFS_SPECFILE_CHR: + fattr->cf_mode |= S_IFCHR; + fattr->cf_rdev = reparse_nfs_mkdev(buf); + break; + case NFS_SPECFILE_BLK: + fattr->cf_mode |= S_IFBLK; + fattr->cf_rdev = reparse_nfs_mkdev(buf); + break; + case NFS_SPECFILE_FIFO: + fattr->cf_mode |= S_IFIFO; + break; + case NFS_SPECFILE_SOCK: + fattr->cf_mode |= S_IFSOCK; + break; + case NFS_SPECFILE_LNK: + fattr->cf_mode |= S_IFLNK; + break; + default: + WARN_ON_ONCE(1); + return false; + } + goto out; + } + + switch (tag) { + case IO_REPARSE_TAG_DFS: + case IO_REPARSE_TAG_DFSR: + case IO_REPARSE_TAG_MOUNT_POINT: + /* See cifs_create_junction_fattr() */ + fattr->cf_mode = S_IFDIR | 0711; + break; + case IO_REPARSE_TAG_LX_SYMLINK: + case IO_REPARSE_TAG_LX_FIFO: + case IO_REPARSE_TAG_AF_UNIX: + case IO_REPARSE_TAG_LX_CHR: + case IO_REPARSE_TAG_LX_BLK: + wsl_to_fattr(data, cifs_sb, tag, fattr); + break; + case 0: /* SMB1 symlink */ + case IO_REPARSE_TAG_SYMLINK: + case IO_REPARSE_TAG_NFS: + fattr->cf_mode |= S_IFLNK; + break; + default: + return false; + } +out: + fattr->cf_dtype = S_DT(fattr->cf_mode); + return true; +} diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h new file mode 100644 index 000000000000..6b55d1df9e2f --- /dev/null +++ b/fs/smb/client/reparse.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024 Paulo Alcantara <pc@xxxxxxxxxxxxx> + */ + +#ifndef _CIFS_REPARSE_H +#define _CIFS_REPARSE_H + +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/uidgid.h> +#include "fs_context.h" +#include "cifsglob.h" + +static inline dev_t reparse_nfs_mkdev(struct reparse_posix_data *buf) +{ + u64 v = le64_to_cpu(*(__le64 *)buf->DataBuffer); + + return MKDEV(v >> 32, v & 0xffffffff); +} + +static inline dev_t wsl_mkdev(void *ptr) +{ + u64 v = le64_to_cpu(*(__le64 *)ptr); + + return MKDEV(v & 0xffffffff, v >> 32); +} + +static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, + void *ptr) +{ + u32 uid = le32_to_cpu(*(__le32 *)ptr); + + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_OVERR_UID) + return cifs_sb->ctx->linux_uid; + return make_kuid(current_user_ns(), uid); +} + +static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb, + void *ptr) +{ + u32 gid = le32_to_cpu(*(__le32 *)ptr); + + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_OVERR_GID) + return cifs_sb->ctx->linux_gid; + return make_kgid(current_user_ns(), gid); +} + +static inline u64 reparse_mode_nfs_type(mode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFBLK: return NFS_SPECFILE_BLK; + case S_IFCHR: return NFS_SPECFILE_CHR; + case S_IFIFO: return NFS_SPECFILE_FIFO; + case S_IFSOCK: return NFS_SPECFILE_SOCK; + } + return 0; +} + +static inline u32 reparse_mode_wsl_tag(mode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFBLK: return IO_REPARSE_TAG_LX_BLK; + case S_IFCHR: return IO_REPARSE_TAG_LX_CHR; + case S_IFIFO: return IO_REPARSE_TAG_LX_FIFO; + case S_IFSOCK: return IO_REPARSE_TAG_AF_UNIX; + } + return 0; +} + +/* + * Match a reparse point inode if reparse tag and ctime haven't changed. + * + * Windows Server updates ctime of reparse points when their data have changed. + * The server doesn't allow changing reparse tags from existing reparse points, + * though it's worth checking. + */ +static inline bool reparse_inode_match(struct inode *inode, + struct cifs_fattr *fattr) +{ + struct timespec64 ctime = inode_get_ctime(inode); + + return (CIFS_I(inode)->cifsAttrs & ATTR_REPARSE) && + CIFS_I(inode)->reparse_tag == fattr->cf_cifstag && + timespec64_equal(&ctime, &fattr->cf_ctime); +} + +static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data) +{ + struct smb2_file_all_info *fi = &data->fi; + u32 attrs = le32_to_cpu(fi->Attributes); + bool ret; + + ret = data->reparse_point || (attrs & ATTR_REPARSE); + if (ret) + attrs |= ATTR_REPARSE; + fi->Attributes = cpu_to_le32(attrs); + return ret; +} + +bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, + struct cifs_fattr *fattr, + struct cifs_open_info_data *data); +int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, const char *symname); +int smb2_mknod_reparse(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev); +int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov, + struct cifs_open_info_data *data); + +#endif /* _CIFS_REPARSE_H */ diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index e4168cd8b6c2..3216f786908f 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -108,6 +108,7 @@ cifs_chan_clear_in_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server) { unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + if (chan_index == CIFS_INVAL_CHAN_INDEX) return; @@ -119,6 +120,7 @@ cifs_chan_in_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server) { unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + if (chan_index == CIFS_INVAL_CHAN_INDEX) return true; /* err on the safer side */ @@ -130,6 +132,7 @@ cifs_chan_set_need_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server) { unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + if (chan_index == CIFS_INVAL_CHAN_INDEX) return; @@ -143,6 +146,7 @@ cifs_chan_clear_need_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server) { unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + if (chan_index == CIFS_INVAL_CHAN_INDEX) return; @@ -156,6 +160,7 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server) { unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + if (chan_index == CIFS_INVAL_CHAN_INDEX) return true; /* err on the safer side */ @@ -167,6 +172,7 @@ cifs_chan_is_iface_active(struct cifs_ses *ses, struct TCP_Server_Info *server) { unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + if (chan_index == CIFS_INVAL_CHAN_INDEX) return true; /* err on the safer side */ @@ -331,10 +337,10 @@ cifs_disable_secondary_channels(struct cifs_ses *ses) if (iface) { spin_lock(&ses->iface_lock); - kref_put(&iface->refcount, release_iface); iface->num_channels--; if (iface->weight_fulfilled) iface->weight_fulfilled--; + kref_put(&iface->refcount, release_iface); spin_unlock(&ses->iface_lock); } @@ -436,8 +442,14 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server) } if (!iface) { - cifs_dbg(FYI, "unable to get the interface matching: %pIS\n", - &ss); + if (!chan_index) + cifs_dbg(FYI, "unable to get the interface matching: %pIS\n", + &ss); + else { + cifs_dbg(FYI, "unable to find another interface to replace: %pIS\n", + &old_iface->sockaddr); + } + spin_unlock(&ses->iface_lock); return; } @@ -455,10 +467,6 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server) iface->weight_fulfilled++; kref_put(&old_iface->refcount, release_iface); - } else if (old_iface) { - /* if a new candidate is not found, keep things as is */ - cifs_dbg(FYI, "could not replace iface: %pIS\n", - &old_iface->sockaddr); } else if (!chan_index) { /* special case: update interface for primary channel */ cifs_dbg(FYI, "referencing primary channel iface: %pIS\n", @@ -477,8 +485,6 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server) ses->chans[chan_index].iface = iface; spin_unlock(&ses->chan_lock); - - return; } /* @@ -677,8 +683,7 @@ static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, /* Now no need to set SMBFLG_CASELESS or obsolete CANONICAL PATH */ - /* BB verify whether signing required on neg or just on auth frame - (and NTLM case) */ + /* BB verify whether signing required on neg or just auth frame (and NTLM case) */ capabilities = CAP_LARGE_FILES | CAP_NT_SMBS | CAP_LEVEL_II_OPLOCKS | CAP_LARGE_WRITE_X | CAP_LARGE_READ_X; @@ -735,8 +740,10 @@ static void unicode_domain_string(char **pbcc_area, struct cifs_ses *ses, /* copy domain */ if (ses->domainName == NULL) { - /* Sending null domain better than using a bogus domain name (as - we did briefly in 2.6.18) since server will use its default */ + /* + * Sending null domain better than using a bogus domain name (as + * we did briefly in 2.6.18) since server will use its default + */ *bcc_ptr = 0; *(bcc_ptr+1) = 0; bytes_ret = 0; @@ -755,8 +762,7 @@ static void unicode_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, char *bcc_ptr = *pbcc_area; int bytes_ret = 0; - /* BB FIXME add check that strings total less - than 335 or will need to send them as arrays */ + /* BB FIXME add check that strings less than 335 or will need to send as arrays */ /* copy user */ if (ses->user_name == NULL) { @@ -801,8 +807,7 @@ static void ascii_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, if (WARN_ON_ONCE(len < 0)) len = CIFS_MAX_DOMAINNAME_LEN - 1; bcc_ptr += len; - } /* else we will send a null domain name - so the server will default to its own domain */ + } /* else we send a null domain name so server will default to its own domain */ *bcc_ptr = 0; bcc_ptr++; @@ -898,11 +903,14 @@ static void decode_ascii_ssetup(char **pbcc_area, __u16 bleft, if (len > bleft) return; - /* No domain field in LANMAN case. Domain is - returned by old servers in the SMB negprot response */ - /* BB For newer servers which do not support Unicode, - but thus do return domain here we could add parsing - for it later, but it is not very important */ + /* + * No domain field in LANMAN case. Domain is + * returned by old servers in the SMB negprot response + * + * BB For newer servers which do not support Unicode, + * but thus do return domain here, we could add parsing + * for it later, but it is not very important + */ cifs_dbg(FYI, "ascii: bytes left %d\n", bleft); } #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ @@ -958,9 +966,12 @@ int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len, ses->ntlmssp->server_flags = server_flags; memcpy(ses->ntlmssp->cryptkey, pblob->Challenge, CIFS_CRYPTO_KEY_SIZE); - /* In particular we can examine sign flags */ - /* BB spec says that if AvId field of MsvAvTimestamp is populated then - we must set the MIC field of the AUTHENTICATE_MESSAGE */ + /* + * In particular we can examine sign flags + * + * BB spec says that if AvId field of MsvAvTimestamp is populated then + * we must set the MIC field of the AUTHENTICATE_MESSAGE + */ tioffset = le32_to_cpu(pblob->TargetInfoArray.BufferOffset); tilen = le16_to_cpu(pblob->TargetInfoArray.Length); @@ -1201,10 +1212,16 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer, memcpy(sec_blob->Signature, NTLMSSP_SIGNATURE, 8); sec_blob->MessageType = NtLmAuthenticate; + /* send version information in ntlmssp authenticate also */ flags = ses->ntlmssp->server_flags | NTLMSSP_REQUEST_TARGET | - NTLMSSP_NEGOTIATE_TARGET_INFO | NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED; - /* we only send version information in ntlmssp negotiate, so do not set this flag */ - flags = flags & ~NTLMSSP_NEGOTIATE_VERSION; + NTLMSSP_NEGOTIATE_TARGET_INFO | NTLMSSP_NEGOTIATE_VERSION | + NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED; + + sec_blob->Version.ProductMajorVersion = LINUX_VERSION_MAJOR; + sec_blob->Version.ProductMinorVersion = LINUX_VERSION_PATCHLEVEL; + sec_blob->Version.ProductBuild = cpu_to_le16(SMB3_PRODUCT_BUILD); + sec_blob->Version.NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; + tmp = *pbuffer + sizeof(AUTHENTICATE_MESSAGE); sec_blob->NegotiateFlags = cpu_to_le32(flags); diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 1aebcf95c195..212ec6f66ec6 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -1041,15 +1041,7 @@ cifs_make_node(unsigned int xid, struct inode *inode, { struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct inode *newinode = NULL; - int rc = -EPERM; - struct cifs_open_info_data buf = {}; - struct cifs_io_parms io_parms; - __u32 oplock = 0; - struct cifs_fid fid; - struct cifs_open_parms oparms; - unsigned int bytes_written; - struct win_dev *pdev; - struct kvec iov[2]; + int rc; if (tcon->unix_ext) { /* @@ -1083,74 +1075,18 @@ cifs_make_node(unsigned int xid, struct inode *inode, d_instantiate(dentry, newinode); return rc; } - /* - * SMB1 SFU emulation: should work with all servers, but only - * support block and char device (no socket & fifo) + * Check if mounted with mount parm 'sfu' mount parm. + * SFU emulation should work with all servers, but only + * supports block and char device (no socket & fifo), + * and was used by default in earlier versions of Windows */ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL)) - return rc; - - if (!S_ISCHR(mode) && !S_ISBLK(mode)) - return rc; - - cifs_dbg(FYI, "sfu compat create special file\n"); - - oparms = (struct cifs_open_parms) { - .tcon = tcon, - .cifs_sb = cifs_sb, - .desired_access = GENERIC_WRITE, - .create_options = cifs_create_options(cifs_sb, CREATE_NOT_DIR | - CREATE_OPTION_SPECIAL), - .disposition = FILE_CREATE, - .path = full_path, - .fid = &fid, - }; - - if (tcon->ses->server->oplocks) - oplock = REQ_OPLOCK; - else - oplock = 0; - rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, &buf); - if (rc) - return rc; - - /* - * BB Do not bother to decode buf since no local inode yet to put - * timestamps in, but we can reuse it safely. - */ - - pdev = (struct win_dev *)&buf.fi; - io_parms.pid = current->tgid; - io_parms.tcon = tcon; - io_parms.offset = 0; - io_parms.length = sizeof(struct win_dev); - iov[1].iov_base = &buf.fi; - iov[1].iov_len = sizeof(struct win_dev); - if (S_ISCHR(mode)) { - memcpy(pdev->type, "IntxCHR", 8); - pdev->major = cpu_to_le64(MAJOR(dev)); - pdev->minor = cpu_to_le64(MINOR(dev)); - rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms, - &bytes_written, iov, 1); - } else if (S_ISBLK(mode)) { - memcpy(pdev->type, "IntxBLK", 8); - pdev->major = cpu_to_le64(MAJOR(dev)); - pdev->minor = cpu_to_le64(MINOR(dev)); - rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms, - &bytes_written, iov, 1); - } - tcon->ses->server->ops->close(xid, tcon, &fid); - d_drop(dentry); - - /* FIXME: add code here to set EAs */ - - cifs_free_open_info(&buf); - return rc; + return -EPERM; + return cifs_sfu_make_node(xid, inode, dentry, tcon, + full_path, mode, dev); } - - struct smb_version_operations smb1_operations = { .send_cancel = send_nt_cancel, .compare_fids = cifs_compare_fids, diff --git a/fs/smb/client/smb2glob.h b/fs/smb/client/smb2glob.h index 82e916ad167c..2466e6155136 100644 --- a/fs/smb/client/smb2glob.h +++ b/fs/smb/client/smb2glob.h @@ -23,17 +23,22 @@ * Identifiers for functions that use the open, operation, close pattern * in smb2inode.c:smb2_compound_op() */ -#define SMB2_OP_SET_DELETE 1 -#define SMB2_OP_SET_INFO 2 -#define SMB2_OP_QUERY_INFO 3 -#define SMB2_OP_QUERY_DIR 4 -#define SMB2_OP_MKDIR 5 -#define SMB2_OP_RENAME 6 -#define SMB2_OP_DELETE 7 -#define SMB2_OP_HARDLINK 8 -#define SMB2_OP_SET_EOF 9 -#define SMB2_OP_RMDIR 10 -#define SMB2_OP_POSIX_QUERY_INFO 11 +enum smb2_compound_ops { + SMB2_OP_SET_DELETE = 1, + SMB2_OP_SET_INFO, + SMB2_OP_QUERY_INFO, + SMB2_OP_QUERY_DIR, + SMB2_OP_MKDIR, + SMB2_OP_RENAME, + SMB2_OP_DELETE, + SMB2_OP_HARDLINK, + SMB2_OP_SET_EOF, + SMB2_OP_RMDIR, + SMB2_OP_POSIX_QUERY_INFO, + SMB2_OP_SET_REPARSE, + SMB2_OP_GET_REPARSE, + SMB2_OP_QUERY_WSL_EA, +}; /* Used when constructing chained read requests. */ #define CHAINED_REQUEST 1 diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 6cac0b107a2d..86f8c8179137 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -26,15 +26,6 @@ #include "cached_dir.h" #include "smb2status.h" -static void -free_set_inf_compound(struct smb_rqst *rqst) -{ - if (rqst[1].rq_iov) - SMB2_set_info_free(&rqst[1]); - if (rqst[2].rq_iov) - SMB2_close_free(&rqst[2]); -} - static inline __u32 file_create_options(struct dentry *dentry) { struct cifsInodeInfo *ci; @@ -47,6 +38,129 @@ static inline __u32 file_create_options(struct dentry *dentry) return 0; } +static struct reparse_data_buffer *reparse_buf_ptr(struct kvec *iov) +{ + struct reparse_data_buffer *buf; + struct smb2_ioctl_rsp *io = iov->iov_base; + u32 off, count, len; + + count = le32_to_cpu(io->OutputCount); + off = le32_to_cpu(io->OutputOffset); + if (check_add_overflow(off, count, &len) || len > iov->iov_len) + return ERR_PTR(-EIO); + + buf = (struct reparse_data_buffer *)((u8 *)io + off); + len = sizeof(*buf); + if (count < len || count < le16_to_cpu(buf->ReparseDataLength) + len) + return ERR_PTR(-EIO); + return buf; +} + +/* Parse owner and group from SMB3.1.1 POSIX query info */ +static int parse_posix_sids(struct cifs_open_info_data *data, + struct kvec *rsp_iov) +{ + struct smb2_query_info_rsp *qi = rsp_iov->iov_base; + unsigned int out_len = le32_to_cpu(qi->OutputBufferLength); + unsigned int qi_len = sizeof(data->posix_fi); + int owner_len, group_len; + u8 *sidsbuf, *sidsbuf_end; + + if (out_len <= qi_len) + return -EINVAL; + + sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; + sidsbuf_end = sidsbuf + out_len - qi_len; + + owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end); + if (owner_len == -1) + return -EINVAL; + + memcpy(&data->posix_owner, sidsbuf, owner_len); + group_len = posix_info_sid_size(sidsbuf + owner_len, sidsbuf_end); + if (group_len == -1) + return -EINVAL; + + memcpy(&data->posix_group, sidsbuf + owner_len, group_len); + return 0; +} + +struct wsl_query_ea { + __le32 next; + __u8 name_len; + __u8 name[SMB2_WSL_XATTR_NAME_LEN + 1]; +} __packed; + +#define NEXT_OFF cpu_to_le32(sizeof(struct wsl_query_ea)) + +static const struct wsl_query_ea wsl_query_eas[] = { + { .next = NEXT_OFF, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_UID, }, + { .next = NEXT_OFF, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_GID, }, + { .next = NEXT_OFF, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_MODE, }, + { .next = 0, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_DEV, }, +}; + +static int check_wsl_eas(struct kvec *rsp_iov) +{ + struct smb2_file_full_ea_info *ea; + struct smb2_query_info_rsp *rsp = rsp_iov->iov_base; + unsigned long addr; + u32 outlen, next; + u16 vlen; + u8 nlen; + u8 *end; + + outlen = le32_to_cpu(rsp->OutputBufferLength); + if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || + outlen > SMB2_WSL_MAX_QUERY_EA_RESP_SIZE) + return -EINVAL; + + ea = (void *)((u8 *)rsp_iov->iov_base + + le16_to_cpu(rsp->OutputBufferOffset)); + end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + for (;;) { + if ((u8 *)ea > end - sizeof(*ea)) + return -EINVAL; + + nlen = ea->ea_name_length; + vlen = le16_to_cpu(ea->ea_value_length); + if (nlen != SMB2_WSL_XATTR_NAME_LEN || + (u8 *)ea + nlen + 1 + vlen > end) + return -EINVAL; + + switch (vlen) { + case 4: + if (strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) && + strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) && + strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen)) + return -EINVAL; + break; + case 8: + if (strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) + return -EINVAL; + break; + case 0: + if (!strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) || + !strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) || + !strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen) || + !strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) + break; + fallthrough; + default: + return -EINVAL; + } + + next = le32_to_cpu(ea->next_entry_offset); + if (!next) + break; + if (!IS_ALIGNED(next, 4) || + check_add_overflow((unsigned long)ea, next, &addr)) + return -EINVAL; + ea = (void *)addr; + } + return 0; +} + /* * note: If cfile is passed, the reference to it is dropped here. * So make sure that you do not reuse cfile after return from this func. @@ -57,13 +171,14 @@ static inline __u32 file_create_options(struct dentry *dentry) */ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *full_path, - __u32 desired_access, __u32 create_disposition, __u32 create_options, - umode_t mode, void *ptr, int command, struct cifsFileInfo *cfile, - __u8 **extbuf, size_t *extbuflen, - struct kvec *out_iov, int *out_buftype) + struct cifs_open_parms *oparms, struct kvec *in_iov, + int *cmds, int num_cmds, struct cifsFileInfo *cfile, + struct kvec *out_iov, int *out_buftype, struct dentry *dentry) { + + struct reparse_data_buffer *rbuf; struct smb2_compound_vars *vars = NULL; - struct kvec *rsp_iov; + struct kvec *rsp_iov, *iov; struct smb_rqst *rqst; int rc; __le16 *utf16_path = NULL; @@ -71,15 +186,24 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_fid fid; struct cifs_ses *ses = tcon->ses; struct TCP_Server_Info *server; - int num_rqst = 0; - int resp_buftype[3]; + int num_rqst = 0, i; + int resp_buftype[MAX_COMPOUND]; struct smb2_query_info_rsp *qi_rsp = NULL; struct cifs_open_info_data *idata; + struct inode *inode = NULL; int flags = 0; __u8 delete_pending[8] = {1, 0, 0, 0, 0, 0, 0, 0}; unsigned int size[2]; void *data[2]; - int len; + unsigned int len; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + oplock = SMB2_OPLOCK_LEVEL_NONE; + num_rqst = 0; + server = cifs_pick_channel(ses); vars = kzalloc(sizeof(*vars), GFP_ATOMIC); if (vars == NULL) @@ -87,12 +211,11 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, rqst = &vars->rqst[0]; rsp_iov = &vars->rsp_iov[0]; - server = cifs_pick_channel(ses); - if (smb3_encryption_required(tcon)) flags |= CIFS_TRANSFORM_REQ; - resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER; + for (i = 0; i < ARRAY_SIZE(resp_buftype); i++) + resp_buftype[i] = CIFS_NO_BUFFER; /* We already have a handle so we can skip the open */ if (cfile) @@ -105,16 +228,28 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, goto finished; } - vars->oparms = (struct cifs_open_parms) { - .tcon = tcon, - .path = full_path, - .desired_access = desired_access, - .disposition = create_disposition, - .create_options = cifs_create_options(cifs_sb, create_options), - .fid = &fid, - .mode = mode, - .cifs_sb = cifs_sb, - }; + /* if there is an existing lease, reuse it */ + + /* + * note: files with hardlinks cause unexpected behaviour. As per MS-SMB2, + * lease keys are associated with the filepath. We are maintaining lease keys + * with the inode on the client. If the file has hardlinks, it is possible + * that the lease for a file be reused for an operation on its hardlink or + * vice versa. + * As a workaround, send request using an existing lease key and if the server + * returns STATUS_INVALID_PARAMETER, which maps to EINVAL, send the request + * again without the lease. + */ + if (dentry) { + inode = d_inode(dentry); + if (CIFS_I(inode)->lease_granted && server->ops->get_lease_key) { + oplock = SMB2_OPLOCK_LEVEL_LEASE; + server->ops->get_lease_key(inode, &fid); + } + } + + vars->oparms = *oparms; + vars->oparms.fid = &fid; rqst[num_rqst].rq_iov = &vars->open_iov[0]; rqst[num_rqst].rq_nvec = SMB2_CREATE_IOV_SIZE; @@ -130,242 +265,330 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, num_rqst++; rc = 0; - /* Operation */ - switch (command) { - case SMB2_OP_QUERY_INFO: - rqst[num_rqst].rq_iov = &vars->qi_iov; - rqst[num_rqst].rq_nvec = 1; - - if (cfile) - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - FILE_ALL_INFORMATION, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb2_file_all_info) + - PATH_MAX * 2, 0, NULL); - else { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - FILE_ALL_INFORMATION, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb2_file_all_info) + - PATH_MAX * 2, 0, NULL); - if (!rc) { + for (i = 0; i < num_cmds; i++) { + /* Operation */ + switch (cmds[i]) { + case SMB2_OP_QUERY_INFO: + rqst[num_rqst].rq_iov = &vars->qi_iov; + rqst[num_rqst].rq_nvec = 1; + + if (cfile) { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FILE_ALL_INFORMATION, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb2_file_all_info) + + PATH_MAX * 2, 0, NULL); + } else { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, + COMPOUND_FID, + FILE_ALL_INFORMATION, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb2_file_all_info) + + PATH_MAX * 2, 0, NULL); + } + if (!rc && (!cfile || num_rqst > 1)) { smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; } - } + num_rqst++; + trace_smb3_query_info_compound_enter(xid, ses->Suid, + tcon->tid, full_path); + break; + case SMB2_OP_POSIX_QUERY_INFO: + rqst[num_rqst].rq_iov = &vars->qi_iov; + rqst[num_rqst].rq_nvec = 1; - if (rc) - goto finished; - num_rqst++; - trace_smb3_query_info_compound_enter(xid, ses->Suid, tcon->tid, - full_path); - break; - case SMB2_OP_POSIX_QUERY_INFO: - rqst[num_rqst].rq_iov = &vars->qi_iov; - rqst[num_rqst].rq_nvec = 1; - - if (cfile) - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - SMB_FIND_FILE_POSIX_INFO, - SMB2_O_INFO_FILE, 0, + if (cfile) { /* TBD: fix following to allow for longer SIDs */ - sizeof(struct smb311_posix_qinfo *) + (PATH_MAX * 2) + - (sizeof(struct cifs_sid) * 2), 0, NULL); - else { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - SMB_FIND_FILE_POSIX_INFO, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb311_posix_qinfo *) + (PATH_MAX * 2) + - (sizeof(struct cifs_sid) * 2), 0, NULL); - if (!rc) { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + SMB_FIND_FILE_POSIX_INFO, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb311_posix_qinfo *) + + (PATH_MAX * 2) + + (sizeof(struct cifs_sid) * 2), 0, NULL); + } else { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, + COMPOUND_FID, + SMB_FIND_FILE_POSIX_INFO, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb311_posix_qinfo *) + + (PATH_MAX * 2) + + (sizeof(struct cifs_sid) * 2), 0, NULL); + } + if (!rc && (!cfile || num_rqst > 1)) { smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; } - } - - if (rc) - goto finished; - num_rqst++; - trace_smb3_posix_query_info_compound_enter(xid, ses->Suid, tcon->tid, full_path); - break; - case SMB2_OP_DELETE: - trace_smb3_delete_enter(xid, ses->Suid, tcon->tid, full_path); - break; - case SMB2_OP_MKDIR: - /* - * Directories are created through parameters in the - * SMB2_open() call. - */ - trace_smb3_mkdir_enter(xid, ses->Suid, tcon->tid, full_path); - break; - case SMB2_OP_RMDIR: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; - rqst[num_rqst].rq_nvec = 1; - - size[0] = 1; /* sizeof __u8 See MS-FSCC section 2.4.11 */ - data[0] = &delete_pending[0]; - - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], COMPOUND_FID, - COMPOUND_FID, current->tgid, - FILE_DISPOSITION_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - if (rc) - goto finished; - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst++]); - trace_smb3_rmdir_enter(xid, ses->Suid, tcon->tid, full_path); - break; - case SMB2_OP_SET_EOF: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; - rqst[num_rqst].rq_nvec = 1; + num_rqst++; + trace_smb3_posix_query_info_compound_enter(xid, ses->Suid, + tcon->tid, full_path); + break; + case SMB2_OP_DELETE: + trace_smb3_delete_enter(xid, ses->Suid, tcon->tid, full_path); + break; + case SMB2_OP_MKDIR: + /* + * Directories are created through parameters in the + * SMB2_open() call. + */ + trace_smb3_mkdir_enter(xid, ses->Suid, tcon->tid, full_path); + break; + case SMB2_OP_RMDIR: + rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_nvec = 1; - size[0] = 8; /* sizeof __le64 */ - data[0] = ptr; + size[0] = 1; /* sizeof __u8 See MS-FSCC section 2.4.11 */ + data[0] = &delete_pending[0]; - if (cfile) { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - current->tgid, - FILE_END_OF_FILE_INFORMATION, - SMB2_O_INFO_FILE, 0, - data, size); - } else { rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - current->tgid, - FILE_END_OF_FILE_INFORMATION, - SMB2_O_INFO_FILE, 0, - data, size); - if (!rc) { + &rqst[num_rqst], COMPOUND_FID, + COMPOUND_FID, current->tgid, + FILE_DISPOSITION_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + if (rc) + goto finished; + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst++]); + trace_smb3_rmdir_enter(xid, ses->Suid, tcon->tid, full_path); + break; + case SMB2_OP_SET_EOF: + rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_nvec = 1; + + size[0] = in_iov[i].iov_len; + data[0] = in_iov[i].iov_base; + + if (cfile) { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + current->tgid, + FILE_END_OF_FILE_INFORMATION, + SMB2_O_INFO_FILE, 0, + data, size); + } else { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, + COMPOUND_FID, + current->tgid, + FILE_END_OF_FILE_INFORMATION, + SMB2_O_INFO_FILE, 0, + data, size); + } + if (!rc && (!cfile || num_rqst > 1)) { smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; } - } - if (rc) - goto finished; - num_rqst++; - trace_smb3_set_eof_enter(xid, ses->Suid, tcon->tid, full_path); - break; - case SMB2_OP_SET_INFO: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; - rqst[num_rqst].rq_nvec = 1; - - - size[0] = sizeof(FILE_BASIC_INFO); - data[0] = ptr; - - if (cfile) - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, current->tgid, - FILE_BASIC_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - else { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, current->tgid, - FILE_BASIC_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - if (!rc) { + num_rqst++; + trace_smb3_set_eof_enter(xid, ses->Suid, tcon->tid, full_path); + break; + case SMB2_OP_SET_INFO: + rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_nvec = 1; + + size[0] = in_iov[i].iov_len; + data[0] = in_iov[i].iov_base; + + if (cfile) { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, current->tgid, + FILE_BASIC_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + } else { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, + COMPOUND_FID, current->tgid, + FILE_BASIC_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + } + if (!rc && (!cfile || num_rqst > 1)) { smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; } - } - - if (rc) - goto finished; - num_rqst++; - trace_smb3_set_info_compound_enter(xid, ses->Suid, tcon->tid, - full_path); - break; - case SMB2_OP_RENAME: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; - rqst[num_rqst].rq_nvec = 2; + num_rqst++; + trace_smb3_set_info_compound_enter(xid, ses->Suid, + tcon->tid, full_path); + break; + case SMB2_OP_RENAME: + rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_nvec = 2; - len = (2 * UniStrnlen((wchar_t *)ptr, PATH_MAX)); + len = in_iov[i].iov_len; - vars->rename_info.ReplaceIfExists = 1; - vars->rename_info.RootDirectory = 0; - vars->rename_info.FileNameLength = cpu_to_le32(len); + vars->rename_info.ReplaceIfExists = 1; + vars->rename_info.RootDirectory = 0; + vars->rename_info.FileNameLength = cpu_to_le32(len); - size[0] = sizeof(struct smb2_file_rename_info); - data[0] = &vars->rename_info; + size[0] = sizeof(struct smb2_file_rename_info); + data[0] = &vars->rename_info; - size[1] = len + 2 /* null */; - data[1] = (__le16 *)ptr; + size[1] = len + 2 /* null */; + data[1] = in_iov[i].iov_base; - if (cfile) - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - current->tgid, FILE_RENAME_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - else { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, COMPOUND_FID, - current->tgid, FILE_RENAME_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - if (!rc) { + if (cfile) { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + current->tgid, FILE_RENAME_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + } else { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, COMPOUND_FID, + current->tgid, FILE_RENAME_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + } + if (!rc && (!cfile || num_rqst > 1)) { smb2_set_next_command(tcon, &rqst[num_rqst]); smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; } - } - if (rc) - goto finished; - num_rqst++; - trace_smb3_rename_enter(xid, ses->Suid, tcon->tid, full_path); - break; - case SMB2_OP_HARDLINK: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; - rqst[num_rqst].rq_nvec = 2; + num_rqst++; + trace_smb3_rename_enter(xid, ses->Suid, tcon->tid, full_path); + break; + case SMB2_OP_HARDLINK: + rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_nvec = 2; - len = (2 * UniStrnlen((wchar_t *)ptr, PATH_MAX)); + len = in_iov[i].iov_len; - vars->link_info.ReplaceIfExists = 0; - vars->link_info.RootDirectory = 0; - vars->link_info.FileNameLength = cpu_to_le32(len); + vars->link_info.ReplaceIfExists = 0; + vars->link_info.RootDirectory = 0; + vars->link_info.FileNameLength = cpu_to_le32(len); - size[0] = sizeof(struct smb2_file_link_info); - data[0] = &vars->link_info; + size[0] = sizeof(struct smb2_file_link_info); + data[0] = &vars->link_info; - size[1] = len + 2 /* null */; - data[1] = (__le16 *)ptr; + size[1] = len + 2 /* null */; + data[1] = in_iov[i].iov_base; - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], COMPOUND_FID, - COMPOUND_FID, current->tgid, - FILE_LINK_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - if (rc) - goto finished; - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst++]); - trace_smb3_hardlink_enter(xid, ses->Suid, tcon->tid, full_path); - break; - default: - cifs_dbg(VFS, "Invalid command\n"); - rc = -EINVAL; + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], COMPOUND_FID, + COMPOUND_FID, current->tgid, + FILE_LINK_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + if (rc) + goto finished; + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst++]); + trace_smb3_hardlink_enter(xid, ses->Suid, tcon->tid, full_path); + break; + case SMB2_OP_SET_REPARSE: + rqst[num_rqst].rq_iov = vars->io_iov; + rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov); + + if (cfile) { + rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FSCTL_SET_REPARSE_POINT, + in_iov[i].iov_base, + in_iov[i].iov_len, 0); + } else { + rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], + COMPOUND_FID, COMPOUND_FID, + FSCTL_SET_REPARSE_POINT, + in_iov[i].iov_base, + in_iov[i].iov_len, 0); + } + if (!rc && (!cfile || num_rqst > 1)) { + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; + } + num_rqst++; + trace_smb3_set_reparse_compound_enter(xid, ses->Suid, + tcon->tid, full_path); + break; + case SMB2_OP_GET_REPARSE: + rqst[num_rqst].rq_iov = vars->io_iov; + rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov); + + if (cfile) { + rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FSCTL_GET_REPARSE_POINT, + NULL, 0, CIFSMaxBufSize); + } else { + rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], + COMPOUND_FID, COMPOUND_FID, + FSCTL_GET_REPARSE_POINT, + NULL, 0, CIFSMaxBufSize); + } + if (!rc && (!cfile || num_rqst > 1)) { + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; + } + num_rqst++; + trace_smb3_get_reparse_compound_enter(xid, ses->Suid, + tcon->tid, full_path); + break; + case SMB2_OP_QUERY_WSL_EA: + rqst[num_rqst].rq_iov = &vars->ea_iov; + rqst[num_rqst].rq_nvec = 1; + + if (cfile) { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, 0, + SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, + sizeof(wsl_query_eas), + (void *)wsl_query_eas); + } else { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, + COMPOUND_FID, + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, 0, + SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, + sizeof(wsl_query_eas), + (void *)wsl_query_eas); + } + if (!rc && (!cfile || num_rqst > 1)) { + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; + } + num_rqst++; + break; + default: + cifs_dbg(VFS, "Invalid command\n"); + rc = -EINVAL; + } } if (rc) goto finished; @@ -387,157 +610,219 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, num_rqst++; if (cfile) { + if (retries) + for (i = 1; i < num_rqst - 2; i++) + smb2_set_replay(server, &rqst[i]); + rc = compound_send_recv(xid, ses, server, flags, num_rqst - 2, &rqst[1], &resp_buftype[1], &rsp_iov[1]); - } else + } else { + if (retries) + for (i = 0; i < num_rqst; i++) + smb2_set_replay(server, &rqst[i]); + rc = compound_send_recv(xid, ses, server, flags, num_rqst, rqst, resp_buftype, rsp_iov); + } - finished: - SMB2_open_free(&rqst[0]); +finished: + num_rqst = 0; + SMB2_open_free(&rqst[num_rqst++]); if (rc == -EREMCHG) { pr_warn_once("server share %s deleted\n", tcon->tree_name); tcon->need_reconnect = true; } - switch (command) { - case SMB2_OP_QUERY_INFO: - idata = ptr; - if (rc == 0 && cfile && cfile->symlink_target) { - idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); - if (!idata->symlink_target) - rc = -ENOMEM; - } - if (rc == 0) { - qi_rsp = (struct smb2_query_info_rsp *) - rsp_iov[1].iov_base; - rc = smb2_validate_and_copy_iov( - le16_to_cpu(qi_rsp->OutputBufferOffset), - le32_to_cpu(qi_rsp->OutputBufferLength), - &rsp_iov[1], sizeof(idata->fi), (char *)&idata->fi); - } - if (rqst[1].rq_iov) - SMB2_query_info_free(&rqst[1]); - if (rqst[2].rq_iov) - SMB2_close_free(&rqst[2]); - if (rc) - trace_smb3_query_info_compound_err(xid, ses->Suid, - tcon->tid, rc); - else - trace_smb3_query_info_compound_done(xid, ses->Suid, - tcon->tid); - break; - case SMB2_OP_POSIX_QUERY_INFO: - idata = ptr; - if (rc == 0 && cfile && cfile->symlink_target) { - idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); - if (!idata->symlink_target) - rc = -ENOMEM; - } - if (rc == 0) { - qi_rsp = (struct smb2_query_info_rsp *) - rsp_iov[1].iov_base; - rc = smb2_validate_and_copy_iov( - le16_to_cpu(qi_rsp->OutputBufferOffset), - le32_to_cpu(qi_rsp->OutputBufferLength), - &rsp_iov[1], sizeof(idata->posix_fi) /* add SIDs */, - (char *)&idata->posix_fi); - } - if (rc == 0) { - unsigned int length = le32_to_cpu(qi_rsp->OutputBufferLength); - - if (length > sizeof(idata->posix_fi)) { - char *base = (char *)rsp_iov[1].iov_base + - le16_to_cpu(qi_rsp->OutputBufferOffset) + - sizeof(idata->posix_fi); - *extbuflen = length - sizeof(idata->posix_fi); - *extbuf = kmemdup(base, *extbuflen, GFP_KERNEL); - if (!*extbuf) + for (i = 0; i < num_cmds; i++) { + switch (cmds[i]) { + case SMB2_OP_QUERY_INFO: + idata = in_iov[i].iov_base; + if (rc == 0 && cfile && cfile->symlink_target) { + idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); + if (!idata->symlink_target) rc = -ENOMEM; + } + if (rc == 0) { + qi_rsp = (struct smb2_query_info_rsp *) + rsp_iov[i + 1].iov_base; + rc = smb2_validate_and_copy_iov( + le16_to_cpu(qi_rsp->OutputBufferOffset), + le32_to_cpu(qi_rsp->OutputBufferLength), + &rsp_iov[i + 1], sizeof(idata->fi), (char *)&idata->fi); + } + SMB2_query_info_free(&rqst[num_rqst++]); + if (rc) + trace_smb3_query_info_compound_err(xid, ses->Suid, + tcon->tid, rc); + else + trace_smb3_query_info_compound_done(xid, ses->Suid, + tcon->tid); + break; + case SMB2_OP_POSIX_QUERY_INFO: + idata = in_iov[i].iov_base; + if (rc == 0 && cfile && cfile->symlink_target) { + idata->symlink_target = kstrdup(cfile->symlink_target, GFP_KERNEL); + if (!idata->symlink_target) + rc = -ENOMEM; + } + if (rc == 0) { + qi_rsp = (struct smb2_query_info_rsp *) + rsp_iov[i + 1].iov_base; + rc = smb2_validate_and_copy_iov( + le16_to_cpu(qi_rsp->OutputBufferOffset), + le32_to_cpu(qi_rsp->OutputBufferLength), + &rsp_iov[i + 1], sizeof(idata->posix_fi) /* add SIDs */, + (char *)&idata->posix_fi); + } + if (rc == 0) + rc = parse_posix_sids(idata, &rsp_iov[i + 1]); + + SMB2_query_info_free(&rqst[num_rqst++]); + if (rc) + trace_smb3_posix_query_info_compound_err(xid, ses->Suid, + tcon->tid, rc); + else + trace_smb3_posix_query_info_compound_done(xid, ses->Suid, + tcon->tid); + break; + case SMB2_OP_DELETE: + if (rc) + trace_smb3_delete_err(xid, ses->Suid, tcon->tid, rc); + else { + /* + * If dentry (hence, inode) is NULL, lease break is going to + * take care of degrading leases on handles for deleted files. + */ + if (inode) + cifs_mark_open_handles_for_deleted_file(inode, full_path); + trace_smb3_delete_done(xid, ses->Suid, tcon->tid); + } + break; + case SMB2_OP_MKDIR: + if (rc) + trace_smb3_mkdir_err(xid, ses->Suid, tcon->tid, rc); + else + trace_smb3_mkdir_done(xid, ses->Suid, tcon->tid); + break; + case SMB2_OP_HARDLINK: + if (rc) + trace_smb3_hardlink_err(xid, ses->Suid, tcon->tid, rc); + else + trace_smb3_hardlink_done(xid, ses->Suid, tcon->tid); + SMB2_set_info_free(&rqst[num_rqst++]); + break; + case SMB2_OP_RENAME: + if (rc) + trace_smb3_rename_err(xid, ses->Suid, tcon->tid, rc); + else + trace_smb3_rename_done(xid, ses->Suid, tcon->tid); + SMB2_set_info_free(&rqst[num_rqst++]); + break; + case SMB2_OP_RMDIR: + if (rc) + trace_smb3_rmdir_err(xid, ses->Suid, tcon->tid, rc); + else + trace_smb3_rmdir_done(xid, ses->Suid, tcon->tid); + SMB2_set_info_free(&rqst[num_rqst++]); + break; + case SMB2_OP_SET_EOF: + if (rc) + trace_smb3_set_eof_err(xid, ses->Suid, tcon->tid, rc); + else + trace_smb3_set_eof_done(xid, ses->Suid, tcon->tid); + SMB2_set_info_free(&rqst[num_rqst++]); + break; + case SMB2_OP_SET_INFO: + if (rc) + trace_smb3_set_info_compound_err(xid, ses->Suid, + tcon->tid, rc); + else + trace_smb3_set_info_compound_done(xid, ses->Suid, + tcon->tid); + SMB2_set_info_free(&rqst[num_rqst++]); + break; + case SMB2_OP_SET_REPARSE: + if (rc) { + trace_smb3_set_reparse_compound_err(xid, ses->Suid, + tcon->tid, rc); + } else { + trace_smb3_set_reparse_compound_done(xid, ses->Suid, + tcon->tid); + } + SMB2_ioctl_free(&rqst[num_rqst++]); + break; + case SMB2_OP_GET_REPARSE: + if (!rc) { + iov = &rsp_iov[i + 1]; + idata = in_iov[i].iov_base; + idata->reparse.io.iov = *iov; + idata->reparse.io.buftype = resp_buftype[i + 1]; + rbuf = reparse_buf_ptr(iov); + if (IS_ERR(rbuf)) { + rc = PTR_ERR(rbuf); + trace_smb3_set_reparse_compound_err(xid, ses->Suid, + tcon->tid, rc); + } else { + idata->reparse.tag = le32_to_cpu(rbuf->ReparseTag); + trace_smb3_set_reparse_compound_done(xid, ses->Suid, + tcon->tid); + } + memset(iov, 0, sizeof(*iov)); + resp_buftype[i + 1] = CIFS_NO_BUFFER; + } else { + trace_smb3_set_reparse_compound_err(xid, ses->Suid, + tcon->tid, rc); + } + SMB2_ioctl_free(&rqst[num_rqst++]); + break; + case SMB2_OP_QUERY_WSL_EA: + if (!rc) { + idata = in_iov[i].iov_base; + qi_rsp = rsp_iov[i + 1].iov_base; + data[0] = (u8 *)qi_rsp + le16_to_cpu(qi_rsp->OutputBufferOffset); + size[0] = le32_to_cpu(qi_rsp->OutputBufferLength); + rc = check_wsl_eas(&rsp_iov[i + 1]); + if (!rc) { + memcpy(idata->wsl.eas, data[0], size[0]); + idata->wsl.eas_len = size[0]; + } + } + if (!rc) { + trace_smb3_query_wsl_ea_compound_done(xid, ses->Suid, + tcon->tid); } else { - rc = -EINVAL; + trace_smb3_query_wsl_ea_compound_err(xid, ses->Suid, + tcon->tid, rc); } + SMB2_query_info_free(&rqst[num_rqst++]); + break; } - if (rqst[1].rq_iov) - SMB2_query_info_free(&rqst[1]); - if (rqst[2].rq_iov) - SMB2_close_free(&rqst[2]); - if (rc) - trace_smb3_posix_query_info_compound_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_posix_query_info_compound_done(xid, ses->Suid, tcon->tid); - break; - case SMB2_OP_DELETE: - if (rc) - trace_smb3_delete_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_delete_done(xid, ses->Suid, tcon->tid); - if (rqst[1].rq_iov) - SMB2_close_free(&rqst[1]); - break; - case SMB2_OP_MKDIR: - if (rc) - trace_smb3_mkdir_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_mkdir_done(xid, ses->Suid, tcon->tid); - if (rqst[1].rq_iov) - SMB2_close_free(&rqst[1]); - break; - case SMB2_OP_HARDLINK: - if (rc) - trace_smb3_hardlink_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_hardlink_done(xid, ses->Suid, tcon->tid); - free_set_inf_compound(rqst); - break; - case SMB2_OP_RENAME: - if (rc) - trace_smb3_rename_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_rename_done(xid, ses->Suid, tcon->tid); - free_set_inf_compound(rqst); - break; - case SMB2_OP_RMDIR: - if (rc) - trace_smb3_rmdir_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_rmdir_done(xid, ses->Suid, tcon->tid); - free_set_inf_compound(rqst); - break; - case SMB2_OP_SET_EOF: - if (rc) - trace_smb3_set_eof_err(xid, ses->Suid, tcon->tid, rc); - else - trace_smb3_set_eof_done(xid, ses->Suid, tcon->tid); - free_set_inf_compound(rqst); - break; - case SMB2_OP_SET_INFO: - if (rc) - trace_smb3_set_info_compound_err(xid, ses->Suid, - tcon->tid, rc); - else - trace_smb3_set_info_compound_done(xid, ses->Suid, - tcon->tid); - free_set_inf_compound(rqst); - break; } + SMB2_close_free(&rqst[num_rqst]); - if (cfile) - cifsFileInfo_put(cfile); - + num_cmds += 2; if (out_iov && out_buftype) { - memcpy(out_iov, rsp_iov, 3 * sizeof(*out_iov)); - memcpy(out_buftype, resp_buftype, 3 * sizeof(*out_buftype)); + memcpy(out_iov, rsp_iov, num_cmds * sizeof(*out_iov)); + memcpy(out_buftype, resp_buftype, + num_cmds * sizeof(*out_buftype)); } else { - 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); + for (i = 0; i < num_cmds; i++) + free_rsp_buf(resp_buftype[i], rsp_iov[i].iov_base); } + num_cmds -= 2; /* correct num_cmds as there could be a retry */ kfree(vars); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + + if (cfile) + cifsFileInfo_put(cfile); + return rc; } @@ -577,38 +862,64 @@ int smb2_query_path_info(const unsigned int xid, const char *full_path, struct cifs_open_info_data *data) { + struct cifs_open_parms oparms; __u32 create_options = 0; struct cifsFileInfo *cfile; struct cached_fid *cfid = NULL; struct smb2_hdr *hdr; - struct kvec out_iov[3] = {}; + struct kvec in_iov[3], out_iov[3] = {}; int out_buftype[3] = {}; + int cmds[3]; bool islink; + int i, num_cmds = 0; int rc, rc2; data->adjust_tz = false; data->reparse_point = false; - if (strcmp(full_path, "")) - rc = -ENOENT; - else - rc = open_cached_dir(xid, tcon, full_path, cifs_sb, false, &cfid); - /* If it is a root and its handle is cached then use it */ - if (!rc) { - if (cfid->file_all_info_is_valid) { - memcpy(&data->fi, &cfid->file_all_info, sizeof(data->fi)); + /* + * BB TODO: Add support for using cached root handle in SMB3.1.1 POSIX. + * Create SMB2_query_posix_info worker function to do non-compounded + * query when we already have an open file handle for this. For now this + * is fast enough (always using the compounded version). + */ + if (!tcon->posix_extensions) { + if (*full_path) { + rc = -ENOENT; } else { - rc = SMB2_query_info(xid, tcon, cfid->fid.persistent_fid, - cfid->fid.volatile_fid, &data->fi); + rc = open_cached_dir(xid, tcon, full_path, + cifs_sb, false, &cfid); + } + /* If it is a root and its handle is cached then use it */ + if (!rc) { + if (cfid->file_all_info_is_valid) { + memcpy(&data->fi, &cfid->file_all_info, + sizeof(data->fi)); + } else { + rc = SMB2_query_info(xid, tcon, + cfid->fid.persistent_fid, + cfid->fid.volatile_fid, + &data->fi); + } + close_cached_dir(cfid); + return rc; } - close_cached_dir(cfid); - return rc; + cmds[num_cmds++] = SMB2_OP_QUERY_INFO; + } else { + cmds[num_cmds++] = SMB2_OP_POSIX_QUERY_INFO; } + in_iov[0].iov_base = data; + in_iov[0].iov_len = sizeof(*data); + in_iov[1] = in_iov[0]; + in_iov[2] = in_iov[0]; + cifs_get_readable_path(tcon, full_path, &cfile); - rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES, FILE_OPEN, - create_options, ACL_NO_MODE, data, SMB2_OP_QUERY_INFO, cfile, - NULL, NULL, out_iov, out_buftype); + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_READ_ATTRIBUTES, + FILE_OPEN, create_options, ACL_NO_MODE); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, + &oparms, in_iov, cmds, num_cmds, + cfile, out_iov, out_buftype, NULL); hdr = out_iov[0].iov_base; /* * If first iov is unset, then SMB session was dropped or we've got a @@ -620,18 +931,30 @@ int smb2_query_path_info(const unsigned int xid, switch (rc) { case 0: case -EOPNOTSUPP: + /* + * BB TODO: When support for special files added to Samba + * re-verify this path. + */ rc = parse_create_response(data, cifs_sb, &out_iov[0]); if (rc || !data->reparse_point) goto out; - create_options |= OPEN_REPARSE_POINT; - /* Failed on a symbolic link - query a reparse point info */ + cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA; + /* + * Skip SMB2_OP_GET_REPARSE if symlink already parsed in create + * response. + */ + if (data->reparse.tag != IO_REPARSE_TAG_SYMLINK) + cmds[num_cmds++] = SMB2_OP_GET_REPARSE; + + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, + FILE_READ_ATTRIBUTES | FILE_READ_EA, + FILE_OPEN, create_options | + OPEN_REPARSE_POINT, ACL_NO_MODE); cifs_get_readable_path(tcon, full_path, &cfile); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, - FILE_READ_ATTRIBUTES, FILE_OPEN, - create_options, ACL_NO_MODE, data, - SMB2_OP_QUERY_INFO, cfile, NULL, NULL, - NULL, NULL); + &oparms, in_iov, cmds, num_cmds, + cfile, NULL, NULL, NULL); break; case -EREMOTE: break; @@ -649,93 +972,8 @@ int smb2_query_path_info(const unsigned int xid, } out: - free_rsp_buf(out_buftype[0], out_iov[0].iov_base); - free_rsp_buf(out_buftype[1], out_iov[1].iov_base); - free_rsp_buf(out_buftype[2], out_iov[2].iov_base); - return rc; -} - -int smb311_posix_query_path_info(const unsigned int xid, - struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, - const char *full_path, - struct cifs_open_info_data *data, - struct cifs_sid *owner, - struct cifs_sid *group) -{ - int rc; - __u32 create_options = 0; - struct cifsFileInfo *cfile; - struct kvec out_iov[3] = {}; - int out_buftype[3] = {}; - __u8 *sidsbuf = NULL; - __u8 *sidsbuf_end = NULL; - size_t sidsbuflen = 0; - size_t owner_len, group_len; - - data->adjust_tz = false; - data->reparse_point = false; - - /* - * BB TODO: Add support for using the cached root handle. - * Create SMB2_query_posix_info worker function to do non-compounded query - * when we already have an open file handle for this. For now this is fast enough - * (always using the compounded version). - */ - - cifs_get_readable_path(tcon, full_path, &cfile); - rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES, FILE_OPEN, - create_options, ACL_NO_MODE, data, SMB2_OP_POSIX_QUERY_INFO, cfile, - &sidsbuf, &sidsbuflen, out_iov, out_buftype); - /* - * If first iov is unset, then SMB session was dropped or we've got a - * cached open file (@cfile). - */ - if (!out_iov[0].iov_base || out_buftype[0] == CIFS_NO_BUFFER) - goto out; - - switch (rc) { - case 0: - case -EOPNOTSUPP: - /* BB TODO: When support for special files added to Samba re-verify this path */ - rc = parse_create_response(data, cifs_sb, &out_iov[0]); - if (rc || !data->reparse_point) - goto out; - - create_options |= OPEN_REPARSE_POINT; - /* Failed on a symbolic link - query a reparse point info */ - cifs_get_readable_path(tcon, full_path, &cfile); - rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES, - FILE_OPEN, create_options, ACL_NO_MODE, data, - SMB2_OP_POSIX_QUERY_INFO, cfile, - &sidsbuf, &sidsbuflen, NULL, NULL); - break; - } - -out: - if (rc == 0) { - sidsbuf_end = sidsbuf + sidsbuflen; - - owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end); - if (owner_len == -1) { - rc = -EINVAL; - goto out; - } - memcpy(owner, sidsbuf, owner_len); - - group_len = posix_info_sid_size( - sidsbuf + owner_len, sidsbuf_end); - if (group_len == -1) { - rc = -EINVAL; - goto out; - } - memcpy(group, sidsbuf + owner_len, group_len); - } - - kfree(sidsbuf); - free_rsp_buf(out_buftype[0], out_iov[0].iov_base); - free_rsp_buf(out_buftype[1], out_iov[1].iov_base); - free_rsp_buf(out_buftype[2], out_iov[2].iov_base); + for (i = 0; i < ARRAY_SIZE(out_buftype); i++) + free_rsp_buf(out_buftype[i], out_iov[i].iov_base); return rc; } @@ -744,10 +982,14 @@ smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode, struct cifs_tcon *tcon, const char *name, struct cifs_sb_info *cifs_sb) { - return smb2_compound_op(xid, tcon, cifs_sb, name, - FILE_WRITE_ATTRIBUTES, FILE_CREATE, - CREATE_NOT_FILE, mode, NULL, SMB2_OP_MKDIR, - NULL, NULL, NULL, NULL, NULL); + struct cifs_open_parms oparms; + + oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES, + FILE_CREATE, CREATE_NOT_FILE, mode); + return smb2_compound_op(xid, tcon, cifs_sb, + name, &oparms, NULL, + &(int){SMB2_OP_MKDIR}, 1, + NULL, NULL, NULL, NULL); } void @@ -755,21 +997,26 @@ smb2_mkdir_setinfo(struct inode *inode, const char *name, struct cifs_sb_info *cifs_sb, struct cifs_tcon *tcon, const unsigned int xid) { - FILE_BASIC_INFO data; + struct cifs_open_parms oparms; + FILE_BASIC_INFO data = {}; struct cifsInodeInfo *cifs_i; struct cifsFileInfo *cfile; + struct kvec in_iov; u32 dosattrs; int tmprc; - memset(&data, 0, sizeof(data)); + in_iov.iov_base = &data; + in_iov.iov_len = sizeof(data); cifs_i = CIFS_I(inode); dosattrs = cifs_i->cifsAttrs | ATTR_READONLY; data.Attributes = cpu_to_le32(dosattrs); cifs_get_writable_path(tcon, name, FIND_WR_ANY, &cfile); + oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES, + FILE_CREATE, CREATE_NOT_FILE, ACL_NO_MODE); tmprc = smb2_compound_op(xid, tcon, cifs_sb, name, - FILE_WRITE_ATTRIBUTES, FILE_CREATE, - CREATE_NOT_FILE, ACL_NO_MODE, - &data, SMB2_OP_SET_INFO, cfile, NULL, NULL, NULL, NULL); + &oparms, &in_iov, + &(int){SMB2_OP_SET_INFO}, 1, + cfile, NULL, NULL, NULL); if (tmprc == 0) cifs_i->cifsAttrs = dosattrs; } @@ -778,27 +1025,48 @@ int smb2_rmdir(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct cifs_sb_info *cifs_sb) { + struct cifs_open_parms oparms; + drop_cached_dir_by_name(xid, tcon, name, cifs_sb); - return smb2_compound_op(xid, tcon, cifs_sb, name, DELETE, FILE_OPEN, - CREATE_NOT_FILE, ACL_NO_MODE, - NULL, SMB2_OP_RMDIR, NULL, NULL, NULL, NULL, NULL); + oparms = CIFS_OPARMS(cifs_sb, tcon, name, DELETE, + FILE_OPEN, CREATE_NOT_FILE, ACL_NO_MODE); + return smb2_compound_op(xid, tcon, cifs_sb, + name, &oparms, NULL, + &(int){SMB2_OP_RMDIR}, 1, + NULL, NULL, NULL, NULL); } int smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name, - struct cifs_sb_info *cifs_sb) + struct cifs_sb_info *cifs_sb, struct dentry *dentry) { - return smb2_compound_op(xid, tcon, cifs_sb, name, DELETE, FILE_OPEN, - CREATE_DELETE_ON_CLOSE | OPEN_REPARSE_POINT, - ACL_NO_MODE, NULL, SMB2_OP_DELETE, NULL, NULL, NULL, NULL, NULL); + struct cifs_open_parms oparms; + + oparms = CIFS_OPARMS(cifs_sb, tcon, name, + DELETE, FILE_OPEN, + CREATE_DELETE_ON_CLOSE | OPEN_REPARSE_POINT, + ACL_NO_MODE); + int rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms, + NULL, &(int){SMB2_OP_DELETE}, 1, + NULL, NULL, NULL, dentry); + if (rc == -EINVAL) { + cifs_dbg(FYI, "invalid lease key, resending request without lease"); + rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms, + NULL, &(int){SMB2_OP_DELETE}, 1, + NULL, NULL, NULL, NULL); + } + return rc; } static int smb2_set_path_attr(const unsigned int xid, struct cifs_tcon *tcon, const char *from_name, const char *to_name, struct cifs_sb_info *cifs_sb, __u32 create_options, __u32 access, - int command, struct cifsFileInfo *cfile) + int command, struct cifsFileInfo *cfile, + struct dentry *dentry) { + struct cifs_open_parms oparms; + struct kvec in_iov; __le16 *smb2_to_name = NULL; int rc; @@ -807,9 +1075,13 @@ static int smb2_set_path_attr(const unsigned int xid, struct cifs_tcon *tcon, rc = -ENOMEM; goto smb2_rename_path; } - rc = smb2_compound_op(xid, tcon, cifs_sb, from_name, access, - FILE_OPEN, create_options, ACL_NO_MODE, smb2_to_name, - command, cfile, NULL, NULL, NULL, NULL); + in_iov.iov_base = smb2_to_name; + in_iov.iov_len = 2 * UniStrnlen((wchar_t *)smb2_to_name, PATH_MAX); + oparms = CIFS_OPARMS(cifs_sb, tcon, from_name, access, FILE_OPEN, + create_options, ACL_NO_MODE); + rc = smb2_compound_op(xid, tcon, cifs_sb, from_name, + &oparms, &in_iov, &command, 1, + cfile, NULL, NULL, dentry); smb2_rename_path: kfree(smb2_to_name); return rc; @@ -827,8 +1099,14 @@ int smb2_rename_path(const unsigned int xid, drop_cached_dir_by_name(xid, tcon, from_name, cifs_sb); cifs_get_writable_path(tcon, from_name, FIND_WR_WITH_DELETE, &cfile); - return smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, - co, DELETE, SMB2_OP_RENAME, cfile); + int rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, + co, DELETE, SMB2_OP_RENAME, cfile, source_dentry); + if (rc == -EINVAL) { + cifs_dbg(FYI, "invalid lease key, resending request without lease"); + rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, + co, DELETE, SMB2_OP_RENAME, cfile, NULL); + } + return rc; } int smb2_create_hardlink(const unsigned int xid, @@ -841,31 +1119,51 @@ int smb2_create_hardlink(const unsigned int xid, return smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, co, FILE_READ_ATTRIBUTES, - SMB2_OP_HARDLINK, NULL); + SMB2_OP_HARDLINK, NULL, NULL); } int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon, const char *full_path, __u64 size, - struct cifs_sb_info *cifs_sb, bool set_alloc) + struct cifs_sb_info *cifs_sb, bool set_alloc, + struct dentry *dentry) { - __le64 eof = cpu_to_le64(size); + struct cifs_open_parms oparms; struct cifsFileInfo *cfile; + struct kvec in_iov; + __le64 eof = cpu_to_le64(size); + int rc; + in_iov.iov_base = &eof; + in_iov.iov_len = sizeof(eof); cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); - return smb2_compound_op(xid, tcon, cifs_sb, full_path, - FILE_WRITE_DATA, FILE_OPEN, 0, ACL_NO_MODE, - &eof, SMB2_OP_SET_EOF, cfile, NULL, NULL, NULL, NULL); + + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_DATA, + FILE_OPEN, 0, ACL_NO_MODE); + rc = smb2_compound_op(xid, tcon, cifs_sb, + full_path, &oparms, &in_iov, + &(int){SMB2_OP_SET_EOF}, 1, + cfile, NULL, NULL, dentry); + if (rc == -EINVAL) { + cifs_dbg(FYI, "invalid lease key, resending request without lease"); + rc = smb2_compound_op(xid, tcon, cifs_sb, + full_path, &oparms, &in_iov, + &(int){SMB2_OP_SET_EOF}, 1, + cfile, NULL, NULL, NULL); + } + return rc; } int smb2_set_file_info(struct inode *inode, const char *full_path, FILE_BASIC_INFO *buf, const unsigned int xid) { + struct cifs_open_parms oparms; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct tcon_link *tlink; struct cifs_tcon *tcon; struct cifsFileInfo *cfile; + struct kvec in_iov = { .iov_base = buf, .iov_len = sizeof(*buf), }; int rc; if ((buf->CreationTime == 0) && (buf->LastAccessTime == 0) && @@ -879,10 +1177,100 @@ smb2_set_file_info(struct inode *inode, const char *full_path, tcon = tlink_tcon(tlink); cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); - rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, - FILE_WRITE_ATTRIBUTES, FILE_OPEN, - 0, ACL_NO_MODE, buf, SMB2_OP_SET_INFO, cfile, - NULL, NULL, NULL, NULL); + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_ATTRIBUTES, + FILE_OPEN, 0, ACL_NO_MODE); + rc = smb2_compound_op(xid, tcon, cifs_sb, + full_path, &oparms, &in_iov, + &(int){SMB2_OP_SET_INFO}, 1, + cfile, NULL, NULL, NULL); cifs_put_tlink(tlink); return rc; } + +struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, + struct super_block *sb, + const unsigned int xid, + struct cifs_tcon *tcon, + const char *full_path, + struct kvec *reparse_iov, + struct kvec *xattr_iov) +{ + struct cifs_open_parms oparms; + struct cifs_sb_info *cifs_sb = CIFS_SB(sb); + struct cifsFileInfo *cfile; + struct inode *new = NULL; + struct kvec in_iov[2]; + int cmds[2]; + int rc; + + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, + SYNCHRONIZE | DELETE | + FILE_READ_ATTRIBUTES | + FILE_WRITE_ATTRIBUTES, + FILE_CREATE, + CREATE_NOT_DIR | OPEN_REPARSE_POINT, + ACL_NO_MODE); + if (xattr_iov) + oparms.ea_cctx = xattr_iov; + + cmds[0] = SMB2_OP_SET_REPARSE; + in_iov[0] = *reparse_iov; + in_iov[1].iov_base = data; + in_iov[1].iov_len = sizeof(*data); + + if (tcon->posix_extensions) { + cmds[1] = SMB2_OP_POSIX_QUERY_INFO; + cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, + in_iov, cmds, 2, cfile, NULL, NULL, NULL); + if (!rc) { + rc = smb311_posix_get_inode_info(&new, full_path, + data, sb, xid); + } + } else { + cmds[1] = SMB2_OP_QUERY_INFO; + cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, + in_iov, cmds, 2, cfile, NULL, NULL, NULL); + if (!rc) { + rc = cifs_get_inode_info(&new, full_path, + data, sb, xid, NULL); + } + } + return rc ? ERR_PTR(rc) : new; +} + +int smb2_query_reparse_point(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, + u32 *tag, struct kvec *rsp, + int *rsp_buftype) +{ + struct cifs_open_parms oparms; + struct cifs_open_info_data data = {}; + struct cifsFileInfo *cfile; + struct kvec in_iov = { .iov_base = &data, .iov_len = sizeof(data), }; + int rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, full_path); + + cifs_get_readable_path(tcon, full_path, &cfile); + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_READ_ATTRIBUTES, + FILE_OPEN, OPEN_REPARSE_POINT, ACL_NO_MODE); + rc = smb2_compound_op(xid, tcon, cifs_sb, + full_path, &oparms, &in_iov, + &(int){SMB2_OP_GET_REPARSE}, 1, + cfile, NULL, NULL, NULL); + if (rc) + goto out; + + *tag = data.reparse.tag; + *rsp = data.reparse.io.iov; + *rsp_buftype = data.reparse.io.buftype; + memset(&data.reparse.io.iov, 0, sizeof(data.reparse.io.iov)); + data.reparse.io.buftype = CIFS_NO_BUFFER; +out: + cifs_free_open_info(&data); + return rc; +} diff --git a/fs/smb/client/smb2maperror.c b/fs/smb/client/smb2maperror.c index 1a90dd78b238..ac1895358908 100644 --- a/fs/smb/client/smb2maperror.c +++ b/fs/smb/client/smb2maperror.c @@ -1210,6 +1210,8 @@ static const struct status_to_posix_error smb2_error_map_table[] = { {STATUS_INVALID_TASK_INDEX, -EIO, "STATUS_INVALID_TASK_INDEX"}, {STATUS_THREAD_ALREADY_IN_TASK, -EIO, "STATUS_THREAD_ALREADY_IN_TASK"}, {STATUS_CALLBACK_BYPASS, -EIO, "STATUS_CALLBACK_BYPASS"}, + {STATUS_SERVER_UNAVAILABLE, -EAGAIN, "STATUS_SERVER_UNAVAILABLE"}, + {STATUS_FILE_NOT_AVAILABLE, -EAGAIN, "STATUS_FILE_NOT_AVAILABLE"}, {STATUS_PORT_CLOSED, -EIO, "STATUS_PORT_CLOSED"}, {STATUS_MESSAGE_LOST, -EIO, "STATUS_MESSAGE_LOST"}, {STATUS_INVALID_MESSAGE, -EIO, "STATUS_INVALID_MESSAGE"}, diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index cc72be5a93a9..677ef6f99a5b 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -767,7 +767,7 @@ smb2_cancelled_close_fid(struct work_struct *work) if (rc) cifs_tcon_dbg(VFS, "Close cancelled mid failed rc:%d\n", rc); - cifs_put_tcon(tcon); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close_fid); kfree(cancelled); } @@ -811,6 +811,8 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid, if (tcon->tc_count <= 0) { struct TCP_Server_Info *server = NULL; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_cancelled_close); WARN_ONCE(tcon->tc_count < 0, "tcon refcount is negative"); spin_unlock(&cifs_tcp_ses_lock); @@ -823,12 +825,14 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid, return 0; } tcon->tc_count++; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_cancelled_close); spin_unlock(&cifs_tcp_ses_lock); rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0, persistent_fid, volatile_fid); if (rc) - cifs_put_tcon(tcon); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close); return rc; } @@ -856,7 +860,7 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve rsp->PersistentFileId, rsp->VolatileFileId); if (rc) - cifs_put_tcon(tcon); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_mid); return rc; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 04fea874d0a3..66cfce456263 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -28,6 +28,7 @@ #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 @@ -1108,7 +1109,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, { struct smb2_compound_vars *vars; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; struct smb_rqst *rqst; struct kvec *rsp_iov; __le16 *utf16_path = NULL; @@ -1124,6 +1125,13 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, struct smb2_file_full_ea_info *ea = NULL; 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; @@ -1197,6 +1205,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, .disposition = FILE_OPEN, .create_options = cifs_create_options(cifs_sb, 0), .fid = &fid, + .replay = !!(retries), }; rc = SMB2_open_init(tcon, server, @@ -1244,6 +1253,12 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, 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); @@ -1260,6 +1275,11 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, 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 @@ -1485,7 +1505,7 @@ smb2_ioctl_query_info(const unsigned int xid, struct smb_rqst *rqst; struct kvec *rsp_iov; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; char __user *arg = (char __user *)p; struct smb_query_info qi; struct smb_query_info __user *pqi; @@ -1502,6 +1522,13 @@ smb2_ioctl_query_info(const unsigned int xid, 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) @@ -1545,6 +1572,7 @@ smb2_ioctl_query_info(const unsigned int xid, .disposition = FILE_OPEN, .create_options = cifs_create_options(cifs_sb, create_options), .fid = &fid, + .replay = !!(retries), }; if (qi.flags & PASSTHRU_FSCTL) { @@ -1642,6 +1670,12 @@ smb2_ioctl_query_info(const unsigned int xid, 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); @@ -1702,6 +1736,11 @@ smb2_ioctl_query_info(const unsigned int xid, kfree(buffer); free_vars: kfree(vars); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -1932,7 +1971,6 @@ static int smb2_set_file_size(const unsigned int xid, struct cifs_tcon *tcon, struct cifsFileInfo *cfile, __u64 size, bool set_alloc) { - __le64 eof = cpu_to_le64(size); struct inode *inode; /* @@ -1949,7 +1987,7 @@ smb2_set_file_size(const unsigned int xid, struct cifs_tcon *tcon, } return SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, - cfile->fid.volatile_fid, cfile->pid, &eof); + cfile->fid.volatile_fid, cfile->pid, size); } static int @@ -2229,8 +2267,14 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_open_parms oparms; struct smb2_query_directory_rsp *qd_rsp = NULL; struct smb2_create_rsp *op_rsp = NULL; - struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses); - int retry_count = 0; + struct TCP_Server_Info *server; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + oplock = SMB2_OPLOCK_LEVEL_NONE; + server = cifs_pick_channel(tcon->ses); utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); if (!utf16_path) @@ -2255,6 +2299,7 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, .disposition = FILE_OPEN, .create_options = cifs_create_options(cifs_sb, 0), .fid = fid, + .replay = !!(retries), }; rc = SMB2_open_init(tcon, server, @@ -2280,14 +2325,15 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, smb2_set_related(&rqst[1]); -again: + if (retries) { + smb2_set_replay(server, &rqst[0]); + smb2_set_replay(server, &rqst[1]); + } + rc = compound_send_recv(xid, tcon->ses, server, flags, 2, rqst, resp_buftype, rsp_iov); - if (rc == -EAGAIN && retry_count++ < 10) - goto again; - /* If the open failed there is nothing to do */ op_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; if (op_rsp == NULL || op_rsp->hdr.Status != STATUS_SUCCESS) { @@ -2335,6 +2381,11 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon, SMB2_query_directory_free(&rqst[1]); free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -2461,6 +2512,22 @@ smb2_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid, CIFS_CACHE_READ(cinode) ? 1 : 0); } +void +smb2_set_replay(struct TCP_Server_Info *server, struct smb_rqst *rqst) +{ + struct smb2_hdr *shdr; + + if (server->dialect < SMB30_PROT_ID) + return; + + shdr = (struct smb2_hdr *)(rqst->rq_iov[0].iov_base); + if (shdr == NULL) { + cifs_dbg(FYI, "shdr NULL in smb2_set_related\n"); + return; + } + shdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION; +} + void smb2_set_related(struct smb_rqst *rqst) { @@ -2533,6 +2600,27 @@ smb2_set_next_command(struct cifs_tcon *tcon, struct smb_rqst *rqst) shdr->NextCommand = cpu_to_le32(len); } +/* + * helper function for exponential backoff and check if replayable + */ +bool smb2_should_replay(struct cifs_tcon *tcon, + int *pretries, + int *pcur_sleep) +{ + if (!pretries || !pcur_sleep) + return false; + + if (tcon->retry || (*pretries)++ < tcon->ses->server->retrans) { + msleep(*pcur_sleep); + (*pcur_sleep) = ((*pcur_sleep) << 1); + if ((*pcur_sleep) > CIFS_MAX_SLEEP) + (*pcur_sleep) = CIFS_MAX_SLEEP; + return true; + } + + return false; +} + /* * Passes the query info response back to the caller on success. * Caller need to free this with free_rsp_buf(). @@ -2546,7 +2634,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, { struct smb2_compound_vars *vars; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; int flags = CIFS_CP_CREATE_CLOSE_OP; struct smb_rqst *rqst; int resp_buftype[3]; @@ -2557,6 +2645,13 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, int rc; __le16 *utf16_path; struct cached_fid *cfid = NULL; + 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 (!path) path = ""; @@ -2593,6 +2688,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, .disposition = FILE_OPEN, .create_options = cifs_create_options(cifs_sb, 0), .fid = &fid, + .replay = !!(retries), }; rc = SMB2_open_init(tcon, server, @@ -2637,6 +2733,14 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, goto qic_exit; smb2_set_related(&rqst[2]); + if (retries) { + if (!cfid) { + smb2_set_replay(server, &rqst[0]); + smb2_set_replay(server, &rqst[2]); + } + smb2_set_replay(server, &rqst[1]); + } + if (cfid) { rc = compound_send_recv(xid, ses, server, flags, 1, &rqst[1], @@ -2669,6 +2773,11 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, 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; } @@ -2806,8 +2915,11 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, tcon = list_first_entry_or_null(&ses->tcon_list, struct cifs_tcon, tcon_list); - if (tcon) + if (tcon) { tcon->tc_count++; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_dfs_refer); + } spin_unlock(&cifs_tcp_ses_lock); } @@ -2871,6 +2983,8 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, /* ipc tcons are not refcounted */ spin_lock(&cifs_tcp_ses_lock); tcon->tc_count--; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_dec_dfs_refer); /* tc_count can never go negative */ WARN_ON(tcon->tc_count < 0); spin_unlock(&cifs_tcp_ses_lock); @@ -2881,260 +2995,6 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, return rc; } -/* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */ -static int parse_reparse_posix(struct reparse_posix_data *buf, - struct cifs_sb_info *cifs_sb, - struct cifs_open_info_data *data) -{ - unsigned int len; - u64 type; - - switch ((type = le64_to_cpu(buf->InodeType))) { - case NFS_SPECFILE_LNK: - len = le16_to_cpu(buf->ReparseDataLength); - data->symlink_target = cifs_strndup_from_utf16(buf->DataBuffer, - len, true, - cifs_sb->local_nls); - if (!data->symlink_target) - return -ENOMEM; - convert_delimiter(data->symlink_target, '/'); - cifs_dbg(FYI, "%s: target path: %s\n", - __func__, data->symlink_target); - break; - case NFS_SPECFILE_CHR: - case NFS_SPECFILE_BLK: - case NFS_SPECFILE_FIFO: - case NFS_SPECFILE_SOCK: - break; - default: - cifs_dbg(VFS, "%s: unhandled inode type: 0x%llx\n", - __func__, type); - return -EOPNOTSUPP; - } - return 0; -} - -static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, - u32 plen, bool unicode, - struct cifs_sb_info *cifs_sb, - struct cifs_open_info_data *data) -{ - unsigned int len; - unsigned int offs; - - /* We handle Symbolic Link reparse tag here. See: MS-FSCC 2.1.2.4 */ - - offs = le16_to_cpu(sym->SubstituteNameOffset); - len = le16_to_cpu(sym->SubstituteNameLength); - if (offs + 20 > plen || offs + len + 20 > plen) { - cifs_dbg(VFS, "srv returned malformed symlink buffer\n"); - return -EIO; - } - - data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs, - len, unicode, - cifs_sb->local_nls); - if (!data->symlink_target) - return -ENOMEM; - - convert_delimiter(data->symlink_target, '/'); - cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target); - - return 0; -} - -int parse_reparse_point(struct reparse_data_buffer *buf, - u32 plen, struct cifs_sb_info *cifs_sb, - bool unicode, struct cifs_open_info_data *data) -{ - if (plen < sizeof(*buf)) { - cifs_dbg(VFS, "%s: reparse buffer is too small. Must be at least 8 bytes but was %d\n", - __func__, plen); - return -EIO; - } - - if (plen < le16_to_cpu(buf->ReparseDataLength) + sizeof(*buf)) { - cifs_dbg(VFS, "%s: invalid reparse buf length: %d\n", - __func__, plen); - return -EIO; - } - - data->reparse.buf = buf; - - /* See MS-FSCC 2.1.2 */ - switch (le32_to_cpu(buf->ReparseTag)) { - case IO_REPARSE_TAG_NFS: - return parse_reparse_posix((struct reparse_posix_data *)buf, - cifs_sb, data); - case IO_REPARSE_TAG_SYMLINK: - return parse_reparse_symlink( - (struct reparse_symlink_data_buffer *)buf, - plen, unicode, cifs_sb, data); - case IO_REPARSE_TAG_LX_SYMLINK: - case IO_REPARSE_TAG_AF_UNIX: - case IO_REPARSE_TAG_LX_FIFO: - case IO_REPARSE_TAG_LX_CHR: - case IO_REPARSE_TAG_LX_BLK: - return 0; - default: - cifs_dbg(VFS, "%s: unhandled reparse tag: 0x%08x\n", - __func__, le32_to_cpu(buf->ReparseTag)); - return -EOPNOTSUPP; - } -} - -static int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, - struct kvec *rsp_iov, - struct cifs_open_info_data *data) -{ - struct reparse_data_buffer *buf; - struct smb2_ioctl_rsp *io = rsp_iov->iov_base; - u32 plen = le32_to_cpu(io->OutputCount); - - buf = (struct reparse_data_buffer *)((u8 *)io + - le32_to_cpu(io->OutputOffset)); - return parse_reparse_point(buf, plen, cifs_sb, true, data); -} - -static int smb2_query_reparse_point(const unsigned int xid, - struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, - const char *full_path, - u32 *tag, struct kvec *rsp, - int *rsp_buftype) -{ - struct smb2_compound_vars *vars; - int rc; - __le16 *utf16_path = NULL; - __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; - struct cifs_open_parms oparms; - struct cifs_fid fid; - struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses); - int flags = CIFS_CP_CREATE_CLOSE_OP; - struct smb_rqst *rqst; - int resp_buftype[3]; - struct kvec *rsp_iov; - struct smb2_ioctl_rsp *ioctl_rsp; - struct reparse_data_buffer *reparse_buf; - u32 off, count, len; - - cifs_dbg(FYI, "%s: path: %s\n", __func__, full_path); - - if (smb3_encryption_required(tcon)) - flags |= CIFS_TRANSFORM_REQ; - - utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); - if (!utf16_path) - return -ENOMEM; - - 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; - - /* - * setup smb2open - TODO add optimization to call cifs_get_readable_path - * to see if there is a handle already open that we can use - */ - rqst[0].rq_iov = vars->open_iov; - rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; - - 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, OPEN_REPARSE_POINT), - .fid = &fid, - }; - - rc = SMB2_open_init(tcon, server, - &rqst[0], &oplock, &oparms, utf16_path); - if (rc) - goto query_rp_exit; - smb2_set_next_command(tcon, &rqst[0]); - - - /* IOCTL */ - rqst[1].rq_iov = vars->io_iov; - rqst[1].rq_nvec = SMB2_IOCTL_IOV_SIZE; - - rc = SMB2_ioctl_init(tcon, server, - &rqst[1], COMPOUND_FID, - COMPOUND_FID, FSCTL_GET_REPARSE_POINT, NULL, 0, - CIFSMaxBufSize - - MAX_SMB2_CREATE_RESPONSE_SIZE - - MAX_SMB2_CLOSE_RESPONSE_SIZE); - if (rc) - goto query_rp_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 query_rp_exit; - - smb2_set_related(&rqst[2]); - - rc = compound_send_recv(xid, tcon->ses, server, - flags, 3, rqst, - resp_buftype, rsp_iov); - - ioctl_rsp = rsp_iov[1].iov_base; - - /* - * Open was successful and we got an ioctl response. - */ - if (rc == 0) { - /* See MS-FSCC 2.3.23 */ - off = le32_to_cpu(ioctl_rsp->OutputOffset); - count = le32_to_cpu(ioctl_rsp->OutputCount); - if (check_add_overflow(off, count, &len) || - len > rsp_iov[1].iov_len) { - cifs_tcon_dbg(VFS, "%s: invalid ioctl: off=%d count=%d\n", - __func__, off, count); - rc = -EIO; - goto query_rp_exit; - } - - reparse_buf = (void *)((u8 *)ioctl_rsp + off); - len = sizeof(*reparse_buf); - if (count < len || - count < le16_to_cpu(reparse_buf->ReparseDataLength) + len) { - cifs_tcon_dbg(VFS, "%s: invalid ioctl: off=%d count=%d\n", - __func__, off, count); - rc = -EIO; - goto query_rp_exit; - } - *tag = le32_to_cpu(reparse_buf->ReparseTag); - *rsp = rsp_iov[1]; - *rsp_buftype = resp_buftype[1]; - resp_buftype[1] = CIFS_NO_BUFFER; - } - - query_rp_exit: - SMB2_open_free(&rqst[0]); - SMB2_ioctl_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); - return rc; -} - static struct cifs_ntsd * get_smb2_acl_by_fid(struct cifs_sb_info *cifs_sb, const struct cifs_fid *cifsfid, u32 *pacllen, u32 info) @@ -3335,7 +3195,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, unsigned long long new_size; long rc; unsigned int xid; - __le64 eof; xid = get_xid(); @@ -3365,9 +3224,8 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, */ new_size = offset + len; if (keep_size == false && (unsigned long long)i_size_read(inode) < new_size) { - eof = cpu_to_le64(new_size); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, - cfile->fid.volatile_fid, cfile->pid, &eof); + cfile->fid.volatile_fid, cfile->pid, new_size); if (rc >= 0) { truncate_setsize(inode, new_size); fscache_resize_cookie(cifs_inode_cookie(inode), new_size); @@ -3560,7 +3418,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; long rc = -EOPNOTSUPP; unsigned int xid; - __le64 eof; + loff_t new_eof; xid = get_xid(); @@ -3589,14 +3447,14 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) smb2_set_sparse(xid, tcon, cfile, inode, false); - eof = cpu_to_le64(off + len); + new_eof = off + len; rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, - cfile->fid.volatile_fid, cfile->pid, &eof); + cfile->fid.volatile_fid, cfile->pid, new_eof); if (rc == 0) { - cifsi->server_eof = off + len; - cifs_setsize(inode, off + len); + cifsi->server_eof = new_eof; + cifs_setsize(inode, new_eof); cifs_truncate_page(inode->i_mapping, inode->i_size); - truncate_setsize(inode, off + len); + truncate_setsize(inode, new_eof); } goto out; } @@ -3687,8 +3545,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, struct inode *inode = file_inode(file); struct cifsFileInfo *cfile = file->private_data; struct cifsInodeInfo *cifsi = CIFS_I(inode); - __le64 eof; - loff_t old_eof; + loff_t old_eof, new_eof; xid = get_xid(); @@ -3713,9 +3570,9 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, if (rc < 0) goto out_2; - eof = cpu_to_le64(old_eof - len); + new_eof = old_eof - len; rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, - cfile->fid.volatile_fid, cfile->pid, &eof); + cfile->fid.volatile_fid, cfile->pid, new_eof); if (rc < 0) goto out_2; @@ -3739,8 +3596,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, unsigned int xid; struct cifsFileInfo *cfile = file->private_data; struct inode *inode = file_inode(file); - __le64 eof; - __u64 count, old_eof; + __u64 count, old_eof, new_eof; xid = get_xid(); @@ -3753,20 +3609,20 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, } count = old_eof - off; - eof = cpu_to_le64(old_eof + len); + new_eof = old_eof + len; filemap_invalidate_lock(inode->i_mapping); - rc = filemap_write_and_wait_range(inode->i_mapping, off, old_eof + len - 1); + rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); if (rc < 0) goto out_2; truncate_pagecache_range(inode, off, old_eof); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, - cfile->fid.volatile_fid, cfile->pid, &eof); + cfile->fid.volatile_fid, cfile->pid, new_eof); if (rc < 0) goto out_2; - truncate_setsize(inode, old_eof + len); + truncate_setsize(inode, new_eof); fscache_resize_cookie(cifs_inode_cookie(inode), i_size_read(inode)); rc = smb2_copychunk_range(xid, cfile, cfile, off, count, off + len); @@ -5105,98 +4961,107 @@ static int smb2_next_header(struct TCP_Server_Info *server, char *buf, return 0; } -static int -smb2_make_node(unsigned int xid, struct inode *inode, - struct dentry *dentry, struct cifs_tcon *tcon, - const char *full_path, umode_t mode, dev_t dev) +static int __cifs_sfu_make_node(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev) { + struct TCP_Server_Info *server = tcon->ses->server; + struct cifs_open_parms oparms; + struct cifs_io_parms io_parms = {}; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); - int rc = -EPERM; - struct cifs_open_info_data buf = {}; - struct cifs_io_parms io_parms = {0}; - __u32 oplock = 0; struct cifs_fid fid; - struct cifs_open_parms oparms; unsigned int bytes_written; - struct win_dev *pdev; + struct win_dev pdev = {}; struct kvec iov[2]; + __u32 oplock = server->oplocks ? REQ_OPLOCK : 0; + int rc; - /* - * Check if mounted with mount parm 'sfu' mount parm. - * SFU emulation should work with all servers, but only - * supports block and char device (no socket & fifo), - * and was used by default in earlier versions of Windows - */ - if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL)) - return rc; + switch (mode & S_IFMT) { + case S_IFCHR: + strscpy(pdev.type, "IntxCHR", strlen("IntxChr")); + pdev.major = cpu_to_le64(MAJOR(dev)); + pdev.minor = cpu_to_le64(MINOR(dev)); + break; + case S_IFBLK: + strscpy(pdev.type, "IntxBLK", strlen("IntxBLK")); + pdev.major = cpu_to_le64(MAJOR(dev)); + pdev.minor = cpu_to_le64(MINOR(dev)); + break; + case S_IFIFO: + strscpy(pdev.type, "LnxFIFO", strlen("LnxFIFO")); + break; + default: + return -EPERM; + } - /* - * TODO: Add ability to create instead via reparse point. Windows (e.g. - * their current NFS server) uses this approach to expose special files - * over SMB2/SMB3 and Samba will do this with SMB3.1.1 POSIX Extensions - */ + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, GENERIC_WRITE, + FILE_CREATE, CREATE_NOT_DIR | + CREATE_OPTION_SPECIAL, ACL_NO_MODE); + oparms.fid = &fid; - if (!S_ISCHR(mode) && !S_ISBLK(mode) && !S_ISFIFO(mode)) + rc = server->ops->open(xid, &oparms, &oplock, NULL); + if (rc) return rc; - cifs_dbg(FYI, "sfu compat create special file\n"); + io_parms.pid = current->tgid; + io_parms.tcon = tcon; + io_parms.length = sizeof(pdev); + iov[1].iov_base = &pdev; + iov[1].iov_len = sizeof(pdev); - oparms = (struct cifs_open_parms) { - .tcon = tcon, - .cifs_sb = cifs_sb, - .desired_access = GENERIC_WRITE, - .create_options = cifs_create_options(cifs_sb, CREATE_NOT_DIR | - CREATE_OPTION_SPECIAL), - .disposition = FILE_CREATE, - .path = full_path, - .fid = &fid, - }; + rc = server->ops->sync_write(xid, &fid, &io_parms, + &bytes_written, iov, 1); + server->ops->close(xid, tcon, &fid); + return rc; +} - if (tcon->ses->server->oplocks) - oplock = REQ_OPLOCK; - else - oplock = 0; - rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, &buf); +int cifs_sfu_make_node(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev) +{ + struct inode *new = NULL; + int rc; + + rc = __cifs_sfu_make_node(xid, inode, dentry, tcon, + full_path, mode, dev); if (rc) return rc; + if (tcon->posix_extensions) { + rc = smb311_posix_get_inode_info(&new, full_path, NULL, + inode->i_sb, xid); + } else if (tcon->unix_ext) { + rc = cifs_get_inode_info_unix(&new, full_path, + inode->i_sb, xid); + } else { + rc = cifs_get_inode_info(&new, full_path, NULL, + inode->i_sb, xid, NULL); + } + if (!rc) + d_instantiate(dentry, new); + return rc; +} + +static int smb2_make_node(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + int rc; + /* - * BB Do not bother to decode buf since no local inode yet to put - * timestamps in, but we can reuse it safely. + * Check if mounted with mount parm 'sfu' mount parm. + * SFU emulation should work with all servers, but only + * supports block and char device (no socket & fifo), + * and was used by default in earlier versions of Windows */ - - pdev = (struct win_dev *)&buf.fi; - io_parms.pid = current->tgid; - io_parms.tcon = tcon; - io_parms.offset = 0; - io_parms.length = sizeof(struct win_dev); - iov[1].iov_base = &buf.fi; - iov[1].iov_len = sizeof(struct win_dev); - if (S_ISCHR(mode)) { - memcpy(pdev->type, "IntxCHR", 8); - pdev->major = cpu_to_le64(MAJOR(dev)); - pdev->minor = cpu_to_le64(MINOR(dev)); - rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms, - &bytes_written, iov, 1); - } else if (S_ISBLK(mode)) { - memcpy(pdev->type, "IntxBLK", 8); - pdev->major = cpu_to_le64(MAJOR(dev)); - pdev->minor = cpu_to_le64(MINOR(dev)); - rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms, - &bytes_written, iov, 1); - } else if (S_ISFIFO(mode)) { - memcpy(pdev->type, "LnxFIFO", 8); - pdev->major = 0; - pdev->minor = 0; - rc = tcon->ses->server->ops->sync_write(xid, &fid, &io_parms, - &bytes_written, iov, 1); - } - tcon->ses->server->ops->close(xid, tcon, &fid); - d_drop(dentry); - - /* FIXME: add code here to set EAs */ - - cifs_free_open_info(&buf); + if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) { + rc = cifs_sfu_make_node(xid, inode, dentry, tcon, + full_path, mode, dev); + } else { + rc = smb2_mknod_reparse(xid, inode, dentry, tcon, + full_path, mode, dev); + } return rc; } @@ -5253,6 +5118,7 @@ struct smb_version_operations smb20_operations = { .parse_reparse_point = smb2_parse_reparse_point, .query_mf_symlink = smb3_query_mf_symlink, .create_mf_symlink = smb3_create_mf_symlink, + .create_reparse_symlink = smb2_create_reparse_symlink, .open = smb2_open_file, .set_fid = smb2_set_fid, .close = smb2_close_file, @@ -5355,6 +5221,7 @@ struct smb_version_operations smb21_operations = { .parse_reparse_point = smb2_parse_reparse_point, .query_mf_symlink = smb3_query_mf_symlink, .create_mf_symlink = smb3_create_mf_symlink, + .create_reparse_symlink = smb2_create_reparse_symlink, .open = smb2_open_file, .set_fid = smb2_set_fid, .close = smb2_close_file, @@ -5461,6 +5328,7 @@ struct smb_version_operations smb30_operations = { .parse_reparse_point = smb2_parse_reparse_point, .query_mf_symlink = smb3_query_mf_symlink, .create_mf_symlink = smb3_create_mf_symlink, + .create_reparse_symlink = smb2_create_reparse_symlink, .open = smb2_open_file, .set_fid = smb2_set_fid, .close = smb2_close_file, @@ -5576,6 +5444,7 @@ struct smb_version_operations smb311_operations = { .parse_reparse_point = smb2_parse_reparse_point, .query_mf_symlink = smb3_query_mf_symlink, .create_mf_symlink = smb3_create_mf_symlink, + .create_reparse_symlink = smb2_create_reparse_symlink, .open = smb2_open_file, .set_fid = smb2_set_fid, .close = smb2_close_file, diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 70530108b9bb..a5efce03cb58 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -743,7 +743,7 @@ assemble_neg_contexts(struct smb2_negotiate_req *req, pneg_ctxt += sizeof(struct smb2_posix_neg_context); neg_context_count++; - if (server->compress_algorithm) { + if (server->compression.requested) { build_compression_ctxt((struct smb2_compression_capabilities_context *) pneg_ctxt); ctxt_len = ALIGN(sizeof(struct smb2_compression_capabilities_context), 8); @@ -791,6 +791,9 @@ static void decode_compress_ctx(struct TCP_Server_Info *server, struct smb2_compression_capabilities_context *ctxt) { unsigned int len = le16_to_cpu(ctxt->DataLength); + __le16 alg; + + server->compression.enabled = false; /* * Caller checked that DataLength remains within SMB boundary. We still @@ -801,15 +804,22 @@ static void decode_compress_ctx(struct TCP_Server_Info *server, pr_warn_once("server sent bad compression cntxt\n"); return; } + if (le16_to_cpu(ctxt->CompressionAlgorithmCount) != 1) { - pr_warn_once("Invalid SMB3 compress algorithm count\n"); + pr_warn_once("invalid SMB3 compress algorithm count\n"); return; } - if (le16_to_cpu(ctxt->CompressionAlgorithms[0]) > 3) { - pr_warn_once("unknown compression algorithm\n"); + + alg = ctxt->CompressionAlgorithms[0]; + + /* 'NONE' (0) compressor type is never negotiated */ + if (alg == 0 || le16_to_cpu(alg) > 3) { + pr_warn_once("invalid compression algorithm '%u'\n", alg); return; } - server->compress_algorithm = ctxt->CompressionAlgorithms[0]; + + server->compression.alg = alg; + server->compression.enabled = true; } static int decode_encrypt_ctx(struct TCP_Server_Info *server, @@ -2019,10 +2029,7 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, __le16 *unc_path = NULL; int flags = 0; unsigned int total_len; - struct TCP_Server_Info *server; - - /* always use master channel */ - server = ses->server; + struct TCP_Server_Info *server = cifs_pick_channel(ses); cifs_dbg(FYI, "TCON\n"); @@ -2155,6 +2162,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon) struct smb2_tree_disconnect_req *req; /* response is trivial */ int rc = 0; struct cifs_ses *ses = tcon->ses; + struct TCP_Server_Info *server = cifs_pick_channel(ses); int flags = 0; unsigned int total_len; struct kvec iov[1]; @@ -2177,7 +2185,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon) invalidate_all_cached_dirs(tcon); - rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, ses->server, + rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, server, (void **) &req, &total_len); if (rc) @@ -2195,7 +2203,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon) rqst.rq_iov = iov; rqst.rq_nvec = 1; - rc = cifs_send_recv(xid, ses, ses->server, + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buf_type, flags, &rsp_iov); cifs_small_buf_release(req); if (rc) { @@ -2423,8 +2431,13 @@ create_durable_v2_buf(struct cifs_open_parms *oparms) */ buf->dcontext.Timeout = cpu_to_le32(oparms->tcon->handle_timeout); buf->dcontext.Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT); - generate_random_uuid(buf->dcontext.CreateGuid); - memcpy(pfid->create_guid, buf->dcontext.CreateGuid, 16); + + /* for replay, we should not overwrite the existing create guid */ + if (!oparms->replay) { + generate_random_uuid(buf->dcontext.CreateGuid); + memcpy(pfid->create_guid, buf->dcontext.CreateGuid, 16); + } else + memcpy(buf->dcontext.CreateGuid, pfid->create_guid, 16); /* SMB2_CREATE_DURABLE_HANDLE_REQUEST is "DH2Q" */ buf->Name[0] = 'D'; @@ -2729,6 +2742,17 @@ add_query_id_context(struct kvec *iov, unsigned int *num_iovec) return 0; } +static void add_ea_context(struct cifs_open_parms *oparms, + struct kvec *rq_iov, unsigned int *num_iovs) +{ + struct kvec *iov = oparms->ea_cctx; + + if (iov && iov->iov_base && iov->iov_len) { + rq_iov[(*num_iovs)++] = *iov; + memset(iov, 0, sizeof(*iov)); + } +} + static int alloc_path_with_tree_prefix(__le16 **out_path, int *out_size, int *out_len, const char *treename, const __le16 *path) @@ -2797,7 +2821,14 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, int flags = 0; unsigned int total_len; __le16 *utf16_path = NULL; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + n_iov = 2; + server = cifs_pick_channel(ses); cifs_dbg(FYI, "mkdir\n"); @@ -2901,6 +2932,10 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, /* no need to inc num_remote_opens because we close it just below */ trace_smb3_posix_mkdir_enter(xid, tcon->tid, ses->Suid, full_path, CREATE_NOT_FILE, FILE_WRITE_ATTRIBUTES); + + if (retries) + smb2_set_replay(server, &rqst); + /* resource #4: response buffer */ rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); @@ -2938,6 +2973,11 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, cifs_small_buf_release(req); err_free_path: kfree(utf16_path); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -3079,6 +3119,7 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server, } add_query_id_context(iov, &n_iov); + add_ea_context(oparms, iov, &n_iov); if (n_iov > 2) { /* @@ -3133,12 +3174,19 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, struct smb2_create_rsp *rsp = NULL; struct cifs_tcon *tcon = oparms->tcon; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; struct kvec iov[SMB2_CREATE_IOV_SIZE]; struct kvec rsp_iov = {NULL, 0}; int resp_buftype = CIFS_NO_BUFFER; int rc = 0; int flags = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); + oparms->replay = !!(retries); cifs_dbg(FYI, "create/open\n"); if (!ses || !server) @@ -3160,6 +3208,9 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, trace_smb3_open_enter(xid, tcon->tid, tcon->ses->Suid, oparms->path, oparms->create_options, oparms->desired_access); + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); @@ -3213,6 +3264,11 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, creat_exit: SMB2_open_free(&rqst); free_rsp_buf(resp_buftype, rsp); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -3337,15 +3393,7 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, int resp_buftype = CIFS_NO_BUFFER; int rc = 0; int flags = 0; - - cifs_dbg(FYI, "SMB2 IOCTL\n"); - - if (out_data != NULL) - *out_data = NULL; - - /* zero out returned data len, in case of error */ - if (plen) - *plen = 0; + int retries = 0, cur_sleep = 1; if (!tcon) return -EIO; @@ -3354,10 +3402,23 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, if (!ses) return -EIO; +replay_again: + /* reinitialize for possible replay */ + flags = 0; server = cifs_pick_channel(ses); + if (!server) return -EIO; + cifs_dbg(FYI, "SMB2 IOCTL\n"); + + if (out_data != NULL) + *out_data = NULL; + + /* zero out returned data len, in case of error */ + if (plen) + *plen = 0; + if (smb3_encryption_required(tcon)) flags |= CIFS_TRANSFORM_REQ; @@ -3372,6 +3433,9 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, if (rc) goto ioctl_exit; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); @@ -3441,6 +3505,11 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, ioctl_exit: SMB2_ioctl_free(&rqst); free_rsp_buf(resp_buftype, rsp); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -3512,13 +3581,20 @@ __SMB2_close(const unsigned int xid, struct cifs_tcon *tcon, struct smb_rqst rqst; struct smb2_close_rsp *rsp = NULL; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; struct kvec iov[1]; struct kvec rsp_iov; int resp_buftype = CIFS_NO_BUFFER; int rc = 0; int flags = 0; bool query_attrs = false; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + query_attrs = false; + server = cifs_pick_channel(ses); cifs_dbg(FYI, "Close\n"); @@ -3544,6 +3620,9 @@ __SMB2_close(const unsigned int xid, struct cifs_tcon *tcon, if (rc) goto close_exit; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); rsp = (struct smb2_close_rsp *)rsp_iov.iov_base; @@ -3577,6 +3656,11 @@ __SMB2_close(const unsigned int xid, struct cifs_tcon *tcon, cifs_dbg(VFS, "handle cancelled close fid 0x%llx returned error %d\n", persistent_fid, tmp_rc); } + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -3707,12 +3791,19 @@ query_info(const unsigned int xid, struct cifs_tcon *tcon, struct TCP_Server_Info *server; int flags = 0; bool allocated = false; + int retries = 0, cur_sleep = 1; cifs_dbg(FYI, "Query Info\n"); if (!ses) return -EIO; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + allocated = false; server = cifs_pick_channel(ses); + if (!server) return -EIO; @@ -3734,6 +3825,9 @@ query_info(const unsigned int xid, struct cifs_tcon *tcon, trace_smb3_query_info_enter(xid, persistent_fid, tcon->tid, ses->Suid, info_class, (__u32)info_type); + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); rsp = (struct smb2_query_info_rsp *)rsp_iov.iov_base; @@ -3776,6 +3870,11 @@ query_info(const unsigned int xid, struct cifs_tcon *tcon, qinf_exit: SMB2_query_info_free(&rqst); free_rsp_buf(resp_buftype, rsp); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -3876,7 +3975,7 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, u32 *plen /* returned data len */) { struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; struct smb_rqst rqst; struct smb2_change_notify_rsp *smb_rsp; struct kvec iov[1]; @@ -3884,6 +3983,12 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, int resp_buftype = CIFS_NO_BUFFER; int flags = 0; int rc = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); cifs_dbg(FYI, "change notify\n"); if (!ses || !server) @@ -3908,6 +4013,10 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, trace_smb3_notify_enter(xid, persistent_fid, tcon->tid, ses->Suid, (u8)watch_tree, completion_filter); + + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); @@ -3942,6 +4051,11 @@ SMB2_change_notify(const unsigned int xid, struct cifs_tcon *tcon, if (rqst.rq_iov) cifs_small_buf_release(rqst.rq_iov[0].iov_base); /* request */ free_rsp_buf(resp_buftype, rsp_iov.iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -3979,7 +4093,7 @@ void smb2_reconnect_server(struct work_struct *work) struct cifs_ses *ses, *ses2; struct cifs_tcon *tcon, *tcon2; struct list_head tmp_list, tmp_ses_list; - bool tcon_exist = false, ses_exist = false; + bool ses_exist = false; bool tcon_selected = false; int rc; bool resched = false; @@ -4024,8 +4138,10 @@ void smb2_reconnect_server(struct work_struct *work) list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { if (tcon->need_reconnect || tcon->need_reopen_files) { tcon->tc_count++; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_reconnect_server); list_add_tail(&tcon->rlist, &tmp_list); - tcon_selected = tcon_exist = true; + tcon_selected = true; } } /* @@ -4034,7 +4150,7 @@ void smb2_reconnect_server(struct work_struct *work) */ if (ses->tcon_ipc && ses->tcon_ipc->need_reconnect) { list_add_tail(&ses->tcon_ipc->rlist, &tmp_list); - tcon_selected = tcon_exist = true; + tcon_selected = true; cifs_smb_ses_inc_refcount(ses); } /* @@ -4062,14 +4178,14 @@ void smb2_reconnect_server(struct work_struct *work) if (tcon->ipc) cifs_put_smb_ses(tcon->ses); else - cifs_put_tcon(tcon); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_reconnect_server); } if (!ses_exist) goto done; /* allocate a dummy tcon struct used for reconnect */ - tcon = tcon_info_alloc(false); + tcon = tcon_info_alloc(false, netfs_trace_tcon_ref_new_reconnect_server); if (!tcon) { resched = true; list_for_each_entry_safe(ses, ses2, &tmp_ses_list, rlist) { @@ -4092,7 +4208,7 @@ void smb2_reconnect_server(struct work_struct *work) list_del_init(&ses->rlist); cifs_put_smb_ses(ses); } - tconInfoFree(tcon); + tconInfoFree(tcon, netfs_trace_tcon_ref_free_reconnect_server); done: cifs_dbg(FYI, "Reconnecting tcons and channels finished\n"); @@ -4184,10 +4300,16 @@ SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, struct smb_rqst rqst; struct kvec iov[1]; struct kvec rsp_iov = {NULL, 0}; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; int resp_buftype = CIFS_NO_BUFFER; int flags = 0; int rc = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); cifs_dbg(FYI, "flush\n"); if (!ses || !(ses->server)) @@ -4207,6 +4329,10 @@ SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, goto flush_exit; trace_smb3_flush_enter(xid, persistent_fid, tcon->tid, ses->Suid); + + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); @@ -4221,6 +4347,11 @@ SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, flush_exit: SMB2_flush_free(&rqst); free_rsp_buf(resp_buftype, rsp_iov.iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -4700,7 +4831,7 @@ smb2_async_writev(struct cifs_writedata *wdata, struct cifs_io_parms *io_parms = NULL; int credit_request; - if (!wdata->server) + if (!wdata->server || wdata->replay) server = wdata->server = cifs_pick_channel(tcon->ses); /* @@ -4785,6 +4916,8 @@ smb2_async_writev(struct cifs_writedata *wdata, rqst.rq_nvec = 1; rqst.rq_iter = wdata->iter; rqst.rq_iter_size = iov_iter_count(&rqst.rq_iter); + if (wdata->replay) + smb2_set_replay(server, &rqst); #ifdef CONFIG_CIFS_SMB_DIRECT if (wdata->mr) iov[0].iov_len += sizeof(struct smbd_buffer_descriptor_v1); @@ -4858,18 +4991,21 @@ SMB2_write(const unsigned int xid, struct cifs_io_parms *io_parms, int flags = 0; unsigned int total_len; struct TCP_Server_Info *server; + int retries = 0, cur_sleep = 1; +replay_again: + /* reinitialize for possible replay */ + flags = 0; *nbytes = 0; - - if (n_vec < 1) - return rc; - if (!io_parms->server) io_parms->server = cifs_pick_channel(io_parms->tcon->ses); server = io_parms->server; if (server == NULL) return -ECONNABORTED; + if (n_vec < 1) + return rc; + rc = smb2_plain_req_init(SMB2_WRITE, io_parms->tcon, server, (void **) &req, &total_len); if (rc) @@ -4903,6 +5039,9 @@ SMB2_write(const unsigned int xid, struct cifs_io_parms *io_parms, rqst.rq_iov = iov; rqst.rq_nvec = n_vec + 1; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, io_parms->tcon->ses, server, &rqst, &resp_buftype, flags, &rsp_iov); @@ -4927,6 +5066,11 @@ SMB2_write(const unsigned int xid, struct cifs_io_parms *io_parms, cifs_small_buf_release(req); free_rsp_buf(resp_buftype, rsp); + + if (is_replayable_error(rc) && + smb2_should_replay(io_parms->tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -5244,8 +5388,14 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon, struct kvec rsp_iov; int rc = 0; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; int flags = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); if (!ses || !(ses->server)) return -EIO; @@ -5265,6 +5415,9 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon, if (rc) goto qdir_exit; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); rsp = (struct smb2_query_directory_rsp *)rsp_iov.iov_base; @@ -5299,6 +5452,11 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon, qdir_exit: SMB2_query_directory_free(&rqst); free_rsp_buf(resp_buftype, rsp); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -5365,8 +5523,14 @@ send_set_info(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int resp_buftype; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; int flags = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); if (!ses || !server) return -EIO; @@ -5394,6 +5558,8 @@ send_set_info(const unsigned int xid, struct cifs_tcon *tcon, return rc; } + if (retries) + smb2_set_replay(server, &rqst); rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, @@ -5409,23 +5575,28 @@ send_set_info(const unsigned int xid, struct cifs_tcon *tcon, free_rsp_buf(resp_buftype, rsp); kfree(iov); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, - u64 volatile_fid, u32 pid, __le64 *eof) + u64 volatile_fid, u32 pid, loff_t new_eof) { struct smb2_file_eof_info info; void *data; unsigned int size; - info.EndOfFile = *eof; + info.EndOfFile = cpu_to_le64(new_eof); data = &info; size = sizeof(struct smb2_file_eof_info); - trace_smb3_set_eof(xid, persistent_fid, tcon->tid, tcon->ses->Suid, le64_to_cpu(*eof)); + trace_smb3_set_eof(xid, persistent_fid, tcon->tid, tcon->ses->Suid, new_eof); return send_set_info(xid, tcon, persistent_fid, volatile_fid, pid, FILE_END_OF_FILE_INFORMATION, SMB2_O_INFO_FILE, @@ -5461,12 +5632,18 @@ SMB2_oplock_break(const unsigned int xid, struct cifs_tcon *tcon, int rc; struct smb2_oplock_break *req = NULL; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; int flags = CIFS_OBREAK_OP; unsigned int total_len; struct kvec iov[1]; struct kvec rsp_iov; int resp_buf_type; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = CIFS_OBREAK_OP; + server = cifs_pick_channel(ses); cifs_dbg(FYI, "SMB2_oplock_break\n"); rc = smb2_plain_req_init(SMB2_OPLOCK_BREAK, tcon, server, @@ -5491,15 +5668,21 @@ SMB2_oplock_break(const unsigned int xid, struct cifs_tcon *tcon, rqst.rq_iov = iov; rqst.rq_nvec = 1; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buf_type, flags, &rsp_iov); cifs_small_buf_release(req); - if (rc) { cifs_stats_fail_inc(tcon, SMB2_OPLOCK_BREAK_HE); cifs_dbg(FYI, "Send error in Oplock Break = %d\n", rc); } + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -5585,9 +5768,15 @@ SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int resp_buftype; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; FILE_SYSTEM_POSIX_INFO *info = NULL; int flags = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); rc = build_qfs_info_req(&iov, tcon, server, FS_POSIX_INFORMATION, @@ -5603,6 +5792,9 @@ SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon, rqst.rq_iov = &iov; rqst.rq_nvec = 1; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); free_qfs_info_req(&iov); @@ -5622,6 +5814,11 @@ SMB311_posix_qfs_info(const unsigned int xid, struct cifs_tcon *tcon, posix_qfsinf_exit: free_rsp_buf(resp_buftype, rsp_iov.iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -5636,9 +5833,15 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int resp_buftype; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; struct smb2_fs_full_size_info *info = NULL; int flags = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); rc = build_qfs_info_req(&iov, tcon, server, FS_FULL_SIZE_INFORMATION, @@ -5654,6 +5857,9 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon, rqst.rq_iov = &iov; rqst.rq_nvec = 1; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); free_qfs_info_req(&iov); @@ -5673,6 +5879,11 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon, qfsinf_exit: free_rsp_buf(resp_buftype, rsp_iov.iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -5687,9 +5898,15 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int resp_buftype, max_len, min_len; struct cifs_ses *ses = tcon->ses; - struct TCP_Server_Info *server = cifs_pick_channel(ses); + struct TCP_Server_Info *server; unsigned int rsp_len, offset; int flags = 0; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = 0; + server = cifs_pick_channel(ses); if (level == FS_DEVICE_INFORMATION) { max_len = sizeof(FILE_SYSTEM_DEVICE_INFO); @@ -5721,6 +5938,9 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon, rqst.rq_iov = &iov; rqst.rq_nvec = 1; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, ses, server, &rqst, &resp_buftype, flags, &rsp_iov); free_qfs_info_req(&iov); @@ -5758,6 +5978,11 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon, qfsattr_exit: free_rsp_buf(resp_buftype, rsp_iov.iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } @@ -5775,7 +6000,13 @@ smb2_lockv(const unsigned int xid, struct cifs_tcon *tcon, unsigned int count; int flags = CIFS_NO_RSP_BUF; unsigned int total_len; - struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses); + struct TCP_Server_Info *server; + int retries = 0, cur_sleep = 1; + +replay_again: + /* reinitialize for possible replay */ + flags = CIFS_NO_RSP_BUF; + server = cifs_pick_channel(tcon->ses); cifs_dbg(FYI, "smb2_lockv num lock %d\n", num_lock); @@ -5806,6 +6037,9 @@ smb2_lockv(const unsigned int xid, struct cifs_tcon *tcon, rqst.rq_iov = iov; rqst.rq_nvec = 2; + if (retries) + smb2_set_replay(server, &rqst); + rc = cifs_send_recv(xid, tcon->ses, server, &rqst, &resp_buf_type, flags, &rsp_iov); @@ -5817,6 +6051,10 @@ smb2_lockv(const unsigned int xid, struct cifs_tcon *tcon, tcon->ses->Suid, rc); } + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto replay_again; + return rc; } diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h index b00f707bddfc..5c458ab3b05a 100644 --- a/fs/smb/client/smb2pdu.h +++ b/fs/smb/client/smb2pdu.h @@ -117,9 +117,10 @@ struct share_redirect_error_context_rsp { * [4] : posix context * [5] : time warp context * [6] : query id context - * [7] : compound padding + * [7] : create ea context + * [8] : compound padding */ -#define SMB2_CREATE_IOV_SIZE 8 +#define SMB2_CREATE_IOV_SIZE 9 /* * Maximum size of a SMB2_CREATE response is 64 (smb2 header) + @@ -144,7 +145,7 @@ struct durable_context_v2 { } __packed; struct create_durable_v2 { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct durable_context_v2 dcontext; } __packed; @@ -166,7 +167,7 @@ struct durable_reconnect_context_v2_rsp { } __packed; struct create_durable_handle_reconnect_v2 { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct durable_reconnect_context_v2 dcontext; __u8 Pad[4]; @@ -174,7 +175,7 @@ struct create_durable_handle_reconnect_v2 { /* See MS-SMB2 2.2.13.2.5 */ struct crt_twarp_ctxt { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le64 Timestamp; @@ -182,12 +183,12 @@ struct crt_twarp_ctxt { /* See MS-SMB2 2.2.13.2.9 */ struct crt_query_id_ctxt { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; } __packed; struct crt_sd_ctxt { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct smb3_sd sd; } __packed; @@ -413,4 +414,35 @@ struct smb2_posix_info_parsed { const u8 *name; }; +struct smb2_create_ea_ctx { + struct create_context_hdr ctx; + __u8 name[8]; + struct smb2_file_full_ea_info ea; +} __packed; + +#define SMB2_WSL_XATTR_UID "$LXUID" +#define SMB2_WSL_XATTR_GID "$LXGID" +#define SMB2_WSL_XATTR_MODE "$LXMOD" +#define SMB2_WSL_XATTR_DEV "$LXDEV" +#define SMB2_WSL_XATTR_NAME_LEN 6 +#define SMB2_WSL_NUM_XATTRS 4 + +#define SMB2_WSL_XATTR_UID_SIZE 4 +#define SMB2_WSL_XATTR_GID_SIZE 4 +#define SMB2_WSL_XATTR_MODE_SIZE 4 +#define SMB2_WSL_XATTR_DEV_SIZE 8 + +#define SMB2_WSL_MIN_QUERY_EA_RESP_SIZE \ + (ALIGN((SMB2_WSL_NUM_XATTRS - 1) * \ + (SMB2_WSL_XATTR_NAME_LEN + 1 + \ + sizeof(struct smb2_file_full_ea_info)), 4) + \ + SMB2_WSL_XATTR_NAME_LEN + 1 + sizeof(struct smb2_file_full_ea_info)) + +#define SMB2_WSL_MAX_QUERY_EA_RESP_SIZE \ + (ALIGN(SMB2_WSL_MIN_QUERY_EA_RESP_SIZE + \ + SMB2_WSL_XATTR_UID_SIZE + \ + SMB2_WSL_XATTR_GID_SIZE + \ + SMB2_WSL_XATTR_MODE_SIZE + \ + SMB2_WSL_XATTR_DEV_SIZE, 4)) + #endif /* _SMB2PDU_H */ diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index a8084ce7fcbd..732169d8a67a 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -56,6 +56,19 @@ extern int smb3_handle_read_data(struct TCP_Server_Info *server, extern int smb2_query_reparse_tag(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *path, __u32 *reparse_tag); +struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, + struct super_block *sb, + const unsigned int xid, + struct cifs_tcon *tcon, + const char *full_path, + struct kvec *reparse_iov, + struct kvec *xattr_iov); +int smb2_query_reparse_point(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, + u32 *tag, struct kvec *rsp, + int *rsp_buftype); int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, @@ -63,7 +76,8 @@ int smb2_query_path_info(const unsigned int xid, struct cifs_open_info_data *data); extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon, const char *full_path, __u64 size, - struct cifs_sb_info *cifs_sb, bool set_alloc); + struct cifs_sb_info *cifs_sb, bool set_alloc, + struct dentry *dentry); extern int smb2_set_file_info(struct inode *inode, const char *full_path, FILE_BASIC_INFO *buf, const unsigned int xid); extern int smb311_posix_mkdir(const unsigned int xid, struct inode *inode, @@ -79,7 +93,8 @@ extern void smb2_mkdir_setinfo(struct inode *inode, const char *full_path, extern int smb2_rmdir(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct cifs_sb_info *cifs_sb); extern int smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, - const char *name, struct cifs_sb_info *cifs_sb); + const char *name, struct cifs_sb_info *cifs_sb, + struct dentry *dentry); int smb2_rename_path(const unsigned int xid, struct cifs_tcon *tcon, struct dentry *source_dentry, @@ -110,6 +125,11 @@ extern unsigned long smb_rqst_len(struct TCP_Server_Info *server, extern void smb2_set_next_command(struct cifs_tcon *tcon, struct smb_rqst *rqst); extern void smb2_set_related(struct smb_rqst *rqst); +extern void smb2_set_replay(struct TCP_Server_Info *server, + struct smb_rqst *rqst); +extern bool smb2_should_replay(struct cifs_tcon *tcon, + int *pretries, + int *pcur_sleep); /* * SMB2 Worker functions - most of protocol specific implementation details @@ -209,7 +229,7 @@ extern int SMB2_query_directory_init(unsigned int xid, struct cifs_tcon *tcon, extern void SMB2_query_directory_free(struct smb_rqst *rqst); extern int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, u32 pid, - __le64 *eof); + loff_t new_eof); extern int SMB2_set_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server, struct smb_rqst *rqst, @@ -287,10 +307,15 @@ int smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *full_path, - struct cifs_open_info_data *data, - struct cifs_sid *owner, - struct cifs_sid *group); + struct cifs_open_info_data *data); int posix_info_parse(const void *beg, const void *end, struct smb2_posix_info_parsed *out); int posix_info_sid_size(const void *beg, const void *end); +int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, const char *symname); +int smb2_make_nfs_node(unsigned int xid, struct inode *inode, + struct dentry *dentry, struct cifs_tcon *tcon, + const char *full_path, umode_t mode, dev_t dev); + #endif /* _SMB2PROTO_H */ diff --git a/fs/smb/client/smb2status.h b/fs/smb/client/smb2status.h index a9e958166fc5..9c6d79b0bd49 100644 --- a/fs/smb/client/smb2status.h +++ b/fs/smb/client/smb2status.h @@ -982,6 +982,8 @@ struct ntstatus { #define STATUS_INVALID_TASK_INDEX cpu_to_le32(0xC0000501) #define STATUS_THREAD_ALREADY_IN_TASK cpu_to_le32(0xC0000502) #define STATUS_CALLBACK_BYPASS cpu_to_le32(0xC0000503) +#define STATUS_SERVER_UNAVAILABLE cpu_to_le32(0xC0000466) +#define STATUS_FILE_NOT_AVAILABLE cpu_to_le32(0xC0000467) #define STATUS_PORT_CLOSED cpu_to_le32(0xC0000700) #define STATUS_MESSAGE_LOST cpu_to_le32(0xC0000701) #define STATUS_INVALID_MESSAGE cpu_to_le32(0xC0000702) diff --git a/fs/smb/client/smb2transport.c b/fs/smb/client/smb2transport.c index 5a3ca62d2f07..8f346aafc4cf 100644 --- a/fs/smb/client/smb2transport.c +++ b/fs/smb/client/smb2transport.c @@ -189,6 +189,8 @@ smb2_find_smb_sess_tcon_unlocked(struct cifs_ses *ses, __u32 tid) if (tcon->tid != tid) continue; ++tcon->tc_count; + trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_find_sess_tcon); return tcon; } diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 94df9eec3d8d..d74e829de51c 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -2136,7 +2136,7 @@ static int allocate_mr_list(struct smbd_connection *info) for (i = 0; i < info->responder_resources * 2; i++) { smbdirect_mr = kzalloc(sizeof(*smbdirect_mr), GFP_KERNEL); if (!smbdirect_mr) - goto out; + goto cleanup_entries; smbdirect_mr->mr = ib_alloc_mr(info->pd, info->mr_type, info->max_frmr_depth); if (IS_ERR(smbdirect_mr->mr)) { @@ -2162,7 +2162,7 @@ static int allocate_mr_list(struct smbd_connection *info) out: kfree(smbdirect_mr); - +cleanup_entries: list_for_each_entry_safe(smbdirect_mr, tmp, &info->mr_list, list) { list_del(&smbdirect_mr->list); ib_dereg_mr(smbdirect_mr->mr); diff --git a/fs/smb/client/smbencrypt.c b/fs/smb/client/smbencrypt.c index f0ce26414f17..1d1ee9f18f37 100644 --- a/fs/smb/client/smbencrypt.c +++ b/fs/smb/client/smbencrypt.c @@ -26,13 +26,6 @@ #include "cifsproto.h" #include "../common/md4.h" -#ifndef false -#define false 0 -#endif -#ifndef true -#define true 1 -#endif - /* following came from the other byteorder.h to avoid include conflicts */ #define CVAL(buf,pos) (((unsigned char *)(buf))[pos]) #define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8) diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index de199ec9f726..604e52876cd2 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -3,6 +3,9 @@ * Copyright (C) 2018, Microsoft Corporation. * * Author(s): Steve French <stfrench@xxxxxxxxxxxxx> + * + * Please use this 3-part article as a reference for writing new tracepoints: + * https://lwn.net/Articles/379903/ */ #undef TRACE_SYSTEM #define TRACE_SYSTEM cifs @@ -15,9 +18,70 @@ #include <linux/inet.h> /* - * Please use this 3-part article as a reference for writing new tracepoints: - * https://lwn.net/Articles/379903/ + * Specify enums for tracing information. + */ +#define smb3_tcon_ref_traces \ + EM(netfs_trace_tcon_ref_dec_dfs_refer, "DEC DfsRef") \ + EM(netfs_trace_tcon_ref_free, "FRE ") \ + EM(netfs_trace_tcon_ref_free_fail, "FRE Fail ") \ + EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \ + EM(netfs_trace_tcon_ref_free_ipc_fail, "FRE Ipc-F ") \ + EM(netfs_trace_tcon_ref_free_reconnect_server, "FRE Reconn") \ + EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \ + EM(netfs_trace_tcon_ref_get_dfs_refer, "GET DfsRef") \ + EM(netfs_trace_tcon_ref_get_find, "GET Find ") \ + EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \ + EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \ + EM(netfs_trace_tcon_ref_new, "NEW ") \ + EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \ + EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \ + EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \ + EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \ + EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \ + EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ + EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \ + EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ + EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \ + EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \ + EM(netfs_trace_tcon_ref_see_fscache_okay, "SEE FV-Ok ") \ + EM(netfs_trace_tcon_ref_see_fscache_relinq, "SEE FV-Rlq") \ + E_(netfs_trace_tcon_ref_see_umount, "SEE Umount") + +#undef EM +#undef E_ + +/* + * Define those tracing enums. + */ +#ifndef __SMB3_DECLARE_TRACE_ENUMS_ONCE_ONLY +#define __SMB3_DECLARE_TRACE_ENUMS_ONCE_ONLY + +#define EM(a, b) a, +#define E_(a, b) a + +enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte); + +#undef EM +#undef E_ +#endif + +/* + * Export enum symbols via userspace. + */ +#define EM(a, b) TRACE_DEFINE_ENUM(a); +#define E_(a, b) TRACE_DEFINE_ENUM(a); + +smb3_tcon_ref_traces; + +#undef EM +#undef E_ + +/* + * Now redefine the EM() and E_() macros to map the enums to the strings that + * will be printed in the output. */ +#define EM(a, b) { a, b }, +#define E_(a, b) { a, b } /* For logging errors in read or write */ DECLARE_EVENT_CLASS(smb3_rw_err_class, @@ -370,10 +434,12 @@ DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(rename_enter); DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(rmdir_enter); DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(set_eof_enter); DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(set_info_compound_enter); +DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(set_reparse_compound_enter); +DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(get_reparse_compound_enter); DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(delete_enter); DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(mkdir_enter); DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(tdis_enter); - +DEFINE_SMB3_INF_COMPOUND_ENTER_EVENT(mknod_enter); DECLARE_EVENT_CLASS(smb3_inf_compound_done_class, TP_PROTO(unsigned int xid, @@ -408,10 +474,13 @@ DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(rename_done); DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(rmdir_done); DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(set_eof_done); DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(set_info_compound_done); +DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(set_reparse_compound_done); +DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(get_reparse_compound_done); +DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(query_wsl_ea_compound_done); DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(delete_done); DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(mkdir_done); DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(tdis_done); - +DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(mknod_done); DECLARE_EVENT_CLASS(smb3_inf_compound_err_class, TP_PROTO(unsigned int xid, @@ -451,9 +520,13 @@ DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(rename_err); DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(rmdir_err); DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(set_eof_err); DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(set_info_compound_err); +DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(set_reparse_compound_err); +DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(get_reparse_compound_err); +DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(query_wsl_ea_compound_err); DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(mkdir_err); DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(delete_err); DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(tdis_err); +DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(mknod_err); /* * For logging SMB3 Status code and Command for responses which return errors @@ -1025,6 +1098,38 @@ DEFINE_EVENT(smb3_ses_class, smb3_##name, \ DEFINE_SMB3_SES_EVENT(ses_not_found); +DECLARE_EVENT_CLASS(smb3_ioctl_class, + TP_PROTO(unsigned int xid, + __u64 fid, + unsigned int command), + TP_ARGS(xid, fid, command), + TP_STRUCT__entry( + __field(unsigned int, xid) + __field(__u64, fid) + __field(unsigned int, command) + ), + TP_fast_assign( + __entry->xid = xid; + __entry->fid = fid; + __entry->command = command; + ), + TP_printk("xid=%u fid=0x%llx ioctl cmd=0x%x", + __entry->xid, __entry->fid, __entry->command) +) + +#define DEFINE_SMB3_IOCTL_EVENT(name) \ +DEFINE_EVENT(smb3_ioctl_class, smb3_##name, \ + TP_PROTO(unsigned int xid, \ + __u64 fid, \ + unsigned int command), \ + TP_ARGS(xid, fid, command)) + +DEFINE_SMB3_IOCTL_EVENT(ioctl); + + + + + DECLARE_EVENT_CLASS(smb3_credit_class, TP_PROTO(__u64 currmid, __u64 conn_id, @@ -1084,6 +1189,30 @@ DEFINE_SMB3_CREDIT_EVENT(waitff_credits); DEFINE_SMB3_CREDIT_EVENT(overflow_credits); DEFINE_SMB3_CREDIT_EVENT(set_credits); + +TRACE_EVENT(smb3_tcon_ref, + TP_PROTO(unsigned int tcon_debug_id, int ref, + enum smb3_tcon_ref_trace trace), + TP_ARGS(tcon_debug_id, ref, trace), + TP_STRUCT__entry( + __field(unsigned int, tcon) + __field(int, ref) + __field(enum smb3_tcon_ref_trace, trace) + ), + TP_fast_assign( + __entry->tcon = tcon_debug_id; + __entry->ref = ref; + __entry->trace = trace; + ), + TP_printk("TC=%08x %s r=%u", + __entry->tcon, + __print_symbolic(__entry->trace, smb3_tcon_ref_traces), + __entry->ref) + ); + + +#undef EM +#undef E_ #endif /* _CIFS_TRACE_H */ #undef TRACE_INCLUDE_PATH diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h index 22c94dea5211..8d10be1fe18a 100644 --- a/fs/smb/common/smb2pdu.h +++ b/fs/smb/common/smb2pdu.h @@ -34,6 +34,7 @@ #define SMB2_QUERY_INFO_HE 0x0010 #define SMB2_SET_INFO_HE 0x0011 #define SMB2_OPLOCK_BREAK_HE 0x0012 +#define SMB2_SERVER_TO_CLIENT_NOTIFICATION 0x0013 /* The same list in little endian */ #define SMB2_NEGOTIATE cpu_to_le16(SMB2_NEGOTIATE_HE) @@ -207,38 +208,45 @@ struct smb2_transform_hdr { __le64 SessionId; } __packed; +/* + * These are simplified versions from the spec, as we don't need a fully fledged + * form of both unchained and chained structs. + * + * Moreover, even in chained compressed payloads, the initial compression header + * has the form of the unchained one -- i.e. it never has the + * OriginalPayloadSize field and ::Offset field always represent an offset + * (instead of a length, as it is in the chained header). + * + * See MS-SMB2 2.2.42 for more details. + */ +#define SMB2_COMPRESSION_FLAG_NONE 0x0000 +#define SMB2_COMPRESSION_FLAG_CHAINED 0x0001 -/* See MS-SMB2 2.2.42 */ -struct smb2_compression_transform_hdr_unchained { - __le32 ProtocolId; /* 0xFC 'S' 'M' 'B' */ +struct smb2_compression_hdr { + __le32 ProtocolId; /* 0xFC 'S' 'M' 'B' */ __le32 OriginalCompressedSegmentSize; __le16 CompressionAlgorithm; __le16 Flags; - __le16 Length; /* if chained it is length, else offset */ + __le32 Offset; /* this is the size of the uncompressed SMB2 header below */ + /* uncompressed SMB2 header (READ or WRITE) goes here */ + /* compressed data goes here */ } __packed; -/* See MS-SMB2 2.2.42.1 */ -#define SMB2_COMPRESSION_FLAG_NONE 0x0000 -#define SMB2_COMPRESSION_FLAG_CHAINED 0x0001 - -struct compression_payload_header { +/* + * ... OTOH, set compression payload header to always have OriginalPayloadSize + * as it's easier to pass the struct size minus sizeof(OriginalPayloadSize) + * than to juggle around the header/data memory. + */ +struct smb2_compression_payload_hdr { __le16 CompressionAlgorithm; __le16 Flags; __le32 Length; /* length of compressed playload including field below if present */ - /* __le32 OriginalPayloadSize; */ /* optional, present when LZNT1, LZ77, LZ77+Huffman */ -} __packed; - -/* See MS-SMB2 2.2.42.2 */ -struct smb2_compression_transform_hdr_chained { - __le32 ProtocolId; /* 0xFC 'S' 'M' 'B' */ - __le32 OriginalCompressedSegmentSize; - /* struct compression_payload_header[] */ + __le32 OriginalPayloadSize; /* accounted when LZNT1, LZ77, LZ77+Huffman */ } __packed; -/* See MS-SMB2 2.2.42.2.2 */ -struct compression_pattern_payload_v1 { - __le16 Pattern; - __le16 Reserved1; +struct smb2_compression_pattern_v1 { + __u8 Pattern; + __u8 Reserved1; __le16 Reserved2; __le32 Repetitions; } __packed; @@ -272,15 +280,16 @@ struct smb3_blob_data { #define SE_GROUP_RESOURCE 0x20000000 #define SE_GROUP_LOGON_ID 0xC0000000 -/* struct sid_attr_data is SidData array in BlobData format then le32 Attr */ - struct sid_array_data { __le16 SidAttrCount; /* SidAttrList - array of sid_attr_data structs */ } __packed; -struct luid_attr_data { - +/* struct sid_attr_data is SidData array in BlobData format then le32 Attr */ +struct sid_attr_data { + __le16 BlobSize; + __u8 BlobData[]; + /* __le32 Attr */ } __packed; /* @@ -411,6 +420,7 @@ struct smb2_tree_disconnect_rsp { #define SMB2_GLOBAL_CAP_PERSISTENT_HANDLES 0x00000010 /* New to SMB3 */ #define SMB2_GLOBAL_CAP_DIRECTORY_LEASING 0x00000020 /* New to SMB3 */ #define SMB2_GLOBAL_CAP_ENCRYPTION 0x00000040 /* New to SMB3 */ +#define SMB2_GLOBAL_CAP_NOTIFICATIONS 0x00000080 /* New to SMB3.1.1 */ /* Internal types */ #define SMB2_NT_FIND 0x00100000 #define SMB2_LARGE_FILES 0x00200000 @@ -493,6 +503,7 @@ struct smb2_encryption_neg_context { #define SMB3_COMPRESS_LZ77_HUFF cpu_to_le16(0x0003) /* Pattern scanning algorithm See MS-SMB2 3.1.4.4.1 */ #define SMB3_COMPRESS_PATTERN cpu_to_le16(0x0004) /* Pattern_V1 */ +#define SMB3_COMPRESS_LZ4 cpu_to_le16(0x0005) /* Compression Flags */ #define SMB2_COMPRESSION_CAPABILITIES_FLAG_NONE cpu_to_le32(0x00000000) @@ -984,6 +995,19 @@ struct smb2_change_notify_rsp { __u8 Buffer[]; /* array of file notify structs */ } __packed; +/* + * SMB2_SERVER_TO_CLIENT_NOTIFICATION: See MS-SMB2 section 2.2.44 + */ + +#define SMB2_NOTIFY_SESSION_CLOSED 0x0000 + +struct smb2_server_client_notification { + struct smb2_hdr hdr; + __le16 StructureSize; + __u16 Reserved; /* MBZ */ + __le32 NotificationType; + __u8 NotificationBuffer[4]; /* MBZ */ +} __packed; /* * SMB2_CREATE See MS-SMB2 section 2.2.13 @@ -1100,16 +1124,23 @@ struct smb2_change_notify_rsp { #define FILE_WRITE_THROUGH_LE cpu_to_le32(0x00000002) #define FILE_SEQUENTIAL_ONLY_LE cpu_to_le32(0x00000004) #define FILE_NO_INTERMEDIATE_BUFFERING_LE cpu_to_le32(0x00000008) +/* FILE_SYNCHRONOUS_IO_ALERT_LE cpu_to_le32(0x00000010) should be zero, ignored */ +/* FILE_SYNCHRONOUS_IO_NONALERT cpu_to_le32(0x00000020) should be zero, ignored */ #define FILE_NON_DIRECTORY_FILE_LE cpu_to_le32(0x00000040) #define FILE_COMPLETE_IF_OPLOCKED_LE cpu_to_le32(0x00000100) #define FILE_NO_EA_KNOWLEDGE_LE cpu_to_le32(0x00000200) +/* FILE_OPEN_REMOTE_INSTANCE cpu_to_le32(0x00000400) should be zero, ignored */ #define FILE_RANDOM_ACCESS_LE cpu_to_le32(0x00000800) -#define FILE_DELETE_ON_CLOSE_LE cpu_to_le32(0x00001000) +#define FILE_DELETE_ON_CLOSE_LE cpu_to_le32(0x00001000) /* MBZ */ #define FILE_OPEN_BY_FILE_ID_LE cpu_to_le32(0x00002000) #define FILE_OPEN_FOR_BACKUP_INTENT_LE cpu_to_le32(0x00004000) #define FILE_NO_COMPRESSION_LE cpu_to_le32(0x00008000) +/* FILE_OPEN_REQUIRING_OPLOCK cpu_to_le32(0x00010000) should be zero, ignored */ +/* FILE_DISALLOW_EXCLUSIVE cpu_to_le32(0x00020000) should be zero, ignored */ +/* FILE_RESERVE_OPFILTER cpu_to_le32(0x00100000) MBZ */ #define FILE_OPEN_REPARSE_POINT_LE cpu_to_le32(0x00200000) #define FILE_OPEN_NO_RECALL_LE cpu_to_le32(0x00400000) +/* #define FILE_OPEN_FOR_FREE_SPACE_QUERY cpu_to_le32(0x00800000) should be zero, ignored */ #define CREATE_OPTIONS_MASK_LE cpu_to_le32(0x00FFFFFF) #define FILE_READ_RIGHTS_LE (FILE_READ_DATA_LE | FILE_READ_EA_LE \ @@ -1140,12 +1171,15 @@ struct smb2_change_notify_rsp { #define SMB2_CREATE_FLAG_REPARSEPOINT 0x01 struct create_context { - __le32 Next; - __le16 NameOffset; - __le16 NameLength; - __le16 Reserved; - __le16 DataOffset; - __le32 DataLength; + /* New members must be added within the struct_group() macro below. */ + __struct_group(create_context_hdr, hdr, __packed, + __le32 Next; + __le16 NameOffset; + __le16 NameLength; + __le16 Reserved; + __le16 DataOffset; + __le32 DataLength; + ); __u8 Buffer[]; } __packed; @@ -1191,7 +1225,7 @@ struct smb2_create_rsp { } __packed; struct create_posix { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[16]; __le32 Mode; __u32 Reserved; @@ -1199,7 +1233,7 @@ struct create_posix { /* See MS-SMB2 2.2.13.2.3 and MS-SMB2 2.2.13.2.4 */ struct create_durable { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; union { __u8 Reserved[16]; @@ -1212,14 +1246,14 @@ struct create_durable { /* See MS-SMB2 2.2.13.2.5 */ struct create_mxac_req { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le64 Timestamp; } __packed; /* See MS-SMB2 2.2.14.2.5 */ struct create_mxac_rsp { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le32 QueryStatus; __le32 MaximalAccess; @@ -1255,13 +1289,13 @@ struct lease_context_v2 { } __packed; struct create_lease { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct lease_context lcontext; } __packed; struct create_lease_v2 { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct lease_context_v2 lcontext; __u8 Pad[4]; @@ -1269,7 +1303,7 @@ struct create_lease_v2 { /* See MS-SMB2 2.2.14.2.9 */ struct create_disk_id_rsp { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le64 DiskFileId; __le64 VolumeId; @@ -1278,7 +1312,7 @@ struct create_disk_id_rsp { /* See MS-SMB2 2.2.13.2.13 */ struct create_app_inst_id { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[16]; __le32 StructureSize; /* Must be 20 */ __u16 Reserved; @@ -1287,7 +1321,7 @@ struct create_app_inst_id { /* See MS-SMB2 2.2.13.2.15 */ struct create_app_inst_id_vers { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[16]; __le32 StructureSize; /* Must be 24 */ __u16 Reserved; diff --git a/fs/smb/common/smbfsctl.h b/fs/smb/common/smbfsctl.h index edd7fc2a7921..a94d658b88e8 100644 --- a/fs/smb/common/smbfsctl.h +++ b/fs/smb/common/smbfsctl.h @@ -158,12 +158,6 @@ #define IO_REPARSE_TAG_LX_CHR 0x80000025 #define IO_REPARSE_TAG_LX_BLK 0x80000026 -#define IO_REPARSE_TAG_LX_SYMLINK_LE cpu_to_le32(0xA000001D) -#define IO_REPARSE_TAG_AF_UNIX_LE cpu_to_le32(0x80000023) -#define IO_REPARSE_TAG_LX_FIFO_LE cpu_to_le32(0x80000024) -#define IO_REPARSE_TAG_LX_CHR_LE cpu_to_le32(0x80000025) -#define IO_REPARSE_TAG_LX_BLK_LE cpu_to_le32(0x80000026) - /* fsctl flags */ /* If Flags is set to this value, the request is an FSCTL not ioctl request */ #define SMB2_0_IOCTL_IS_FSCTL 0x00000001 diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c index 229a6527870d..09b20039636e 100644 --- a/fs/smb/server/auth.c +++ b/fs/smb/server/auth.c @@ -208,10 +208,12 @@ static int calc_ntlmv2_hash(struct ksmbd_conn *conn, struct ksmbd_session *sess, /** * ksmbd_auth_ntlmv2() - NTLMv2 authentication handler - * @sess: session of connection + * @conn: connection + * @sess: session of connection * @ntlmv2: NTLMv2 challenge response * @blen: NTLMv2 blob length * @domain_name: domain name + * @cryptkey: session crypto key * * Return: 0 on success, error number on error */ @@ -294,7 +296,8 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess, * ksmbd_decode_ntlmssp_auth_blob() - helper function to construct * authenticate blob * @authblob: authenticate blob source pointer - * @usr: user details + * @blob_len: length of the @authblob message + * @conn: connection * @sess: session of connection * * Return: 0 on success, error number on error @@ -376,8 +379,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob, * ksmbd_decode_ntlmssp_neg_blob() - helper function to construct * negotiate blob * @negblob: negotiate blob source pointer - * @rsp: response header pointer to be updated - * @sess: session of connection + * @blob_len: length of the @authblob message + * @conn: connection * */ int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob, @@ -403,8 +406,7 @@ int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob, * ksmbd_build_ntlmssp_challenge_blob() - helper function to construct * challenge blob * @chgblob: challenge blob source pointer to initialize - * @rsp: response header pointer to be updated - * @sess: session of connection + * @conn: connection * */ unsigned int diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h index 4464a62228cf..f4e55199938d 100644 --- a/fs/smb/server/ksmbd_netlink.h +++ b/fs/smb/server/ksmbd_netlink.h @@ -75,6 +75,7 @@ struct ksmbd_heartbeat { #define KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION BIT(1) #define KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL BIT(2) #define KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF BIT(3) +#define KSMBD_GLOBAL_FLAG_DURABLE_HANDLE BIT(4) /* * IPC request for ksmbd server startup @@ -339,23 +340,24 @@ enum KSMBD_TREE_CONN_STATUS { /* * Share config flags. */ -#define KSMBD_SHARE_FLAG_INVALID (0) -#define KSMBD_SHARE_FLAG_AVAILABLE BIT(0) -#define KSMBD_SHARE_FLAG_BROWSEABLE BIT(1) -#define KSMBD_SHARE_FLAG_WRITEABLE BIT(2) -#define KSMBD_SHARE_FLAG_READONLY BIT(3) -#define KSMBD_SHARE_FLAG_GUEST_OK BIT(4) -#define KSMBD_SHARE_FLAG_GUEST_ONLY BIT(5) -#define KSMBD_SHARE_FLAG_STORE_DOS_ATTRS BIT(6) -#define KSMBD_SHARE_FLAG_OPLOCKS BIT(7) -#define KSMBD_SHARE_FLAG_PIPE BIT(8) -#define KSMBD_SHARE_FLAG_HIDE_DOT_FILES BIT(9) -#define KSMBD_SHARE_FLAG_INHERIT_OWNER BIT(10) -#define KSMBD_SHARE_FLAG_STREAMS BIT(11) -#define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS BIT(12) -#define KSMBD_SHARE_FLAG_ACL_XATTR BIT(13) -#define KSMBD_SHARE_FLAG_UPDATE BIT(14) -#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15) +#define KSMBD_SHARE_FLAG_INVALID (0) +#define KSMBD_SHARE_FLAG_AVAILABLE BIT(0) +#define KSMBD_SHARE_FLAG_BROWSEABLE BIT(1) +#define KSMBD_SHARE_FLAG_WRITEABLE BIT(2) +#define KSMBD_SHARE_FLAG_READONLY BIT(3) +#define KSMBD_SHARE_FLAG_GUEST_OK BIT(4) +#define KSMBD_SHARE_FLAG_GUEST_ONLY BIT(5) +#define KSMBD_SHARE_FLAG_STORE_DOS_ATTRS BIT(6) +#define KSMBD_SHARE_FLAG_OPLOCKS BIT(7) +#define KSMBD_SHARE_FLAG_PIPE BIT(8) +#define KSMBD_SHARE_FLAG_HIDE_DOT_FILES BIT(9) +#define KSMBD_SHARE_FLAG_INHERIT_OWNER BIT(10) +#define KSMBD_SHARE_FLAG_STREAMS BIT(11) +#define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS BIT(12) +#define KSMBD_SHARE_FLAG_ACL_XATTR BIT(13) +#define KSMBD_SHARE_FLAG_UPDATE BIT(14) +#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15) +#define KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY BIT(16) /* * Tree connect request flags. diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 15f68ee05089..aec0a7a12405 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -156,7 +156,7 @@ void ksmbd_session_destroy(struct ksmbd_session *sess) kfree(sess); } -static struct ksmbd_session *__session_lookup(unsigned long long id) +struct ksmbd_session *__session_lookup(unsigned long long id) { struct ksmbd_session *sess; @@ -305,6 +305,32 @@ struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn, return sess; } +void destroy_previous_session(struct ksmbd_conn *conn, + struct ksmbd_user *user, u64 id) +{ + struct ksmbd_session *prev_sess; + struct ksmbd_user *prev_user; + + down_write(&sessions_table_lock); + down_write(&conn->session_lock); + prev_sess = __session_lookup(id); + if (!prev_sess || prev_sess->state == SMB2_SESSION_EXPIRED) + goto out; + + prev_user = prev_sess->user; + if (!prev_user || + strcmp(user->name, prev_user->name) || + user->passkey_sz != prev_user->passkey_sz || + memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) + goto out; + + ksmbd_destroy_file_table(&prev_sess->file_table); + prev_sess->state = SMB2_SESSION_EXPIRED; +out: + up_write(&conn->session_lock); + up_write(&sessions_table_lock); +} + static bool ksmbd_preauth_session_id_match(struct preauth_session *sess, unsigned long long id) { diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index 63cb08fffde8..dc9fded2cd43 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -88,8 +88,11 @@ struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn, int ksmbd_session_register(struct ksmbd_conn *conn, struct ksmbd_session *sess); void ksmbd_sessions_deregister(struct ksmbd_conn *conn); +struct ksmbd_session *__session_lookup(unsigned long long id); struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, unsigned long long id); +void destroy_previous_session(struct ksmbd_conn *conn, + struct ksmbd_user *user, u64 id); struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn, u64 sess_id); struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn, diff --git a/fs/smb/server/misc.c b/fs/smb/server/misc.c index 9e8afaa686e3..1a5faa6f6e7b 100644 --- a/fs/smb/server/misc.c +++ b/fs/smb/server/misc.c @@ -261,6 +261,7 @@ char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name) /** * ksmbd_extract_sharename() - get share name from tree connect request + * @um: pointer to a unicode_map structure for character encoding handling * @treename: buffer containing tree name and share name * * Return: share name on success, otherwise error diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index dc729ab980dc..b7adb6549aa0 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -159,7 +159,8 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci) opinfo = list_first_or_null_rcu(&ci->m_op_list, struct oplock_info, op_entry); if (opinfo) { - if (!atomic_inc_not_zero(&opinfo->refcount)) + if (opinfo->conn == NULL || + !atomic_inc_not_zero(&opinfo->refcount)) opinfo = NULL; else { atomic_inc(&opinfo->conn->r_count); @@ -527,7 +528,7 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci, */ read_lock(&ci->m_lock); list_for_each_entry(opinfo, &ci->m_op_list, op_entry) { - if (!opinfo->is_lease) + if (!opinfo->is_lease || !opinfo->conn) continue; read_unlock(&ci->m_lock); lease = opinfo->o_lease; @@ -651,7 +652,7 @@ static void __smb2_oplock_break_noti(struct work_struct *wk) struct smb2_hdr *rsp_hdr; struct ksmbd_file *fp; - fp = ksmbd_lookup_durable_fd(br_info->fid); + fp = ksmbd_lookup_global_fd(br_info->fid); if (!fp) goto out; @@ -1115,7 +1116,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp, read_lock(&p_ci->m_lock); list_for_each_entry(opinfo, &p_ci->m_op_list, op_entry) { - if (!opinfo->is_lease) + if (opinfo->conn == NULL || !opinfo->is_lease) continue; if (opinfo->o_lease->state != SMB2_OPLOCK_LEVEL_NONE && @@ -1151,7 +1152,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp) opinfo = rcu_dereference(fp->f_opinfo); rcu_read_unlock(); - if (!opinfo->is_lease || opinfo->o_lease->version != 2) + if (!opinfo || !opinfo->is_lease || opinfo->o_lease->version != 2) return; p_ci = ksmbd_inode_lookup_lock(fp->filp->f_path.dentry->d_parent); @@ -1160,7 +1161,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp) read_lock(&p_ci->m_lock); list_for_each_entry(opinfo, &p_ci->m_op_list, op_entry) { - if (!opinfo->is_lease) + if (opinfo->conn == NULL || !opinfo->is_lease) continue; if (opinfo->o_lease->state != SMB2_OPLOCK_LEVEL_NONE) { @@ -1372,6 +1373,9 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp, rcu_read_lock(); list_for_each_entry_rcu(brk_op, &ci->m_op_list, op_entry) { + if (brk_op->conn == NULL) + continue; + if (!atomic_inc_not_zero(&brk_op->refcount)) continue; @@ -1508,11 +1512,10 @@ void create_lease_buf(u8 *rbuf, struct lease *lease) /** * parse_lease_state() - parse lease context containted in file open request * @open_req: buffer containing smb2 file open(create) request - * @is_dir: whether leasing file is directory * * Return: oplock state, -ENOENT if create lease context not found */ -struct lease_ctx_info *parse_lease_state(void *open_req, bool is_dir) +struct lease_ctx_info *parse_lease_state(void *open_req) { struct create_context *cc; struct smb2_create_req *req = (struct smb2_create_req *)open_req; @@ -1530,12 +1533,7 @@ struct lease_ctx_info *parse_lease_state(void *open_req, bool is_dir) struct create_lease_v2 *lc = (struct create_lease_v2 *)cc; memcpy(lreq->lease_key, lc->lcontext.LeaseKey, SMB2_LEASE_KEY_SIZE); - if (is_dir) { - lreq->req_state = lc->lcontext.LeaseState & - ~SMB2_LEASE_WRITE_CACHING_LE; - lreq->is_dir = true; - } else - lreq->req_state = lc->lcontext.LeaseState; + lreq->req_state = lc->lcontext.LeaseState; lreq->flags = lc->lcontext.LeaseFlags; lreq->epoch = lc->lcontext.Epoch; lreq->duration = lc->lcontext.LeaseDuration; @@ -1659,6 +1657,8 @@ void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp) buf->Name[3] = 'Q'; buf->Timeout = cpu_to_le32(fp->durable_timeout); + if (fp->is_persistent) + buf->Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT); } /** @@ -1826,3 +1826,71 @@ struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn, read_unlock(&lease_list_lock); return ret_op; } + +int smb2_check_durable_oplock(struct ksmbd_conn *conn, + struct ksmbd_share_config *share, + struct ksmbd_file *fp, + struct lease_ctx_info *lctx, + char *name) +{ + struct oplock_info *opinfo = opinfo_get(fp); + int ret = 0; + + if (!opinfo) + return 0; + + if (opinfo->is_lease == false) { + if (lctx) { + pr_err("create context include lease\n"); + ret = -EBADF; + goto out; + } + + if (opinfo->level != SMB2_OPLOCK_LEVEL_BATCH) { + pr_err("oplock level is not equal to SMB2_OPLOCK_LEVEL_BATCH\n"); + ret = -EBADF; + } + + goto out; + } + + if (memcmp(conn->ClientGUID, fp->client_guid, + SMB2_CLIENT_GUID_SIZE)) { + ksmbd_debug(SMB, "Client guid of fp is not equal to the one of connection\n"); + ret = -EBADF; + goto out; + } + + if (!lctx) { + ksmbd_debug(SMB, "create context does not include lease\n"); + ret = -EBADF; + goto out; + } + + if (memcmp(opinfo->o_lease->lease_key, lctx->lease_key, + SMB2_LEASE_KEY_SIZE)) { + ksmbd_debug(SMB, + "lease key of fp does not match lease key in create context\n"); + ret = -EBADF; + goto out; + } + + if (!(opinfo->o_lease->state & SMB2_LEASE_HANDLE_CACHING_LE)) { + ksmbd_debug(SMB, "lease state does not contain SMB2_LEASE_HANDLE_CACHING\n"); + ret = -EBADF; + goto out; + } + + if (opinfo->o_lease->version != lctx->version) { + ksmbd_debug(SMB, + "lease version of fp does not match the one in create context\n"); + ret = -EBADF; + goto out; + } + + if (!ksmbd_inode_pending_delete(fp)) + ret = ksmbd_validate_name_reconnect(share, fp, name); +out: + opinfo_put(opinfo); + return ret; +} diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h index 5b93ea9196c0..e9da63f25b20 100644 --- a/fs/smb/server/oplock.h +++ b/fs/smb/server/oplock.h @@ -111,7 +111,7 @@ void opinfo_put(struct oplock_info *opinfo); /* Lease related functions */ void create_lease_buf(u8 *rbuf, struct lease *lease); -struct lease_ctx_info *parse_lease_state(void *open_req, bool is_dir); +struct lease_ctx_info *parse_lease_state(void *open_req); __u8 smb2_map_lease_to_oplock(__le32 lease_state); int lease_read_to_write(struct oplock_info *opinfo); @@ -130,4 +130,9 @@ void destroy_lease_table(struct ksmbd_conn *conn); void smb_send_parent_lease_break_noti(struct ksmbd_file *fp, struct lease_ctx_info *lctx); void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp); +int smb2_check_durable_oplock(struct ksmbd_conn *conn, + struct ksmbd_share_config *share, + struct ksmbd_file *fp, + struct lease_ctx_info *lctx, + char *name); #endif /* __KSMBD_OPLOCK_H */ diff --git a/fs/smb/server/smb2misc.c b/fs/smb/server/smb2misc.c index 03dded29a980..727cb49926ee 100644 --- a/fs/smb/server/smb2misc.c +++ b/fs/smb/server/smb2misc.c @@ -101,13 +101,17 @@ static int smb2_get_data_area_len(unsigned int *off, unsigned int *len, *len = le16_to_cpu(((struct smb2_sess_setup_req *)hdr)->SecurityBufferLength); break; case SMB2_TREE_CONNECT: - *off = le16_to_cpu(((struct smb2_tree_connect_req *)hdr)->PathOffset); + *off = max_t(unsigned short int, + le16_to_cpu(((struct smb2_tree_connect_req *)hdr)->PathOffset), + offsetof(struct smb2_tree_connect_req, Buffer)); *len = le16_to_cpu(((struct smb2_tree_connect_req *)hdr)->PathLength); break; case SMB2_CREATE: { unsigned short int name_off = - le16_to_cpu(((struct smb2_create_req *)hdr)->NameOffset); + max_t(unsigned short int, + le16_to_cpu(((struct smb2_create_req *)hdr)->NameOffset), + offsetof(struct smb2_create_req, Buffer)); unsigned short int name_len = le16_to_cpu(((struct smb2_create_req *)hdr)->NameLength); @@ -128,11 +132,15 @@ static int smb2_get_data_area_len(unsigned int *off, unsigned int *len, break; } case SMB2_QUERY_INFO: - *off = le16_to_cpu(((struct smb2_query_info_req *)hdr)->InputBufferOffset); + *off = max_t(unsigned int, + le16_to_cpu(((struct smb2_query_info_req *)hdr)->InputBufferOffset), + offsetof(struct smb2_query_info_req, Buffer)); *len = le32_to_cpu(((struct smb2_query_info_req *)hdr)->InputBufferLength); break; case SMB2_SET_INFO: - *off = le16_to_cpu(((struct smb2_set_info_req *)hdr)->BufferOffset); + *off = max_t(unsigned int, + le16_to_cpu(((struct smb2_set_info_req *)hdr)->BufferOffset), + offsetof(struct smb2_set_info_req, Buffer)); *len = le32_to_cpu(((struct smb2_set_info_req *)hdr)->BufferLength); break; case SMB2_READ: @@ -142,7 +150,7 @@ static int smb2_get_data_area_len(unsigned int *off, unsigned int *len, case SMB2_WRITE: if (((struct smb2_write_req *)hdr)->DataOffset || ((struct smb2_write_req *)hdr)->Length) { - *off = max_t(unsigned int, + *off = max_t(unsigned short int, le16_to_cpu(((struct smb2_write_req *)hdr)->DataOffset), offsetof(struct smb2_write_req, Buffer)); *len = le32_to_cpu(((struct smb2_write_req *)hdr)->Length); @@ -153,7 +161,9 @@ static int smb2_get_data_area_len(unsigned int *off, unsigned int *len, *len = le16_to_cpu(((struct smb2_write_req *)hdr)->WriteChannelInfoLength); break; case SMB2_QUERY_DIRECTORY: - *off = le16_to_cpu(((struct smb2_query_directory_req *)hdr)->FileNameOffset); + *off = max_t(unsigned short int, + le16_to_cpu(((struct smb2_query_directory_req *)hdr)->FileNameOffset), + offsetof(struct smb2_query_directory_req, Buffer)); *len = le16_to_cpu(((struct smb2_query_directory_req *)hdr)->FileNameLength); break; case SMB2_LOCK: @@ -168,7 +178,9 @@ static int smb2_get_data_area_len(unsigned int *off, unsigned int *len, break; } case SMB2_IOCTL: - *off = le32_to_cpu(((struct smb2_ioctl_req *)hdr)->InputOffset); + *off = max_t(unsigned int, + le32_to_cpu(((struct smb2_ioctl_req *)hdr)->InputOffset), + offsetof(struct smb2_ioctl_req, Buffer)); *len = le32_to_cpu(((struct smb2_ioctl_req *)hdr)->InputCount); break; default: diff --git a/fs/smb/server/smb2ops.c b/fs/smb/server/smb2ops.c index 8600f32c981a..606aa3c5189a 100644 --- a/fs/smb/server/smb2ops.c +++ b/fs/smb/server/smb2ops.c @@ -261,6 +261,9 @@ void init_smb3_02_server(struct ksmbd_conn *conn) if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE) + conn->vals->capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES; } /** @@ -283,6 +286,9 @@ int init_smb3_11_server(struct ksmbd_conn *conn) if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; + if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE) + conn->vals->capabilities |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES; + INIT_LIST_HEAD(&conn->preauth_sess_table); return 0; } diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index fb9eea631069..1e536ae27761 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -611,30 +611,6 @@ int smb2_check_user_session(struct ksmbd_work *work) return -ENOENT; } -static void destroy_previous_session(struct ksmbd_conn *conn, - struct ksmbd_user *user, u64 id) -{ - struct ksmbd_session *prev_sess = ksmbd_session_lookup_slowpath(id); - struct ksmbd_user *prev_user; - struct channel *chann; - long index; - - if (!prev_sess) - return; - - prev_user = prev_sess->user; - - if (!prev_user || - strcmp(user->name, prev_user->name) || - user->passkey_sz != prev_user->passkey_sz || - memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) - return; - - prev_sess->state = SMB2_SESSION_EXPIRED; - xa_for_each(&prev_sess->ksmbd_chann_list, index, chann) - ksmbd_conn_set_exiting(chann->conn); -} - /** * smb2_get_name() - get filename string from on the wire smb format * @src: source buffer @@ -1955,7 +1931,7 @@ int smb2_tree_connect(struct ksmbd_work *work) WORK_BUFFERS(work, req, rsp); - treename = smb_strndup_from_utf16(req->Buffer, + treename = smb_strndup_from_utf16((char *)req + le16_to_cpu(req->PathOffset), le16_to_cpu(req->PathLength), true, conn->local_nls); if (IS_ERR(treename)) { @@ -2012,7 +1988,12 @@ int smb2_tree_connect(struct ksmbd_work *work) write_unlock(&sess->tree_conns_lock); rsp->StructureSize = cpu_to_le16(16); out_err1: - rsp->Capabilities = 0; + if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE && + test_share_config_flag(share, + KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY)) + rsp->Capabilities = SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY; + else + rsp->Capabilities = 0; rsp->Reserved = 0; /* default manual caching */ rsp->ShareFlags = SMB2_SHAREFLAG_MANUAL_CACHING; @@ -2646,6 +2627,165 @@ static void ksmbd_acls_fattr(struct smb_fattr *fattr, } } +enum { + DURABLE_RECONN_V2 = 1, + DURABLE_RECONN, + DURABLE_REQ_V2, + DURABLE_REQ, +}; + +struct durable_info { + struct ksmbd_file *fp; + unsigned short int type; + bool persistent; + bool reconnected; + unsigned int timeout; + char *CreateGuid; +}; + +static int parse_durable_handle_context(struct ksmbd_work *work, + struct smb2_create_req *req, + struct lease_ctx_info *lc, + struct durable_info *dh_info) +{ + struct ksmbd_conn *conn = work->conn; + struct create_context *context; + int dh_idx, err = 0; + u64 persistent_id = 0; + int req_op_level; + static const char * const durable_arr[] = {"DH2C", "DHnC", "DH2Q", "DHnQ"}; + + req_op_level = req->RequestedOplockLevel; + for (dh_idx = DURABLE_RECONN_V2; dh_idx <= ARRAY_SIZE(durable_arr); + dh_idx++) { + context = smb2_find_context_vals(req, durable_arr[dh_idx - 1], 4); + if (IS_ERR(context)) { + err = PTR_ERR(context); + goto out; + } + if (!context) + continue; + + switch (dh_idx) { + case DURABLE_RECONN_V2: + { + struct create_durable_reconn_v2_req *recon_v2; + + if (dh_info->type == DURABLE_RECONN || + dh_info->type == DURABLE_REQ_V2) { + err = -EINVAL; + goto out; + } + + recon_v2 = (struct create_durable_reconn_v2_req *)context; + persistent_id = recon_v2->Fid.PersistentFileId; + dh_info->fp = ksmbd_lookup_durable_fd(persistent_id); + if (!dh_info->fp) { + ksmbd_debug(SMB, "Failed to get durable handle state\n"); + err = -EBADF; + goto out; + } + + if (memcmp(dh_info->fp->create_guid, recon_v2->CreateGuid, + SMB2_CREATE_GUID_SIZE)) { + err = -EBADF; + ksmbd_put_durable_fd(dh_info->fp); + goto out; + } + + dh_info->type = dh_idx; + dh_info->reconnected = true; + ksmbd_debug(SMB, + "reconnect v2 Persistent-id from reconnect = %llu\n", + persistent_id); + break; + } + case DURABLE_RECONN: + { + struct create_durable_reconn_req *recon; + + if (dh_info->type == DURABLE_RECONN_V2 || + dh_info->type == DURABLE_REQ_V2) { + err = -EINVAL; + goto out; + } + + recon = (struct create_durable_reconn_req *)context; + persistent_id = recon->Data.Fid.PersistentFileId; + dh_info->fp = ksmbd_lookup_durable_fd(persistent_id); + if (!dh_info->fp) { + ksmbd_debug(SMB, "Failed to get durable handle state\n"); + err = -EBADF; + goto out; + } + + dh_info->type = dh_idx; + dh_info->reconnected = true; + ksmbd_debug(SMB, "reconnect Persistent-id from reconnect = %llu\n", + persistent_id); + break; + } + case DURABLE_REQ_V2: + { + struct create_durable_req_v2 *durable_v2_blob; + + if (dh_info->type == DURABLE_RECONN || + dh_info->type == DURABLE_RECONN_V2) { + err = -EINVAL; + goto out; + } + + durable_v2_blob = + (struct create_durable_req_v2 *)context; + ksmbd_debug(SMB, "Request for durable v2 open\n"); + dh_info->fp = ksmbd_lookup_fd_cguid(durable_v2_blob->CreateGuid); + if (dh_info->fp) { + if (!memcmp(conn->ClientGUID, dh_info->fp->client_guid, + SMB2_CLIENT_GUID_SIZE)) { + if (!(req->hdr.Flags & SMB2_FLAGS_REPLAY_OPERATION)) { + err = -ENOEXEC; + goto out; + } + + dh_info->fp->conn = conn; + dh_info->reconnected = true; + goto out; + } + } + + if (((lc && (lc->req_state & SMB2_LEASE_HANDLE_CACHING_LE)) || + req_op_level == SMB2_OPLOCK_LEVEL_BATCH)) { + dh_info->CreateGuid = + durable_v2_blob->CreateGuid; + dh_info->persistent = + le32_to_cpu(durable_v2_blob->Flags); + dh_info->timeout = + le32_to_cpu(durable_v2_blob->Timeout); + dh_info->type = dh_idx; + } + break; + } + case DURABLE_REQ: + if (dh_info->type == DURABLE_RECONN) + goto out; + if (dh_info->type == DURABLE_RECONN_V2 || + dh_info->type == DURABLE_REQ_V2) { + err = -EINVAL; + goto out; + } + + if (((lc && (lc->req_state & SMB2_LEASE_HANDLE_CACHING_LE)) || + req_op_level == SMB2_OPLOCK_LEVEL_BATCH)) { + ksmbd_debug(SMB, "Request for durable open\n"); + dh_info->type = dh_idx; + } + } + } + +out: + return err; +} + /** * smb2_open() - handler for smb file open request * @work: smb work containing request buffer @@ -2669,6 +2809,7 @@ int smb2_open(struct ksmbd_work *work) struct lease_ctx_info *lc = NULL; struct create_ea_buf_req *ea_buf = NULL; struct oplock_info *opinfo; + struct durable_info dh_info = {0}; __le32 *next_ptr = NULL; int req_op_level = 0, open_flags = 0, may_flags = 0, file_info = 0; int rc = 0; @@ -2708,7 +2849,7 @@ int smb2_open(struct ksmbd_work *work) goto err_out2; } - name = smb2_get_name(req->Buffer, + name = smb2_get_name((char *)req + le16_to_cpu(req->NameOffset), le16_to_cpu(req->NameLength), work->conn->local_nls); if (IS_ERR(name)) { @@ -2749,6 +2890,49 @@ int smb2_open(struct ksmbd_work *work) } } + req_op_level = req->RequestedOplockLevel; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE && + req->CreateContextsOffset) { + lc = parse_lease_state(req); + rc = parse_durable_handle_context(work, req, lc, &dh_info); + if (rc) { + ksmbd_debug(SMB, "error parsing durable handle context\n"); + goto err_out2; + } + + if (dh_info.reconnected == true) { + rc = smb2_check_durable_oplock(conn, share, dh_info.fp, lc, name); + if (rc) { + ksmbd_put_durable_fd(dh_info.fp); + goto err_out2; + } + + rc = ksmbd_reopen_durable_fd(work, dh_info.fp); + if (rc) { + ksmbd_put_durable_fd(dh_info.fp); + goto err_out2; + } + + if (ksmbd_override_fsids(work)) { + rc = -ENOMEM; + ksmbd_put_durable_fd(dh_info.fp); + goto err_out2; + } + + fp = dh_info.fp; + file_info = FILE_OPENED; + + rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat); + if (rc) + goto err_out2; + + ksmbd_put_durable_fd(fp); + goto reconnected_fp; + } + } else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) + lc = parse_lease_state(req); + if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE)) { pr_err("Invalid impersonationlevel : 0x%x\n", le32_to_cpu(req->ImpersonationLevel)); @@ -3211,10 +3395,6 @@ int smb2_open(struct ksmbd_work *work) need_truncate = 1; } - req_op_level = req->RequestedOplockLevel; - if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) - lc = parse_lease_state(req, S_ISDIR(file_inode(filp)->i_mode)); - share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp); if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS) || (req_op_level == SMB2_OPLOCK_LEVEL_LEASE && @@ -3225,6 +3405,11 @@ int smb2_open(struct ksmbd_work *work) } } else { if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) { + if (S_ISDIR(file_inode(filp)->i_mode)) { + lc->req_state &= ~SMB2_LEASE_WRITE_CACHING_LE; + lc->is_dir = true; + } + /* * Compare parent lease using parent key. If there is no * a lease that has same parent key, Send lease break @@ -3321,6 +3506,26 @@ int smb2_open(struct ksmbd_work *work) memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE); + if (dh_info.type == DURABLE_REQ_V2 || dh_info.type == DURABLE_REQ) { + if (dh_info.type == DURABLE_REQ_V2 && dh_info.persistent && + test_share_config_flag(work->tcon->share_conf, + KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY)) + fp->is_persistent = true; + else + fp->is_durable = true; + + if (dh_info.type == DURABLE_REQ_V2) { + memcpy(fp->create_guid, dh_info.CreateGuid, + SMB2_CREATE_GUID_SIZE); + if (dh_info.timeout) + fp->durable_timeout = min(dh_info.timeout, + 300000); + else + fp->durable_timeout = 60; + } + } + +reconnected_fp: rsp->StructureSize = cpu_to_le16(89); rcu_read_lock(); opinfo = rcu_dereference(fp->f_opinfo); @@ -3407,6 +3612,33 @@ int smb2_open(struct ksmbd_work *work) next_off = conn->vals->create_disk_id_size; } + if (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2) { + struct create_context *durable_ccontext; + + durable_ccontext = (struct create_context *)(rsp->Buffer + + le32_to_cpu(rsp->CreateContextsLength)); + contxt_cnt++; + if (dh_info.type == DURABLE_REQ) { + create_durable_rsp_buf(rsp->Buffer + + le32_to_cpu(rsp->CreateContextsLength)); + le32_add_cpu(&rsp->CreateContextsLength, + conn->vals->create_durable_size); + iov_len += conn->vals->create_durable_size; + } else { + create_durable_v2_rsp_buf(rsp->Buffer + + le32_to_cpu(rsp->CreateContextsLength), + fp); + le32_add_cpu(&rsp->CreateContextsLength, + conn->vals->create_durable_v2_size); + iov_len += conn->vals->create_durable_v2_size; + } + + if (next_ptr) + *next_ptr = cpu_to_le32(next_off); + next_ptr = &durable_ccontext->Next; + next_off = conn->vals->create_durable_size; + } + if (posix_ctxt) { contxt_cnt++; create_posix_rsp_buf(rsp->Buffer + @@ -4084,7 +4316,7 @@ int smb2_query_dir(struct ksmbd_work *work) } srch_flag = req->Flags; - srch_ptr = smb_strndup_from_utf16(req->Buffer, + srch_ptr = smb_strndup_from_utf16((char *)req + le16_to_cpu(req->FileNameOffset), le16_to_cpu(req->FileNameLength), 1, conn->local_nls); if (IS_ERR(srch_ptr)) { @@ -4344,7 +4576,8 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp, sizeof(struct smb2_ea_info_req)) return -EINVAL; - ea_req = (struct smb2_ea_info_req *)req->Buffer; + ea_req = (struct smb2_ea_info_req *)((char *)req + + le16_to_cpu(req->InputBufferOffset)); } else { /* need to send all EAs, if no specific EA is requested*/ if (le32_to_cpu(req->Flags) & SL_RETURN_SINGLE_ENTRY) @@ -5991,6 +6224,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, struct ksmbd_share_config *share) { unsigned int buf_len = le32_to_cpu(req->BufferLength); + char *buffer = (char *)req + le16_to_cpu(req->BufferOffset); switch (req->FileInfoClass) { case FILE_BASIC_INFORMATION: @@ -5998,7 +6232,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, if (buf_len < sizeof(struct smb2_file_basic_info)) return -EINVAL; - return set_file_basic_info(fp, (struct smb2_file_basic_info *)req->Buffer, share); + return set_file_basic_info(fp, (struct smb2_file_basic_info *)buffer, share); } case FILE_ALLOCATION_INFORMATION: { @@ -6006,7 +6240,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, return -EINVAL; return set_file_allocation_info(work, fp, - (struct smb2_file_alloc_info *)req->Buffer); + (struct smb2_file_alloc_info *)buffer); } case FILE_END_OF_FILE_INFORMATION: { @@ -6014,7 +6248,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, return -EINVAL; return set_end_of_file_info(work, fp, - (struct smb2_file_eof_info *)req->Buffer); + (struct smb2_file_eof_info *)buffer); } case FILE_RENAME_INFORMATION: { @@ -6022,7 +6256,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, return -EINVAL; return set_rename_info(work, fp, - (struct smb2_file_rename_info *)req->Buffer, + (struct smb2_file_rename_info *)buffer, buf_len); } case FILE_LINK_INFORMATION: @@ -6031,7 +6265,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, return -EINVAL; return smb2_create_link(work, work->tcon->share_conf, - (struct smb2_file_link_info *)req->Buffer, + (struct smb2_file_link_info *)buffer, buf_len, fp->filp, work->conn->local_nls); } @@ -6041,7 +6275,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, return -EINVAL; return set_file_disposition_info(fp, - (struct smb2_file_disposition_info *)req->Buffer); + (struct smb2_file_disposition_info *)buffer); } case FILE_FULL_EA_INFORMATION: { @@ -6054,7 +6288,7 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, if (buf_len < sizeof(struct smb2_ea_info)) return -EINVAL; - return smb2_set_ea((struct smb2_ea_info *)req->Buffer, + return smb2_set_ea((struct smb2_ea_info *)buffer, buf_len, &fp->filp->f_path, true); } case FILE_POSITION_INFORMATION: @@ -6062,14 +6296,14 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp, if (buf_len < sizeof(struct smb2_file_pos_info)) return -EINVAL; - return set_file_position_info(fp, (struct smb2_file_pos_info *)req->Buffer); + return set_file_position_info(fp, (struct smb2_file_pos_info *)buffer); } case FILE_MODE_INFORMATION: { if (buf_len < sizeof(struct smb2_file_mode_info)) return -EINVAL; - return set_file_mode_info(fp, (struct smb2_file_mode_info *)req->Buffer); + return set_file_mode_info(fp, (struct smb2_file_mode_info *)buffer); } } @@ -6150,7 +6384,7 @@ int smb2_set_info(struct ksmbd_work *work) } rc = smb2_set_info_sec(fp, le32_to_cpu(req->AdditionalInformation), - req->Buffer, + (char *)req + le16_to_cpu(req->BufferOffset), le32_to_cpu(req->BufferLength)); ksmbd_revert_fsids(work); break; @@ -7596,7 +7830,7 @@ static int fsctl_pipe_transceive(struct ksmbd_work *work, u64 id, struct smb2_ioctl_rsp *rsp) { struct ksmbd_rpc_command *rpc_resp; - char *data_buf = (char *)&req->Buffer[0]; + char *data_buf = (char *)req + le32_to_cpu(req->InputOffset); int nbytes = 0; rpc_resp = ksmbd_rpc_ioctl(work->sess, id, data_buf, @@ -7709,6 +7943,7 @@ int smb2_ioctl(struct ksmbd_work *work) u64 id = KSMBD_NO_FID; struct ksmbd_conn *conn = work->conn; int ret = 0; + char *buffer; if (work->next_smb2_rcv_hdr_off) { req = ksmbd_req_buf_next(work); @@ -7731,6 +7966,8 @@ int smb2_ioctl(struct ksmbd_work *work) goto out; } + buffer = (char *)req + le32_to_cpu(req->InputOffset); + cnt_code = le32_to_cpu(req->CtlCode); ret = smb2_calc_max_out_buf_len(work, 48, le32_to_cpu(req->MaxOutputResponse)); @@ -7788,7 +8025,7 @@ int smb2_ioctl(struct ksmbd_work *work) } ret = fsctl_validate_negotiate_info(conn, - (struct validate_negotiate_info_req *)&req->Buffer[0], + (struct validate_negotiate_info_req *)buffer, (struct validate_negotiate_info_rsp *)&rsp->Buffer[0], in_buf_len); if (ret < 0) @@ -7841,7 +8078,7 @@ int smb2_ioctl(struct ksmbd_work *work) rsp->VolatileFileId = req->VolatileFileId; rsp->PersistentFileId = req->PersistentFileId; fsctl_copychunk(work, - (struct copychunk_ioctl_req *)&req->Buffer[0], + (struct copychunk_ioctl_req *)buffer, le32_to_cpu(req->CtlCode), le32_to_cpu(req->InputCount), req->VolatileFileId, @@ -7854,8 +8091,7 @@ int smb2_ioctl(struct ksmbd_work *work) goto out; } - ret = fsctl_set_sparse(work, id, - (struct file_sparse *)&req->Buffer[0]); + ret = fsctl_set_sparse(work, id, (struct file_sparse *)buffer); if (ret < 0) goto out; break; @@ -7878,7 +8114,7 @@ int smb2_ioctl(struct ksmbd_work *work) } zero_data = - (struct file_zero_data_information *)&req->Buffer[0]; + (struct file_zero_data_information *)buffer; off = le64_to_cpu(zero_data->FileOffset); bfz = le64_to_cpu(zero_data->BeyondFinalZero); @@ -7909,7 +8145,7 @@ int smb2_ioctl(struct ksmbd_work *work) } ret = fsctl_query_allocated_ranges(work, id, - (struct file_allocated_range_buffer *)&req->Buffer[0], + (struct file_allocated_range_buffer *)buffer, (struct file_allocated_range_buffer *)&rsp->Buffer[0], out_buf_len / sizeof(struct file_allocated_range_buffer), &nbytes); @@ -7953,7 +8189,7 @@ int smb2_ioctl(struct ksmbd_work *work) goto out; } - dup_ext = (struct duplicate_extents_to_file *)&req->Buffer[0]; + dup_ext = (struct duplicate_extents_to_file *)buffer; fp_in = ksmbd_lookup_fd_slow(work, dup_ext->VolatileFileHandle, dup_ext->PersistentFileHandle); diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index d12cfd3b0927..643f5e1cfe35 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -64,7 +64,7 @@ struct preauth_integrity_info { #define SMB2_SESSION_TIMEOUT (10 * HZ) struct create_durable_req_v2 { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le32 Timeout; __le32 Flags; @@ -72,8 +72,20 @@ struct create_durable_req_v2 { __u8 CreateGuid[16]; } __packed; +struct create_durable_reconn_req { + struct create_context_hdr ccontext; + __u8 Name[8]; + union { + __u8 Reserved[16]; + struct { + __u64 PersistentFileId; + __u64 VolatileFileId; + } Fid; + } Data; +} __packed; + struct create_durable_reconn_v2_req { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct { __u64 PersistentFileId; @@ -84,13 +96,13 @@ struct create_durable_reconn_v2_req { } __packed; struct create_alloc_size_req { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le64 AllocationSize; } __packed; struct create_durable_rsp { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; union { __u8 Reserved[8]; @@ -98,8 +110,11 @@ struct create_durable_rsp { } Data; } __packed; +/* See MS-SMB2 2.2.13.2.11 */ +/* Flags */ +#define SMB2_DHANDLE_FLAG_PERSISTENT 0x00000002 struct create_durable_v2_rsp { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; __le32 Timeout; __le32 Flags; @@ -107,7 +122,7 @@ struct create_durable_v2_rsp { /* equivalent of the contents of SMB3.1.1 POSIX open context response */ struct create_posix_rsp { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[16]; __le32 nlink; __le32 reparse_tag; @@ -366,13 +381,13 @@ struct smb2_ea_info { } __packed; /* level 15 Query */ struct create_ea_buf_req { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct smb2_ea_info ea; } __packed; struct create_sd_buf_req { - struct create_context ccontext; + struct create_context_hdr ccontext; __u8 Name[8]; struct smb_ntsd ntsd; } __packed; diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c index 0012919309f1..6633fa78e9b9 100644 --- a/fs/smb/server/transport_tcp.c +++ b/fs/smb/server/transport_tcp.c @@ -365,6 +365,7 @@ static int ksmbd_tcp_readv(struct tcp_transport *t, struct kvec *iov_orig, * @t: TCP transport instance * @buf: buffer to store read data from socket * @to_read: number of bytes to read from socket + * @max_retries: number of retries if reading from socket fails * * Return: on success return number of bytes read from socket, * otherwise return error number @@ -416,6 +417,7 @@ static void tcp_destroy_socket(struct socket *ksmbd_socket) /** * create_socket - create socket for ksmbd/0 + * @iface: interface to bind the created socket to * * Return: 0 on success, error number otherwise */ diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 626406b0cf4a..255811996935 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -49,6 +49,10 @@ static void ksmbd_vfs_inherit_owner(struct ksmbd_work *work, /** * ksmbd_vfs_lock_parent() - lock parent dentry if it is stable + * @parent: parent dentry + * @child: child dentry + * + * Returns: %0 on success, %-ENOENT if the parent dentry is not stable */ int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child) { @@ -360,7 +364,7 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end, /** * ksmbd_vfs_read() - vfs helper for smb file read * @work: smb work - * @fid: file id of open file + * @fp: ksmbd file pointer * @count: read byte count * @pos: file pos * @rbuf: read data buffer @@ -474,7 +478,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos, /** * ksmbd_vfs_write() - vfs helper for smb file write * @work: work - * @fid: file id of open file + * @fp: ksmbd file pointer * @buf: buf containing data for writing * @count: read byte count * @pos: file pos @@ -545,10 +549,8 @@ int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp, /** * ksmbd_vfs_getattr() - vfs helper for smb getattr - * @work: work - * @fid: file id of open file - * @attrs: inode attributes - * + * @path: path of dentry + * @stat: pointer to returned kernel stat structure * Return: 0 on success, otherwise error */ int ksmbd_vfs_getattr(const struct path *path, struct kstat *stat) @@ -565,6 +567,7 @@ int ksmbd_vfs_getattr(const struct path *path, struct kstat *stat) * ksmbd_vfs_fsync() - vfs helper for smb fsync * @work: work * @fid: file id of open file + * @p_id: persistent file id * * Return: 0 on success, otherwise error */ @@ -587,7 +590,8 @@ int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id) /** * ksmbd_vfs_remove_file() - vfs helper for smb rmdir or unlink - * @name: directory or file name that is relative to share + * @work: work + * @path: path of dentry * * Return: 0 on success, otherwise error */ @@ -623,6 +627,7 @@ int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path) /** * ksmbd_vfs_link() - vfs helper for creating smb hardlink + * @work: work * @oldname: source file name * @newname: hardlink name that is relative to share * @@ -800,7 +805,7 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path, /** * ksmbd_vfs_truncate() - vfs helper for smb file truncate * @work: work - * @fid: file id of old file + * @fp: ksmbd file pointer * @size: truncate to given size * * Return: 0 on success, otherwise error @@ -843,7 +848,6 @@ int ksmbd_vfs_truncate(struct ksmbd_work *work, * ksmbd_vfs_listxattr() - vfs helper for smb list extended attributes * @dentry: dentry of file for listing xattrs * @list: destination buffer - * @size: destination buffer length * * Return: xattr list length on success, otherwise error */ @@ -952,7 +956,7 @@ int ksmbd_vfs_setxattr(struct mnt_idmap *idmap, /** * ksmbd_vfs_set_fadvise() - convert smb IO caching options to linux options * @filp: file pointer for IO - * @options: smb IO options + * @option: smb IO options */ void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option) { @@ -1164,6 +1168,7 @@ static bool __caseless_lookup(struct dir_context *ctx, const char *name, * @dir: path info * @name: filename to lookup * @namelen: filename length + * @um: &struct unicode_map to use * * Return: 0 on success, otherwise error */ @@ -1194,6 +1199,7 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name, /** * ksmbd_vfs_kern_path_locked() - lookup a file and get path info + * @work: work * @name: file path that is relative to share * @flags: lookup flags * @parent_path: if lookup succeed, return parent_path info @@ -1641,6 +1647,8 @@ int ksmbd_vfs_get_dos_attrib_xattr(struct mnt_idmap *idmap, * ksmbd_vfs_init_kstat() - convert unix stat information to smb stat format * @p: destination buffer * @ksmbd_kstat: ksmbd kstat wrapper + * + * Returns: pointer to the converted &struct file_directory_info */ void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat) { diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index 4e82ff627d12..030f70700036 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -305,7 +305,8 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp) fd_limit_close(); __ksmbd_remove_durable_fd(fp); - __ksmbd_remove_fd(ft, fp); + if (ft) + __ksmbd_remove_fd(ft, fp); close_id_del_oplock(fp); filp = fp->filp; @@ -465,11 +466,32 @@ struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id, return fp; } -struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id) +struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id) { return __ksmbd_lookup_fd(&global_ft, id); } +struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id) +{ + struct ksmbd_file *fp; + + fp = __ksmbd_lookup_fd(&global_ft, id); + if (fp && fp->conn) { + ksmbd_put_durable_fd(fp); + fp = NULL; + } + + return fp; +} + +void ksmbd_put_durable_fd(struct ksmbd_file *fp) +{ + if (!atomic_dec_and_test(&fp->refcount)) + return; + + __ksmbd_close_fd(NULL, fp); +} + struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid) { struct ksmbd_file *fp = NULL; @@ -639,6 +661,32 @@ __close_file_table_ids(struct ksmbd_file_table *ft, return num; } +static inline bool is_reconnectable(struct ksmbd_file *fp) +{ + struct oplock_info *opinfo = opinfo_get(fp); + bool reconn = false; + + if (!opinfo) + return false; + + if (opinfo->op_state != OPLOCK_STATE_NONE) { + opinfo_put(opinfo); + return false; + } + + if (fp->is_resilient || fp->is_persistent) + reconn = true; + else if (fp->is_durable && opinfo->is_lease && + opinfo->o_lease->state & SMB2_LEASE_HANDLE_CACHING_LE) + reconn = true; + + else if (fp->is_durable && opinfo->level == SMB2_OPLOCK_LEVEL_BATCH) + reconn = true; + + opinfo_put(opinfo); + return reconn; +} + static bool tree_conn_fd_check(struct ksmbd_tree_connect *tcon, struct ksmbd_file *fp) { @@ -648,7 +696,28 @@ static bool tree_conn_fd_check(struct ksmbd_tree_connect *tcon, static bool session_fd_check(struct ksmbd_tree_connect *tcon, struct ksmbd_file *fp) { - return false; + struct ksmbd_inode *ci; + struct oplock_info *op; + struct ksmbd_conn *conn; + + if (!is_reconnectable(fp)) + return false; + + conn = fp->conn; + ci = fp->f_ci; + write_lock(&ci->m_lock); + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + if (op->conn != conn) + continue; + op->conn = NULL; + } + write_unlock(&ci->m_lock); + + fp->conn = NULL; + fp->tcon = NULL; + fp->volatile_id = KSMBD_NO_FID; + + return true; } void ksmbd_close_tree_conn_fds(struct ksmbd_work *work) @@ -687,6 +756,68 @@ void ksmbd_free_global_file_table(void) ksmbd_destroy_file_table(&global_ft); } +int ksmbd_validate_name_reconnect(struct ksmbd_share_config *share, + struct ksmbd_file *fp, char *name) +{ + char *pathname, *ab_pathname; + int ret = 0; + + pathname = kmalloc(PATH_MAX, GFP_KERNEL); + if (!pathname) + return -EACCES; + + ab_pathname = d_path(&fp->filp->f_path, pathname, PATH_MAX); + if (IS_ERR(ab_pathname)) { + kfree(pathname); + return -EACCES; + } + + if (name && strcmp(&ab_pathname[share->path_sz + 1], name)) { + ksmbd_debug(SMB, "invalid name reconnect %s\n", name); + ret = -EINVAL; + } + + kfree(pathname); + + return ret; +} + +int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) +{ + struct ksmbd_inode *ci; + struct oplock_info *op; + + if (!fp->is_durable || fp->conn || fp->tcon) { + pr_err("Invalid durable fd [%p:%p]\n", fp->conn, fp->tcon); + return -EBADF; + } + + if (has_file_id(fp->volatile_id)) { + pr_err("Still in use durable fd: %llu\n", fp->volatile_id); + return -EBADF; + } + + fp->conn = work->conn; + fp->tcon = work->tcon; + + ci = fp->f_ci; + write_lock(&ci->m_lock); + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + if (op->conn) + continue; + op->conn = fp->conn; + } + write_unlock(&ci->m_lock); + + __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID); + if (!has_file_id(fp->volatile_id)) { + fp->conn = NULL; + fp->tcon = NULL; + return -EBADF; + } + return 0; +} + int ksmbd_init_file_table(struct ksmbd_file_table *ft) { ft->idr = kzalloc(sizeof(struct idr), GFP_KERNEL); diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h index a528f0cc775a..ed44fb4e18e7 100644 --- a/fs/smb/server/vfs_cache.h +++ b/fs/smb/server/vfs_cache.h @@ -14,6 +14,7 @@ #include <linux/workqueue.h> #include "vfs.h" +#include "mgmt/share_config.h" /* Windows style file permissions for extended response */ #define FILE_GENERIC_ALL 0x1F01FF @@ -106,6 +107,9 @@ struct ksmbd_file { int dot_dotdot[2]; unsigned int f_state; bool reserve_lease_break; + bool is_durable; + bool is_persistent; + bool is_resilient; }; static inline void set_ctx_actor(struct dir_context *ctx, @@ -141,7 +145,9 @@ struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id, void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp); struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d); void ksmbd_inode_put(struct ksmbd_inode *ci); +struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id); struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id); +void ksmbd_put_durable_fd(struct ksmbd_file *fp); struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid); struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry); unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp); @@ -173,6 +179,9 @@ void ksmbd_set_inode_pending_delete(struct ksmbd_file *fp); void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp); void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp, int file_info); +int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp); +int ksmbd_validate_name_reconnect(struct ksmbd_share_config *share, + struct ksmbd_file *fp, char *name); int ksmbd_init_file_cache(void); void ksmbd_exit_file_cache(void); #endif /* __VFS_CACHE_H__ */ diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 5b6e86b2c37a..a7b65d4ab616 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -229,6 +229,19 @@ static inline unsigned int disk_openers(struct gendisk *disk) return atomic_read(&disk->part0->bd_openers); } +/** + * disk_has_partscan - return %true if partition scanning is enabled on a disk + * @disk: disk to check + * + * Returns %true if partitions scanning is enabled for @disk, or %false if + * partition scanning is disabled either permanently or temporarily. + */ +static inline bool disk_has_partscan(struct gendisk *disk) +{ + return !(disk->flags & (GENHD_FL_NO_PART | GENHD_FL_HIDDEN)) && + !test_bit(GD_SUPPRESS_PART_SCAN, &disk->state); +} + /* * The gendisk is refcounted by the part0 block_device, and the bd_device * therein is also used for device model presentation in sysfs. diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h index fc0d6f32c687..dfaae3e3ec15 100644 --- a/include/linux/bpf_types.h +++ b/include/linux/bpf_types.h @@ -142,9 +142,12 @@ BPF_LINK_TYPE(BPF_LINK_TYPE_ITER, iter) #ifdef CONFIG_NET BPF_LINK_TYPE(BPF_LINK_TYPE_NETNS, netns) BPF_LINK_TYPE(BPF_LINK_TYPE_XDP, xdp) +BPF_LINK_TYPE(BPF_LINK_TYPE_NETFILTER, netfilter) +BPF_LINK_TYPE(BPF_LINK_TYPE_TCX, tcx) #endif #ifdef CONFIG_PERF_EVENTS BPF_LINK_TYPE(BPF_LINK_TYPE_PERF_EVENT, perf) #endif BPF_LINK_TYPE(BPF_LINK_TYPE_KPROBE_MULTI, kprobe_multi) BPF_LINK_TYPE(BPF_LINK_TYPE_STRUCT_OPS, struct_ops) +BPF_LINK_TYPE(BPF_LINK_TYPE_UPROBE_MULTI, uprobe_multi) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 35c5f75a3a5e..7367ef7e92f5 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -1662,6 +1662,15 @@ struct hci_cp_le_set_event_mask { __u8 mask[8]; } __packed; +/* BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 4, Part E + * 7.8.2 LE Read Buffer Size command + * MAX_LE_MTU is 0xffff. + * 0 is also valid. It means that no dedicated LE Buffer exists. + * It should use the HCI_Read_Buffer_Size command and mtu is shared + * between BR/EDR and LE. + */ +#define HCI_MIN_LE_MTU 0x001b + #define HCI_OP_LE_READ_BUFFER_SIZE 0x2002 struct hci_rp_le_read_buffer_size { __u8 status; diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index e6f659ce534e..b5b0a1e1bba0 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -707,6 +707,7 @@ struct hci_conn { __u16 handle; __u16 sync_handle; __u16 state; + __u16 mtu; __u8 mode; __u8 type; __u8 role; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 1f8c8d65350c..aea7f06c107e 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -939,11 +939,37 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst, { struct hci_conn *conn; + switch (type) { + case ACL_LINK: + if (!hdev->acl_mtu) + return ERR_PTR(-ECONNREFUSED); + break; + case ISO_LINK: + if (hdev->iso_mtu) + /* Dedicated ISO Buffer exists */ + break; + fallthrough; + case LE_LINK: + if (hdev->le_mtu && hdev->le_mtu < HCI_MIN_LE_MTU) + return ERR_PTR(-ECONNREFUSED); + if (!hdev->le_mtu && hdev->acl_mtu < HCI_MIN_LE_MTU) + return ERR_PTR(-ECONNREFUSED); + break; + case SCO_LINK: + case ESCO_LINK: + if (!hdev->sco_pkts) + /* Controller does not support SCO or eSCO over HCI */ + return ERR_PTR(-ECONNREFUSED); + break; + default: + return ERR_PTR(-ECONNREFUSED); + } + bt_dev_dbg(hdev, "dst %pMR handle 0x%4.4x", dst, handle); conn = kzalloc(sizeof(*conn), GFP_KERNEL); if (!conn) - return NULL; + return ERR_PTR(-ENOMEM); bacpy(&conn->dst, dst); bacpy(&conn->src, &hdev->bdaddr); @@ -974,10 +1000,12 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst, switch (type) { case ACL_LINK: conn->pkt_type = hdev->pkt_type & ACL_PTYPE_MASK; + conn->mtu = hdev->acl_mtu; break; case LE_LINK: /* conn->src should reflect the local identity address */ hci_copy_identity_address(hdev, &conn->src, &conn->src_type); + conn->mtu = hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu; break; case ISO_LINK: /* conn->src should reflect the local identity address */ @@ -989,6 +1017,8 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst, else if (conn->role == HCI_ROLE_MASTER) conn->cleanup = cis_cleanup; + conn->mtu = hdev->iso_mtu ? hdev->iso_mtu : + hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu; break; case SCO_LINK: if (lmp_esco_capable(hdev)) @@ -996,9 +1026,12 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst, (hdev->esco_type & EDR_ESCO_MASK); else conn->pkt_type = hdev->pkt_type & SCO_PTYPE_MASK; + + conn->mtu = hdev->sco_mtu; break; case ESCO_LINK: conn->pkt_type = hdev->esco_type & ~EDR_ESCO_MASK; + conn->mtu = hdev->sco_mtu; break; } @@ -1041,7 +1074,7 @@ struct hci_conn *hci_conn_add_unset(struct hci_dev *hdev, int type, handle = hci_conn_hash_alloc_unset(hdev); if (unlikely(handle < 0)) - return NULL; + return ERR_PTR(-ECONNREFUSED); return hci_conn_add(hdev, type, dst, role, handle); } @@ -1384,8 +1417,8 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst, bacpy(&conn->dst, dst); } else { conn = hci_conn_add_unset(hdev, LE_LINK, dst, role); - if (!conn) - return ERR_PTR(-ENOMEM); + if (IS_ERR(conn)) + return conn; hci_conn_hold(conn); conn->pending_sec_level = sec_level; } @@ -1549,8 +1582,8 @@ static struct hci_conn *hci_add_bis(struct hci_dev *hdev, bdaddr_t *dst, return ERR_PTR(-EADDRINUSE); conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); - if (!conn) - return ERR_PTR(-ENOMEM); + if (IS_ERR(conn)) + return conn; conn->state = BT_CONNECT; @@ -1593,8 +1626,8 @@ struct hci_conn *hci_connect_le_scan(struct hci_dev *hdev, bdaddr_t *dst, BT_DBG("requesting refresh of dst_addr"); conn = hci_conn_add_unset(hdev, LE_LINK, dst, HCI_ROLE_MASTER); - if (!conn) - return ERR_PTR(-ENOMEM); + if (IS_ERR(conn)) + return conn; if (hci_explicit_conn_params_set(hdev, dst, dst_type) < 0) { hci_conn_del(conn); @@ -1641,8 +1674,8 @@ struct hci_conn *hci_connect_acl(struct hci_dev *hdev, bdaddr_t *dst, acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); if (!acl) { acl = hci_conn_add_unset(hdev, ACL_LINK, dst, HCI_ROLE_MASTER); - if (!acl) - return ERR_PTR(-ENOMEM); + if (IS_ERR(acl)) + return acl; } hci_conn_hold(acl); @@ -1701,9 +1734,9 @@ struct hci_conn *hci_connect_sco(struct hci_dev *hdev, int type, bdaddr_t *dst, sco = hci_conn_hash_lookup_ba(hdev, type, dst); if (!sco) { sco = hci_conn_add_unset(hdev, type, dst, HCI_ROLE_MASTER); - if (!sco) { + if (IS_ERR(sco)) { hci_conn_drop(acl); - return ERR_PTR(-ENOMEM); + return sco; } } @@ -1893,8 +1926,8 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, qos->ucast.cis); if (!cis) { cis = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); - if (!cis) - return ERR_PTR(-ENOMEM); + if (IS_ERR(cis)) + return cis; cis->cleanup = cis_cleanup; cis->dst_type = dst_type; cis->iso_qos.ucast.cig = BT_ISO_QOS_CIG_UNSET; @@ -2029,14 +2062,8 @@ static void hci_iso_qos_setup(struct hci_dev *hdev, struct hci_conn *conn, struct bt_iso_io_qos *qos, __u8 phy) { /* Only set MTU if PHY is enabled */ - if (!qos->sdu && qos->phy) { - if (hdev->iso_mtu > 0) - qos->sdu = hdev->iso_mtu; - else if (hdev->le_mtu > 0) - qos->sdu = hdev->le_mtu; - else - qos->sdu = hdev->acl_mtu; - } + if (!qos->sdu && qos->phy) + qos->sdu = conn->mtu; /* Use the same PHY as ACL if set to any */ if (qos->phy == BT_ISO_PHY_ANY) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 9274d3255049..361e2c68a51a 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -958,6 +958,9 @@ static u8 hci_cc_read_buffer_size(struct hci_dev *hdev, void *data, BT_DBG("%s acl mtu %d:%d sco mtu %d:%d", hdev->name, hdev->acl_mtu, hdev->acl_pkts, hdev->sco_mtu, hdev->sco_pkts); + if (!hdev->acl_mtu || !hdev->acl_pkts) + return HCI_ERROR_INVALID_PARAMETERS; + return rp->status; } @@ -1267,6 +1270,9 @@ static u8 hci_cc_le_read_buffer_size(struct hci_dev *hdev, void *data, BT_DBG("%s le mtu %d:%d", hdev->name, hdev->le_mtu, hdev->le_pkts); + if (hdev->le_mtu && hdev->le_mtu < HCI_MIN_LE_MTU) + return HCI_ERROR_INVALID_PARAMETERS; + return rp->status; } @@ -2351,8 +2357,8 @@ static void hci_cs_create_conn(struct hci_dev *hdev, __u8 status) if (!conn) { conn = hci_conn_add_unset(hdev, ACL_LINK, &cp->bdaddr, HCI_ROLE_MASTER); - if (!conn) - bt_dev_err(hdev, "no memory for new connection"); + if (IS_ERR(conn)) + bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn)); } } @@ -3165,8 +3171,8 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data, BDADDR_BREDR)) { conn = hci_conn_add_unset(hdev, ev->link_type, &ev->bdaddr, HCI_ROLE_SLAVE); - if (!conn) { - bt_dev_err(hdev, "no memory for new conn"); + if (IS_ERR(conn)) { + bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn)); goto unlock; } } else { @@ -3356,8 +3362,8 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data, if (!conn) { conn = hci_conn_add_unset(hdev, ev->link_type, &ev->bdaddr, HCI_ROLE_SLAVE); - if (!conn) { - bt_dev_err(hdev, "no memory for new connection"); + if (IS_ERR(conn)) { + bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn)); goto unlock; } } @@ -3834,6 +3840,9 @@ static u8 hci_cc_le_read_buffer_size_v2(struct hci_dev *hdev, void *data, BT_DBG("%s acl mtu %d:%d iso mtu %d:%d", hdev->name, hdev->acl_mtu, hdev->acl_pkts, hdev->iso_mtu, hdev->iso_pkts); + if (hdev->le_mtu && hdev->le_mtu < HCI_MIN_LE_MTU) + return HCI_ERROR_INVALID_PARAMETERS; + return rp->status; } @@ -5925,8 +5934,8 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, goto unlock; conn = hci_conn_add_unset(hdev, LE_LINK, bdaddr, role); - if (!conn) { - bt_dev_err(hdev, "no memory for new connection"); + if (IS_ERR(conn)) { + bt_dev_err(hdev, "connection err: %ld", PTR_ERR(conn)); goto unlock; } @@ -7051,7 +7060,7 @@ static void hci_le_cis_req_evt(struct hci_dev *hdev, void *data, if (!cis) { cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE, cis_handle); - if (!cis) { + if (IS_ERR(cis)) { hci_le_reject_cis(hdev, ev->cis_handle); goto unlock; } @@ -7170,7 +7179,7 @@ static void hci_le_big_sync_established_evt(struct hci_dev *hdev, void *data, if (!bis) { bis = hci_conn_add(hdev, ISO_LINK, BDADDR_ANY, HCI_ROLE_SLAVE, handle); - if (!bis) + if (IS_ERR(bis)) continue; } @@ -7242,7 +7251,7 @@ static void hci_le_big_info_adv_report_evt(struct hci_dev *hdev, void *data, pa_sync = hci_conn_add_unset(hdev, ISO_LINK, BDADDR_ANY, HCI_ROLE_SLAVE); - if (!pa_sync) + if (IS_ERR(pa_sync)) goto unlock; pa_sync->sync_handle = le16_to_cpu(ev->sync_handle); diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 2f63ea9e62ec..05b9edb480f0 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1135,7 +1135,7 @@ static int iso_sock_sendmsg(struct socket *sock, struct msghdr *msg, return -ENOTCONN; } - mtu = iso_pi(sk)->conn->hcon->hdev->iso_mtu; + mtu = iso_pi(sk)->conn->hcon->mtu; release_sock(sk); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1e961cfaa07b..ab5d0204086f 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -3905,13 +3905,12 @@ static inline int l2cap_command_rej(struct l2cap_conn *conn, return 0; } -static struct l2cap_chan *l2cap_connect(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, - u8 *data, u8 rsp_code, u8 amp_id) +static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, + u8 *data, u8 rsp_code, u8 amp_id) { struct l2cap_conn_req *req = (struct l2cap_conn_req *) data; struct l2cap_conn_rsp rsp; - struct l2cap_chan *chan = NULL, *pchan; + struct l2cap_chan *chan = NULL, *pchan = NULL; int result, status = L2CAP_CS_NO_INFO; u16 dcid = 0, scid = __le16_to_cpu(req->scid); @@ -3924,7 +3923,7 @@ static struct l2cap_chan *l2cap_connect(struct l2cap_conn *conn, &conn->hcon->dst, ACL_LINK); if (!pchan) { result = L2CAP_CR_BAD_PSM; - goto sendresp; + goto response; } mutex_lock(&conn->chan_lock); @@ -4011,17 +4010,15 @@ static struct l2cap_chan *l2cap_connect(struct l2cap_conn *conn, } response: - l2cap_chan_unlock(pchan); - mutex_unlock(&conn->chan_lock); - l2cap_chan_put(pchan); - -sendresp: rsp.scid = cpu_to_le16(scid); rsp.dcid = cpu_to_le16(dcid); rsp.result = cpu_to_le16(result); rsp.status = cpu_to_le16(status); l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp); + if (!pchan) + return; + if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) { struct l2cap_info_req info; info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK); @@ -4044,7 +4041,9 @@ static struct l2cap_chan *l2cap_connect(struct l2cap_conn *conn, chan->num_conf_req++; } - return chan; + l2cap_chan_unlock(pchan); + mutex_unlock(&conn->chan_lock); + l2cap_chan_put(pchan); } static int l2cap_connect_req(struct l2cap_conn *conn, @@ -6242,7 +6241,7 @@ static int l2cap_finish_move(struct l2cap_chan *chan) BT_DBG("chan %p", chan); chan->rx_state = L2CAP_RX_STATE_RECV; - chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu; + chan->conn->mtu = chan->conn->hcon->mtu; return l2cap_resegment(chan); } @@ -6309,7 +6308,7 @@ static int l2cap_rx_state_wait_f(struct l2cap_chan *chan, */ chan->next_tx_seq = control->reqseq; chan->unacked_frames = 0; - chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu; + chan->conn->mtu = chan->conn->hcon->mtu; err = l2cap_resegment(chan); @@ -6849,18 +6848,7 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon) BT_DBG("hcon %p conn %p hchan %p", hcon, conn, hchan); - switch (hcon->type) { - case LE_LINK: - if (hcon->hdev->le_mtu) { - conn->mtu = hcon->hdev->le_mtu; - break; - } - fallthrough; - default: - conn->mtu = hcon->hdev->acl_mtu; - break; - } - + conn->mtu = hcon->mtu; conn->feat_mask = 0; conn->local_fixed_chan = L2CAP_FC_SIG_BREDR | L2CAP_FC_CONNLESS; diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index ede7391f3aa9..3c3650902c83 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -126,7 +126,6 @@ static void sco_sock_clear_timer(struct sock *sk) /* ---- SCO connections ---- */ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) { - struct hci_dev *hdev = hcon->hdev; struct sco_conn *conn = hcon->sco_data; if (conn) { @@ -144,9 +143,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) hcon->sco_data = conn; conn->hcon = hcon; + conn->mtu = hcon->mtu; - if (hdev->sco_mtu > 0) - conn->mtu = hdev->sco_mtu; + if (hcon->mtu > 0) + conn->mtu = hcon->mtu; else conn->mtu = 60; diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index bc700f85f80b..ea277c55a38d 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -38,6 +38,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, u8 *end_work = scratch + SCRATCH_SIZE; u8 *priv, *pub; u16 priv_len, pub_len; + int ret; priv_len = get_unaligned_be16(src) + 2; priv = src; @@ -57,8 +58,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, unsigned char bool[3], *w = bool; /* tag 0 is emptyAuth */ w = asn1_encode_boolean(w, w + sizeof(bool), true); - if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) - return PTR_ERR(w); + if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) { + ret = PTR_ERR(w); + goto err; + } work = asn1_encode_tag(work, end_work, 0, bool, w - bool); } @@ -69,8 +72,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, * trigger, so if it does there's something nefarious going on */ if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE, - "BUG: scratch buffer is too small")) - return -EINVAL; + "BUG: scratch buffer is too small")) { + ret = -EINVAL; + goto err; + } work = asn1_encode_integer(work, end_work, options->keyhandle); work = asn1_encode_octet_string(work, end_work, pub, pub_len); @@ -79,10 +84,18 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, work1 = payload->blob; work1 = asn1_encode_sequence(work1, work1 + sizeof(payload->blob), scratch, work - scratch); - if (WARN(IS_ERR(work1), "BUG: ASN.1 encoder failed")) - return PTR_ERR(work1); + if (IS_ERR(work1)) { + ret = PTR_ERR(work1); + pr_err("BUG: ASN.1 encoder failed with %d\n", ret); + goto err; + } + kfree(scratch); return work1 - payload->blob; + +err: + kfree(scratch); + return ret; } struct tpm2_key_context { diff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h index e8eecbc83a60..ad7b97e16f37 100644 --- a/tools/testing/selftests/kselftest.h +++ b/tools/testing/selftests/kselftest.h @@ -48,6 +48,7 @@ #include <stdlib.h> #include <unistd.h> #include <stdarg.h> +#include <string.h> #include <stdio.h> #include <sys/utsname.h> #endif @@ -156,6 +157,19 @@ static inline void ksft_print_msg(const char *msg, ...) va_end(args); } +static inline void ksft_perror(const char *msg) +{ +#ifndef NOLIBC + ksft_print_msg("%s: %s (%d)\n", msg, strerror(errno), errno); +#else + /* + * nolibc doesn't provide strerror() and it seems + * inappropriate to add one, just print the errno. + */ + ksft_print_msg("%s: %d)\n", msg, errno); +#endif +} + static inline void ksft_test_result_pass(const char *msg, ...) { int saved_errno = errno;