Import of kernel-4.18.0-553.115.1.el8_10
This commit is contained in:
parent
4248d3d5bb
commit
195ec817d0
@ -147,6 +147,11 @@ enabled driver and mode options. The value in the file is a bit mask where the
|
||||
bit definitions are the same as those used with MTSETDRVBUFFER in setting the
|
||||
options.
|
||||
|
||||
Each directory contains the entry 'position_lost_in_reset'. If this value is
|
||||
one, reading and writing to the device is blocked after device reset. Most
|
||||
devices rewind the tape after reset and the writes/read don't access the
|
||||
tape position the user expects.
|
||||
|
||||
A link named 'tape' is made from the SCSI device directory to the class
|
||||
directory corresponding to the mode 0 auto-rewind device (e.g., st0).
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ RHEL_MINOR = 10
|
||||
#
|
||||
# Use this spot to avoid future merge conflicts.
|
||||
# Do not trim this comment.
|
||||
RHEL_RELEASE = 553.111.1
|
||||
RHEL_RELEASE = 553.115.1
|
||||
|
||||
#
|
||||
# ZSTREAM
|
||||
|
||||
@ -350,6 +350,7 @@ ENTRY(startup_kdump)
|
||||
.quad 0 # INITRD_SIZE
|
||||
.quad 0 # OLDMEM_BASE
|
||||
.quad 0 # OLDMEM_SIZE
|
||||
.quad 0 # KERNEL_VERSION
|
||||
.quad COMMAND_LINE_SIZE
|
||||
|
||||
.org COMMAND_LINE
|
||||
|
||||
@ -254,8 +254,10 @@ void *kexec_file_add_components(struct kimage *image,
|
||||
if (image->kernel_buf_len < minsize + max_command_line_size)
|
||||
goto out;
|
||||
|
||||
if (image->cmdline_buf_len >= max_command_line_size)
|
||||
if (image->cmdline_buf_len >= max_command_line_size) {
|
||||
pr_err("Kernel command line exceeds supported limit of %lu", max_command_line_size);
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(data.parm->command_line, image->cmdline_buf,
|
||||
image->cmdline_buf_len);
|
||||
|
||||
@ -1107,3 +1107,27 @@ bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check ctx,
|
||||
else
|
||||
return regs->sp <= ret->stack;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IA32_EMULATION
|
||||
unsigned long arch_uprobe_get_xol_area(void)
|
||||
{
|
||||
struct thread_info *ti = current_thread_info();
|
||||
unsigned long vaddr;
|
||||
|
||||
/*
|
||||
* HACK: we are not in a syscall, but x86 get_unmapped_area() paths
|
||||
* ignore TIF_ADDR32 and rely on in_32bit_syscall() to calculate
|
||||
* vm_unmapped_area_info.high_limit.
|
||||
*
|
||||
* The #ifdef above doesn't cover the CONFIG_X86_X32_ABI=y case,
|
||||
* but in this case in_32bit_syscall() -> in_x32_syscall() always
|
||||
* (falsely) returns true because ->orig_ax == -1.
|
||||
*/
|
||||
if (test_thread_flag(TIF_ADDR32))
|
||||
ti->status |= TS_COMPAT;
|
||||
vaddr = get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE, PAGE_SIZE, 0, 0);
|
||||
ti->status &= ~TS_COMPAT;
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1565,10 +1565,16 @@ destroy_macvlan_port:
|
||||
/* the macvlan port may be freed by macvlan_uninit when fail to register.
|
||||
* so we destroy the macvlan port only when it's valid.
|
||||
*/
|
||||
if (create && macvlan_port_get_rtnl(lowerdev)) {
|
||||
if (macvlan_port_get_rtnl(lowerdev)) {
|
||||
macvlan_flush_sources(port, vlan);
|
||||
macvlan_port_destroy(port->dev);
|
||||
if (create)
|
||||
macvlan_port_destroy(port->dev);
|
||||
}
|
||||
/* @dev might have been made visible before an error was detected.
|
||||
* Make sure to observe an RCU grace period before our caller
|
||||
* (rtnl_newlink()) frees it.
|
||||
*/
|
||||
synchronize_net();
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(macvlan_common_newlink);
|
||||
|
||||
@ -541,6 +541,18 @@ int scsi_check_sense(struct scsi_cmnd *scmd)
|
||||
|
||||
scsi_report_sense(sdev, &sshdr);
|
||||
|
||||
if (sshdr.sense_key == UNIT_ATTENTION) {
|
||||
/*
|
||||
* Increment the counters for Power on/Reset or New Media so
|
||||
* that all ULDs interested in these can see that those have
|
||||
* happened, even if someone else gets the sense data.
|
||||
*/
|
||||
if (sshdr.asc == 0x28)
|
||||
atomic_inc(&sdev->ua_new_media_ctr);
|
||||
else if (sshdr.asc == 0x29)
|
||||
atomic_inc(&sdev->ua_por_ctr);
|
||||
}
|
||||
|
||||
if (scsi_sense_is_deferred(&sshdr))
|
||||
return NEEDS_RETRY;
|
||||
|
||||
|
||||
@ -161,9 +161,11 @@ static const char *st_formats[] = {
|
||||
|
||||
static int debugging = DEBUG;
|
||||
|
||||
/* Setting these non-zero may risk recognizing resets */
|
||||
#define MAX_RETRIES 0
|
||||
#define MAX_WRITE_RETRIES 0
|
||||
#define MAX_READY_RETRIES 0
|
||||
|
||||
#define NO_TAPE NOT_READY
|
||||
|
||||
#define ST_TIMEOUT (900 * HZ)
|
||||
@ -362,10 +364,18 @@ static int st_chk_result(struct scsi_tape *STp, struct st_request * SRpnt)
|
||||
{
|
||||
int result = SRpnt->result;
|
||||
u8 scode;
|
||||
unsigned int ctr;
|
||||
DEB(const char *stp;)
|
||||
char *name = tape_name(STp);
|
||||
struct st_cmdstatus *cmdstatp;
|
||||
|
||||
ctr = scsi_get_ua_por_ctr(STp->device);
|
||||
if (ctr != STp->por_ctr) {
|
||||
STp->por_ctr = ctr;
|
||||
STp->pos_unknown = 1; /* ASC => power on / reset */
|
||||
st_printk(KERN_WARNING, STp, "Power on/reset recognized.");
|
||||
}
|
||||
|
||||
if (!result)
|
||||
return 0;
|
||||
|
||||
@ -418,10 +428,11 @@ static int st_chk_result(struct scsi_tape *STp, struct st_request * SRpnt)
|
||||
if (cmdstatp->have_sense &&
|
||||
cmdstatp->sense_hdr.asc == 0 && cmdstatp->sense_hdr.ascq == 0x17)
|
||||
STp->cleaning_req = 1; /* ASC and ASCQ => cleaning requested */
|
||||
if (cmdstatp->have_sense && scode == UNIT_ATTENTION && cmdstatp->sense_hdr.asc == 0x29)
|
||||
if (cmdstatp->have_sense && scode == UNIT_ATTENTION &&
|
||||
cmdstatp->sense_hdr.asc == 0x29 && !STp->pos_unknown) {
|
||||
STp->pos_unknown = 1; /* ASC => power on / reset */
|
||||
|
||||
STp->pos_unknown |= STp->device->was_reset;
|
||||
st_printk(KERN_WARNING, STp, "Power on/reset recognized.");
|
||||
}
|
||||
|
||||
if (cmdstatp->have_sense &&
|
||||
scode == RECOVERED_ERROR
|
||||
@ -836,6 +847,9 @@ static int flush_buffer(struct scsi_tape *STp, int seek_next)
|
||||
int backspace, result;
|
||||
struct st_partstat *STps;
|
||||
|
||||
if (STp->ready != ST_READY)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If there was a bus reset, block further access
|
||||
* to this device.
|
||||
@ -843,8 +857,6 @@ static int flush_buffer(struct scsi_tape *STp, int seek_next)
|
||||
if (STp->pos_unknown)
|
||||
return (-EIO);
|
||||
|
||||
if (STp->ready != ST_READY)
|
||||
return 0;
|
||||
STps = &(STp->ps[STp->partition]);
|
||||
if (STps->rw == ST_WRITING) /* Writing */
|
||||
return st_flush_write_buffer(STp);
|
||||
@ -953,7 +965,6 @@ static void reset_state(struct scsi_tape *STp)
|
||||
STp->partition = find_partition(STp);
|
||||
if (STp->partition < 0)
|
||||
STp->partition = 0;
|
||||
STp->new_partition = STp->partition;
|
||||
}
|
||||
}
|
||||
|
||||
@ -970,6 +981,7 @@ static int test_ready(struct scsi_tape *STp, int do_wait)
|
||||
{
|
||||
int attentions, waits, max_wait, scode;
|
||||
int retval = CHKRES_READY, new_session = 0;
|
||||
unsigned int ctr;
|
||||
unsigned char cmd[MAX_COMMAND_SIZE];
|
||||
struct st_request *SRpnt = NULL;
|
||||
struct st_cmdstatus *cmdstatp = &STp->buffer->cmdstat;
|
||||
@ -1026,6 +1038,13 @@ static int test_ready(struct scsi_tape *STp, int do_wait)
|
||||
}
|
||||
}
|
||||
|
||||
ctr = scsi_get_ua_new_media_ctr(STp->device);
|
||||
if (ctr != STp->new_media_ctr) {
|
||||
STp->new_media_ctr = ctr;
|
||||
new_session = 1;
|
||||
DEBC_printk(STp, "New tape session.");
|
||||
}
|
||||
|
||||
retval = (STp->buffer)->syscall_result;
|
||||
if (!retval)
|
||||
retval = new_session ? CHKRES_NEW_SESSION : CHKRES_READY;
|
||||
@ -2898,7 +2917,6 @@ static int st_int_ioctl(struct scsi_tape *STp, unsigned int cmd_in, unsigned lon
|
||||
timeout = STp->long_timeout * 8;
|
||||
|
||||
DEBC_printk(STp, "Erasing tape.\n");
|
||||
fileno = blkno = at_sm = 0;
|
||||
break;
|
||||
case MTSETBLK: /* Set block length */
|
||||
case MTSETDENSITY: /* Set tape density */
|
||||
@ -2931,14 +2949,17 @@ static int st_int_ioctl(struct scsi_tape *STp, unsigned int cmd_in, unsigned lon
|
||||
if (cmd_in == MTSETDENSITY) {
|
||||
(STp->buffer)->b_data[4] = arg;
|
||||
STp->density_changed = 1; /* At least we tried ;-) */
|
||||
STp->changed_density = arg;
|
||||
} else if (cmd_in == SET_DENS_AND_BLK)
|
||||
(STp->buffer)->b_data[4] = arg >> 24;
|
||||
else
|
||||
(STp->buffer)->b_data[4] = STp->density;
|
||||
if (cmd_in == MTSETBLK || cmd_in == SET_DENS_AND_BLK) {
|
||||
ltmp = arg & MT_ST_BLKSIZE_MASK;
|
||||
if (cmd_in == MTSETBLK)
|
||||
if (cmd_in == MTSETBLK) {
|
||||
STp->blksize_changed = 1; /* At least we tried ;-) */
|
||||
STp->changed_blksize = arg;
|
||||
}
|
||||
} else
|
||||
ltmp = STp->block_size;
|
||||
(STp->buffer)->b_data[9] = (ltmp >> 16);
|
||||
@ -3085,7 +3106,9 @@ static int st_int_ioctl(struct scsi_tape *STp, unsigned int cmd_in, unsigned lon
|
||||
cmd_in == MTSETDRVBUFFER ||
|
||||
cmd_in == SET_DENS_AND_BLK) {
|
||||
if (cmdstatp->sense_hdr.sense_key == ILLEGAL_REQUEST &&
|
||||
!(STp->use_pf & PF_TESTED)) {
|
||||
cmdstatp->sense_hdr.asc == 0x24 &&
|
||||
(STp->device)->scsi_level <= SCSI_2 &&
|
||||
!(STp->use_pf & PF_TESTED)) {
|
||||
/* Try the other possible state of Page Format if not
|
||||
already tried */
|
||||
STp->use_pf = (STp->use_pf ^ USE_PF) | PF_TESTED;
|
||||
@ -3505,8 +3528,69 @@ static int partition_tape(struct scsi_tape *STp, int size)
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handles any extra state needed for ioctls which are not st-specific.
|
||||
* Called with the scsi_tape lock held, released before return
|
||||
*/
|
||||
static long st_common_ioctl(struct scsi_tape *STp, struct st_modedef *STm,
|
||||
struct file *file, unsigned int cmd_in,
|
||||
unsigned long arg)
|
||||
{
|
||||
int i, retval = 0;
|
||||
void __user *p = (void __user *)arg;
|
||||
|
||||
if (!STm->defined) {
|
||||
retval = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (cmd_in) {
|
||||
case SCSI_IOCTL_GET_IDLUN:
|
||||
case SCSI_IOCTL_GET_BUS_NUMBER:
|
||||
case SCSI_IOCTL_GET_PCI:
|
||||
break;
|
||||
case SG_IO:
|
||||
case SCSI_IOCTL_SEND_COMMAND:
|
||||
case CDROM_SEND_PACKET:
|
||||
if (!capable(CAP_SYS_RAWIO)) {
|
||||
retval = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
retval = scsi_cmd_ioctl(STp->disk->queue, STp->disk,
|
||||
file->f_mode, cmd_in, p);
|
||||
|
||||
if (retval != -ENOTTY)
|
||||
goto out;
|
||||
|
||||
fallthrough;
|
||||
default:
|
||||
if ((i = flush_buffer(STp, 0)) < 0) {
|
||||
retval = i;
|
||||
goto out;
|
||||
} else { /* flush_buffer succeeds */
|
||||
if (STp->can_partitions) {
|
||||
i = switch_partition(STp);
|
||||
if (i < 0) {
|
||||
retval = i;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&STp->lock);
|
||||
|
||||
retval = scsi_ioctl(STp->device, cmd_in, p);
|
||||
if (!retval && cmd_in == SCSI_IOCTL_STOP_UNIT) {
|
||||
/* unload */
|
||||
STp->rew_at_close = 0;
|
||||
STp->ready = ST_NO_TAPE;
|
||||
}
|
||||
return retval;
|
||||
out:
|
||||
mutex_unlock(&STp->lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* The ioctl command */
|
||||
static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
||||
@ -3544,6 +3628,15 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
switch (cmd_in) {
|
||||
case MTIOCPOS:
|
||||
case MTIOCGET:
|
||||
case MTIOCTOP:
|
||||
break;
|
||||
default:
|
||||
return st_common_ioctl(STp, STm, file, cmd_in, arg);
|
||||
}
|
||||
|
||||
cmd_type = _IOC_TYPE(cmd_in);
|
||||
cmd_nr = _IOC_NR(cmd_in);
|
||||
|
||||
@ -3637,9 +3730,23 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
||||
retval = (-EIO);
|
||||
goto out;
|
||||
}
|
||||
reset_state(STp);
|
||||
/* remove this when the midlevel properly clears was_reset */
|
||||
STp->device->was_reset = 0;
|
||||
reset_state(STp); /* Clears pos_unknown */
|
||||
|
||||
/* Fix the device settings after reset, ignore errors */
|
||||
if (mtc.mt_op == MTREW || mtc.mt_op == MTSEEK ||
|
||||
mtc.mt_op == MTEOM) {
|
||||
if (STp->can_partitions) {
|
||||
/* STp->new_partition contains the
|
||||
* latest partition set
|
||||
*/
|
||||
STp->partition = 0;
|
||||
switch_partition(STp);
|
||||
}
|
||||
if (STp->density_changed)
|
||||
st_int_ioctl(STp, MTSETDENSITY, STp->changed_density);
|
||||
if (STp->blksize_changed)
|
||||
st_int_ioctl(STp, MTSETBLK, STp->changed_blksize);
|
||||
}
|
||||
}
|
||||
|
||||
if (mtc.mt_op != MTNOP && mtc.mt_op != MTSETBLK &&
|
||||
@ -3846,33 +3953,7 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg)
|
||||
i = copy_to_user(p, &mt_pos, sizeof(struct mtpos));
|
||||
if (i)
|
||||
retval = (-EFAULT);
|
||||
goto out;
|
||||
}
|
||||
mutex_unlock(&STp->lock);
|
||||
switch (cmd_in) {
|
||||
case SCSI_IOCTL_GET_IDLUN:
|
||||
case SCSI_IOCTL_GET_BUS_NUMBER:
|
||||
break;
|
||||
default:
|
||||
if ((cmd_in == SG_IO ||
|
||||
cmd_in == SCSI_IOCTL_SEND_COMMAND ||
|
||||
cmd_in == CDROM_SEND_PACKET) &&
|
||||
!capable(CAP_SYS_RAWIO))
|
||||
i = -EPERM;
|
||||
else
|
||||
i = scsi_cmd_ioctl(STp->disk->queue, STp->disk,
|
||||
file->f_mode, cmd_in, p);
|
||||
if (i != -ENOTTY)
|
||||
return i;
|
||||
break;
|
||||
}
|
||||
retval = scsi_ioctl(STp->device, cmd_in, p);
|
||||
if (!retval && cmd_in == SCSI_IOCTL_STOP_UNIT) { /* unload */
|
||||
STp->rew_at_close = 0;
|
||||
STp->ready = ST_NO_TAPE;
|
||||
}
|
||||
return retval;
|
||||
|
||||
out:
|
||||
mutex_unlock(&STp->lock);
|
||||
return retval;
|
||||
@ -4133,7 +4214,7 @@ static void validate_options(void)
|
||||
*/
|
||||
static int __init st_setup(char *str)
|
||||
{
|
||||
int i, len, ints[5];
|
||||
int i, len, ints[ARRAY_SIZE(parms) + 1];
|
||||
char *stp;
|
||||
|
||||
stp = get_options(str, ARRAY_SIZE(ints), ints);
|
||||
@ -4412,6 +4493,9 @@ static int st_probe(struct device *dev)
|
||||
goto out_idr_remove;
|
||||
}
|
||||
|
||||
tpnt->new_media_ctr = scsi_get_ua_new_media_ctr(SDp);
|
||||
tpnt->por_ctr = scsi_get_ua_por_ctr(SDp);
|
||||
|
||||
dev_set_drvdata(dev, tpnt);
|
||||
|
||||
|
||||
@ -4699,6 +4783,24 @@ options_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
}
|
||||
static DEVICE_ATTR_RO(options);
|
||||
|
||||
/**
|
||||
* position_lost_in_reset_show - Value 1 indicates that reads, writes, etc.
|
||||
* are blocked because a device reset has occurred and no operation positioning
|
||||
* the tape has been issued.
|
||||
* @dev: struct device
|
||||
* @attr: attribute structure
|
||||
* @buf: buffer to return formatted data in
|
||||
*/
|
||||
static ssize_t position_lost_in_reset_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct st_modedef *STm = dev_get_drvdata(dev);
|
||||
struct scsi_tape *STp = STm->tape;
|
||||
|
||||
return sprintf(buf, "%d", STp->pos_unknown);
|
||||
}
|
||||
static DEVICE_ATTR_RO(position_lost_in_reset);
|
||||
|
||||
/* Support for tape stats */
|
||||
|
||||
/**
|
||||
@ -4883,6 +4985,7 @@ static struct attribute *st_dev_attrs[] = {
|
||||
&dev_attr_default_density.attr,
|
||||
&dev_attr_default_compression.attr,
|
||||
&dev_attr_options.attr,
|
||||
&dev_attr_position_lost_in_reset.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
||||
@ -168,6 +168,7 @@ struct scsi_tape {
|
||||
unsigned char compression_changed;
|
||||
unsigned char drv_buffer;
|
||||
unsigned char density;
|
||||
unsigned char changed_density;
|
||||
unsigned char door_locked;
|
||||
unsigned char autorew_dev; /* auto-rewind device */
|
||||
unsigned char rew_at_close; /* rewind necessary at close */
|
||||
@ -175,11 +176,16 @@ struct scsi_tape {
|
||||
unsigned char cleaning_req; /* cleaning requested? */
|
||||
unsigned char first_tur; /* first TEST UNIT READY */
|
||||
int block_size;
|
||||
int changed_blksize;
|
||||
int min_block;
|
||||
int max_block;
|
||||
int recover_count; /* From tape opening */
|
||||
int recover_reg; /* From last status call */
|
||||
|
||||
/* The saved values of midlevel counters */
|
||||
unsigned int new_media_ctr;
|
||||
unsigned int por_ctr;
|
||||
|
||||
#if DEBUG
|
||||
unsigned char write_pending;
|
||||
int nbr_finished;
|
||||
|
||||
@ -11,13 +11,13 @@ cifs-y := trace.o cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o \
|
||||
readdir.o ioctl.o sess.o export.o smb1ops.o unc.o winucase.o \
|
||||
smb2ops.o smb2maperror.o smb2transport.o \
|
||||
smb2misc.o smb2pdu.o smb2inode.o smb2file.o cifsacl.o fs_context.o \
|
||||
dns_resolve.o
|
||||
dns_resolve.o namespace.o
|
||||
|
||||
cifs-$(CONFIG_CIFS_XATTR) += xattr.o
|
||||
|
||||
cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
|
||||
|
||||
cifs-$(CONFIG_CIFS_DFS_UPCALL) += cifs_dfs_ref.o dfs_cache.o
|
||||
cifs-$(CONFIG_CIFS_DFS_UPCALL) += dfs_cache.o dfs.o
|
||||
|
||||
cifs-$(CONFIG_CIFS_SWN_UPCALL) += netlink.o cifs_swn.o
|
||||
|
||||
|
||||
@ -134,6 +134,12 @@ static void cifs_debug_tcon(struct seq_file *m, struct cifs_tcon *tcon)
|
||||
|
||||
if (tcon->need_reconnect)
|
||||
seq_puts(m, "\tDISCONNECTED ");
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (tcon->origin_fullpath) {
|
||||
seq_printf(m, "\n\tDFS origin fullpath: %s",
|
||||
tcon->origin_fullpath);
|
||||
}
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
seq_putc(m, '\n');
|
||||
}
|
||||
|
||||
@ -398,6 +404,10 @@ skip_rdma:
|
||||
seq_printf(m, "\nIn Send: %d In MaxReq Wait: %d",
|
||||
atomic_read(&server->in_send),
|
||||
atomic_read(&server->num_waiters));
|
||||
if (server->leaf_fullpath) {
|
||||
seq_printf(m, "\nDFS leaf full path: %s",
|
||||
server->leaf_fullpath);
|
||||
}
|
||||
|
||||
seq_printf(m, "\n\n\tSessions: ");
|
||||
i = 0;
|
||||
@ -443,6 +453,11 @@ skip_rdma:
|
||||
from_kuid(&init_user_ns, ses->linux_uid),
|
||||
from_kuid(&init_user_ns, ses->cred_uid));
|
||||
|
||||
if (ses->dfs_root_ses) {
|
||||
seq_printf(m, "\n\tDFS root session id: 0x%llx",
|
||||
ses->dfs_root_ses->Suid);
|
||||
}
|
||||
|
||||
spin_lock(&ses->chan_lock);
|
||||
if (CIFS_CHAN_NEEDS_RECONNECT(ses, 0))
|
||||
seq_puts(m, "\tPrimary channel: DISCONNECTED ");
|
||||
|
||||
@ -1,361 +0,0 @@
|
||||
/*
|
||||
* Contains the CIFS DFS referral mounting routines used for handling
|
||||
* traversal via DFS junction point
|
||||
*
|
||||
* Copyright (c) 2007 Igor Mammedov
|
||||
* Copyright (C) International Business Machines Corp., 2008
|
||||
* Author(s): Igor Mammedov (niallain@gmail.com)
|
||||
* Steve French (sfrench@us.ibm.com)
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vfs.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/inet.h>
|
||||
#include "cifsglob.h"
|
||||
#include "cifsproto.h"
|
||||
#include "cifsfs.h"
|
||||
#include "dns_resolve.h"
|
||||
#include "cifs_debug.h"
|
||||
#include "cifs_unicode.h"
|
||||
#include "dfs_cache.h"
|
||||
#include "fs_context.h"
|
||||
|
||||
static LIST_HEAD(cifs_dfs_automount_list);
|
||||
|
||||
static void cifs_dfs_expire_automounts(struct work_struct *work);
|
||||
static DECLARE_DELAYED_WORK(cifs_dfs_automount_task,
|
||||
cifs_dfs_expire_automounts);
|
||||
static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ;
|
||||
|
||||
static void cifs_dfs_expire_automounts(struct work_struct *work)
|
||||
{
|
||||
struct list_head *list = &cifs_dfs_automount_list;
|
||||
|
||||
mark_mounts_for_expiry(list);
|
||||
if (!list_empty(list))
|
||||
schedule_delayed_work(&cifs_dfs_automount_task,
|
||||
cifs_dfs_mountpoint_expiry_timeout);
|
||||
}
|
||||
|
||||
void cifs_dfs_release_automount_timer(void)
|
||||
{
|
||||
BUG_ON(!list_empty(&cifs_dfs_automount_list));
|
||||
cancel_delayed_work_sync(&cifs_dfs_automount_task);
|
||||
}
|
||||
|
||||
/**
|
||||
* cifs_build_devname - build a devicename from a UNC and optional prepath
|
||||
* @nodename: pointer to UNC string
|
||||
* @prepath: pointer to prefixpath (or NULL if there isn't one)
|
||||
*
|
||||
* Build a new cifs devicename after chasing a DFS referral. Allocate a buffer
|
||||
* big enough to hold the final thing. Copy the UNC from the nodename, and
|
||||
* concatenate the prepath onto the end of it if there is one.
|
||||
*
|
||||
* Returns pointer to the built string, or a ERR_PTR. Caller is responsible
|
||||
* for freeing the returned string.
|
||||
*/
|
||||
static char *
|
||||
cifs_build_devname(char *nodename, const char *prepath)
|
||||
{
|
||||
size_t pplen;
|
||||
size_t unclen;
|
||||
char *dev;
|
||||
char *pos;
|
||||
|
||||
/* skip over any preceding delimiters */
|
||||
nodename += strspn(nodename, "\\");
|
||||
if (!*nodename)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/* get length of UNC and set pos to last char */
|
||||
unclen = strlen(nodename);
|
||||
pos = nodename + unclen - 1;
|
||||
|
||||
/* trim off any trailing delimiters */
|
||||
while (*pos == '\\') {
|
||||
--pos;
|
||||
--unclen;
|
||||
}
|
||||
|
||||
/* allocate a buffer:
|
||||
* +2 for preceding "//"
|
||||
* +1 for delimiter between UNC and prepath
|
||||
* +1 for trailing NULL
|
||||
*/
|
||||
pplen = prepath ? strlen(prepath) : 0;
|
||||
dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL);
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pos = dev;
|
||||
/* add the initial "//" */
|
||||
*pos = '/';
|
||||
++pos;
|
||||
*pos = '/';
|
||||
++pos;
|
||||
|
||||
/* copy in the UNC portion from referral */
|
||||
memcpy(pos, nodename, unclen);
|
||||
pos += unclen;
|
||||
|
||||
/* copy the prefixpath remainder (if there is one) */
|
||||
if (pplen) {
|
||||
*pos = '/';
|
||||
++pos;
|
||||
memcpy(pos, prepath, pplen);
|
||||
pos += pplen;
|
||||
}
|
||||
|
||||
/* NULL terminator */
|
||||
*pos = '\0';
|
||||
|
||||
convert_delimiter(dev, '/');
|
||||
return dev;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* cifs_compose_mount_options - creates mount options for referral
|
||||
* @sb_mountdata: parent/root DFS mount options (template)
|
||||
* @fullpath: full path in UNC format
|
||||
* @ref: optional server's referral
|
||||
* @devname: return the built cifs device name if passed pointer not NULL
|
||||
* creates mount options for submount based on template options sb_mountdata
|
||||
* and replacing unc,ip,prefixpath options with ones we've got form ref_unc.
|
||||
*
|
||||
* Returns: pointer to new mount options or ERR_PTR.
|
||||
* Caller is responsible for freeing returned value if it is not error.
|
||||
*/
|
||||
char *cifs_compose_mount_options(const char *sb_mountdata,
|
||||
const char *fullpath,
|
||||
const struct dfs_info3_param *ref,
|
||||
char **devname)
|
||||
{
|
||||
int rc;
|
||||
char *name;
|
||||
char *mountdata = NULL;
|
||||
const char *prepath = NULL;
|
||||
int md_len;
|
||||
const char *start, *end;
|
||||
char *srvIP = NULL;
|
||||
char sep = ',';
|
||||
|
||||
if (sb_mountdata == NULL)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (ref) {
|
||||
if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (strlen(fullpath) - ref->path_consumed) {
|
||||
prepath = fullpath + ref->path_consumed;
|
||||
/* skip initial delimiter */
|
||||
if (*prepath == '/' || *prepath == '\\')
|
||||
prepath++;
|
||||
}
|
||||
|
||||
name = cifs_build_devname(ref->node_name, prepath);
|
||||
if (IS_ERR(name)) {
|
||||
rc = PTR_ERR(name);
|
||||
name = NULL;
|
||||
goto compose_mount_options_err;
|
||||
}
|
||||
} else {
|
||||
name = cifs_build_devname((char *)fullpath, NULL);
|
||||
if (IS_ERR(name)) {
|
||||
rc = PTR_ERR(name);
|
||||
name = NULL;
|
||||
goto compose_mount_options_err;
|
||||
}
|
||||
}
|
||||
|
||||
rc = dns_resolve_server_name_to_ip(name, &srvIP, NULL);
|
||||
if (rc < 0) {
|
||||
cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
|
||||
__func__, name, rc);
|
||||
goto compose_mount_options_err;
|
||||
}
|
||||
|
||||
/*
|
||||
* In most cases, we'll be building a shorter string than the original,
|
||||
* but we do have to assume that the address in the ip= option may be
|
||||
* much longer than the original. Add the max length of an address
|
||||
* string to the length of the original string to allow for worst case.
|
||||
*/
|
||||
md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN;
|
||||
mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL);
|
||||
if (mountdata == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto compose_mount_options_err;
|
||||
}
|
||||
|
||||
/* copy all options except of unc,ip,prefixpath,cruid */
|
||||
start = end = sb_mountdata;
|
||||
for (;;) {
|
||||
end = strchrnul(end, sep);
|
||||
while (*end && end[0] == sep && end[1] == sep)
|
||||
end += 2;
|
||||
|
||||
if (strncasecmp(start, "prefixpath=", 11) == 0 ||
|
||||
strncasecmp(start, "cruid=", 6) == 0 ||
|
||||
strncasecmp(start, "unc=", 4) == 0 ||
|
||||
strncasecmp(start, "ip=", 3) == 0)
|
||||
goto next_opt;
|
||||
|
||||
if (*mountdata)
|
||||
strncat(mountdata, &sep, 1);
|
||||
strncat(mountdata, start, end - start);
|
||||
next_opt:
|
||||
if (!*end)
|
||||
break;
|
||||
start = ++end;
|
||||
}
|
||||
mountdata[md_len] = '\0';
|
||||
|
||||
/* copy new IP and ref share name */
|
||||
if (*mountdata)
|
||||
strncat(mountdata, &sep, 1);
|
||||
strcat(mountdata, "ip=");
|
||||
strcat(mountdata, srvIP);
|
||||
|
||||
if (devname)
|
||||
*devname = name;
|
||||
else
|
||||
kfree(name);
|
||||
|
||||
cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);
|
||||
cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata);
|
||||
|
||||
compose_mount_options_out:
|
||||
kfree(srvIP);
|
||||
return mountdata;
|
||||
|
||||
compose_mount_options_err:
|
||||
kfree(mountdata);
|
||||
mountdata = ERR_PTR(rc);
|
||||
kfree(name);
|
||||
goto compose_mount_options_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* cifs_dfs_do_mount - mounts specified path using DFS full path
|
||||
*
|
||||
* Always pass down @fullpath to smb3_do_mount() so we can use the root server
|
||||
* to perform failover in case we failed to connect to the first target in the
|
||||
* referral.
|
||||
*
|
||||
* @cifs_sb: parent/root superblock
|
||||
* @fullpath: full path in UNC format
|
||||
*/
|
||||
static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
|
||||
struct cifs_sb_info *cifs_sb,
|
||||
const char *fullpath)
|
||||
{
|
||||
struct vfsmount *mnt;
|
||||
char *mountdata;
|
||||
char *devname;
|
||||
|
||||
devname = kstrdup(fullpath, GFP_KERNEL);
|
||||
if (!devname)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
convert_delimiter(devname, '/');
|
||||
|
||||
/* TODO: change to call fs_context_for_mount(), fill in context directly, call fc_mount */
|
||||
|
||||
/* See afs_mntpt_do_automount in fs/afs/mntpt.c for an example */
|
||||
|
||||
/* strip first '\' from fullpath */
|
||||
mountdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options,
|
||||
fullpath + 1, NULL, NULL);
|
||||
if (IS_ERR(mountdata)) {
|
||||
kfree(devname);
|
||||
return (struct vfsmount *)mountdata;
|
||||
}
|
||||
|
||||
mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
|
||||
kfree(mountdata);
|
||||
kfree(devname);
|
||||
return mnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a vfsmount that we can automount
|
||||
*/
|
||||
static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
void *page;
|
||||
char *full_path;
|
||||
struct vfsmount *mnt;
|
||||
|
||||
cifs_dbg(FYI, "in %s\n", __func__);
|
||||
BUG_ON(IS_ROOT(mntpt));
|
||||
|
||||
/*
|
||||
* The MSDFS spec states that paths in DFS referral requests and
|
||||
* responses must be prefixed by a single '\' character instead of
|
||||
* the double backslashes usually used in the UNC. This function
|
||||
* gives us the latter, so we must adjust the result.
|
||||
*/
|
||||
cifs_sb = CIFS_SB(mntpt->d_sb);
|
||||
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
|
||||
mnt = ERR_PTR(-EREMOTE);
|
||||
goto cdda_exit;
|
||||
}
|
||||
|
||||
page = alloc_dentry_path();
|
||||
/* always use tree name prefix */
|
||||
full_path = build_path_from_dentry_optional_prefix(mntpt, page, true);
|
||||
if (IS_ERR(full_path)) {
|
||||
mnt = ERR_CAST(full_path);
|
||||
goto free_full_path;
|
||||
}
|
||||
|
||||
convert_delimiter(full_path, '\\');
|
||||
cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
|
||||
|
||||
mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path);
|
||||
cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt);
|
||||
|
||||
free_full_path:
|
||||
free_dentry_path(page);
|
||||
cdda_exit:
|
||||
cifs_dbg(FYI, "leaving %s\n" , __func__);
|
||||
return mnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to automount the referral
|
||||
*/
|
||||
struct vfsmount *cifs_dfs_d_automount(struct path *path)
|
||||
{
|
||||
struct vfsmount *newmnt;
|
||||
|
||||
cifs_dbg(FYI, "in %s\n", __func__);
|
||||
|
||||
newmnt = cifs_dfs_do_automount(path->dentry);
|
||||
if (IS_ERR(newmnt)) {
|
||||
cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__);
|
||||
return newmnt;
|
||||
}
|
||||
|
||||
mntget(newmnt); /* prevent immediate expiration */
|
||||
mnt_set_expiry(newmnt, &cifs_dfs_automount_list);
|
||||
schedule_delayed_work(&cifs_dfs_automount_task,
|
||||
cifs_dfs_mountpoint_expiry_timeout);
|
||||
cifs_dbg(FYI, "leaving %s [ok]\n" , __func__);
|
||||
return newmnt;
|
||||
}
|
||||
|
||||
const struct inode_operations cifs_dfs_referral_inode_operations = {
|
||||
};
|
||||
@ -60,8 +60,6 @@ struct cifs_sb_info {
|
||||
/* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
|
||||
char *prepath;
|
||||
|
||||
/* randomly generated 128-bit number for indexing dfs mount groups in referral cache */
|
||||
uuid_t dfs_mount_id;
|
||||
/*
|
||||
* Indicate whether serverino option was turned off later
|
||||
* (cifs_autodisable_serverino) in order to match new mounts.
|
||||
|
||||
@ -384,59 +384,72 @@ build_avpair_blob(struct cifs_ses *ses, const struct nls_table *nls_cp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Server has provided av pairs/target info in the type 2 challenge
|
||||
* packet and we have plucked it and stored within smb session.
|
||||
* We parse that blob here to find netbios domain name to be used
|
||||
* as part of ntlmv2 authentication (in Target String), if not already
|
||||
* specified on the command line.
|
||||
* If this function returns without any error but without fetching
|
||||
* domain name, authentication may fail against some server but
|
||||
* may not fail against other (those who are not very particular
|
||||
* about target string i.e. for some, just user name might suffice.
|
||||
*/
|
||||
static int
|
||||
find_domain_name(struct cifs_ses *ses, const struct nls_table *nls_cp)
|
||||
#define AV_TYPE(av) (le16_to_cpu(av->type))
|
||||
#define AV_LEN(av) (le16_to_cpu(av->length))
|
||||
#define AV_DATA_PTR(av) ((void *)av->data)
|
||||
|
||||
#define av_for_each_entry(ses, av) \
|
||||
for (av = NULL; (av = find_next_av(ses, av));)
|
||||
|
||||
static struct ntlmssp2_name *find_next_av(struct cifs_ses *ses,
|
||||
struct ntlmssp2_name *av)
|
||||
{
|
||||
unsigned int attrsize;
|
||||
unsigned int type;
|
||||
unsigned int onesize = sizeof(struct ntlmssp2_name);
|
||||
unsigned char *blobptr;
|
||||
unsigned char *blobend;
|
||||
struct ntlmssp2_name *attrptr;
|
||||
u16 len;
|
||||
u8 *end;
|
||||
|
||||
if (!ses->auth_key.len || !ses->auth_key.response)
|
||||
return 0;
|
||||
|
||||
blobptr = ses->auth_key.response;
|
||||
blobend = blobptr + ses->auth_key.len;
|
||||
|
||||
while (blobptr + onesize < blobend) {
|
||||
attrptr = (struct ntlmssp2_name *) blobptr;
|
||||
type = le16_to_cpu(attrptr->type);
|
||||
if (type == NTLMSSP_AV_EOL)
|
||||
break;
|
||||
blobptr += 2; /* advance attr type */
|
||||
attrsize = le16_to_cpu(attrptr->length);
|
||||
blobptr += 2; /* advance attr size */
|
||||
if (blobptr + attrsize > blobend)
|
||||
break;
|
||||
if (type == NTLMSSP_AV_NB_DOMAIN_NAME) {
|
||||
if (!attrsize || attrsize >= CIFS_MAX_DOMAINNAME_LEN)
|
||||
break;
|
||||
if (!ses->domainName) {
|
||||
ses->domainName =
|
||||
kmalloc(attrsize + 1, GFP_KERNEL);
|
||||
if (!ses->domainName)
|
||||
return -ENOMEM;
|
||||
cifs_from_utf16(ses->domainName,
|
||||
(__le16 *)blobptr, attrsize, attrsize,
|
||||
nls_cp, NO_MAP_UNI_RSVD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
blobptr += attrsize; /* advance attr value */
|
||||
end = (u8 *)ses->auth_key.response + ses->auth_key.len;
|
||||
if (!av) {
|
||||
if (unlikely(!ses->auth_key.response || !ses->auth_key.len))
|
||||
return NULL;
|
||||
av = (void *)ses->auth_key.response;
|
||||
} else {
|
||||
av = (void *)((u8 *)av + sizeof(*av) + AV_LEN(av));
|
||||
}
|
||||
|
||||
if ((u8 *)av + sizeof(*av) > end)
|
||||
return NULL;
|
||||
|
||||
len = AV_LEN(av);
|
||||
if (AV_TYPE(av) == NTLMSSP_AV_EOL)
|
||||
return NULL;
|
||||
if ((u8 *)av + sizeof(*av) + len > end)
|
||||
return NULL;
|
||||
return av;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if server has provided av pair of @type in the NTLMSSP
|
||||
* CHALLENGE_MESSAGE blob.
|
||||
*/
|
||||
static int find_av_name(struct cifs_ses *ses, u16 type, char **name, u16 maxlen)
|
||||
{
|
||||
const struct nls_table *nlsc = ses->local_nls;
|
||||
struct ntlmssp2_name *av;
|
||||
u16 len, nlen;
|
||||
|
||||
if (*name)
|
||||
return 0;
|
||||
|
||||
av_for_each_entry(ses, av) {
|
||||
len = AV_LEN(av);
|
||||
if (AV_TYPE(av) != type || !len)
|
||||
continue;
|
||||
if (!IS_ALIGNED(len, sizeof(__le16))) {
|
||||
cifs_dbg(VFS | ONCE, "%s: bad length(%u) for type %u\n",
|
||||
__func__, len, type);
|
||||
continue;
|
||||
}
|
||||
nlen = len / sizeof(__le16);
|
||||
if (nlen <= maxlen) {
|
||||
++nlen;
|
||||
*name = kmalloc(nlen, GFP_KERNEL);
|
||||
if (!*name)
|
||||
return -ENOMEM;
|
||||
cifs_from_utf16(*name, AV_DATA_PTR(av), nlen,
|
||||
len, nlsc, NO_MAP_UNI_RSVD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -446,40 +459,16 @@ find_domain_name(struct cifs_ses *ses, const struct nls_table *nls_cp)
|
||||
* as part of ntlmv2 authentication (or local current time as
|
||||
* default in case of failure)
|
||||
*/
|
||||
static __le64
|
||||
find_timestamp(struct cifs_ses *ses)
|
||||
static __le64 find_timestamp(struct cifs_ses *ses)
|
||||
{
|
||||
unsigned int attrsize;
|
||||
unsigned int type;
|
||||
unsigned int onesize = sizeof(struct ntlmssp2_name);
|
||||
unsigned char *blobptr;
|
||||
unsigned char *blobend;
|
||||
struct ntlmssp2_name *attrptr;
|
||||
struct ntlmssp2_name *av;
|
||||
struct timespec64 ts;
|
||||
|
||||
if (!ses->auth_key.len || !ses->auth_key.response)
|
||||
return 0;
|
||||
|
||||
blobptr = ses->auth_key.response;
|
||||
blobend = blobptr + ses->auth_key.len;
|
||||
|
||||
while (blobptr + onesize < blobend) {
|
||||
attrptr = (struct ntlmssp2_name *) blobptr;
|
||||
type = le16_to_cpu(attrptr->type);
|
||||
if (type == NTLMSSP_AV_EOL)
|
||||
break;
|
||||
blobptr += 2; /* advance attr type */
|
||||
attrsize = le16_to_cpu(attrptr->length);
|
||||
blobptr += 2; /* advance attr size */
|
||||
if (blobptr + attrsize > blobend)
|
||||
break;
|
||||
if (type == NTLMSSP_AV_TIMESTAMP) {
|
||||
if (attrsize == sizeof(u64))
|
||||
return *((__le64 *)blobptr);
|
||||
}
|
||||
blobptr += attrsize; /* advance attr value */
|
||||
av_for_each_entry(ses, av) {
|
||||
if (AV_TYPE(av) == NTLMSSP_AV_TIMESTAMP &&
|
||||
AV_LEN(av) == sizeof(u64))
|
||||
return *((__le64 *)AV_DATA_PTR(av));
|
||||
}
|
||||
|
||||
ktime_get_real_ts64(&ts);
|
||||
return cpu_to_le64(cifs_UnixTimeToNT(ts));
|
||||
}
|
||||
@ -660,16 +649,29 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp)
|
||||
if (ses->server->negflavor == CIFS_NEGFLAVOR_EXTENDED) {
|
||||
if (!ses->domainName) {
|
||||
if (ses->domainAuto) {
|
||||
rc = find_domain_name(ses, nls_cp);
|
||||
if (rc) {
|
||||
cifs_dbg(VFS, "error %d finding domain name\n",
|
||||
rc);
|
||||
/*
|
||||
* Domain (workgroup) hasn't been specified in
|
||||
* mount options, so try to find it in
|
||||
* CHALLENGE_MESSAGE message and then use it as
|
||||
* part of NTLMv2 authentication.
|
||||
*/
|
||||
rc = find_av_name(ses, NTLMSSP_AV_NB_DOMAIN_NAME,
|
||||
&ses->domainName,
|
||||
CIFS_MAX_DOMAINNAME_LEN);
|
||||
if (rc)
|
||||
goto setup_ntlmv2_rsp_ret;
|
||||
}
|
||||
} else {
|
||||
ses->domainName = kstrdup("", GFP_KERNEL);
|
||||
if (!ses->domainName) {
|
||||
rc = -ENOMEM;
|
||||
goto setup_ntlmv2_rsp_ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
rc = find_av_name(ses, NTLMSSP_AV_DNS_DOMAIN_NAME,
|
||||
&ses->dns_dom, CIFS_MAX_DOMAINNAME_LEN);
|
||||
if (rc)
|
||||
goto setup_ntlmv2_rsp_ret;
|
||||
} else {
|
||||
rc = build_avpair_blob(ses, nls_cp);
|
||||
if (rc) {
|
||||
|
||||
@ -713,13 +713,16 @@ static void cifs_umount_begin(struct super_block *sb)
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if ((tcon->tc_count > 1) || (tcon->status == TID_EXITING)) {
|
||||
/* we have other mounts to same share or we have
|
||||
already tried to force umount this and woken up
|
||||
already tried to umount this and woken up
|
||||
all waiting network requests, nothing to do */
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
return;
|
||||
} else if (tcon->tc_count == 1)
|
||||
tcon->status = TID_EXITING;
|
||||
}
|
||||
/*
|
||||
* can not set tcon->status to TID_EXITING yet since we don't know if umount -f will
|
||||
* fail later (e.g. due to open files). TID_EXITING will be set just before tdis req sent
|
||||
*/
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
|
||||
@ -868,12 +871,6 @@ cifs_smb3_do_mount(struct file_system_type *fs_type,
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = cifs_setup_volume_info(cifs_sb->ctx, NULL, NULL);
|
||||
if (rc) {
|
||||
root = ERR_PTR(rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = cifs_setup_cifs_sb(cifs_sb);
|
||||
if (rc) {
|
||||
root = ERR_PTR(rc);
|
||||
@ -1720,7 +1717,7 @@ exit_cifs(void)
|
||||
cifs_dbg(NOISY, "exit_smb3\n");
|
||||
unregister_filesystem(&cifs_fs_type);
|
||||
unregister_filesystem(&smb3_fs_type);
|
||||
cifs_dfs_release_automount_timer();
|
||||
cifs_release_automount_timer();
|
||||
exit_cifs_idmap();
|
||||
#ifdef CONFIG_CIFS_SWN_UPCALL
|
||||
cifs_genl_exit();
|
||||
|
||||
@ -77,7 +77,7 @@ extern int cifs_fiemap(struct inode *, struct fiemap_extent_info *, u64 start,
|
||||
|
||||
extern const struct inode_operations cifs_file_inode_ops;
|
||||
extern const struct inode_operations cifs_symlink_inode_ops;
|
||||
extern const struct inode_operations cifs_dfs_referral_inode_operations;
|
||||
extern const struct inode_operations cifs_namespace_inode_operations;
|
||||
|
||||
|
||||
/* Functions related to files and directories */
|
||||
@ -111,11 +111,7 @@ extern int cifs_readdir(struct file *file, struct dir_context *ctx);
|
||||
extern const struct dentry_operations cifs_dentry_ops;
|
||||
extern const struct dentry_operations cifs_ci_dentry_ops;
|
||||
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
|
||||
#else
|
||||
#define cifs_dfs_d_automount NULL
|
||||
#endif
|
||||
extern struct vfsmount *cifs_d_automount(struct path *path);
|
||||
|
||||
/* Functions related to symlinks */
|
||||
extern const char *cifs_get_link(struct dentry *, struct inode *,
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/utsname.h>
|
||||
#include <linux/sched/mm.h>
|
||||
#include <linux/ctype.h>
|
||||
#include "cifs_fs_sb.h"
|
||||
#include "cifsacl.h"
|
||||
#include <crypto/internal/hash.h>
|
||||
@ -105,6 +106,8 @@
|
||||
|
||||
#define CIFS_MAX_WORKSTATION_LEN (__NEW_UTS_LEN + 1) /* reasonable max for client */
|
||||
|
||||
#define CIFS_DFS_ROOT_SES(ses) ((ses)->dfs_root_ses ?: (ses))
|
||||
|
||||
/*
|
||||
* CIFS vfs client Status information (based on what we know.)
|
||||
*/
|
||||
@ -737,22 +740,13 @@ struct TCP_Server_Info {
|
||||
bool use_swn_dstaddr;
|
||||
struct sockaddr_storage swn_dstaddr;
|
||||
#endif
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
bool is_dfs_conn; /* if a dfs connection */
|
||||
struct mutex refpath_lock; /* protects leaf_fullpath */
|
||||
/*
|
||||
* Canonical DFS full paths that were used to chase referrals in mount and reconnect.
|
||||
*
|
||||
* origin_fullpath: first or original referral path
|
||||
* leaf_fullpath: last referral path (might be changed due to nested links in reconnect)
|
||||
*
|
||||
* current_fullpath: pointer to either origin_fullpath or leaf_fullpath
|
||||
* NOTE: cannot be accessed outside cifs_reconnect() and smb2_reconnect()
|
||||
*
|
||||
* format: \\HOST\SHARE\[OPTIONAL PATH]
|
||||
* Canonical DFS referral path used in cifs_reconnect() for failover as
|
||||
* well as in DFS cache refresher.
|
||||
*/
|
||||
char *origin_fullpath, *leaf_fullpath, *current_fullpath;
|
||||
#endif
|
||||
char *leaf_fullpath;
|
||||
bool dfs_conn:1;
|
||||
char dns_dom[CIFS_MAX_DOMAINNAME_LEN + 1];
|
||||
};
|
||||
|
||||
static inline void cifs_server_lock(struct TCP_Server_Info *server)
|
||||
@ -970,43 +964,6 @@ release_iface(struct kref *ref)
|
||||
kfree(iface);
|
||||
}
|
||||
|
||||
/*
|
||||
* compare two interfaces a and b
|
||||
* return 0 if everything matches.
|
||||
* return 1 if a has higher link speed, or rdma capable, or rss capable
|
||||
* return -1 otherwise.
|
||||
*/
|
||||
static inline int
|
||||
iface_cmp(struct cifs_server_iface *a, struct cifs_server_iface *b)
|
||||
{
|
||||
int cmp_ret = 0;
|
||||
|
||||
WARN_ON(!a || !b);
|
||||
if (a->speed == b->speed) {
|
||||
if (a->rdma_capable == b->rdma_capable) {
|
||||
if (a->rss_capable == b->rss_capable) {
|
||||
cmp_ret = memcmp(&a->sockaddr, &b->sockaddr,
|
||||
sizeof(a->sockaddr));
|
||||
if (!cmp_ret)
|
||||
return 0;
|
||||
else if (cmp_ret > 0)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else if (a->rss_capable > b->rss_capable)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else if (a->rdma_capable > b->rdma_capable)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else if (a->speed > b->speed)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct cifs_chan {
|
||||
unsigned int in_reconnect : 1; /* if session setup in progress for this channel */
|
||||
struct TCP_Server_Info *server;
|
||||
@ -1021,6 +978,7 @@ struct cifs_ses {
|
||||
struct list_head smb_ses_list;
|
||||
struct list_head rlist; /* reconnect list */
|
||||
struct list_head tcon_list;
|
||||
struct list_head dlist; /* dfs list */
|
||||
struct cifs_tcon *tcon_ipc;
|
||||
spinlock_t ses_lock; /* protect anything here that is not protected */
|
||||
struct mutex session_mutex;
|
||||
@ -1102,6 +1060,9 @@ struct cifs_ses {
|
||||
*/
|
||||
unsigned long chans_need_reconnect;
|
||||
/* ========= end: protected by chan_lock ======== */
|
||||
struct cifs_ses *dfs_root_ses;
|
||||
struct nls_table *local_nls;
|
||||
char *dns_dom; /* FQDN of the domain */
|
||||
};
|
||||
|
||||
static inline bool
|
||||
@ -1218,9 +1179,11 @@ struct cifs_tcon {
|
||||
struct cached_fid crfid; /* Cached root fid */
|
||||
/* BB add field for back pointer to sb struct(s)? */
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
struct list_head ulist; /* cache update list */
|
||||
struct delayed_work dfs_cache_work;
|
||||
struct list_head dfs_ses_list;
|
||||
#endif
|
||||
struct delayed_work query_interfaces; /* query interfaces workqueue job */
|
||||
char *origin_fullpath; /* canonical copy of smb3_fs_context::source */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1754,11 +1717,32 @@ struct cifs_fattr {
|
||||
u32 cf_cifstag;
|
||||
};
|
||||
|
||||
struct cifs_mount_ctx {
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
struct smb3_fs_context *fs_ctx;
|
||||
unsigned int xid;
|
||||
struct TCP_Server_Info *server;
|
||||
struct cifs_ses *ses;
|
||||
struct cifs_tcon *tcon;
|
||||
};
|
||||
|
||||
static inline void __free_dfs_info_param(struct dfs_info3_param *param)
|
||||
{
|
||||
kfree(param->path_name);
|
||||
kfree(param->node_name);
|
||||
}
|
||||
|
||||
static inline void free_dfs_info_param(struct dfs_info3_param *param)
|
||||
{
|
||||
if (param)
|
||||
__free_dfs_info_param(param);
|
||||
}
|
||||
|
||||
static inline void zfree_dfs_info_param(struct dfs_info3_param *param)
|
||||
{
|
||||
if (param) {
|
||||
kfree(param->path_name);
|
||||
kfree(param->node_name);
|
||||
__free_dfs_info_param(param);
|
||||
memset(param, 0, sizeof(*param));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2208,4 +2192,24 @@ static inline struct scatterlist *cifs_sg_set_buf(struct scatterlist *sg,
|
||||
return sg;
|
||||
}
|
||||
|
||||
static inline bool cifs_netbios_name(const char *name, size_t namelen)
|
||||
{
|
||||
bool ret = false;
|
||||
size_t i;
|
||||
|
||||
if (namelen >= 1 && namelen <= RFC1001_NAME_LEN) {
|
||||
for (i = 0; i < namelen; i++) {
|
||||
const unsigned char c = name[i];
|
||||
|
||||
if (c == '\\' || c == '/' || c == ':' || c == '*' ||
|
||||
c == '?' || c == '"' || c == '<' || c == '>' ||
|
||||
c == '|' || c == '.')
|
||||
return false;
|
||||
if (!ret && isalpha(c))
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* _CIFS_GLOB_H */
|
||||
|
||||
@ -688,7 +688,7 @@ typedef union smb_com_session_setup_andx {
|
||||
struct ntlmssp2_name {
|
||||
__le16 type;
|
||||
__le16 length;
|
||||
/* char name[length]; */
|
||||
__u8 data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ntlmv2_resp {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#ifndef _CIFSPROTO_H
|
||||
#define _CIFSPROTO_H
|
||||
#include <linux/nls.h>
|
||||
#include <linux/ctype.h>
|
||||
#include "trace.h"
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
#include "dfs_cache.h"
|
||||
@ -57,6 +58,9 @@ extern void exit_cifs_idmap(void);
|
||||
extern int init_cifs_spnego(void);
|
||||
extern void exit_cifs_spnego(void);
|
||||
extern const char *build_path_from_dentry(struct dentry *, void *);
|
||||
char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
const char *tree, int tree_len,
|
||||
bool prefix);
|
||||
extern char *build_path_from_dentry_optional_prefix(struct dentry *direntry,
|
||||
void *page, bool prefix);
|
||||
static inline void *alloc_dentry_path(void)
|
||||
@ -75,16 +79,17 @@ extern char *cifs_build_path_to_root(struct smb3_fs_context *ctx,
|
||||
struct cifs_tcon *tcon,
|
||||
int add_treename);
|
||||
extern char *build_wildcard_path_from_dentry(struct dentry *direntry);
|
||||
extern char *cifs_compose_mount_options(const char *sb_mountdata,
|
||||
const char *fullpath, const struct dfs_info3_param *ref,
|
||||
char **devname);
|
||||
char *cifs_build_devname(char *nodename, const char *prepath);
|
||||
extern void delete_mid(struct mid_q_entry *mid);
|
||||
void __release_mid(struct kref *refcount);
|
||||
extern void cifs_wake_up_task(struct mid_q_entry *mid);
|
||||
extern int cifs_handle_standard(struct TCP_Server_Info *server,
|
||||
struct mid_q_entry *mid);
|
||||
extern char *smb3_fs_context_fullpath(const struct smb3_fs_context *ctx,
|
||||
char dirsep);
|
||||
extern int smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx);
|
||||
extern int smb3_parse_opt(const char *options, const char *key, char **val);
|
||||
extern int cifs_ipaddr_cmp(struct sockaddr *srcaddr, struct sockaddr *rhs);
|
||||
extern bool cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs);
|
||||
extern int cifs_discard_remaining_data(struct TCP_Server_Info *server);
|
||||
extern int cifs_call_async(struct TCP_Server_Info *server,
|
||||
@ -241,6 +246,10 @@ extern int cifs_read_page_from_socket(struct TCP_Server_Info *server,
|
||||
unsigned int page_offset,
|
||||
unsigned int to_read);
|
||||
extern int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb);
|
||||
void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx);
|
||||
int cifs_mount_get_session(struct cifs_mount_ctx *mnt_ctx);
|
||||
int cifs_is_path_remote(struct cifs_mount_ctx *mnt_ctx);
|
||||
int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx);
|
||||
extern int cifs_match_super(struct super_block *, void *);
|
||||
extern int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx);
|
||||
extern void cifs_umount(struct cifs_sb_info *);
|
||||
@ -265,11 +274,7 @@ extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
|
||||
int from_reconnect);
|
||||
extern void cifs_put_tcon(struct cifs_tcon *tcon);
|
||||
|
||||
#if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
|
||||
extern void cifs_dfs_release_automount_timer(void);
|
||||
#else /* ! IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) */
|
||||
#define cifs_dfs_release_automount_timer() do { } while (0)
|
||||
#endif /* ! IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) */
|
||||
extern void cifs_release_automount_timer(void);
|
||||
|
||||
void cifs_proc_init(void);
|
||||
void cifs_proc_clean(void);
|
||||
@ -278,8 +283,7 @@ extern void cifs_move_llist(struct list_head *source, struct list_head *dest);
|
||||
extern void cifs_free_llist(struct list_head *llist);
|
||||
extern void cifs_del_lock_waiters(struct cifsLockInfo *lock);
|
||||
|
||||
extern int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon,
|
||||
const struct nls_table *nlsc);
|
||||
int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon);
|
||||
|
||||
extern int cifs_negotiate_protocol(const unsigned int xid,
|
||||
struct cifs_ses *ses,
|
||||
@ -551,13 +555,12 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
|
||||
extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8,
|
||||
unsigned char *p24);
|
||||
|
||||
extern int
|
||||
cifs_setup_volume_info(struct smb3_fs_context *ctx, const char *mntopts, const char *devname);
|
||||
|
||||
extern struct TCP_Server_Info *
|
||||
cifs_find_tcp_session(struct smb3_fs_context *ctx);
|
||||
|
||||
extern void cifs_put_smb_ses(struct cifs_ses *ses);
|
||||
struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal);
|
||||
|
||||
void __cifs_put_smb_ses(struct cifs_ses *ses);
|
||||
|
||||
extern struct cifs_ses *
|
||||
cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx);
|
||||
@ -641,7 +644,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov,
|
||||
int resp_buftype,
|
||||
struct cifs_search_info *srch_inf);
|
||||
|
||||
struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server);
|
||||
struct super_block *cifs_get_dfs_tcon_super(struct cifs_tcon *tcon);
|
||||
void cifs_put_tcp_super(struct super_block *sb);
|
||||
int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix);
|
||||
char *extract_hostname(const char *unc);
|
||||
@ -658,7 +661,7 @@ static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
|
||||
}
|
||||
|
||||
int match_target_ip(struct TCP_Server_Info *server,
|
||||
const char *share, size_t share_len,
|
||||
const char *host, size_t hostlen,
|
||||
bool *result);
|
||||
#endif
|
||||
|
||||
@ -679,4 +682,37 @@ static inline void release_mid(struct mid_q_entry *mid)
|
||||
kref_put(&mid->refcount, __release_mid);
|
||||
}
|
||||
|
||||
static inline void cifs_put_smb_ses(struct cifs_ses *ses)
|
||||
{
|
||||
__cifs_put_smb_ses(ses);
|
||||
}
|
||||
|
||||
/* Get an active reference of @ses and its children.
|
||||
*
|
||||
* NOTE: make sure to call this function when incrementing reference count of
|
||||
* @ses to ensure that any DFS root session attached to it (@ses->dfs_root_ses)
|
||||
* will also get its reference count incremented.
|
||||
*
|
||||
* cifs_put_smb_ses() will put all references, so call it when you're done.
|
||||
*/
|
||||
static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
|
||||
{
|
||||
lockdep_assert_held(&cifs_tcp_ses_lock);
|
||||
ses->ses_count++;
|
||||
}
|
||||
|
||||
static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
|
||||
{
|
||||
if (strlen(s1) != strlen(s2))
|
||||
return false;
|
||||
for (; *s1; s1++, s2++) {
|
||||
if (*s1 == '/' || *s1 == '\\') {
|
||||
if (*s2 != '/' && *s2 != '\\')
|
||||
return false;
|
||||
} else if (tolower(*s1) != tolower(*s2))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* _CIFSPROTO_H */
|
||||
|
||||
@ -90,13 +90,16 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
|
||||
struct list_head *tmp1;
|
||||
|
||||
/* only send once per connect */
|
||||
spin_lock(&tcon->ses->ses_lock);
|
||||
if ((tcon->ses->ses_status != SES_GOOD) || (tcon->status != TID_NEED_RECON)) {
|
||||
spin_unlock(&tcon->ses->ses_lock);
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (tcon->need_reconnect)
|
||||
tcon->status = TID_NEED_RECON;
|
||||
|
||||
if (tcon->status != TID_NEED_RECON) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return;
|
||||
}
|
||||
tcon->status = TID_IN_FILES_INVALIDATE;
|
||||
spin_unlock(&tcon->ses->ses_lock);
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
|
||||
/* list all files open on tree connection and mark them invalid */
|
||||
spin_lock(&tcon->open_file_lock);
|
||||
@ -129,10 +132,9 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
|
||||
static int
|
||||
cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
|
||||
{
|
||||
int rc;
|
||||
struct cifs_ses *ses;
|
||||
struct TCP_Server_Info *server;
|
||||
struct nls_table *nls_codepage = NULL;
|
||||
struct cifs_ses *ses;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* SMBs NegProt, SessSetup, uLogoff do not have tcon yet so check for
|
||||
@ -147,13 +149,11 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
|
||||
|
||||
/*
|
||||
* only tree disconnect, open, and write, (and ulogoff which does not
|
||||
* have tcon) are allowed as we start force umount
|
||||
* have tcon) are allowed as we start umount
|
||||
*/
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (tcon->status == TID_EXITING) {
|
||||
if (smb_command != SMB_COM_WRITE_ANDX &&
|
||||
smb_command != SMB_COM_OPEN_ANDX &&
|
||||
smb_command != SMB_COM_TREE_DISCONNECT) {
|
||||
if (smb_command != SMB_COM_TREE_DISCONNECT) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
cifs_dbg(FYI, "can not send cmd %d while umounting\n",
|
||||
smb_command);
|
||||
@ -192,8 +192,6 @@ again:
|
||||
}
|
||||
spin_unlock(&server->srv_lock);
|
||||
|
||||
nls_codepage = load_nls_default();
|
||||
|
||||
/*
|
||||
* need to prevent multiple threads trying to simultaneously
|
||||
* reconnect the same SMB session
|
||||
@ -217,7 +215,7 @@ again:
|
||||
|
||||
rc = cifs_negotiate_protocol(0, ses, server);
|
||||
if (!rc)
|
||||
rc = cifs_setup_session(0, ses, server, nls_codepage);
|
||||
rc = cifs_setup_session(0, ses, server, ses->local_nls);
|
||||
|
||||
/* do we need to reconnect tcon? */
|
||||
if (rc || !tcon->need_reconnect) {
|
||||
@ -227,7 +225,7 @@ again:
|
||||
|
||||
skip_sess_setup:
|
||||
cifs_mark_open_files_invalid(tcon);
|
||||
rc = cifs_tree_connect(0, tcon, nls_codepage);
|
||||
rc = cifs_tree_connect(0, tcon);
|
||||
mutex_unlock(&ses->session_mutex);
|
||||
cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
|
||||
|
||||
@ -263,7 +261,6 @@ out:
|
||||
rc = -EAGAIN;
|
||||
}
|
||||
|
||||
unload_nls(nls_codepage);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -4882,8 +4879,8 @@ getDFSRetry:
|
||||
* CIFSGetDFSRefer() may be called from cifs_reconnect_tcon() and thus
|
||||
* causing an infinite recursion.
|
||||
*/
|
||||
rc = smb_init_no_reconnect(SMB_COM_TRANSACTION2, 15, ses->tcon_ipc,
|
||||
(void **)&pSMB, (void **)&pSMBr);
|
||||
rc = smb_init(SMB_COM_TRANSACTION2, 15, ses->tcon_ipc,
|
||||
(void **)&pSMB, (void **)&pSMBr);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
|
||||
1070
fs/cifs/connect.c
1070
fs/cifs/connect.c
File diff suppressed because it is too large
Load Diff
467
fs/cifs/dfs.c
Normal file
467
fs/cifs/dfs.c
Normal file
@ -0,0 +1,467 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
|
||||
*/
|
||||
|
||||
#include "cifsproto.h"
|
||||
#include "cifs_debug.h"
|
||||
#include "dns_resolve.h"
|
||||
#include "fs_context.h"
|
||||
#include "dfs.h"
|
||||
|
||||
#define DFS_DOM(ctx) (ctx->dfs_root_ses ? ctx->dfs_root_ses->dns_dom : NULL)
|
||||
|
||||
/**
|
||||
* dfs_parse_target_referral - set fs context for dfs target referral
|
||||
*
|
||||
* @full_path: full path in UNC format.
|
||||
* @ref: dfs referral pointer.
|
||||
* @ctx: smb3 fs context pointer.
|
||||
*
|
||||
* Return zero if dfs referral was parsed correctly, otherwise non-zero.
|
||||
*/
|
||||
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
|
||||
struct smb3_fs_context *ctx)
|
||||
{
|
||||
int rc;
|
||||
const char *prepath = NULL;
|
||||
char *path;
|
||||
|
||||
if (!full_path || !*full_path || !ref || !ctx)
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
|
||||
return -EINVAL;
|
||||
|
||||
if (strlen(full_path) - ref->path_consumed) {
|
||||
prepath = full_path + ref->path_consumed;
|
||||
/* skip initial delimiter */
|
||||
if (*prepath == '/' || *prepath == '\\')
|
||||
prepath++;
|
||||
}
|
||||
|
||||
path = cifs_build_devname(ref->node_name, prepath);
|
||||
if (IS_ERR(path))
|
||||
return PTR_ERR(path);
|
||||
|
||||
rc = smb3_parse_devname(path, ctx);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rc = dns_resolve_unc(DFS_DOM(ctx), path,
|
||||
(struct sockaddr *)&ctx->dstaddr);
|
||||
out:
|
||||
kfree(path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
|
||||
{
|
||||
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
||||
int rc;
|
||||
|
||||
ctx->leaf_fullpath = (char *)full_path;
|
||||
ctx->dns_dom = DFS_DOM(ctx);
|
||||
rc = cifs_mount_get_session(mnt_ctx);
|
||||
ctx->leaf_fullpath = ctx->dns_dom = NULL;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get an active reference of @ses so that next call to cifs_put_tcon() won't
|
||||
* release it as any new DFS referrals must go through its IPC tcon.
|
||||
*/
|
||||
static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
|
||||
{
|
||||
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
||||
struct cifs_ses *ses = mnt_ctx->ses;
|
||||
|
||||
if (ses) {
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
cifs_smb_ses_inc_refcount(ses);
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
}
|
||||
ctx->dfs_root_ses = ses;
|
||||
}
|
||||
|
||||
static inline int parse_dfs_target(struct smb3_fs_context *ctx,
|
||||
struct dfs_ref_walk *rw,
|
||||
struct dfs_info3_param *tgt)
|
||||
{
|
||||
int rc;
|
||||
const char *fpath = ref_walk_fpath(rw) + 1;
|
||||
|
||||
rc = ref_walk_get_tgt(rw, tgt);
|
||||
if (!rc)
|
||||
rc = dfs_parse_target_referral(fpath, tgt, ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int setup_dfs_ref(struct dfs_info3_param *tgt, struct dfs_ref_walk *rw)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb = rw->mnt_ctx->cifs_sb;
|
||||
struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx;
|
||||
char *ref_path, *full_path;
|
||||
int rc;
|
||||
|
||||
set_root_smb_session(rw->mnt_ctx);
|
||||
ref_walk_ses(rw) = ctx->dfs_root_ses;
|
||||
|
||||
full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb));
|
||||
if (IS_ERR(full_path))
|
||||
return PTR_ERR(full_path);
|
||||
|
||||
if (!tgt || (tgt->server_type == DFS_TYPE_LINK &&
|
||||
DFS_INTERLINK(tgt->flags)))
|
||||
ref_path = dfs_get_path(cifs_sb, ctx->UNC);
|
||||
else
|
||||
ref_path = dfs_get_path(cifs_sb, full_path);
|
||||
if (IS_ERR(ref_path)) {
|
||||
rc = PTR_ERR(ref_path);
|
||||
kfree(full_path);
|
||||
return rc;
|
||||
}
|
||||
ref_walk_path(rw) = ref_path;
|
||||
ref_walk_fpath(rw) = full_path;
|
||||
|
||||
return dfs_get_referral(rw->mnt_ctx,
|
||||
ref_walk_path(rw) + 1,
|
||||
ref_walk_tl(rw));
|
||||
}
|
||||
|
||||
static int __dfs_referral_walk(struct dfs_ref_walk *rw)
|
||||
{
|
||||
struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx;
|
||||
struct cifs_mount_ctx *mnt_ctx = rw->mnt_ctx;
|
||||
struct dfs_info3_param tgt = {};
|
||||
int rc = -ENOENT;
|
||||
|
||||
again:
|
||||
do {
|
||||
ctx->dfs_root_ses = ref_walk_ses(rw);
|
||||
while (ref_walk_next_tgt(rw)) {
|
||||
rc = parse_dfs_target(ctx, rw, &tgt);
|
||||
if (rc)
|
||||
continue;
|
||||
|
||||
cifs_mount_put_conns(mnt_ctx);
|
||||
rc = get_session(mnt_ctx, ref_walk_path(rw));
|
||||
if (rc)
|
||||
continue;
|
||||
|
||||
rc = cifs_mount_get_tcon(mnt_ctx);
|
||||
if (rc) {
|
||||
if (tgt.server_type == DFS_TYPE_LINK &&
|
||||
DFS_INTERLINK(tgt.flags))
|
||||
rc = -EREMOTE;
|
||||
} else {
|
||||
rc = cifs_is_path_remote(mnt_ctx);
|
||||
if (!rc) {
|
||||
ref_walk_set_tgt_hint(rw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rc == -EREMOTE) {
|
||||
rc = ref_walk_advance(rw);
|
||||
if (!rc) {
|
||||
rc = setup_dfs_ref(&tgt, rw);
|
||||
if (rc)
|
||||
break;
|
||||
ref_walk_mark_end(rw);
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (rc && ref_walk_descend(rw));
|
||||
|
||||
free_dfs_info_param(&tgt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
|
||||
struct dfs_ref_walk **rw)
|
||||
{
|
||||
int rc;
|
||||
|
||||
*rw = ref_walk_alloc();
|
||||
if (IS_ERR(*rw)) {
|
||||
rc = PTR_ERR(*rw);
|
||||
*rw = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
ref_walk_init(*rw, mnt_ctx);
|
||||
rc = setup_dfs_ref(NULL, *rw);
|
||||
if (!rc)
|
||||
rc = __dfs_referral_walk(*rw);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
|
||||
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
||||
struct dfs_ref_walk *rw = NULL;
|
||||
struct cifs_tcon *tcon;
|
||||
char *origin_fullpath;
|
||||
int rc;
|
||||
|
||||
origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
|
||||
if (IS_ERR(origin_fullpath))
|
||||
return PTR_ERR(origin_fullpath);
|
||||
|
||||
rc = dfs_referral_walk(mnt_ctx, &rw);
|
||||
if (!rc) {
|
||||
/*
|
||||
* Prevent superblock from being created with any missing
|
||||
* connections.
|
||||
*/
|
||||
if (WARN_ON(!mnt_ctx->server))
|
||||
rc = -EHOSTDOWN;
|
||||
else if (WARN_ON(!mnt_ctx->ses))
|
||||
rc = -EACCES;
|
||||
else if (WARN_ON(!mnt_ctx->tcon))
|
||||
rc = -ENOENT;
|
||||
}
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
tcon = mnt_ctx->tcon;
|
||||
spin_lock(&tcon->tc_lock);
|
||||
tcon->origin_fullpath = origin_fullpath;
|
||||
origin_fullpath = NULL;
|
||||
ref_walk_set_tcon(rw, tcon);
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
|
||||
dfs_cache_get_ttl() * HZ);
|
||||
|
||||
out:
|
||||
kfree(origin_fullpath);
|
||||
ref_walk_free(rw);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* If @ctx->dfs_automount, then update @ctx->dstaddr earlier with the DFS root
|
||||
* server from where we'll start following any referrals. Otherwise rely on the
|
||||
* value provided by mount(2) as the user might not have dns_resolver key set up
|
||||
* and therefore failing to upcall to resolve UNC hostname under @ctx->source.
|
||||
*/
|
||||
static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)
|
||||
{
|
||||
struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
|
||||
int rc = 0;
|
||||
|
||||
if (!ctx->nodfs && ctx->dfs_automount) {
|
||||
rc = dns_resolve_unc(NULL, ctx->source, addr);
|
||||
if (!rc)
|
||||
cifs_set_port(addr, ctx->port);
|
||||
ctx->dfs_automount = false;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
|
||||
{
|
||||
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
||||
bool nodfs = ctx->nodfs;
|
||||
int rc;
|
||||
|
||||
rc = update_fs_context_dstaddr(ctx);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = get_session(mnt_ctx, NULL);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
|
||||
* try to get an DFS referral (even cached) to determine whether it is an DFS mount.
|
||||
*
|
||||
* Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
|
||||
* to respond with PATH_NOT_COVERED to requests that include the prefix.
|
||||
*/
|
||||
if (!nodfs) {
|
||||
rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL);
|
||||
if (rc) {
|
||||
cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n",
|
||||
__func__, ctx->UNC + 1, rc);
|
||||
cifs_dbg(FYI, "%s: assuming non-dfs mount...\n", __func__);
|
||||
nodfs = true;
|
||||
}
|
||||
}
|
||||
if (nodfs) {
|
||||
rc = cifs_mount_get_tcon(mnt_ctx);
|
||||
if (!rc)
|
||||
rc = cifs_is_path_remote(mnt_ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!ctx->dfs_conn) {
|
||||
ctx->dfs_conn = true;
|
||||
cifs_mount_put_conns(mnt_ctx);
|
||||
rc = get_session(mnt_ctx, NULL);
|
||||
}
|
||||
if (!rc)
|
||||
rc = __dfs_mount_share(mnt_ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int target_share_matches_server(struct TCP_Server_Info *server, char *share,
|
||||
bool *target_match)
|
||||
{
|
||||
int rc = 0;
|
||||
const char *dfs_host;
|
||||
size_t dfs_host_len;
|
||||
|
||||
*target_match = true;
|
||||
extract_unc_hostname(share, &dfs_host, &dfs_host_len);
|
||||
|
||||
/* Check if hostnames or addresses match */
|
||||
cifs_server_lock(server);
|
||||
if (dfs_host_len != strlen(server->hostname) ||
|
||||
strncasecmp(dfs_host, server->hostname, dfs_host_len)) {
|
||||
cifs_dbg(FYI, "%s: %.*s doesn't match %s\n", __func__,
|
||||
(int)dfs_host_len, dfs_host, server->hostname);
|
||||
rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
|
||||
if (rc)
|
||||
cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
|
||||
}
|
||||
cifs_server_unlock(server);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tree_connect_dfs_target(const unsigned int xid,
|
||||
struct cifs_tcon *tcon,
|
||||
struct cifs_sb_info *cifs_sb,
|
||||
char *tree, bool islink,
|
||||
struct dfs_cache_tgt_list *tl)
|
||||
{
|
||||
const struct smb_version_operations *ops = tcon->ses->server->ops;
|
||||
struct TCP_Server_Info *server = tcon->ses->server;
|
||||
struct dfs_cache_tgt_iterator *tit;
|
||||
char *share = NULL, *prefix = NULL;
|
||||
bool target_match;
|
||||
int rc = -ENOENT;
|
||||
|
||||
/* Try to tree connect to all dfs targets */
|
||||
for (tit = dfs_cache_get_tgt_iterator(tl);
|
||||
tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
|
||||
kfree(share);
|
||||
kfree(prefix);
|
||||
share = prefix = NULL;
|
||||
|
||||
/* Check if share matches with tcp ses */
|
||||
rc = dfs_cache_get_tgt_share(server->leaf_fullpath + 1, tit, &share, &prefix);
|
||||
if (rc) {
|
||||
cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
|
||||
break;
|
||||
}
|
||||
|
||||
rc = target_share_matches_server(server, share, &target_match);
|
||||
if (rc)
|
||||
break;
|
||||
if (!target_match) {
|
||||
rc = -EHOSTUNREACH;
|
||||
continue;
|
||||
}
|
||||
|
||||
dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit);
|
||||
scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
|
||||
rc = ops->tree_connect(xid, tcon->ses, tree,
|
||||
tcon, tcon->ses->local_nls);
|
||||
if (islink && !rc && cifs_sb)
|
||||
rc = cifs_update_super_prepath(cifs_sb, prefix);
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(share);
|
||||
kfree(prefix);
|
||||
dfs_cache_free_tgts(tl);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon)
|
||||
{
|
||||
int rc;
|
||||
struct TCP_Server_Info *server = tcon->ses->server;
|
||||
const struct smb_version_operations *ops = server->ops;
|
||||
DFS_CACHE_TGT_LIST(tl);
|
||||
struct cifs_sb_info *cifs_sb = NULL;
|
||||
struct super_block *sb = NULL;
|
||||
struct dfs_info3_param ref = {0};
|
||||
char *tree;
|
||||
|
||||
/* only send once per connect */
|
||||
spin_lock(&tcon->tc_lock);
|
||||
|
||||
/* if tcon is marked for needing reconnect, update state */
|
||||
if (tcon->need_reconnect)
|
||||
tcon->status = TID_NEED_TCON;
|
||||
|
||||
if (tcon->status == TID_GOOD) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tcon->status != TID_NEW &&
|
||||
tcon->status != TID_NEED_TCON) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return -EHOSTDOWN;
|
||||
}
|
||||
|
||||
tcon->status = TID_IN_TCON;
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
|
||||
tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
|
||||
if (!tree) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (tcon->ipc) {
|
||||
cifs_server_lock(server);
|
||||
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
|
||||
cifs_server_unlock(server);
|
||||
rc = ops->tree_connect(xid, tcon->ses, tree,
|
||||
tcon, tcon->ses->local_nls);
|
||||
goto out;
|
||||
}
|
||||
|
||||
sb = cifs_get_dfs_tcon_super(tcon);
|
||||
if (!IS_ERR(sb))
|
||||
cifs_sb = CIFS_SB(sb);
|
||||
|
||||
/* Tree connect to last share in @tcon->tree_name if no DFS referral */
|
||||
if (!server->leaf_fullpath ||
|
||||
dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) {
|
||||
rc = ops->tree_connect(xid, tcon->ses, tcon->treeName,
|
||||
tcon, tcon->ses->local_nls);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
|
||||
&tl);
|
||||
free_dfs_info_param(&ref);
|
||||
|
||||
out:
|
||||
kfree(tree);
|
||||
cifs_put_tcp_super(sb);
|
||||
|
||||
if (rc) {
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (tcon->status == TID_IN_TCON)
|
||||
tcon->status = TID_NEED_TCON;
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
} else {
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (tcon->status == TID_IN_TCON)
|
||||
tcon->status = TID_GOOD;
|
||||
tcon->need_reconnect = false;
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
198
fs/cifs/dfs.h
Normal file
198
fs/cifs/dfs.h
Normal file
@ -0,0 +1,198 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
|
||||
*/
|
||||
|
||||
#ifndef _CIFS_DFS_H
|
||||
#define _CIFS_DFS_H
|
||||
|
||||
#include "cifsglob.h"
|
||||
#include "cifsproto.h"
|
||||
#include "fs_context.h"
|
||||
#include "dfs_cache.h"
|
||||
#include "cifs_unicode.h"
|
||||
#include <linux/namei.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#define DFS_INTERLINK(v) \
|
||||
(((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
|
||||
|
||||
struct dfs_ref {
|
||||
char *path;
|
||||
char *full_path;
|
||||
struct cifs_ses *ses;
|
||||
struct dfs_cache_tgt_list tl;
|
||||
struct dfs_cache_tgt_iterator *tit;
|
||||
};
|
||||
|
||||
struct dfs_ref_walk {
|
||||
struct cifs_mount_ctx *mnt_ctx;
|
||||
struct dfs_ref *ref;
|
||||
struct dfs_ref refs[MAX_NESTED_LINKS];
|
||||
};
|
||||
|
||||
#define ref_walk_start(w) ((w)->refs)
|
||||
#define ref_walk_end(w) (&(w)->refs[ARRAY_SIZE((w)->refs) - 1])
|
||||
#define ref_walk_cur(w) ((w)->ref)
|
||||
#define ref_walk_descend(w) (--ref_walk_cur(w) >= ref_walk_start(w))
|
||||
|
||||
#define ref_walk_tit(w) (ref_walk_cur(w)->tit)
|
||||
#define ref_walk_path(w) (ref_walk_cur(w)->path)
|
||||
#define ref_walk_fpath(w) (ref_walk_cur(w)->full_path)
|
||||
#define ref_walk_tl(w) (&ref_walk_cur(w)->tl)
|
||||
#define ref_walk_ses(w) (ref_walk_cur(w)->ses)
|
||||
|
||||
static inline struct dfs_ref_walk *ref_walk_alloc(void)
|
||||
{
|
||||
struct dfs_ref_walk *rw;
|
||||
|
||||
rw = kmalloc(sizeof(*rw), GFP_KERNEL);
|
||||
if (!rw)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
return rw;
|
||||
}
|
||||
|
||||
static inline void ref_walk_init(struct dfs_ref_walk *rw,
|
||||
struct cifs_mount_ctx *mnt_ctx)
|
||||
{
|
||||
memset(rw, 0, sizeof(*rw));
|
||||
rw->mnt_ctx = mnt_ctx;
|
||||
ref_walk_cur(rw) = ref_walk_start(rw);
|
||||
}
|
||||
|
||||
static inline void __ref_walk_free(struct dfs_ref *ref)
|
||||
{
|
||||
kfree(ref->path);
|
||||
kfree(ref->full_path);
|
||||
dfs_cache_free_tgts(&ref->tl);
|
||||
if (ref->ses)
|
||||
cifs_put_smb_ses(ref->ses);
|
||||
memset(ref, 0, sizeof(*ref));
|
||||
}
|
||||
|
||||
static inline void ref_walk_free(struct dfs_ref_walk *rw)
|
||||
{
|
||||
struct dfs_ref *ref;
|
||||
|
||||
if (!rw)
|
||||
return;
|
||||
|
||||
for (ref = ref_walk_start(rw); ref <= ref_walk_end(rw); ref++)
|
||||
__ref_walk_free(ref);
|
||||
kfree(rw);
|
||||
}
|
||||
|
||||
static inline int ref_walk_advance(struct dfs_ref_walk *rw)
|
||||
{
|
||||
struct dfs_ref *ref = ref_walk_cur(rw) + 1;
|
||||
|
||||
if (ref > ref_walk_end(rw))
|
||||
return -ELOOP;
|
||||
__ref_walk_free(ref);
|
||||
ref_walk_cur(rw) = ref;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct dfs_cache_tgt_iterator *
|
||||
ref_walk_next_tgt(struct dfs_ref_walk *rw)
|
||||
{
|
||||
struct dfs_ref *ref = ref_walk_cur(rw);
|
||||
struct dfs_cache_tgt_iterator *tit;
|
||||
|
||||
if (IS_ERR(ref->tit))
|
||||
return NULL;
|
||||
|
||||
if (!ref->tit)
|
||||
tit = dfs_cache_get_tgt_iterator(&ref->tl);
|
||||
else
|
||||
tit = dfs_cache_get_next_tgt(&ref->tl, ref->tit);
|
||||
|
||||
if (!tit) {
|
||||
ref->tit = ERR_PTR(-ENOENT);
|
||||
return NULL;
|
||||
}
|
||||
ref->tit = tit;
|
||||
return ref->tit;
|
||||
}
|
||||
|
||||
static inline int ref_walk_get_tgt(struct dfs_ref_walk *rw,
|
||||
struct dfs_info3_param *tgt)
|
||||
{
|
||||
zfree_dfs_info_param(tgt);
|
||||
return dfs_cache_get_tgt_referral(ref_walk_path(rw) + 1,
|
||||
ref_walk_tit(rw), tgt);
|
||||
}
|
||||
|
||||
static inline void ref_walk_set_tgt_hint(struct dfs_ref_walk *rw)
|
||||
{
|
||||
dfs_cache_noreq_update_tgthint(ref_walk_path(rw) + 1,
|
||||
ref_walk_tit(rw));
|
||||
}
|
||||
|
||||
static inline void ref_walk_set_tcon(struct dfs_ref_walk *rw,
|
||||
struct cifs_tcon *tcon)
|
||||
{
|
||||
struct dfs_ref *ref = ref_walk_start(rw);
|
||||
|
||||
for (; ref <= ref_walk_cur(rw); ref++) {
|
||||
if (WARN_ON_ONCE(!ref->ses))
|
||||
continue;
|
||||
list_add(&ref->ses->dlist, &tcon->dfs_ses_list);
|
||||
ref->ses = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ref_walk_mark_end(struct dfs_ref_walk *rw)
|
||||
{
|
||||
struct dfs_ref *ref = ref_walk_cur(rw) - 1;
|
||||
|
||||
WARN_ON_ONCE(ref < ref_walk_start(rw));
|
||||
dfs_cache_noreq_update_tgthint(ref->path + 1, ref->tit);
|
||||
ref->tit = ERR_PTR(-ENOENT); /* end marker */
|
||||
}
|
||||
|
||||
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
|
||||
struct smb3_fs_context *ctx);
|
||||
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx);
|
||||
|
||||
static inline char *dfs_get_path(struct cifs_sb_info *cifs_sb, const char *path)
|
||||
{
|
||||
return dfs_cache_canonical_path(path, cifs_sb->local_nls, cifs_remap(cifs_sb));
|
||||
}
|
||||
|
||||
static inline int dfs_get_referral(struct cifs_mount_ctx *mnt_ctx,
|
||||
const char *path,
|
||||
struct dfs_cache_tgt_list *tl)
|
||||
{
|
||||
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
||||
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
|
||||
struct cifs_ses *rses = ctx->dfs_root_ses ?: mnt_ctx->ses;
|
||||
|
||||
return dfs_cache_find(mnt_ctx->xid, rses, cifs_sb->local_nls,
|
||||
cifs_remap(cifs_sb), path, NULL, tl);
|
||||
}
|
||||
|
||||
/*
|
||||
* cifs_get_smb_ses() already guarantees an active reference of
|
||||
* @ses->dfs_root_ses when a new session is created, so we need to put extra
|
||||
* references of all DFS root sessions that were used across the mount process
|
||||
* in dfs_mount_share().
|
||||
*/
|
||||
static inline void dfs_put_root_smb_sessions(struct list_head *head)
|
||||
{
|
||||
struct cifs_ses *ses, *n;
|
||||
|
||||
list_for_each_entry_safe(ses, n, head, dlist) {
|
||||
list_del_init(&ses->dlist);
|
||||
cifs_put_smb_ses(ses);
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *dfs_ses_refpath(struct cifs_ses *ses)
|
||||
{
|
||||
const char *path = ses->server->leaf_fullpath;
|
||||
|
||||
return path ? path + 1 : ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
#endif /* _CIFS_DFS_H */
|
||||
@ -19,14 +19,14 @@
|
||||
#include "cifs_unicode.h"
|
||||
#include "smb2glob.h"
|
||||
#include "dns_resolve.h"
|
||||
#include "dfs.h"
|
||||
|
||||
#include "dfs_cache.h"
|
||||
|
||||
#define CACHE_HTABLE_SIZE 32
|
||||
#define CACHE_MAX_ENTRIES 64
|
||||
#define CACHE_MIN_TTL 120 /* 2 minutes */
|
||||
|
||||
#define IS_DFS_INTERLINK(v) (((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
|
||||
#define CACHE_HTABLE_SIZE 512
|
||||
#define CACHE_MAX_ENTRIES 1024
|
||||
#define CACHE_MIN_TTL 120 /* 2 minutes */
|
||||
#define CACHE_DEFAULT_TTL 300 /* 5 minutes */
|
||||
|
||||
struct cache_dfs_tgt {
|
||||
char *name;
|
||||
@ -48,22 +48,10 @@ struct cache_entry {
|
||||
struct cache_dfs_tgt *tgthint;
|
||||
};
|
||||
|
||||
/* List of referral server sessions per dfs mount */
|
||||
struct mount_group {
|
||||
struct list_head list;
|
||||
uuid_t id;
|
||||
struct cifs_ses *sessions[CACHE_MAX_ENTRIES];
|
||||
int num_sessions;
|
||||
spinlock_t lock;
|
||||
struct list_head refresh_list;
|
||||
struct kref refcount;
|
||||
};
|
||||
|
||||
static struct kmem_cache *cache_slab __read_mostly;
|
||||
static struct workqueue_struct *dfscache_wq __read_mostly;
|
||||
struct workqueue_struct *dfscache_wq;
|
||||
|
||||
static int cache_ttl;
|
||||
static DEFINE_SPINLOCK(cache_ttl_lock);
|
||||
atomic_t dfs_cache_ttl;
|
||||
|
||||
static struct nls_table *cache_cp;
|
||||
|
||||
@ -75,106 +63,6 @@ static atomic_t cache_count;
|
||||
static struct hlist_head cache_htable[CACHE_HTABLE_SIZE];
|
||||
static DECLARE_RWSEM(htable_rw_lock);
|
||||
|
||||
static LIST_HEAD(mount_group_list);
|
||||
static DEFINE_MUTEX(mount_group_list_lock);
|
||||
|
||||
static void refresh_cache_worker(struct work_struct *work);
|
||||
|
||||
static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);
|
||||
|
||||
static void get_ipc_unc(const char *ref_path, char *ipc, size_t ipclen)
|
||||
{
|
||||
const char *host;
|
||||
size_t len;
|
||||
|
||||
extract_unc_hostname(ref_path, &host, &len);
|
||||
scnprintf(ipc, ipclen, "\\\\%.*s\\IPC$", (int)len, host);
|
||||
}
|
||||
|
||||
static struct cifs_ses *find_ipc_from_server_path(struct cifs_ses **ses, const char *path)
|
||||
{
|
||||
char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0};
|
||||
|
||||
get_ipc_unc(path, unc, sizeof(unc));
|
||||
for (; *ses; ses++) {
|
||||
if (!strcasecmp(unc, (*ses)->tcon_ipc->treeName))
|
||||
return *ses;
|
||||
}
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
static void __mount_group_release(struct mount_group *mg)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mg->num_sessions; i++)
|
||||
cifs_put_smb_ses(mg->sessions[i]);
|
||||
kfree(mg);
|
||||
}
|
||||
|
||||
static void mount_group_release(struct kref *kref)
|
||||
{
|
||||
struct mount_group *mg = container_of(kref, struct mount_group, refcount);
|
||||
|
||||
mutex_lock(&mount_group_list_lock);
|
||||
list_del(&mg->list);
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
__mount_group_release(mg);
|
||||
}
|
||||
|
||||
static struct mount_group *find_mount_group_locked(const uuid_t *id)
|
||||
{
|
||||
struct mount_group *mg;
|
||||
|
||||
list_for_each_entry(mg, &mount_group_list, list) {
|
||||
if (uuid_equal(&mg->id, id))
|
||||
return mg;
|
||||
}
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
static struct mount_group *__get_mount_group_locked(const uuid_t *id)
|
||||
{
|
||||
struct mount_group *mg;
|
||||
|
||||
mg = find_mount_group_locked(id);
|
||||
if (!IS_ERR(mg))
|
||||
return mg;
|
||||
|
||||
mg = kmalloc(sizeof(*mg), GFP_KERNEL);
|
||||
if (!mg)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
kref_init(&mg->refcount);
|
||||
uuid_copy(&mg->id, id);
|
||||
mg->num_sessions = 0;
|
||||
spin_lock_init(&mg->lock);
|
||||
list_add(&mg->list, &mount_group_list);
|
||||
return mg;
|
||||
}
|
||||
|
||||
static struct mount_group *get_mount_group(const uuid_t *id)
|
||||
{
|
||||
struct mount_group *mg;
|
||||
|
||||
mutex_lock(&mount_group_list_lock);
|
||||
mg = __get_mount_group_locked(id);
|
||||
if (!IS_ERR(mg))
|
||||
kref_get(&mg->refcount);
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
|
||||
return mg;
|
||||
}
|
||||
|
||||
static void free_mount_group_list(void)
|
||||
{
|
||||
struct mount_group *mg, *tmp_mg;
|
||||
|
||||
list_for_each_entry_safe(mg, tmp_mg, &mount_group_list, list) {
|
||||
list_del_init(&mg->list);
|
||||
__mount_group_release(mg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* dfs_cache_canonical_path - get a canonical DFS path
|
||||
*
|
||||
@ -237,6 +125,7 @@ static inline void free_tgts(struct cache_entry *ce)
|
||||
|
||||
static inline void flush_cache_ent(struct cache_entry *ce)
|
||||
{
|
||||
cifs_dbg(FYI, "%s: %s\n", __func__, ce->path);
|
||||
hlist_del_init(&ce->hlist);
|
||||
kfree(ce->path);
|
||||
free_tgts(ce);
|
||||
@ -283,7 +172,7 @@ static int dfscache_proc_show(struct seq_file *m, void *v)
|
||||
"cache entry: path=%s,type=%s,ttl=%d,etime=%ld,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n",
|
||||
ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link",
|
||||
ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags,
|
||||
IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
|
||||
DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
|
||||
ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no");
|
||||
|
||||
list_for_each_entry(t, &ce->tlist, list) {
|
||||
@ -352,7 +241,7 @@ static inline void dump_ce(const struct cache_entry *ce)
|
||||
ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl,
|
||||
ce->etime.tv_nsec,
|
||||
ce->hdr_flags, ce->ref_flags,
|
||||
IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
|
||||
DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
|
||||
ce->path_consumed,
|
||||
cache_entry_expired(ce) ? "yes" : "no");
|
||||
dump_tgts(ce);
|
||||
@ -396,7 +285,9 @@ int dfs_cache_init(void)
|
||||
int rc;
|
||||
int i;
|
||||
|
||||
dfscache_wq = alloc_workqueue("cifs-dfscache", WQ_FREEZABLE | WQ_UNBOUND, 1);
|
||||
dfscache_wq = alloc_workqueue("cifs-dfscache",
|
||||
WQ_UNBOUND|WQ_FREEZABLE|WQ_MEM_RECLAIM,
|
||||
0);
|
||||
if (!dfscache_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -412,6 +303,7 @@ int dfs_cache_init(void)
|
||||
INIT_HLIST_HEAD(&cache_htable[i]);
|
||||
|
||||
atomic_set(&cache_count, 0);
|
||||
atomic_set(&dfs_cache_ttl, CACHE_DEFAULT_TTL);
|
||||
cache_cp = load_nls("utf8");
|
||||
if (!cache_cp)
|
||||
cache_cp = load_nls_default();
|
||||
@ -549,34 +441,31 @@ static struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int n
|
||||
return ce;
|
||||
}
|
||||
|
||||
static void remove_oldest_entry_locked(void)
|
||||
/* Remove all referrals that have a single target or oldest entry */
|
||||
static void purge_cache(void)
|
||||
{
|
||||
int i;
|
||||
struct cache_entry *ce;
|
||||
struct cache_entry *to_del = NULL;
|
||||
|
||||
WARN_ON(!rwsem_is_locked(&htable_rw_lock));
|
||||
struct cache_entry *oldest = NULL;
|
||||
|
||||
for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
|
||||
struct hlist_head *l = &cache_htable[i];
|
||||
struct hlist_node *n;
|
||||
|
||||
hlist_for_each_entry(ce, l, hlist) {
|
||||
hlist_for_each_entry_safe(ce, n, l, hlist) {
|
||||
if (hlist_unhashed(&ce->hlist))
|
||||
continue;
|
||||
if (!to_del || timespec64_compare(&ce->etime,
|
||||
&to_del->etime) < 0)
|
||||
to_del = ce;
|
||||
if (ce->numtgts == 1)
|
||||
flush_cache_ent(ce);
|
||||
else if (!oldest ||
|
||||
timespec64_compare(&ce->etime,
|
||||
&oldest->etime) < 0)
|
||||
oldest = ce;
|
||||
}
|
||||
}
|
||||
|
||||
if (!to_del) {
|
||||
cifs_dbg(FYI, "%s: no entry to remove\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
cifs_dbg(FYI, "%s: removing entry\n", __func__);
|
||||
dump_ce(to_del);
|
||||
flush_cache_ent(to_del);
|
||||
if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES && oldest)
|
||||
flush_cache_ent(oldest);
|
||||
}
|
||||
|
||||
/* Add a new DFS cache entry */
|
||||
@ -586,12 +475,13 @@ static struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs,
|
||||
int rc;
|
||||
struct cache_entry *ce;
|
||||
unsigned int hash;
|
||||
int ttl;
|
||||
|
||||
WARN_ON(!rwsem_is_locked(&htable_rw_lock));
|
||||
|
||||
if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) {
|
||||
cifs_dbg(FYI, "%s: reached max cache size (%d)\n", __func__, CACHE_MAX_ENTRIES);
|
||||
remove_oldest_entry_locked();
|
||||
purge_cache();
|
||||
}
|
||||
|
||||
rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
|
||||
@ -602,15 +492,8 @@ static struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs,
|
||||
if (IS_ERR(ce))
|
||||
return ce;
|
||||
|
||||
spin_lock(&cache_ttl_lock);
|
||||
if (!cache_ttl) {
|
||||
cache_ttl = ce->ttl;
|
||||
queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
|
||||
} else {
|
||||
cache_ttl = min_t(int, cache_ttl, ce->ttl);
|
||||
mod_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
|
||||
}
|
||||
spin_unlock(&cache_ttl_lock);
|
||||
ttl = min_t(int, atomic_read(&dfs_cache_ttl), ce->ttl);
|
||||
atomic_set(&dfs_cache_ttl, ttl);
|
||||
|
||||
hlist_add_head(&ce->hlist, &cache_htable[hash]);
|
||||
dump_ce(ce);
|
||||
@ -722,9 +605,7 @@ static struct cache_entry *lookup_cache_entry(const char *path)
|
||||
*/
|
||||
void dfs_cache_destroy(void)
|
||||
{
|
||||
cancel_delayed_work_sync(&refresh_task);
|
||||
unload_nls(cache_cp);
|
||||
free_mount_group_list();
|
||||
flush_cache_ents();
|
||||
kmem_cache_destroy(cache_slab);
|
||||
destroy_workqueue(dfscache_wq);
|
||||
@ -765,8 +646,6 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
|
||||
int rc;
|
||||
int i;
|
||||
|
||||
cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path);
|
||||
|
||||
*refs = NULL;
|
||||
*numrefs = 0;
|
||||
|
||||
@ -775,6 +654,7 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
|
||||
if (unlikely(!cache_cp))
|
||||
return -EINVAL;
|
||||
|
||||
cifs_dbg(FYI, "%s: ipc=%s referral=%s\n", __func__, ses->tcon_ipc->treeName, path);
|
||||
rc = ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp,
|
||||
NO_MAP_UNI_RSVD);
|
||||
if (!rc) {
|
||||
@ -799,7 +679,8 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
|
||||
*/
|
||||
static struct cache_entry *cache_refresh_path(const unsigned int xid,
|
||||
struct cifs_ses *ses,
|
||||
const char *path)
|
||||
const char *path,
|
||||
bool force_refresh)
|
||||
{
|
||||
struct dfs_info3_param *refs = NULL;
|
||||
struct cache_entry *ce;
|
||||
@ -812,7 +693,7 @@ static struct cache_entry *cache_refresh_path(const unsigned int xid,
|
||||
|
||||
ce = lookup_cache_entry(path);
|
||||
if (!IS_ERR(ce)) {
|
||||
if (!cache_entry_expired(ce))
|
||||
if (!force_refresh && !cache_entry_expired(ce))
|
||||
return ce;
|
||||
} else if (PTR_ERR(ce) != -ENOENT) {
|
||||
up_read(&htable_rw_lock);
|
||||
@ -828,7 +709,8 @@ static struct cache_entry *cache_refresh_path(const unsigned int xid,
|
||||
up_read(&htable_rw_lock);
|
||||
|
||||
/*
|
||||
* Either the entry was not found, or it is expired.
|
||||
* Either the entry was not found, or it is expired, or it is a forced
|
||||
* refresh.
|
||||
* Request a new DFS referral in order to create or update a cache entry.
|
||||
*/
|
||||
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
|
||||
@ -843,7 +725,7 @@ static struct cache_entry *cache_refresh_path(const unsigned int xid,
|
||||
/* Re-check as another task might have it added or refreshed already */
|
||||
ce = lookup_cache_entry(path);
|
||||
if (!IS_ERR(ce)) {
|
||||
if (cache_entry_expired(ce)) {
|
||||
if (force_refresh || cache_entry_expired(ce)) {
|
||||
rc = update_cache_entry_locked(ce, refs, numrefs);
|
||||
if (rc)
|
||||
ce = ERR_PTR(rc);
|
||||
@ -980,7 +862,7 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
|
||||
if (IS_ERR(npath))
|
||||
return PTR_ERR(npath);
|
||||
|
||||
ce = cache_refresh_path(xid, ses, npath);
|
||||
ce = cache_refresh_path(xid, ses, npath, false);
|
||||
if (IS_ERR(ce)) {
|
||||
rc = PTR_ERR(ce);
|
||||
goto out_free_path;
|
||||
@ -1044,66 +926,6 @@ out_unlock:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfs_cache_update_tgthint - update target hint of a DFS cache entry
|
||||
*
|
||||
* If it doesn't find the cache entry, then it will get a DFS referral for @path
|
||||
* and create a new entry.
|
||||
*
|
||||
* In case the cache entry exists but expired, it will get a DFS referral
|
||||
* for @path and then update the respective cache entry.
|
||||
*
|
||||
* @xid: syscall id
|
||||
* @ses: smb session
|
||||
* @cp: codepage
|
||||
* @remap: type of character remapping for paths
|
||||
* @path: path to lookup in DFS referral cache
|
||||
* @it: DFS target iterator
|
||||
*
|
||||
* Return zero if the target hint was updated successfully, otherwise non-zero.
|
||||
*/
|
||||
int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
|
||||
const struct nls_table *cp, int remap, const char *path,
|
||||
const struct dfs_cache_tgt_iterator *it)
|
||||
{
|
||||
struct cache_dfs_tgt *t;
|
||||
struct cache_entry *ce;
|
||||
const char *npath;
|
||||
int rc = 0;
|
||||
|
||||
npath = dfs_cache_canonical_path(path, cp, remap);
|
||||
if (IS_ERR(npath))
|
||||
return PTR_ERR(npath);
|
||||
|
||||
cifs_dbg(FYI, "%s: update target hint - path: %s\n", __func__, npath);
|
||||
|
||||
ce = cache_refresh_path(xid, ses, npath);
|
||||
if (IS_ERR(ce)) {
|
||||
rc = PTR_ERR(ce);
|
||||
goto out_free_path;
|
||||
}
|
||||
|
||||
t = READ_ONCE(ce->tgthint);
|
||||
|
||||
if (likely(!strcasecmp(it->it_name, t->name)))
|
||||
goto out_unlock;
|
||||
|
||||
list_for_each_entry(t, &ce->tlist, list) {
|
||||
if (!strcasecmp(t->name, it->it_name)) {
|
||||
WRITE_ONCE(ce->tgthint, t);
|
||||
cifs_dbg(FYI, "%s: new target hint: %s\n", __func__,
|
||||
it->it_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
up_read(&htable_rw_lock);
|
||||
out_free_path:
|
||||
kfree(npath);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry
|
||||
* without sending any requests to the currently connected server.
|
||||
@ -1118,26 +940,22 @@ out_free_path:
|
||||
*
|
||||
* Return zero if the target hint was updated successfully, otherwise non-zero.
|
||||
*/
|
||||
int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
|
||||
void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
|
||||
{
|
||||
int rc;
|
||||
struct cache_entry *ce;
|
||||
struct cache_dfs_tgt *t;
|
||||
struct cache_entry *ce;
|
||||
|
||||
if (!it)
|
||||
return -EINVAL;
|
||||
if (!path || !it)
|
||||
return;
|
||||
|
||||
cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
|
||||
|
||||
down_read(&htable_rw_lock);
|
||||
|
||||
ce = lookup_cache_entry(path);
|
||||
if (IS_ERR(ce)) {
|
||||
rc = PTR_ERR(ce);
|
||||
if (IS_ERR(ce))
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
t = READ_ONCE(ce->tgthint);
|
||||
|
||||
if (unlikely(!strcasecmp(it->it_name, t->name)))
|
||||
@ -1154,7 +972,6 @@ int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_
|
||||
|
||||
out_unlock:
|
||||
up_read(&htable_rw_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1195,54 +1012,6 @@ out_unlock:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfs_cache_add_refsrv_session - add SMB session of referral server
|
||||
*
|
||||
* @mount_id: mount group uuid to lookup.
|
||||
* @ses: reference counted SMB session of referral server.
|
||||
*/
|
||||
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses)
|
||||
{
|
||||
struct mount_group *mg;
|
||||
|
||||
if (WARN_ON_ONCE(!mount_id || uuid_is_null(mount_id) || !ses))
|
||||
return;
|
||||
|
||||
mg = get_mount_group(mount_id);
|
||||
if (WARN_ON_ONCE(IS_ERR(mg)))
|
||||
return;
|
||||
|
||||
spin_lock(&mg->lock);
|
||||
if (mg->num_sessions < ARRAY_SIZE(mg->sessions))
|
||||
mg->sessions[mg->num_sessions++] = ses;
|
||||
spin_unlock(&mg->lock);
|
||||
kref_put(&mg->refcount, mount_group_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* dfs_cache_put_refsrv_sessions - put all referral server sessions
|
||||
*
|
||||
* Put all SMB sessions from the given mount group id.
|
||||
*
|
||||
* @mount_id: mount group uuid to lookup.
|
||||
*/
|
||||
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id)
|
||||
{
|
||||
struct mount_group *mg;
|
||||
|
||||
if (!mount_id || uuid_is_null(mount_id))
|
||||
return;
|
||||
|
||||
mutex_lock(&mount_group_list_lock);
|
||||
mg = find_mount_group_locked(mount_id);
|
||||
if (IS_ERR(mg)) {
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
kref_put(&mg->refcount, mount_group_release);
|
||||
}
|
||||
|
||||
/* Extract share from DFS target and return a pointer to prefix path or NULL */
|
||||
static const char *parse_target_share(const char *target, char **share)
|
||||
{
|
||||
@ -1323,142 +1092,208 @@ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2)
|
||||
static bool target_share_equal(struct cifs_tcon *tcon, const char *s1)
|
||||
{
|
||||
char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
|
||||
const char *host;
|
||||
size_t hostlen;
|
||||
char *ip = NULL;
|
||||
struct sockaddr sa;
|
||||
struct TCP_Server_Info *server = tcon->ses->server;
|
||||
const char *s2 = &tcon->treeName[1];
|
||||
struct sockaddr_storage ss;
|
||||
bool match;
|
||||
int rc;
|
||||
|
||||
if (strcasecmp(s1, s2))
|
||||
if (strcasecmp(s2, s1))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Resolve share's hostname and check if server address matches. Otherwise just ignore it
|
||||
* as we could not have upcall to resolve hostname or failed to convert ip address.
|
||||
*/
|
||||
match = true;
|
||||
extract_unc_hostname(s1, &host, &hostlen);
|
||||
scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);
|
||||
|
||||
rc = dns_resolve_server_name_to_ip(unc, &ip, NULL);
|
||||
if (rc < 0) {
|
||||
cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
|
||||
__func__, (int)hostlen, host);
|
||||
rc = dns_resolve_unc(server->dns_dom, s1, (struct sockaddr *)&ss);
|
||||
if (rc < 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!cifs_convert_address(&sa, ip, strlen(ip))) {
|
||||
cifs_dbg(VFS, "%s: failed to convert address \'%s\'. skip address matching.\n",
|
||||
__func__, ip);
|
||||
} else {
|
||||
cifs_server_lock(server);
|
||||
match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, &sa);
|
||||
cifs_server_unlock(server);
|
||||
}
|
||||
cifs_server_lock(server);
|
||||
match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
|
||||
cifs_dbg(FYI, "%s: [share=%s] ipaddr matched: %s\n", __func__, s1, match ? "yes" : "no");
|
||||
cifs_server_unlock(server);
|
||||
|
||||
kfree(ip);
|
||||
return match;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark dfs tcon for reconnecting when the currently connected tcon does not match any of the new
|
||||
* target shares in @refs.
|
||||
*/
|
||||
static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cache_tgt_list *tl,
|
||||
const struct dfs_info3_param *refs, int numrefs)
|
||||
static bool is_ses_good(struct cifs_tcon *tcon, struct cifs_ses *ses)
|
||||
{
|
||||
struct dfs_cache_tgt_iterator *it;
|
||||
int i;
|
||||
struct TCP_Server_Info *server = ses->server;
|
||||
struct cifs_tcon *ipc = NULL;
|
||||
bool ret;
|
||||
|
||||
for (it = dfs_cache_get_tgt_iterator(tl); it; it = dfs_cache_get_next_tgt(tl, it)) {
|
||||
for (i = 0; i < numrefs; i++) {
|
||||
if (target_share_equal(tcon->ses->server, dfs_cache_get_tgt_name(it),
|
||||
refs[i].node_name))
|
||||
return;
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
spin_lock(&ses->ses_lock);
|
||||
spin_lock(&ses->chan_lock);
|
||||
|
||||
ret = !cifs_chan_needs_reconnect(ses, server) &&
|
||||
ses->ses_status == SES_GOOD;
|
||||
|
||||
spin_unlock(&ses->chan_lock);
|
||||
|
||||
if (!ret)
|
||||
goto out;
|
||||
|
||||
if (likely(ses->tcon_ipc)) {
|
||||
if (ses->tcon_ipc->need_reconnect) {
|
||||
ret = false;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
spin_unlock(&ses->ses_lock);
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
|
||||
ipc = cifs_setup_ipc(ses, tcon->seal);
|
||||
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
spin_lock(&ses->ses_lock);
|
||||
if (!IS_ERR(ipc)) {
|
||||
if (!ses->tcon_ipc) {
|
||||
ses->tcon_ipc = ipc;
|
||||
ipc = NULL;
|
||||
}
|
||||
} else {
|
||||
ret = false;
|
||||
ipc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cifs_dbg(FYI, "%s: no cached or matched targets. mark dfs share for reconnect.\n", __func__);
|
||||
cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
|
||||
out:
|
||||
spin_unlock(&ses->ses_lock);
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
if (ipc && server->ops->tree_disconnect) {
|
||||
unsigned int xid = get_xid();
|
||||
|
||||
(void)server->ops->tree_disconnect(xid, ipc);
|
||||
_free_xid(xid);
|
||||
}
|
||||
tconInfoFree(ipc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Refresh dfs referral of tcon and mark it for reconnect if needed */
|
||||
static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon,
|
||||
bool force_refresh)
|
||||
/* Refresh dfs referral of @ses */
|
||||
static void refresh_ses_referral(struct cifs_tcon *tcon, struct cifs_ses *ses)
|
||||
{
|
||||
struct cifs_ses *ses;
|
||||
struct cache_entry *ce;
|
||||
struct dfs_info3_param *refs = NULL;
|
||||
int numrefs = 0;
|
||||
bool needs_refresh = false;
|
||||
struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
|
||||
int rc = 0;
|
||||
unsigned int xid;
|
||||
const char *path;
|
||||
int rc = 0;
|
||||
|
||||
ses = find_ipc_from_server_path(sessions, path);
|
||||
if (IS_ERR(ses)) {
|
||||
cifs_dbg(FYI, "%s: could not find ipc session\n", __func__);
|
||||
return PTR_ERR(ses);
|
||||
xid = get_xid();
|
||||
|
||||
path = dfs_ses_refpath(ses);
|
||||
if (IS_ERR(path)) {
|
||||
rc = PTR_ERR(path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ses = CIFS_DFS_ROOT_SES(ses);
|
||||
if (!is_ses_good(tcon, ses)) {
|
||||
cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
|
||||
__func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ce = cache_refresh_path(xid, ses, path, false);
|
||||
if (!IS_ERR(ce))
|
||||
up_read(&htable_rw_lock);
|
||||
else
|
||||
rc = PTR_ERR(ce);
|
||||
|
||||
out:
|
||||
free_xid(xid);
|
||||
}
|
||||
|
||||
static int __refresh_tcon_referral(struct cifs_tcon *tcon,
|
||||
const char *path,
|
||||
struct dfs_info3_param *refs,
|
||||
int numrefs, bool force_refresh)
|
||||
{
|
||||
struct cache_entry *ce;
|
||||
bool reconnect = force_refresh;
|
||||
int rc = 0;
|
||||
int i;
|
||||
|
||||
if (unlikely(!numrefs))
|
||||
return 0;
|
||||
|
||||
if (force_refresh) {
|
||||
for (i = 0; i < numrefs; i++) {
|
||||
/* TODO: include prefix paths in the matching */
|
||||
if (target_share_equal(tcon, refs[i].node_name)) {
|
||||
reconnect = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
down_write(&htable_rw_lock);
|
||||
ce = lookup_cache_entry(path);
|
||||
if (!IS_ERR(ce)) {
|
||||
if (force_refresh || cache_entry_expired(ce))
|
||||
rc = update_cache_entry_locked(ce, refs, numrefs);
|
||||
} else if (PTR_ERR(ce) == -ENOENT) {
|
||||
ce = add_cache_entry_locked(refs, numrefs);
|
||||
}
|
||||
up_write(&htable_rw_lock);
|
||||
|
||||
if (IS_ERR(ce))
|
||||
rc = PTR_ERR(ce);
|
||||
if (reconnect) {
|
||||
cifs_tcon_dbg(FYI, "%s: mark for reconnect\n", __func__);
|
||||
cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void refresh_tcon_referral(struct cifs_tcon *tcon, bool force_refresh)
|
||||
{
|
||||
struct dfs_info3_param *refs = NULL;
|
||||
struct cache_entry *ce;
|
||||
struct cifs_ses *ses;
|
||||
bool needs_refresh;
|
||||
const char *path;
|
||||
unsigned int xid;
|
||||
int numrefs = 0;
|
||||
int rc = 0;
|
||||
|
||||
xid = get_xid();
|
||||
ses = tcon->ses;
|
||||
|
||||
path = dfs_ses_refpath(ses);
|
||||
if (IS_ERR(path)) {
|
||||
rc = PTR_ERR(path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
down_read(&htable_rw_lock);
|
||||
ce = lookup_cache_entry(path);
|
||||
needs_refresh = force_refresh || IS_ERR(ce) || cache_entry_expired(ce);
|
||||
if (!IS_ERR(ce)) {
|
||||
rc = get_targets(ce, &tl);
|
||||
if (rc)
|
||||
cifs_dbg(FYI, "%s: could not get dfs targets: %d\n", __func__, rc);
|
||||
if (!needs_refresh) {
|
||||
up_read(&htable_rw_lock);
|
||||
goto out;
|
||||
}
|
||||
up_read(&htable_rw_lock);
|
||||
|
||||
if (!needs_refresh) {
|
||||
rc = 0;
|
||||
ses = CIFS_DFS_ROOT_SES(ses);
|
||||
if (!is_ses_good(tcon, ses)) {
|
||||
cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
|
||||
__func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
xid = get_xid();
|
||||
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
|
||||
free_xid(xid);
|
||||
|
||||
/* Create or update a cache entry with the new referral */
|
||||
if (!rc) {
|
||||
dump_refs(refs, numrefs);
|
||||
|
||||
down_write(&htable_rw_lock);
|
||||
ce = lookup_cache_entry(path);
|
||||
if (IS_ERR(ce))
|
||||
add_cache_entry_locked(refs, numrefs);
|
||||
else if (force_refresh || cache_entry_expired(ce))
|
||||
update_cache_entry_locked(ce, refs, numrefs);
|
||||
up_write(&htable_rw_lock);
|
||||
|
||||
mark_for_reconnect_if_needed(tcon, &tl, refs, numrefs);
|
||||
rc = __refresh_tcon_referral(tcon, path, refs,
|
||||
numrefs, force_refresh);
|
||||
}
|
||||
|
||||
out:
|
||||
dfs_cache_free_tgts(&tl);
|
||||
free_xid(xid);
|
||||
free_dfs_info_array(refs, numrefs);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
|
||||
{
|
||||
struct TCP_Server_Info *server = tcon->ses->server;
|
||||
|
||||
mutex_lock(&server->refpath_lock);
|
||||
if (server->origin_fullpath) {
|
||||
if (server->leaf_fullpath && strcasecmp(server->leaf_fullpath,
|
||||
server->origin_fullpath))
|
||||
__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, force_refresh);
|
||||
__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, force_refresh);
|
||||
}
|
||||
mutex_unlock(&server->refpath_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1474,40 +1309,19 @@ static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool
|
||||
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
|
||||
{
|
||||
struct cifs_tcon *tcon;
|
||||
struct TCP_Server_Info *server;
|
||||
struct mount_group *mg;
|
||||
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
|
||||
int rc;
|
||||
|
||||
if (!cifs_sb || !cifs_sb->master_tlink)
|
||||
return -EINVAL;
|
||||
|
||||
tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
server = tcon->ses->server;
|
||||
|
||||
if (!server->origin_fullpath) {
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (!tcon->origin_fullpath) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
|
||||
cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&mount_group_list_lock);
|
||||
mg = find_mount_group_locked(&cifs_sb->dfs_mount_id);
|
||||
if (IS_ERR(mg)) {
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__);
|
||||
return PTR_ERR(mg);
|
||||
}
|
||||
kref_get(&mg->refcount);
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
|
||||
spin_lock(&mg->lock);
|
||||
memcpy(&sessions, mg->sessions, mg->num_sessions * sizeof(mg->sessions[0]));
|
||||
spin_unlock(&mg->lock);
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
|
||||
/*
|
||||
* After reconnecting to a different server, unique ids won't match anymore, so we disable
|
||||
@ -1519,111 +1333,23 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
|
||||
* that have different prefix paths.
|
||||
*/
|
||||
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
|
||||
rc = refresh_tcon(sessions, tcon, true);
|
||||
|
||||
kref_put(&mg->refcount, mount_group_release);
|
||||
return rc;
|
||||
refresh_tcon_referral(tcon, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Refresh all active dfs mounts regardless of whether they are in cache or not.
|
||||
* (cache can be cleared)
|
||||
*/
|
||||
static void refresh_mounts(struct cifs_ses **sessions)
|
||||
/* Refresh all DFS referrals related to DFS tcon */
|
||||
void dfs_cache_refresh(struct work_struct *work)
|
||||
{
|
||||
struct TCP_Server_Info *server;
|
||||
struct cifs_tcon *tcon;
|
||||
struct cifs_ses *ses;
|
||||
struct cifs_tcon *tcon, *ntcon;
|
||||
struct list_head tcons;
|
||||
|
||||
INIT_LIST_HEAD(&tcons);
|
||||
tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
|
||||
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
|
||||
spin_lock(&server->srv_lock);
|
||||
if (!server->is_dfs_conn) {
|
||||
spin_unlock(&server->srv_lock);
|
||||
continue;
|
||||
}
|
||||
spin_unlock(&server->srv_lock);
|
||||
list_for_each_entry(ses, &tcon->dfs_ses_list, dlist)
|
||||
refresh_ses_referral(tcon, ses);
|
||||
refresh_tcon_referral(tcon, false);
|
||||
|
||||
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
|
||||
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (!tcon->ipc && !tcon->need_reconnect) {
|
||||
tcon->tc_count++;
|
||||
list_add_tail(&tcon->ulist, &tcons);
|
||||
}
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
|
||||
list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
|
||||
struct TCP_Server_Info *server = tcon->ses->server;
|
||||
|
||||
list_del_init(&tcon->ulist);
|
||||
|
||||
mutex_lock(&server->refpath_lock);
|
||||
if (server->origin_fullpath) {
|
||||
if (server->leaf_fullpath && strcasecmp(server->leaf_fullpath,
|
||||
server->origin_fullpath))
|
||||
__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
|
||||
__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, false);
|
||||
}
|
||||
mutex_unlock(&server->refpath_lock);
|
||||
|
||||
cifs_put_tcon(tcon);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Worker that will refresh DFS cache and active mounts based on lowest TTL value from a DFS
|
||||
* referral.
|
||||
*/
|
||||
static void refresh_cache_worker(struct work_struct *work)
|
||||
{
|
||||
struct list_head mglist;
|
||||
struct mount_group *mg, *tmp_mg;
|
||||
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
|
||||
int max_sessions = ARRAY_SIZE(sessions) - 1;
|
||||
int i = 0, count;
|
||||
|
||||
INIT_LIST_HEAD(&mglist);
|
||||
|
||||
/* Get refereces of mount groups */
|
||||
mutex_lock(&mount_group_list_lock);
|
||||
list_for_each_entry(mg, &mount_group_list, list) {
|
||||
kref_get(&mg->refcount);
|
||||
list_add(&mg->refresh_list, &mglist);
|
||||
}
|
||||
mutex_unlock(&mount_group_list_lock);
|
||||
|
||||
/* Fill in local array with an NULL-terminated list of all referral server sessions */
|
||||
list_for_each_entry(mg, &mglist, refresh_list) {
|
||||
if (i >= max_sessions)
|
||||
break;
|
||||
|
||||
spin_lock(&mg->lock);
|
||||
if (i + mg->num_sessions > max_sessions)
|
||||
count = max_sessions - i;
|
||||
else
|
||||
count = mg->num_sessions;
|
||||
memcpy(&sessions[i], mg->sessions, count * sizeof(mg->sessions[0]));
|
||||
spin_unlock(&mg->lock);
|
||||
i += count;
|
||||
}
|
||||
|
||||
if (sessions[0])
|
||||
refresh_mounts(sessions);
|
||||
|
||||
list_for_each_entry_safe(mg, tmp_mg, &mglist, refresh_list) {
|
||||
list_del_init(&mg->refresh_list);
|
||||
kref_put(&mg->refcount, mount_group_release);
|
||||
}
|
||||
|
||||
spin_lock(&cache_ttl_lock);
|
||||
queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
|
||||
spin_unlock(&cache_ttl_lock);
|
||||
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
|
||||
atomic_read(&dfs_cache_ttl) * HZ);
|
||||
}
|
||||
|
||||
@ -13,7 +13,14 @@
|
||||
#include <linux/uuid.h>
|
||||
#include "cifsglob.h"
|
||||
|
||||
#define DFS_CACHE_TGT_LIST_INIT(var) { .tl_numtgts = 0, .tl_list = LIST_HEAD_INIT((var).tl_list), }
|
||||
extern struct workqueue_struct *dfscache_wq;
|
||||
extern atomic_t dfs_cache_ttl;
|
||||
|
||||
#define DFS_CACHE_TGT_LIST_INIT(var) \
|
||||
{ .tl_numtgts = 0, .tl_list = LIST_HEAD_INIT((var).tl_list), }
|
||||
|
||||
#define DFS_CACHE_TGT_LIST(var) \
|
||||
struct dfs_cache_tgt_list var = DFS_CACHE_TGT_LIST_INIT(var)
|
||||
|
||||
struct dfs_cache_tgt_list {
|
||||
int tl_numtgts;
|
||||
@ -35,25 +42,21 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
|
||||
struct dfs_cache_tgt_list *tgt_list);
|
||||
int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
|
||||
struct dfs_cache_tgt_list *tgt_list);
|
||||
int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
|
||||
const struct nls_table *cp, int remap, const char *path,
|
||||
const struct dfs_cache_tgt_iterator *it);
|
||||
int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it);
|
||||
void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it);
|
||||
int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iterator *it,
|
||||
struct dfs_info3_param *ref);
|
||||
int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share,
|
||||
char **prefix);
|
||||
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id);
|
||||
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses);
|
||||
char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap);
|
||||
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb);
|
||||
void dfs_cache_refresh(struct work_struct *work);
|
||||
|
||||
static inline struct dfs_cache_tgt_iterator *
|
||||
dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,
|
||||
struct dfs_cache_tgt_iterator *it)
|
||||
{
|
||||
if (!tl || list_empty(&tl->tl_list) || !it ||
|
||||
list_is_last(&it->it_list, &tl->tl_list))
|
||||
if (!tl || !tl->tl_numtgts || list_empty(&tl->tl_list) ||
|
||||
!it || list_is_last(&it->it_list, &tl->tl_list))
|
||||
return NULL;
|
||||
return list_next_entry(it, it_list);
|
||||
}
|
||||
@ -72,7 +75,7 @@ static inline void dfs_cache_free_tgts(struct dfs_cache_tgt_list *tl)
|
||||
{
|
||||
struct dfs_cache_tgt_iterator *it, *nit;
|
||||
|
||||
if (!tl || list_empty(&tl->tl_list))
|
||||
if (!tl || !tl->tl_numtgts || list_empty(&tl->tl_list))
|
||||
return;
|
||||
list_for_each_entry_safe(it, nit, &tl->tl_list, it_list) {
|
||||
list_del(&it->it_list);
|
||||
@ -94,4 +97,9 @@ dfs_cache_get_nr_tgts(const struct dfs_cache_tgt_list *tl)
|
||||
return tl ? tl->tl_numtgts : 0;
|
||||
}
|
||||
|
||||
static inline int dfs_cache_get_ttl(void)
|
||||
{
|
||||
return atomic_read(&dfs_cache_ttl);
|
||||
}
|
||||
|
||||
#endif /* _CIFS_DFS_CACHE_H */
|
||||
|
||||
@ -76,14 +76,13 @@ build_path_from_dentry(struct dentry *direntry, void *page)
|
||||
prefix);
|
||||
}
|
||||
|
||||
char *
|
||||
build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
bool prefix)
|
||||
char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
const char *tree, int tree_len,
|
||||
bool prefix)
|
||||
{
|
||||
int dfsplen;
|
||||
int pplen = 0;
|
||||
struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb);
|
||||
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
char dirsep = CIFS_DIR_SEP(cifs_sb);
|
||||
char *s;
|
||||
|
||||
@ -91,7 +90,7 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (prefix)
|
||||
dfsplen = strnlen(tcon->treeName, MAX_TREE_SIZE + 1);
|
||||
dfsplen = strnlen(tree, tree_len + 1);
|
||||
else
|
||||
dfsplen = 0;
|
||||
|
||||
@ -121,7 +120,7 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
}
|
||||
if (dfsplen) {
|
||||
s -= dfsplen;
|
||||
memcpy(s, tcon->treeName, dfsplen);
|
||||
memcpy(s, tree, dfsplen);
|
||||
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) {
|
||||
int i;
|
||||
for (i = 0; i < dfsplen; i++) {
|
||||
@ -133,6 +132,16 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
return s;
|
||||
}
|
||||
|
||||
char *build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
|
||||
bool prefix)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb);
|
||||
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
|
||||
return __build_path_from_dentry_optional_prefix(direntry, page, tcon->treeName,
|
||||
MAX_TREE_SIZE, prefix);
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't allow path components longer than the server max.
|
||||
* Don't allow the separator character in a path component.
|
||||
@ -772,7 +781,7 @@ cifs_d_revalidate(struct dentry *direntry, unsigned int flags)
|
||||
|
||||
const struct dentry_operations cifs_dentry_ops = {
|
||||
.d_revalidate = cifs_d_revalidate,
|
||||
.d_automount = cifs_dfs_d_automount,
|
||||
.d_automount = cifs_d_automount,
|
||||
/* d_delete: cifs_d_delete, */ /* not needed except for debugging */
|
||||
};
|
||||
|
||||
@ -847,5 +856,5 @@ const struct dentry_operations cifs_ci_dentry_ops = {
|
||||
.d_revalidate = cifs_d_revalidate,
|
||||
.d_hash = cifs_ci_hash,
|
||||
.d_compare = cifs_ci_compare,
|
||||
.d_automount = cifs_dfs_d_automount,
|
||||
.d_automount = cifs_d_automount,
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/inet.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dns_resolver.h>
|
||||
#include "dns_resolve.h"
|
||||
@ -19,70 +20,76 @@
|
||||
#include "cifsproto.h"
|
||||
#include "cifs_debug.h"
|
||||
|
||||
/**
|
||||
* dns_resolve_server_name_to_ip - Resolve UNC server name to ip address.
|
||||
* @unc: UNC path specifying the server (with '/' as delimiter)
|
||||
* @ip_addr: Where to return the IP address.
|
||||
* @expiry: Where to return the expiry time for the dns record.
|
||||
*
|
||||
* The IP address will be returned in string form, and the caller is
|
||||
* responsible for freeing it.
|
||||
*
|
||||
* Returns length of result on success, -ve on error.
|
||||
*/
|
||||
int
|
||||
dns_resolve_server_name_to_ip(const char *unc, char **ip_addr, time64_t *expiry)
|
||||
static int resolve_name(const char *name, size_t namelen, struct sockaddr *addr)
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
const char *hostname, *sep;
|
||||
char *name;
|
||||
int len, rc;
|
||||
char *ip;
|
||||
int rc;
|
||||
|
||||
if (!ip_addr || !unc)
|
||||
rc = dns_query(NULL, name, namelen, NULL, &ip, NULL);
|
||||
if (rc < 0) {
|
||||
cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n",
|
||||
__func__, (int)namelen, (int)namelen, name);
|
||||
} else {
|
||||
cifs_dbg(FYI, "%s: resolved: %*.*s to %s\n",
|
||||
__func__, (int)namelen, (int)namelen, name, ip);
|
||||
|
||||
rc = cifs_convert_address(addr, ip, strlen(ip));
|
||||
kfree(ip);
|
||||
if (!rc) {
|
||||
cifs_dbg(FYI, "%s: unable to determine ip address\n",
|
||||
__func__);
|
||||
rc = -EHOSTUNREACH;
|
||||
} else {
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* dns_resolve_name - Perform an upcall to resolve hostname to an ip address.
|
||||
* @dom: DNS domain name (or NULL)
|
||||
* @name: Name to look up
|
||||
* @namelen: Length of name
|
||||
* @ip_addr: Where to return the IP address
|
||||
*
|
||||
* Returns zero on success, -ve code otherwise.
|
||||
*/
|
||||
int dns_resolve_name(const char *dom, const char *name,
|
||||
size_t namelen, struct sockaddr *ip_addr)
|
||||
{
|
||||
size_t len;
|
||||
char *s;
|
||||
int rc;
|
||||
|
||||
cifs_dbg(FYI, "%s: dom=%s name=%.*s\n", __func__, dom, (int)namelen, name);
|
||||
if (!ip_addr || !name || !*name || !namelen)
|
||||
return -EINVAL;
|
||||
|
||||
len = strlen(unc);
|
||||
if (len < 3) {
|
||||
cifs_dbg(FYI, "%s: unc is too short: %s\n", __func__, unc);
|
||||
return -EINVAL;
|
||||
cifs_dbg(FYI, "%s: hostname=%.*s\n", __func__, (int)namelen, name);
|
||||
/* Try to interpret hostname as an IPv4 or IPv6 address */
|
||||
rc = cifs_convert_address(ip_addr, name, namelen);
|
||||
if (rc > 0) {
|
||||
cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n",
|
||||
__func__, (int)namelen, (int)namelen, name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Discount leading slashes for cifs */
|
||||
len -= 2;
|
||||
hostname = unc + 2;
|
||||
/*
|
||||
* If @name contains a NetBIOS name and @dom has been specified, then
|
||||
* convert @name to an FQDN and try resolving it first.
|
||||
*/
|
||||
if (dom && *dom && cifs_netbios_name(name, namelen)) {
|
||||
len = strnlen(dom, CIFS_MAX_DOMAINNAME_LEN) + namelen + 2;
|
||||
s = kmalloc(len, GFP_KERNEL);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Search for server name delimiter */
|
||||
sep = memchr(hostname, '/', len);
|
||||
if (sep)
|
||||
len = sep - hostname;
|
||||
else
|
||||
cifs_dbg(FYI, "%s: probably server name is whole unc: %s\n",
|
||||
__func__, unc);
|
||||
|
||||
/* Try to interpret hostname as an IPv4 or IPv6 address */
|
||||
rc = cifs_convert_address((struct sockaddr *)&ss, hostname, len);
|
||||
if (rc > 0)
|
||||
goto name_is_IP_address;
|
||||
|
||||
/* Perform the upcall */
|
||||
rc = dns_query(NULL, hostname, len, NULL, ip_addr, expiry);
|
||||
if (rc < 0)
|
||||
cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n",
|
||||
__func__, len, len, hostname);
|
||||
else
|
||||
cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n",
|
||||
__func__, len, len, hostname, *ip_addr,
|
||||
expiry ? (*expiry) : 0);
|
||||
return rc;
|
||||
|
||||
name_is_IP_address:
|
||||
name = kmalloc(len + 1, GFP_KERNEL);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
memcpy(name, hostname, len);
|
||||
name[len] = 0;
|
||||
cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %s\n",
|
||||
__func__, name);
|
||||
*ip_addr = name;
|
||||
return 0;
|
||||
scnprintf(s, len, "%.*s.%s", (int)namelen, name, dom);
|
||||
rc = resolve_name(s, len - 1, ip_addr);
|
||||
kfree(s);
|
||||
if (!rc)
|
||||
return 0;
|
||||
}
|
||||
return resolve_name(name, namelen, ip_addr);
|
||||
}
|
||||
|
||||
@ -11,8 +11,31 @@
|
||||
#ifndef _DNS_RESOLVE_H
|
||||
#define _DNS_RESOLVE_H
|
||||
|
||||
#include <linux/net.h>
|
||||
#include "cifsglob.h"
|
||||
#include "cifsproto.h"
|
||||
|
||||
#ifdef __KERNEL__
|
||||
extern int dns_resolve_server_name_to_ip(const char *unc, char **ip_addr, time64_t *expiry);
|
||||
|
||||
int dns_resolve_name(const char *dom, const char *name,
|
||||
size_t namelen, struct sockaddr *ip_addr);
|
||||
|
||||
static inline int dns_resolve_unc(const char *dom, const char *unc,
|
||||
struct sockaddr *ip_addr)
|
||||
{
|
||||
const char *name;
|
||||
size_t namelen;
|
||||
|
||||
if (!unc || strlen(unc) < 3)
|
||||
return -EINVAL;
|
||||
|
||||
extract_unc_hostname(unc, &name, &namelen);
|
||||
if (!namelen)
|
||||
return -EINVAL;
|
||||
|
||||
return dns_resolve_name(dom, name, namelen, ip_addr);
|
||||
}
|
||||
|
||||
#endif /* KERNEL */
|
||||
|
||||
#endif /* _DNS_RESOLVE_H */
|
||||
|
||||
@ -405,6 +405,8 @@ static void cifsFileInfo_put_work(struct work_struct *work)
|
||||
* cifsFileInfo_put - release a reference of file priv data
|
||||
*
|
||||
* Always potentially wait for oplock handler. See _cifsFileInfo_put().
|
||||
*
|
||||
* @cifs_file: cifs/smb3 specific info (eg refcounts) for an open file
|
||||
*/
|
||||
void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
|
||||
{
|
||||
@ -420,8 +422,11 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
|
||||
*
|
||||
* If @wait_for_oplock_handler is true and we are releasing the last
|
||||
* reference, wait for any running oplock break handler of the file
|
||||
* and cancel any pending one. If calling this function from the
|
||||
* oplock break handler, you need to pass false.
|
||||
* and cancel any pending one.
|
||||
*
|
||||
* @cifs_file: cifs/smb3 specific info (eg refcounts) for an open file
|
||||
* @wait_oplock_handler: must be false if called from oplock_break_handler
|
||||
* @offload: not offloaded on close and oplock breaks
|
||||
*
|
||||
*/
|
||||
void _cifsFileInfo_put(struct cifsFileInfo *cifs_file,
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
#include "rfc1002pdu.h"
|
||||
#include "fs_context.h"
|
||||
|
||||
static DEFINE_MUTEX(cifs_mount_mutex);
|
||||
DEFINE_MUTEX(cifs_mount_mutex);
|
||||
|
||||
static const match_table_t cifs_smb_version_tokens = {
|
||||
{ Smb_1, SMB1_VERSION_STRING },
|
||||
@ -241,6 +241,8 @@ cifs_parse_security_flavors(struct fs_context *fc, char *value, struct smb3_fs_c
|
||||
#endif
|
||||
case Opt_sec_none:
|
||||
ctx->nullauth = 1;
|
||||
kfree(ctx->username);
|
||||
ctx->username = NULL;
|
||||
break;
|
||||
default:
|
||||
cifs_errorf(fc, "bad security option: %s\n", value);
|
||||
@ -318,7 +320,6 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
|
||||
{
|
||||
memcpy(new_ctx, ctx, sizeof(*ctx));
|
||||
new_ctx->prepath = NULL;
|
||||
new_ctx->mount_options = NULL;
|
||||
new_ctx->nodename = NULL;
|
||||
new_ctx->username = NULL;
|
||||
new_ctx->password = NULL;
|
||||
@ -327,11 +328,12 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
|
||||
new_ctx->UNC = NULL;
|
||||
new_ctx->source = NULL;
|
||||
new_ctx->iocharset = NULL;
|
||||
new_ctx->leaf_fullpath = NULL;
|
||||
new_ctx->dns_dom = NULL;
|
||||
/*
|
||||
* Make sure to stay in sync with smb3_cleanup_fs_context_contents()
|
||||
*/
|
||||
DUP_CTX_STR(prepath);
|
||||
DUP_CTX_STR(mount_options);
|
||||
DUP_CTX_STR(username);
|
||||
DUP_CTX_STR(password);
|
||||
DUP_CTX_STR(server_hostname);
|
||||
@ -340,6 +342,8 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
|
||||
DUP_CTX_STR(domainname);
|
||||
DUP_CTX_STR(nodename);
|
||||
DUP_CTX_STR(iocharset);
|
||||
DUP_CTX_STR(leaf_fullpath);
|
||||
DUP_CTX_STR(dns_dom);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -451,14 +455,17 @@ out:
|
||||
* but there are some bugs that prevent rename from working if there are
|
||||
* multiple delimiters.
|
||||
*
|
||||
* Returns a sanitized duplicate of @path. @gfp indicates the GFP_* flags
|
||||
* for kstrdup.
|
||||
* Return a sanitized duplicate of @path or NULL for empty prefix paths.
|
||||
* Otherwise, return ERR_PTR.
|
||||
*
|
||||
* @gfp indicates the GFP_* flags for kstrdup.
|
||||
* The caller is responsible for freeing the original.
|
||||
*/
|
||||
#define IS_DELIM(c) ((c) == '/' || (c) == '\\')
|
||||
char *cifs_sanitize_prepath(char *prepath, gfp_t gfp)
|
||||
{
|
||||
char *cursor1 = prepath, *cursor2 = prepath;
|
||||
char *s;
|
||||
|
||||
/* skip all prepended delimiters */
|
||||
while (IS_DELIM(*cursor1))
|
||||
@ -479,8 +486,39 @@ char *cifs_sanitize_prepath(char *prepath, gfp_t gfp)
|
||||
if (IS_DELIM(*(cursor2 - 1)))
|
||||
cursor2--;
|
||||
|
||||
*(cursor2) = '\0';
|
||||
return kstrdup(prepath, gfp);
|
||||
*cursor2 = '\0';
|
||||
if (!*prepath)
|
||||
return NULL;
|
||||
s = kstrdup(prepath, gfp);
|
||||
if (!s)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
return s;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return full path based on the values of @ctx->{UNC,prepath}.
|
||||
*
|
||||
* It is assumed that both values were already parsed by smb3_parse_devname().
|
||||
*/
|
||||
char *smb3_fs_context_fullpath(const struct smb3_fs_context *ctx, char dirsep)
|
||||
{
|
||||
size_t ulen, plen;
|
||||
char *s;
|
||||
|
||||
ulen = strlen(ctx->UNC);
|
||||
plen = ctx->prepath ? strlen(ctx->prepath) + 1 : 0;
|
||||
|
||||
s = kmalloc(ulen + plen + 1, GFP_KERNEL);
|
||||
if (!s)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
memcpy(s, ctx->UNC, ulen);
|
||||
if (plen) {
|
||||
s[ulen] = dirsep;
|
||||
memcpy(s + ulen + 1, ctx->prepath, plen);
|
||||
}
|
||||
s[ulen + plen] = '\0';
|
||||
convert_delimiter(s, dirsep);
|
||||
return s;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -494,6 +532,7 @@ smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx)
|
||||
char *pos;
|
||||
const char *delims = "/\\";
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
if (unlikely(!devname || !*devname)) {
|
||||
cifs_dbg(VFS, "Device name not specified\n");
|
||||
@ -521,6 +560,8 @@ smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx)
|
||||
|
||||
/* now go until next delimiter or end of string */
|
||||
len = strcspn(pos, delims);
|
||||
if (!len)
|
||||
return -EINVAL;
|
||||
|
||||
/* move "pos" up to delimiter or NULL */
|
||||
pos += len;
|
||||
@ -543,8 +584,11 @@ smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx)
|
||||
return 0;
|
||||
|
||||
ctx->prepath = cifs_sanitize_prepath(pos, GFP_KERNEL);
|
||||
if (!ctx->prepath)
|
||||
return -ENOMEM;
|
||||
if (IS_ERR(ctx->prepath)) {
|
||||
rc = PTR_ERR(ctx->prepath);
|
||||
ctx->prepath = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -580,17 +624,12 @@ static const struct fs_context_operations smb3_fs_context_ops = {
|
||||
static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
|
||||
void *data)
|
||||
{
|
||||
struct smb3_fs_context *ctx = smb3_fc2context(fc);
|
||||
char *options = data, *key;
|
||||
int ret = 0;
|
||||
|
||||
if (!options)
|
||||
return 0;
|
||||
|
||||
ctx->mount_options = kstrdup(data, GFP_KERNEL);
|
||||
if (ctx->mount_options == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = security_sb_eat_lsm_opts(options, &fc->security);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -729,9 +768,9 @@ static int smb3_get_tree(struct fs_context *fc)
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
mutex_lock(&cifs_mount_mutex);
|
||||
cifs_mount_lock();
|
||||
ret = smb3_get_tree_common(fc);
|
||||
mutex_unlock(&cifs_mount_mutex);
|
||||
cifs_mount_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1116,12 +1155,13 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
|
||||
cifs_errorf(fc, "Unknown error parsing devname\n");
|
||||
goto cifs_parse_mount_err;
|
||||
}
|
||||
ctx->source = kstrdup(param->string, GFP_KERNEL);
|
||||
if (ctx->source == NULL) {
|
||||
ctx->source = smb3_fs_context_fullpath(ctx, '/');
|
||||
if (IS_ERR(ctx->source)) {
|
||||
ctx->source = NULL;
|
||||
cifs_errorf(fc, "OOM when copying UNC string\n");
|
||||
goto cifs_parse_mount_err;
|
||||
}
|
||||
fc->source = kstrdup(param->string, GFP_KERNEL);
|
||||
fc->source = kstrdup(ctx->source, GFP_KERNEL);
|
||||
if (fc->source == NULL) {
|
||||
cifs_errorf(fc, "OOM when copying UNC string\n");
|
||||
goto cifs_parse_mount_err;
|
||||
@ -1130,6 +1170,8 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
|
||||
case Opt_user:
|
||||
kfree(ctx->username);
|
||||
ctx->username = NULL;
|
||||
if (ctx->nullauth)
|
||||
break;
|
||||
if (strlen(param->string) == 0) {
|
||||
/* null user, ie. anonymous authentication */
|
||||
ctx->nullauth = 1;
|
||||
@ -1542,8 +1584,6 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx)
|
||||
/*
|
||||
* Make sure this stays in sync with smb3_fs_context_dup()
|
||||
*/
|
||||
kfree(ctx->mount_options);
|
||||
ctx->mount_options = NULL;
|
||||
kfree(ctx->username);
|
||||
ctx->username = NULL;
|
||||
kfree_sensitive(ctx->password);
|
||||
@ -1562,6 +1602,10 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx)
|
||||
ctx->iocharset = NULL;
|
||||
kfree(ctx->prepath);
|
||||
ctx->prepath = NULL;
|
||||
kfree(ctx->leaf_fullpath);
|
||||
ctx->leaf_fullpath = NULL;
|
||||
kfree(ctx->dns_dom);
|
||||
ctx->dns_dom = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -259,8 +259,11 @@ struct smb3_fs_context {
|
||||
__u16 compression; /* compression algorithm 0xFFFF default 0=disabled */
|
||||
bool rootfs:1; /* if it's a SMB root file system */
|
||||
bool witness:1; /* use witness protocol */
|
||||
|
||||
char *mount_options;
|
||||
char *leaf_fullpath;
|
||||
struct cifs_ses *dfs_root_ses;
|
||||
bool dfs_automount:1; /* set for dfs automount only */
|
||||
bool dfs_conn:1; /* set for dfs mounts */
|
||||
char *dns_dom;
|
||||
};
|
||||
|
||||
extern const struct fs_parameter_spec smb3_fs_parameters[];
|
||||
@ -279,4 +282,16 @@ extern void smb3_update_mnt_flags(struct cifs_sb_info *cifs_sb);
|
||||
|
||||
extern char *cifs_sanitize_prepath(char *prepath, gfp_t gfp);
|
||||
|
||||
extern struct mutex cifs_mount_mutex;
|
||||
|
||||
static inline void cifs_mount_lock(void)
|
||||
{
|
||||
mutex_lock(&cifs_mount_mutex);
|
||||
}
|
||||
|
||||
static inline void cifs_mount_unlock(void)
|
||||
{
|
||||
mutex_unlock(&cifs_mount_mutex);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -57,13 +57,9 @@ static void cifs_set_ops(struct inode *inode)
|
||||
inode->i_data.a_ops = &cifs_addr_ops;
|
||||
break;
|
||||
case S_IFDIR:
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
if (IS_AUTOMOUNT(inode)) {
|
||||
inode->i_op = &cifs_dfs_referral_inode_operations;
|
||||
inode->i_op = &cifs_namespace_inode_operations;
|
||||
} else {
|
||||
#else /* NO DFS support, treat as a directory */
|
||||
{
|
||||
#endif
|
||||
inode->i_op = &cifs_dir_inode_ops;
|
||||
inode->i_fop = &cifs_dir_ops;
|
||||
}
|
||||
@ -796,11 +792,15 @@ static __u64 simple_hashstr(const char *str)
|
||||
* cifs_backup_query_path_info - SMB1 fallback code to get ino
|
||||
*
|
||||
* Fallback code to get file metadata when we don't have access to
|
||||
* @full_path (EACCES) and have backup creds.
|
||||
* full_path (EACCES) and have backup creds.
|
||||
*
|
||||
* @data will be set to search info result buffer
|
||||
* @resp_buf will be set to cifs resp buf and needs to be freed with
|
||||
* cifs_buf_release() when done with @data.
|
||||
* @xid: transaction id used to identify original request in logs
|
||||
* @tcon: information about the server share we have mounted
|
||||
* @sb: the superblock stores info such as disk space available
|
||||
* @full_path: name of the file we are getting the metadata for
|
||||
* @resp_buf: will be set to cifs resp buf and needs to be freed with
|
||||
* cifs_buf_release() when done with @data
|
||||
* @data: will be set to search info result buffer
|
||||
*/
|
||||
static int
|
||||
cifs_backup_query_path_info(int xid,
|
||||
@ -2263,7 +2263,9 @@ cifs_invalidate_mapping(struct inode *inode)
|
||||
|
||||
/**
|
||||
* cifs_wait_bit_killable - helper for functions that are sleeping on bit locks
|
||||
* @word: long word containing the bit lock
|
||||
*
|
||||
* @key: currently unused
|
||||
* @mode: the task state to sleep in
|
||||
*/
|
||||
static int
|
||||
cifs_wait_bit_killable(struct wait_bit_key *key, int mode)
|
||||
|
||||
101
fs/cifs/misc.c
101
fs/cifs/misc.c
@ -21,6 +21,7 @@
|
||||
#include "cifsfs.h"
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
#include "dns_resolve.h"
|
||||
#include "dfs.h"
|
||||
#endif
|
||||
#include "fs_context.h"
|
||||
|
||||
@ -92,6 +93,7 @@ sesInfoFree(struct cifs_ses *buf_to_free)
|
||||
return;
|
||||
}
|
||||
|
||||
unload_nls(buf_to_free->local_nls);
|
||||
atomic_dec(&sesInfoAllocCount);
|
||||
kfree(buf_to_free->serverOS);
|
||||
kfree(buf_to_free->serverDomain);
|
||||
@ -99,6 +101,7 @@ sesInfoFree(struct cifs_ses *buf_to_free)
|
||||
kfree_sensitive(buf_to_free->password);
|
||||
kfree(buf_to_free->user_name);
|
||||
kfree(buf_to_free->domainName);
|
||||
kfree(buf_to_free->dns_dom);
|
||||
kfree_sensitive(buf_to_free->auth_key.response);
|
||||
spin_lock(&buf_to_free->iface_lock);
|
||||
list_for_each_entry_safe(iface, niface, &buf_to_free->iface_list,
|
||||
@ -133,6 +136,9 @@ tconInfoAlloc(void)
|
||||
spin_lock_init(&ret_buf->stat_lock);
|
||||
atomic_set(&ret_buf->num_local_opens, 0);
|
||||
atomic_set(&ret_buf->num_remote_opens, 0);
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
|
||||
#endif
|
||||
|
||||
return ret_buf;
|
||||
}
|
||||
@ -148,6 +154,7 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
|
||||
kfree(buf_to_free->nativeFileSystem);
|
||||
kfree_sensitive(buf_to_free->password);
|
||||
kfree(buf_to_free->crfid.fid);
|
||||
kfree(buf_to_free->origin_fullpath);
|
||||
kfree(buf_to_free);
|
||||
}
|
||||
|
||||
@ -691,9 +698,9 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
|
||||
*num_of_nodes = le16_to_cpu(rsp->NumberOfReferrals);
|
||||
|
||||
if (*num_of_nodes < 1) {
|
||||
cifs_dbg(VFS, "num_referrals: must be at least > 0, but we get num_referrals = %d\n",
|
||||
*num_of_nodes);
|
||||
rc = -EINVAL;
|
||||
cifs_dbg(VFS | ONCE, "%s: [path=%s] num_referrals must be at least > 0, but we got %d\n",
|
||||
__func__, searchName, *num_of_nodes);
|
||||
rc = -ENOENT;
|
||||
goto parse_DFS_referrals_exit;
|
||||
}
|
||||
|
||||
@ -1040,20 +1047,26 @@ struct super_cb_data {
|
||||
struct super_block *sb;
|
||||
};
|
||||
|
||||
static void tcp_super_cb(struct super_block *sb, void *arg)
|
||||
static void tcon_super_cb(struct super_block *sb, void *arg)
|
||||
{
|
||||
struct super_cb_data *sd = arg;
|
||||
struct TCP_Server_Info *server = sd->data;
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
struct cifs_tcon *tcon;
|
||||
struct cifs_tcon *t1 = sd->data, *t2;
|
||||
|
||||
if (sd->sb)
|
||||
return;
|
||||
|
||||
cifs_sb = CIFS_SB(sb);
|
||||
tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
if (tcon->ses->server == server)
|
||||
t2 = cifs_sb_master_tcon(cifs_sb);
|
||||
|
||||
spin_lock(&t2->tc_lock);
|
||||
if ((t1->ses == t2->ses ||
|
||||
t1->ses->dfs_root_ses == t2->ses->dfs_root_ses) &&
|
||||
t1->ses->server == t2->ses->server &&
|
||||
t2->origin_fullpath &&
|
||||
dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath))
|
||||
sd->sb = sb;
|
||||
spin_unlock(&t2->tc_lock);
|
||||
}
|
||||
|
||||
static struct super_block *__cifs_get_super(void (*f)(struct super_block *, void *),
|
||||
@ -1079,6 +1092,7 @@ static struct super_block *__cifs_get_super(void (*f)(struct super_block *, void
|
||||
return sd.sb;
|
||||
}
|
||||
}
|
||||
pr_warn_once("%s: could not find dfs superblock\n", __func__);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
@ -1088,9 +1102,15 @@ static void __cifs_put_super(struct super_block *sb)
|
||||
cifs_sb_deactive(sb);
|
||||
}
|
||||
|
||||
struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server)
|
||||
struct super_block *cifs_get_dfs_tcon_super(struct cifs_tcon *tcon)
|
||||
{
|
||||
return __cifs_get_super(tcp_super_cb, server);
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (!tcon->origin_fullpath) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return __cifs_get_super(tcon_super_cb, tcon);
|
||||
}
|
||||
|
||||
void cifs_put_tcp_super(struct super_block *sb)
|
||||
@ -1100,64 +1120,45 @@ void cifs_put_tcp_super(struct super_block *sb)
|
||||
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
int match_target_ip(struct TCP_Server_Info *server,
|
||||
const char *share, size_t share_len,
|
||||
const char *host, size_t hostlen,
|
||||
bool *result)
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
int rc;
|
||||
char *target, *tip = NULL;
|
||||
struct sockaddr tipaddr;
|
||||
|
||||
cifs_dbg(FYI, "%s: hostname=%.*s\n", __func__, (int)hostlen, host);
|
||||
|
||||
*result = false;
|
||||
|
||||
target = kzalloc(share_len + 3, GFP_KERNEL);
|
||||
if (!target) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
scnprintf(target, share_len + 3, "\\\\%.*s", (int)share_len, share);
|
||||
|
||||
cifs_dbg(FYI, "%s: target name: %s\n", __func__, target + 2);
|
||||
|
||||
rc = dns_resolve_server_name_to_ip(target, &tip, NULL);
|
||||
rc = dns_resolve_name(server->dns_dom, host, hostlen,
|
||||
(struct sockaddr *)&ss);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
cifs_dbg(FYI, "%s: target ip: %s\n", __func__, tip);
|
||||
|
||||
if (!cifs_convert_address(&tipaddr, tip, strlen(tip))) {
|
||||
cifs_dbg(VFS, "%s: failed to convert target ip address\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
return rc;
|
||||
|
||||
spin_lock(&server->srv_lock);
|
||||
*result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr,
|
||||
&tipaddr);
|
||||
*result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
|
||||
spin_unlock(&server->srv_lock);
|
||||
cifs_dbg(FYI, "%s: ip addresses match: %u\n", __func__, *result);
|
||||
rc = 0;
|
||||
|
||||
out:
|
||||
kfree(target);
|
||||
kfree(tip);
|
||||
|
||||
return rc;
|
||||
cifs_dbg(FYI, "%s: ip addresses matched: %s\n", __func__, *result ? "yes" : "no");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
|
||||
{
|
||||
int rc;
|
||||
|
||||
kfree(cifs_sb->prepath);
|
||||
cifs_sb->prepath = NULL;
|
||||
|
||||
if (prefix && *prefix) {
|
||||
cifs_sb->prepath = cifs_sanitize_prepath(prefix, GFP_ATOMIC);
|
||||
if (!cifs_sb->prepath)
|
||||
return -ENOMEM;
|
||||
|
||||
convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
|
||||
} else
|
||||
cifs_sb->prepath = NULL;
|
||||
if (IS_ERR(cifs_sb->prepath)) {
|
||||
rc = PTR_ERR(cifs_sb->prepath);
|
||||
cifs_sb->prepath = NULL;
|
||||
return rc;
|
||||
}
|
||||
if (cifs_sb->prepath)
|
||||
convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
|
||||
}
|
||||
|
||||
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
|
||||
return 0;
|
||||
|
||||
290
fs/cifs/namespace.c
Normal file
290
fs/cifs/namespace.c
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Contains mounting routines used for handling traversal via SMB junctions.
|
||||
*
|
||||
* Copyright (c) 2007 Igor Mammedov
|
||||
* Copyright (C) International Business Machines Corp., 2008
|
||||
* Author(s): Igor Mammedov (niallain@gmail.com)
|
||||
* Steve French (sfrench@us.ibm.com)
|
||||
* Copyright (c) 2023 Paulo Alcantara <palcantara@suse.de>
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vfs.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/inet.h>
|
||||
#include "cifsglob.h"
|
||||
#include "cifsproto.h"
|
||||
#include "cifsfs.h"
|
||||
#include "cifs_debug.h"
|
||||
#include "fs_context.h"
|
||||
|
||||
static LIST_HEAD(cifs_automount_list);
|
||||
|
||||
static void cifs_expire_automounts(struct work_struct *work);
|
||||
static DECLARE_DELAYED_WORK(cifs_automount_task,
|
||||
cifs_expire_automounts);
|
||||
static int cifs_mountpoint_expiry_timeout = 500 * HZ;
|
||||
|
||||
static void cifs_expire_automounts(struct work_struct *work)
|
||||
{
|
||||
struct list_head *list = &cifs_automount_list;
|
||||
|
||||
mark_mounts_for_expiry(list);
|
||||
if (!list_empty(list))
|
||||
schedule_delayed_work(&cifs_automount_task,
|
||||
cifs_mountpoint_expiry_timeout);
|
||||
}
|
||||
|
||||
void cifs_release_automount_timer(void)
|
||||
{
|
||||
if (WARN_ON(!list_empty(&cifs_automount_list)))
|
||||
return;
|
||||
cancel_delayed_work_sync(&cifs_automount_task);
|
||||
}
|
||||
|
||||
/**
|
||||
* cifs_build_devname - build a devicename from a UNC and optional prepath
|
||||
* @nodename: pointer to UNC string
|
||||
* @prepath: pointer to prefixpath (or NULL if there isn't one)
|
||||
*
|
||||
* Build a new cifs devicename after chasing a DFS referral. Allocate a buffer
|
||||
* big enough to hold the final thing. Copy the UNC from the nodename, and
|
||||
* concatenate the prepath onto the end of it if there is one.
|
||||
*
|
||||
* Returns pointer to the built string, or a ERR_PTR. Caller is responsible
|
||||
* for freeing the returned string.
|
||||
*/
|
||||
char *
|
||||
cifs_build_devname(char *nodename, const char *prepath)
|
||||
{
|
||||
size_t pplen;
|
||||
size_t unclen;
|
||||
char *dev;
|
||||
char *pos;
|
||||
|
||||
/* skip over any preceding delimiters */
|
||||
nodename += strspn(nodename, "\\");
|
||||
if (!*nodename)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/* get length of UNC and set pos to last char */
|
||||
unclen = strlen(nodename);
|
||||
pos = nodename + unclen - 1;
|
||||
|
||||
/* trim off any trailing delimiters */
|
||||
while (*pos == '\\') {
|
||||
--pos;
|
||||
--unclen;
|
||||
}
|
||||
|
||||
/* allocate a buffer:
|
||||
* +2 for preceding "//"
|
||||
* +1 for delimiter between UNC and prepath
|
||||
* +1 for trailing NULL
|
||||
*/
|
||||
pplen = prepath ? strlen(prepath) : 0;
|
||||
dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL);
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pos = dev;
|
||||
/* add the initial "//" */
|
||||
*pos = '/';
|
||||
++pos;
|
||||
*pos = '/';
|
||||
++pos;
|
||||
|
||||
/* copy in the UNC portion from referral */
|
||||
memcpy(pos, nodename, unclen);
|
||||
pos += unclen;
|
||||
|
||||
/* copy the prefixpath remainder (if there is one) */
|
||||
if (pplen) {
|
||||
*pos = '/';
|
||||
++pos;
|
||||
memcpy(pos, prepath, pplen);
|
||||
pos += pplen;
|
||||
}
|
||||
|
||||
/* NULL terminator */
|
||||
*pos = '\0';
|
||||
|
||||
convert_delimiter(dev, '/');
|
||||
return dev;
|
||||
}
|
||||
|
||||
static bool is_dfs_mount(struct dentry *dentry)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);
|
||||
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
bool ret;
|
||||
|
||||
spin_lock(&tcon->tc_lock);
|
||||
ret = !!tcon->origin_fullpath;
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Return full path out of a dentry set for automount */
|
||||
static char *automount_fullpath(struct dentry *dentry, void *page)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);
|
||||
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
size_t len;
|
||||
char *s;
|
||||
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (unlikely(!tcon->origin_fullpath)) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return ERR_PTR(-EREMOTE);
|
||||
}
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
|
||||
s = dentry_path_raw(dentry, page, PATH_MAX);
|
||||
if (IS_ERR(s))
|
||||
return s;
|
||||
/* for root, we want "" */
|
||||
if (!s[1])
|
||||
s++;
|
||||
|
||||
spin_lock(&tcon->tc_lock);
|
||||
len = strlen(tcon->origin_fullpath);
|
||||
if (s < (char *)page + len) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
}
|
||||
|
||||
s -= len;
|
||||
memcpy(s, tcon->origin_fullpath, len);
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
convert_delimiter(s, '/');
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void fs_context_set_ids(struct smb3_fs_context *ctx)
|
||||
{
|
||||
kuid_t uid = current_fsuid();
|
||||
kgid_t gid = current_fsgid();
|
||||
|
||||
if (ctx->multiuser) {
|
||||
if (!ctx->uid_specified)
|
||||
ctx->linux_uid = uid;
|
||||
if (!ctx->gid_specified)
|
||||
ctx->linux_gid = gid;
|
||||
}
|
||||
if (!ctx->cruid_specified)
|
||||
ctx->cred_uid = uid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a vfsmount that we can automount
|
||||
*/
|
||||
static struct vfsmount *cifs_do_automount(struct path *path)
|
||||
{
|
||||
int rc;
|
||||
struct dentry *mntpt = path->dentry;
|
||||
struct fs_context *fc;
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
void *page = NULL;
|
||||
struct smb3_fs_context *ctx, *cur_ctx;
|
||||
struct smb3_fs_context tmp;
|
||||
char *full_path;
|
||||
struct vfsmount *mnt;
|
||||
|
||||
if (IS_ROOT(mntpt))
|
||||
return ERR_PTR(-ESTALE);
|
||||
|
||||
/*
|
||||
* The MSDFS spec states that paths in DFS referral requests and
|
||||
* responses must be prefixed by a single '\' character instead of
|
||||
* the double backslashes usually used in the UNC. This function
|
||||
* gives us the latter, so we must adjust the result.
|
||||
*/
|
||||
cifs_sb = CIFS_SB(mntpt->d_sb);
|
||||
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
|
||||
return ERR_PTR(-EREMOTE);
|
||||
|
||||
cur_ctx = cifs_sb->ctx;
|
||||
|
||||
fc = fs_context_for_submount(path->mnt->mnt_sb->s_type, mntpt);
|
||||
if (IS_ERR(fc))
|
||||
return ERR_CAST(fc);
|
||||
|
||||
ctx = smb3_fc2context(fc);
|
||||
|
||||
page = alloc_dentry_path();
|
||||
full_path = automount_fullpath(mntpt, page);
|
||||
if (IS_ERR(full_path)) {
|
||||
mnt = ERR_CAST(full_path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmp = *cur_ctx;
|
||||
tmp.source = NULL;
|
||||
tmp.leaf_fullpath = NULL;
|
||||
tmp.UNC = tmp.prepath = NULL;
|
||||
tmp.dfs_root_ses = NULL;
|
||||
fs_context_set_ids(&tmp);
|
||||
|
||||
rc = smb3_fs_context_dup(ctx, &tmp);
|
||||
if (rc) {
|
||||
mnt = ERR_PTR(rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = smb3_parse_devname(full_path, ctx);
|
||||
if (rc) {
|
||||
mnt = ERR_PTR(rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ctx->source = smb3_fs_context_fullpath(ctx, '/');
|
||||
if (IS_ERR(ctx->source)) {
|
||||
mnt = ERR_CAST(ctx->source);
|
||||
ctx->source = NULL;
|
||||
goto out;
|
||||
}
|
||||
ctx->dfs_automount = ctx->dfs_conn = is_dfs_mount(mntpt);
|
||||
cifs_dbg(FYI, "%s: ctx: source=%s UNC=%s prepath=%s dfs_automount=%d\n",
|
||||
__func__, ctx->source, ctx->UNC, ctx->prepath, ctx->dfs_automount);
|
||||
|
||||
mnt = fc_mount(fc);
|
||||
out:
|
||||
put_fs_context(fc);
|
||||
free_dentry_path(page);
|
||||
return mnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to automount the referral
|
||||
*/
|
||||
struct vfsmount *cifs_d_automount(struct path *path)
|
||||
{
|
||||
struct vfsmount *newmnt;
|
||||
|
||||
cifs_dbg(FYI, "%s: %pd\n", __func__, path->dentry);
|
||||
|
||||
newmnt = cifs_do_automount(path);
|
||||
if (IS_ERR(newmnt)) {
|
||||
cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__);
|
||||
return newmnt;
|
||||
}
|
||||
|
||||
mntget(newmnt); /* prevent immediate expiration */
|
||||
mnt_set_expiry(newmnt, &cifs_automount_list);
|
||||
schedule_delayed_work(&cifs_automount_task,
|
||||
cifs_mountpoint_expiry_timeout);
|
||||
cifs_dbg(FYI, "leaving %s [ok]\n" , __func__);
|
||||
return newmnt;
|
||||
}
|
||||
|
||||
const struct inode_operations cifs_namespace_inode_operations = {
|
||||
};
|
||||
@ -219,7 +219,7 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
|
||||
spin_lock(&ses->iface_lock);
|
||||
if (!ses->iface_count) {
|
||||
spin_unlock(&ses->iface_lock);
|
||||
cifs_dbg(VFS, "server %s does not advertise interfaces\n",
|
||||
cifs_dbg(ONCE, "server %s does not advertise interfaces\n",
|
||||
ses->server->hostname);
|
||||
break;
|
||||
}
|
||||
@ -330,7 +330,7 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
|
||||
spin_lock(&ses->iface_lock);
|
||||
if (!ses->iface_count) {
|
||||
spin_unlock(&ses->iface_lock);
|
||||
cifs_dbg(VFS, "server %s does not advertise interfaces\n", ses->server->hostname);
|
||||
cifs_dbg(ONCE, "server %s does not advertise interfaces\n", ses->server->hostname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -864,6 +864,10 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve
|
||||
*
|
||||
* Assumes @iov does not contain the rfc1002 length and iov[0] has the
|
||||
* SMB2 header.
|
||||
*
|
||||
* @ses: server session structure
|
||||
* @iov: array containing the SMB request we will send to the server
|
||||
* @nvec: number of array entries for the iov
|
||||
*/
|
||||
int
|
||||
smb311_update_preauth_hash(struct cifs_ses *ses, struct TCP_Server_Info *server,
|
||||
|
||||
@ -43,6 +43,8 @@ static int
|
||||
change_conf(struct TCP_Server_Info *server)
|
||||
{
|
||||
server->credits += server->echo_credits + server->oplock_credits;
|
||||
if (server->credits > server->max_credits)
|
||||
server->credits = server->max_credits;
|
||||
server->oplock_credits = server->echo_credits = 0;
|
||||
switch (server->credits) {
|
||||
case 0:
|
||||
@ -519,6 +521,43 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
|
||||
return rsize;
|
||||
}
|
||||
|
||||
/*
|
||||
* compare two interfaces a and b
|
||||
* return 0 if everything matches.
|
||||
* return 1 if a is rdma capable, or rss capable, or has higher link speed
|
||||
* return -1 otherwise.
|
||||
*/
|
||||
static int
|
||||
iface_cmp(struct cifs_server_iface *a, struct cifs_server_iface *b)
|
||||
{
|
||||
int cmp_ret = 0;
|
||||
|
||||
WARN_ON(!a || !b);
|
||||
if (a->rdma_capable == b->rdma_capable) {
|
||||
if (a->rss_capable == b->rss_capable) {
|
||||
if (a->speed == b->speed) {
|
||||
cmp_ret = cifs_ipaddr_cmp((struct sockaddr *) &a->sockaddr,
|
||||
(struct sockaddr *) &b->sockaddr);
|
||||
if (!cmp_ret)
|
||||
return 0;
|
||||
else if (cmp_ret > 0)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else if (a->speed > b->speed)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else if (a->rss_capable > b->rss_capable)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else if (a->rdma_capable > b->rdma_capable)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
|
||||
size_t buf_len, struct cifs_ses *ses, bool in_mount)
|
||||
@ -2909,7 +2948,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
|
||||
struct fsctl_get_dfs_referral_req *dfs_req = NULL;
|
||||
struct get_dfs_referral_rsp *dfs_rsp = NULL;
|
||||
u32 dfs_req_size = 0, dfs_rsp_size = 0;
|
||||
int retry_count = 0;
|
||||
int retry_once = 0;
|
||||
|
||||
cifs_dbg(FYI, "%s: path: %s\n", __func__, search_name);
|
||||
|
||||
@ -2955,19 +2994,25 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
|
||||
/* Path to resolve in an UTF-16 null-terminated string */
|
||||
memcpy(dfs_req->RequestFileName, utf16_path, utf16_path_len);
|
||||
|
||||
do {
|
||||
for (;;) {
|
||||
rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
|
||||
FSCTL_DFS_GET_REFERRALS,
|
||||
(char *)dfs_req, dfs_req_size, CIFSMaxBufSize,
|
||||
(char **)&dfs_rsp, &dfs_rsp_size);
|
||||
if (!is_retryable_error(rc))
|
||||
if (fatal_signal_pending(current)) {
|
||||
rc = -EINTR;
|
||||
break;
|
||||
}
|
||||
if (!is_retryable_error(rc) || retry_once++)
|
||||
break;
|
||||
usleep_range(512, 2048);
|
||||
} while (++retry_count < 5);
|
||||
}
|
||||
|
||||
if (!rc && !dfs_rsp)
|
||||
rc = -EIO;
|
||||
if (rc) {
|
||||
if (!is_retryable_error(rc) && rc != -ENOENT && rc != -EOPNOTSUPP)
|
||||
cifs_tcon_dbg(VFS, "%s: ioctl error: rc=%d\n", __func__, rc);
|
||||
cifs_tcon_dbg(FYI, "%s: ioctl error: rc=%d\n", __func__, rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -2975,9 +3020,9 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
|
||||
num_of_nodes, target_nodes,
|
||||
nls_codepage, remap, search_name,
|
||||
true /* is_unicode */);
|
||||
if (rc) {
|
||||
cifs_tcon_dbg(VFS, "parse error in %s rc=%d\n", __func__, rc);
|
||||
goto out;
|
||||
if (rc && rc != -ENOENT) {
|
||||
cifs_tcon_dbg(VFS, "%s: failed to parse DFS referral %s: %d\n",
|
||||
__func__, search_name, rc);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
@ -142,9 +142,8 @@ static int
|
||||
smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||
struct TCP_Server_Info *server)
|
||||
{
|
||||
int rc = 0;
|
||||
struct nls_table *nls_codepage = NULL;
|
||||
struct cifs_ses *ses;
|
||||
int rc = 0;
|
||||
|
||||
/*
|
||||
* SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
|
||||
@ -154,23 +153,15 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||
if (tcon == NULL)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Need to also skip SMB2_IOCTL because it is used for checking nested dfs links in
|
||||
* cifs_tree_connect().
|
||||
*/
|
||||
if (smb2_command == SMB2_TREE_CONNECT || smb2_command == SMB2_IOCTL)
|
||||
if (smb2_command == SMB2_TREE_CONNECT)
|
||||
return 0;
|
||||
|
||||
spin_lock(&tcon->tc_lock);
|
||||
if (tcon->status == TID_EXITING) {
|
||||
/*
|
||||
* only tree disconnect, open, and write,
|
||||
* (and ulogoff which does not have tcon)
|
||||
* are allowed as we start force umount.
|
||||
* only tree disconnect allowed when disconnecting ...
|
||||
*/
|
||||
if ((smb2_command != SMB2_WRITE) &&
|
||||
(smb2_command != SMB2_CREATE) &&
|
||||
(smb2_command != SMB2_TREE_DISCONNECT)) {
|
||||
if (smb2_command != SMB2_TREE_DISCONNECT) {
|
||||
spin_unlock(&tcon->tc_lock);
|
||||
cifs_dbg(FYI, "can not send cmd %d while umounting\n",
|
||||
smb2_command);
|
||||
@ -245,8 +236,6 @@ again:
|
||||
}
|
||||
spin_unlock(&server->srv_lock);
|
||||
|
||||
nls_codepage = load_nls_default();
|
||||
|
||||
/*
|
||||
* need to prevent multiple threads trying to simultaneously
|
||||
* reconnect the same SMB session
|
||||
@ -269,7 +258,7 @@ again:
|
||||
|
||||
rc = cifs_negotiate_protocol(0, ses, server);
|
||||
if (!rc) {
|
||||
rc = cifs_setup_session(0, ses, server, nls_codepage);
|
||||
rc = cifs_setup_session(0, ses, server, ses->local_nls);
|
||||
if ((rc == -EACCES) && !tcon->retry) {
|
||||
mutex_unlock(&ses->session_mutex);
|
||||
rc = -EHOSTDOWN;
|
||||
@ -292,7 +281,7 @@ skip_sess_setup:
|
||||
if (tcon->use_persistent)
|
||||
tcon->need_reopen_files = true;
|
||||
|
||||
rc = cifs_tree_connect(0, tcon, nls_codepage);
|
||||
rc = cifs_tree_connect(0, tcon);
|
||||
mutex_unlock(&ses->session_mutex);
|
||||
|
||||
cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
|
||||
@ -324,10 +313,10 @@ out:
|
||||
case SMB2_CHANGE_NOTIFY:
|
||||
case SMB2_QUERY_INFO:
|
||||
case SMB2_SET_INFO:
|
||||
case SMB2_IOCTL:
|
||||
rc = -EAGAIN;
|
||||
}
|
||||
failed:
|
||||
unload_nls(nls_codepage);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -1290,7 +1279,12 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
|
||||
}
|
||||
|
||||
/* enough to enable echos and oplocks and one max size write */
|
||||
req->hdr.CreditRequest = cpu_to_le16(130);
|
||||
if (server->credits >= server->max_credits)
|
||||
req->hdr.CreditRequest = cpu_to_le16(0);
|
||||
else
|
||||
req->hdr.CreditRequest = cpu_to_le16(
|
||||
min_t(int, server->max_credits -
|
||||
server->credits, 130));
|
||||
|
||||
/* only one of SMB2 signing flags may be set in SMB2 request */
|
||||
if (server->sign)
|
||||
@ -1872,7 +1866,12 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
|
||||
rqst.rq_nvec = 2;
|
||||
|
||||
/* Need 64 for max size write so ask for more in case not there yet */
|
||||
req->hdr.CreditRequest = cpu_to_le16(64);
|
||||
if (server->credits >= server->max_credits)
|
||||
req->hdr.CreditRequest = cpu_to_le16(0);
|
||||
else
|
||||
req->hdr.CreditRequest = cpu_to_le16(
|
||||
min_t(int, server->max_credits -
|
||||
server->credits, 64));
|
||||
|
||||
rc = cifs_send_recv(xid, ses, server,
|
||||
&rqst, &resp_buftype, flags, &rsp_iov);
|
||||
@ -1928,7 +1927,7 @@ tcon_exit:
|
||||
|
||||
tcon_error_exit:
|
||||
if (rsp && rsp->hdr.Status == STATUS_BAD_NETWORK_NAME)
|
||||
cifs_tcon_dbg(VFS, "BAD_NETWORK_NAME: %s\n", tree);
|
||||
cifs_dbg(VFS | ONCE, "BAD_NETWORK_NAME: %s\n", tree);
|
||||
goto tcon_exit;
|
||||
}
|
||||
|
||||
@ -3821,7 +3820,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;
|
||||
ses->ses_count++;
|
||||
cifs_smb_ses_inc_refcount(ses);
|
||||
}
|
||||
/*
|
||||
* handle the case where channel needs to reconnect
|
||||
@ -3832,7 +3831,7 @@ void smb2_reconnect_server(struct work_struct *work)
|
||||
if (!tcon_selected && cifs_chan_needs_reconnect(ses, server)) {
|
||||
list_add_tail(&ses->rlist, &tmp_ses_list);
|
||||
ses_exist = true;
|
||||
ses->ses_count++;
|
||||
cifs_smb_ses_inc_refcount(ses);
|
||||
}
|
||||
spin_unlock(&ses->chan_lock);
|
||||
}
|
||||
@ -4217,6 +4216,7 @@ smb2_async_readv(struct cifs_readdata *rdata)
|
||||
struct TCP_Server_Info *server;
|
||||
struct cifs_tcon *tcon = tlink_tcon(rdata->cfile->tlink);
|
||||
unsigned int total_len;
|
||||
int credit_request;
|
||||
|
||||
cifs_dbg(FYI, "%s: offset=%llu bytes=%u\n",
|
||||
__func__, rdata->offset, rdata->bytes);
|
||||
@ -4248,7 +4248,13 @@ smb2_async_readv(struct cifs_readdata *rdata)
|
||||
if (rdata->credits.value > 0) {
|
||||
shdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes,
|
||||
SMB2_MAX_BUFFER_SIZE));
|
||||
shdr->CreditRequest = cpu_to_le16(le16_to_cpu(shdr->CreditCharge) + 8);
|
||||
credit_request = le16_to_cpu(shdr->CreditCharge) + 8;
|
||||
if (server->credits >= server->max_credits)
|
||||
shdr->CreditRequest = cpu_to_le16(0);
|
||||
else
|
||||
shdr->CreditRequest = cpu_to_le16(
|
||||
min_t(int, server->max_credits -
|
||||
server->credits, credit_request));
|
||||
|
||||
rc = adjust_credits(server, &rdata->credits, rdata->bytes);
|
||||
if (rc)
|
||||
@ -4456,6 +4462,7 @@ smb2_async_writev(struct cifs_writedata *wdata,
|
||||
struct kvec iov[1];
|
||||
struct smb_rqst rqst = { };
|
||||
unsigned int total_len;
|
||||
int credit_request;
|
||||
|
||||
if (!wdata->server)
|
||||
server = wdata->server = cifs_pick_channel(tcon->ses);
|
||||
@ -4555,7 +4562,13 @@ smb2_async_writev(struct cifs_writedata *wdata,
|
||||
if (wdata->credits.value > 0) {
|
||||
shdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes,
|
||||
SMB2_MAX_BUFFER_SIZE));
|
||||
shdr->CreditRequest = cpu_to_le16(le16_to_cpu(shdr->CreditCharge) + 8);
|
||||
credit_request = le16_to_cpu(shdr->CreditCharge) + 8;
|
||||
if (server->credits >= server->max_credits)
|
||||
shdr->CreditRequest = cpu_to_le16(0);
|
||||
else
|
||||
shdr->CreditRequest = cpu_to_le16(
|
||||
min_t(int, server->max_credits -
|
||||
server->credits, credit_request));
|
||||
|
||||
rc = adjust_credits(server, &wdata->credits, wdata->bytes);
|
||||
if (rc)
|
||||
|
||||
@ -163,7 +163,7 @@ smb2_find_smb_ses_unlocked(struct TCP_Server_Info *server, __u64 ses_id)
|
||||
spin_unlock(&ses->ses_lock);
|
||||
continue;
|
||||
}
|
||||
++ses->ses_count;
|
||||
cifs_smb_ses_inc_refcount(ses);
|
||||
spin_unlock(&ses->ses_lock);
|
||||
return ses;
|
||||
}
|
||||
|
||||
@ -778,9 +778,13 @@ static inline bool should_fault_in_pages(struct iov_iter *i,
|
||||
if (!iter_is_iovec(i))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Try to fault in multiple pages initially. When that doesn't result
|
||||
* in any progress, fall back to a single page.
|
||||
*/
|
||||
size = PAGE_SIZE;
|
||||
offs = offset_in_page(iocb->ki_pos);
|
||||
if (*prev_count != count || !*window_size) {
|
||||
if (*prev_count != count) {
|
||||
size_t nr_dirtied;
|
||||
|
||||
nr_dirtied = max(current->nr_dirtied_pause -
|
||||
@ -862,6 +866,7 @@ static ssize_t gfs2_file_direct_write(struct kiocb *iocb, struct iov_iter *from,
|
||||
struct gfs2_inode *ip = GFS2_I(inode);
|
||||
size_t prev_count = 0, window_size = 0;
|
||||
size_t written = 0;
|
||||
bool enough_retries;
|
||||
ssize_t ret;
|
||||
|
||||
/*
|
||||
@ -904,11 +909,17 @@ retry:
|
||||
if (ret > 0)
|
||||
written = ret;
|
||||
|
||||
enough_retries = prev_count == iov_iter_count(from) &&
|
||||
window_size <= PAGE_SIZE;
|
||||
if (should_fault_in_pages(from, iocb, &prev_count, &window_size)) {
|
||||
gfs2_glock_dq(gh);
|
||||
window_size -= fault_in_iov_iter_readable(from, window_size);
|
||||
if (window_size)
|
||||
goto retry;
|
||||
if (window_size) {
|
||||
if (!enough_retries)
|
||||
goto retry;
|
||||
/* fall back to buffered I/O */
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
out_unlock:
|
||||
if (gfs2_holder_queued(gh))
|
||||
|
||||
@ -871,7 +871,7 @@ retry:
|
||||
goto fail_gunlock4;
|
||||
|
||||
mark_inode_dirty(inode);
|
||||
d_instantiate(dentry, inode);
|
||||
d_instantiate_new(dentry, inode);
|
||||
/* After instantiate, errors should result in evict which will destroy
|
||||
* both inode and iopen glocks properly. */
|
||||
if (file) {
|
||||
@ -883,7 +883,6 @@ retry:
|
||||
gfs2_glock_dq_uninit(&gh);
|
||||
gfs2_glock_put(io_gl);
|
||||
gfs2_qa_put(dip);
|
||||
unlock_new_inode(inode);
|
||||
return error;
|
||||
|
||||
fail_gunlock4:
|
||||
@ -2168,6 +2167,14 @@ static int gfs2_getattr(const struct path *path, struct kstat *stat,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool fault_in_fiemap(struct fiemap_extent_info *fi)
|
||||
{
|
||||
struct fiemap_extent __user *dest = fi->fi_extents_start;
|
||||
size_t size = sizeof(*dest) * fi->fi_extents_max;
|
||||
|
||||
return fault_in_safe_writeable((char __user *)dest, size) == 0;
|
||||
}
|
||||
|
||||
static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
|
||||
u64 start, u64 len)
|
||||
{
|
||||
@ -2177,14 +2184,22 @@ static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
|
||||
|
||||
inode_lock_shared(inode);
|
||||
|
||||
retry:
|
||||
ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
pagefault_disable();
|
||||
ret = iomap_fiemap(inode, fieinfo, start, len, &gfs2_iomap_ops);
|
||||
pagefault_enable();
|
||||
|
||||
gfs2_glock_dq_uninit(&gh);
|
||||
|
||||
if (ret == -EFAULT && fault_in_fiemap(fieinfo)) {
|
||||
fieinfo->fi_extents_mapped = 0;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
out:
|
||||
inode_unlock_shared(inode);
|
||||
return ret;
|
||||
|
||||
@ -1282,6 +1282,9 @@ static enum evict_behavior evict_should_delete(struct inode *inode,
|
||||
struct gfs2_sbd *sdp = sb->s_fs_info;
|
||||
int ret;
|
||||
|
||||
if (inode->i_nlink)
|
||||
return EVICT_SHOULD_SKIP_DELETE;
|
||||
|
||||
if (gfs2_holder_initialized(&ip->i_iopen_gh) &&
|
||||
test_bit(GLF_DEFER_DELETE, &ip->i_iopen_gh.gh_gl->gl_flags))
|
||||
return EVICT_SHOULD_DEFER_DELETE;
|
||||
@ -1301,11 +1304,9 @@ static enum evict_behavior evict_should_delete(struct inode *inode,
|
||||
if (ret)
|
||||
return EVICT_SHOULD_SKIP_DELETE;
|
||||
|
||||
if (test_bit(GLF_INSTANTIATE_NEEDED, &ip->i_gl->gl_flags)) {
|
||||
ret = gfs2_instantiate(gh);
|
||||
if (ret)
|
||||
return EVICT_SHOULD_SKIP_DELETE;
|
||||
}
|
||||
ret = gfs2_instantiate(gh);
|
||||
if (ret)
|
||||
return EVICT_SHOULD_SKIP_DELETE;
|
||||
|
||||
/*
|
||||
* The inode may have been recreated in the meantime.
|
||||
@ -1322,12 +1323,18 @@ static enum evict_behavior evict_should_delete(struct inode *inode,
|
||||
/**
|
||||
* evict_unlinked_inode - delete the pieces of an unlinked evicted inode
|
||||
* @inode: The inode to evict
|
||||
* @gh: The glock holder structure
|
||||
*/
|
||||
static int evict_unlinked_inode(struct inode *inode)
|
||||
static int evict_unlinked_inode(struct inode *inode, struct gfs2_holder *gh)
|
||||
{
|
||||
struct gfs2_inode *ip = GFS2_I(inode);
|
||||
struct gfs2_glock *gl = ip->i_gl;
|
||||
int ret;
|
||||
|
||||
/* The inode glock must be held exclusively and be instantiated. */
|
||||
BUG_ON(!gfs2_holder_initialized(gh) ||
|
||||
test_bit(GLF_INSTANTIATE_NEEDED, &gl->gl_flags));
|
||||
|
||||
if (S_ISDIR(inode->i_mode) &&
|
||||
(ip->i_diskflags & GFS2_DIF_EXHASH)) {
|
||||
ret = gfs2_dir_exhash_dealloc(ip);
|
||||
@ -1347,9 +1354,6 @@ static int evict_unlinked_inode(struct inode *inode)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ip->i_gl)
|
||||
gfs2_inode_remember_delete(ip->i_gl, ip->i_no_formal_ino);
|
||||
|
||||
/*
|
||||
* As soon as we clear the bitmap for the dinode, gfs2_create_inode()
|
||||
* can get called to recreate it, or even gfs2_inode_lookup() if the
|
||||
@ -1363,6 +1367,9 @@ static int evict_unlinked_inode(struct inode *inode)
|
||||
*/
|
||||
|
||||
ret = gfs2_dinode_dealloc(ip);
|
||||
if (!ret)
|
||||
gfs2_inode_remember_delete(gl, ip->i_no_formal_ino);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
@ -1370,33 +1377,40 @@ out:
|
||||
/*
|
||||
* evict_linked_inode - evict an inode whose dinode has not been unlinked
|
||||
* @inode: The inode to evict
|
||||
* @gh: The glock holder structure
|
||||
*/
|
||||
static int evict_linked_inode(struct inode *inode)
|
||||
static int evict_linked_inode(struct inode *inode, struct gfs2_holder *gh)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct gfs2_sbd *sdp = sb->s_fs_info;
|
||||
struct gfs2_inode *ip = GFS2_I(inode);
|
||||
struct address_space *metamapping;
|
||||
struct gfs2_glock *gl = ip->i_gl;
|
||||
struct address_space *metamapping = gfs2_glock2aspace(gl);
|
||||
int ret;
|
||||
|
||||
gfs2_log_flush(sdp, ip->i_gl, GFS2_LOG_HEAD_FLUSH_NORMAL |
|
||||
if (!(test_bit(GLF_DIRTY, &gl->gl_flags) || inode->i_flags & I_DIRTY))
|
||||
goto clean;
|
||||
|
||||
/* The inode glock must be held exclusively and be instantiated. */
|
||||
if (!gfs2_holder_initialized(gh))
|
||||
ret = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, gh);
|
||||
else
|
||||
ret = gfs2_instantiate(gh);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
gfs2_log_flush(sdp, gl, GFS2_LOG_HEAD_FLUSH_NORMAL |
|
||||
GFS2_LFC_EVICT_INODE);
|
||||
metamapping = gfs2_glock2aspace(ip->i_gl);
|
||||
if (test_bit(GLF_DIRTY, &ip->i_gl->gl_flags)) {
|
||||
if (test_bit(GLF_DIRTY, &gl->gl_flags)) {
|
||||
filemap_fdatawrite(metamapping);
|
||||
filemap_fdatawait(metamapping);
|
||||
}
|
||||
write_inode_now(inode, 1);
|
||||
gfs2_ail_flush(ip->i_gl, 0);
|
||||
gfs2_ail_flush(gl, 0);
|
||||
|
||||
ret = gfs2_trans_begin(sdp, 0, sdp->sd_jdesc->jd_blocks);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Needs to be done before glock release & also in a transaction */
|
||||
clean:
|
||||
truncate_inode_pages(&inode->i_data, 0);
|
||||
truncate_inode_pages(metamapping, 0);
|
||||
gfs2_trans_end(sdp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1431,7 +1445,7 @@ static void gfs2_evict_inode(struct inode *inode)
|
||||
int ret;
|
||||
|
||||
gfs2_holder_mark_uninitialized(&gh);
|
||||
if (inode->i_nlink || sb_rdonly(sb) || !ip->i_no_addr)
|
||||
if (sb_rdonly(sb) || !ip->i_no_addr || !ip->i_gl)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
@ -1455,14 +1469,14 @@ static void gfs2_evict_inode(struct inode *inode)
|
||||
behavior = EVICT_SHOULD_SKIP_DELETE;
|
||||
}
|
||||
if (behavior == EVICT_SHOULD_DELETE)
|
||||
ret = evict_unlinked_inode(inode);
|
||||
ret = evict_unlinked_inode(inode, &gh);
|
||||
else
|
||||
ret = evict_linked_inode(inode);
|
||||
ret = evict_linked_inode(inode, &gh);
|
||||
|
||||
if (gfs2_rs_active(&ip->i_res))
|
||||
gfs2_rs_deltree(&ip->i_res);
|
||||
|
||||
if (ret && ret != GLR_TRYFAILED && ret != -EROFS)
|
||||
if (ret && ret != -EROFS)
|
||||
fs_warn(sdp, "gfs2_evict_inode: %d\n", ret);
|
||||
out:
|
||||
if (gfs2_holder_initialized(&gh))
|
||||
|
||||
@ -272,7 +272,7 @@ int unregister_nls(struct nls_table * nls)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct nls_table *find_nls(char *charset)
|
||||
static struct nls_table *find_nls(const char *charset)
|
||||
{
|
||||
struct nls_table *nls;
|
||||
spin_lock(&nls_lock);
|
||||
@ -288,7 +288,7 @@ static struct nls_table *find_nls(char *charset)
|
||||
return nls;
|
||||
}
|
||||
|
||||
struct nls_table *load_nls(char *charset)
|
||||
struct nls_table *load_nls(const char *charset)
|
||||
{
|
||||
return try_then_request_module(find_nls(charset), "nls_%s", charset);
|
||||
}
|
||||
|
||||
@ -3268,7 +3268,7 @@ xfs_alloc_vextent(
|
||||
{
|
||||
xfs_agblock_t agsize; /* allocation group size */
|
||||
int error;
|
||||
int flags; /* XFS_ALLOC_FLAG_... locking flags */
|
||||
int flags = 0; /* XFS_ALLOC_FLAG_... locking flags */
|
||||
struct xfs_mount *mp; /* mount structure pointer */
|
||||
xfs_agnumber_t sagno; /* starting allocation group number */
|
||||
xfs_alloctype_t type; /* input allocation type */
|
||||
@ -3359,9 +3359,10 @@ xfs_alloc_vextent(
|
||||
/*
|
||||
* Start with allocation group given by bno.
|
||||
*/
|
||||
args->agno = XFS_FSB_TO_AGNO(mp, args->fsbno);
|
||||
args->agno = sagno = XFS_FSB_TO_AGNO(mp, args->fsbno);
|
||||
args->type = XFS_ALLOCTYPE_THIS_AG;
|
||||
sagno = minimum_agno;
|
||||
if (args->agno < minimum_agno)
|
||||
args->agno = sagno = minimum_agno;
|
||||
flags = 0;
|
||||
} else {
|
||||
/*
|
||||
@ -3403,15 +3404,15 @@ xfs_alloc_vextent(
|
||||
/*
|
||||
* If we are try-locking, we can't deadlock on AGF
|
||||
* locks, so we can wrap all the way back to the first
|
||||
* AG. Otherwise, wrap back to the start AG so we can't
|
||||
* deadlock, and let the end of scan handler decide what
|
||||
* to do next.
|
||||
* AG. Otherwise, wrap back to the minimum allowable AG
|
||||
* so we can't deadlock, and let the end of scan handler
|
||||
* decide what to do next.
|
||||
*/
|
||||
if (++(args->agno) == mp->m_sb.sb_agcount) {
|
||||
if (flags & XFS_ALLOC_FLAG_TRYLOCK)
|
||||
args->agno = 0;
|
||||
else
|
||||
args->agno = sagno;
|
||||
args->agno = minimum_agno;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3426,17 +3427,19 @@ xfs_alloc_vextent(
|
||||
}
|
||||
|
||||
/*
|
||||
* Blocking pass next, so we must obey minimum
|
||||
* Blocking pass next. Similar to how FIRST_AG
|
||||
* mode is handled above, we must obey minimum
|
||||
* agno constraints to avoid ABBA AGF deadlocks.
|
||||
*/
|
||||
flags = 0;
|
||||
if (minimum_agno > sagno)
|
||||
sagno = minimum_agno;
|
||||
|
||||
if (type == XFS_ALLOCTYPE_START_BNO) {
|
||||
args->agbno = XFS_FSB_TO_AGBNO(mp,
|
||||
args->fsbno);
|
||||
args->type = XFS_ALLOCTYPE_NEAR_BNO;
|
||||
if (sagno < minimum_agno) {
|
||||
args->agno = sagno = minimum_agno;
|
||||
args->type = XFS_ALLOCTYPE_THIS_AG;
|
||||
}
|
||||
}
|
||||
}
|
||||
xfs_perag_put(args->pag);
|
||||
|
||||
@ -315,6 +315,20 @@ struct mmu_gather {
|
||||
|
||||
RH_KABI_FILL_HOLE(unsigned int vma_pfn : 1)
|
||||
|
||||
/*
|
||||
* Did we unshare (unmap) any shared page tables? For now only
|
||||
* used for hugetlb PMD table sharing.
|
||||
*/
|
||||
RH_KABI_FILL_HOLE(unsigned int unshared_tables : 1)
|
||||
|
||||
/*
|
||||
* Did we unshare any page tables such that they are now exclusive
|
||||
* and could get reused+modified by the new owner? When setting this
|
||||
* flag, "unshared_tables" will be set as well. For now only used
|
||||
* for hugetlb PMD table sharing.
|
||||
*/
|
||||
RH_KABI_FILL_HOLE(unsigned int fully_unshared_tables : 1)
|
||||
|
||||
unsigned int batch_count;
|
||||
|
||||
#ifndef CONFIG_MMU_GATHER_NO_GATHER
|
||||
@ -351,6 +365,7 @@ static inline void __tlb_reset_range(struct mmu_gather *tlb)
|
||||
tlb->cleared_pmds = 0;
|
||||
tlb->cleared_puds = 0;
|
||||
tlb->cleared_p4ds = 0;
|
||||
tlb->unshared_tables = 0;
|
||||
/*
|
||||
* Do not reset mmu_gather::vma_* fields here, we do not
|
||||
* call into tlb_start_vma() again to set them if there is an
|
||||
@ -430,7 +445,7 @@ static inline void tlb_flush_mmu_tlbonly(struct mmu_gather *tlb)
|
||||
* these bits.
|
||||
*/
|
||||
if (!(tlb->freed_tables || tlb->cleared_ptes || tlb->cleared_pmds ||
|
||||
tlb->cleared_puds || tlb->cleared_p4ds))
|
||||
tlb->cleared_puds || tlb->cleared_p4ds || tlb->unshared_tables))
|
||||
return;
|
||||
|
||||
tlb_flush(tlb);
|
||||
@ -672,6 +687,62 @@ static inline void tlb_flush_p4d_range(struct mmu_gather *tlb,
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ARCH_WANT_HUGE_PMD_SHARE
|
||||
static inline void tlb_unshare_pmd_pte(struct mmu_gather *tlb, pte_t *ptep,
|
||||
unsigned long addr)
|
||||
{
|
||||
/*
|
||||
* The caller must make sure that concurrent unsharing + exclusive
|
||||
* reuse is impossible until tlb_flush_unshared_tables() was called.
|
||||
*/
|
||||
put_page(virt_to_page(ptep));
|
||||
|
||||
/* Clearing a PUD pointing at a PMD table with PMD leaves. */
|
||||
tlb_flush_pmd_range(tlb, addr & PUD_MASK, PUD_SIZE);
|
||||
|
||||
/*
|
||||
* If the page table is now exclusively owned, we fully unshared
|
||||
* a page table.
|
||||
*/
|
||||
if (page_count(virt_to_page(ptep)) == 1)
|
||||
tlb->fully_unshared_tables = true;
|
||||
tlb->unshared_tables = true;
|
||||
}
|
||||
|
||||
static inline void tlb_flush_unshared_tables(struct mmu_gather *tlb)
|
||||
{
|
||||
/*
|
||||
* As soon as the caller drops locks to allow for reuse of
|
||||
* previously-shared tables, these tables could get modified and
|
||||
* even reused outside of hugetlb context, so we have to make sure that
|
||||
* any page table walkers (incl. TLB, GUP-fast) are aware of that
|
||||
* change.
|
||||
*
|
||||
* Even if we are not fully unsharing a PMD table, we must
|
||||
* flush the TLB for the unsharer now.
|
||||
*/
|
||||
if (tlb->unshared_tables)
|
||||
tlb_flush_mmu_tlbonly(tlb);
|
||||
|
||||
/*
|
||||
* Similarly, we must make sure that concurrent GUP-fast will not
|
||||
* walk previously-shared page tables that are getting modified+reused
|
||||
* elsewhere. So broadcast an IPI to wait for any concurrent GUP-fast.
|
||||
*
|
||||
* We only perform this when we are the last sharer of a page table,
|
||||
* as the IPI will reach all CPUs: any GUP-fast.
|
||||
*
|
||||
* Note that on configs where tlb_remove_table_sync_one() is a NOP,
|
||||
* the expectation is that the tlb_flush_mmu_tlbonly() would have issued
|
||||
* required IPIs already for us.
|
||||
*/
|
||||
if (tlb->fully_unshared_tables) {
|
||||
tlb_remove_table_sync_one();
|
||||
tlb->fully_unshared_tables = false;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_ARCH_WANT_HUGE_PMD_SHARE */
|
||||
|
||||
#endif /* CONFIG_MMU */
|
||||
|
||||
#endif /* _ASM_GENERIC__TLB_H */
|
||||
|
||||
@ -167,8 +167,9 @@ pte_t *huge_pte_alloc(struct mm_struct *mm,
|
||||
unsigned long addr, unsigned long sz);
|
||||
pte_t *huge_pte_offset(struct mm_struct *mm,
|
||||
unsigned long addr, unsigned long sz);
|
||||
int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma,
|
||||
int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma,
|
||||
unsigned long *addr, pte_t *ptep);
|
||||
void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma);
|
||||
void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma,
|
||||
unsigned long *start, unsigned long *end);
|
||||
struct page *follow_huge_addr(struct mm_struct *mm, unsigned long address,
|
||||
@ -207,13 +208,18 @@ static inline struct address_space *hugetlb_page_mapping_lock_write(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int huge_pmd_unshare(struct mm_struct *mm,
|
||||
static inline int huge_pmd_unshare(struct mmu_gather *tlb,
|
||||
struct vm_area_struct *vma,
|
||||
unsigned long *addr, pte_t *ptep)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void huge_pmd_unshare_flush(struct mmu_gather *tlb,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void adjust_range_if_pmd_sharing_possible(
|
||||
struct vm_area_struct *vma,
|
||||
unsigned long *start, unsigned long *end)
|
||||
|
||||
@ -633,6 +633,7 @@ static inline cpumask_t *mm_cpumask(struct mm_struct *mm)
|
||||
struct mmu_gather;
|
||||
extern void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm);
|
||||
extern void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm);
|
||||
extern void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma);
|
||||
extern void tlb_finish_mmu(struct mmu_gather *tlb);
|
||||
|
||||
static inline void init_tlb_flush_pending(struct mm_struct *mm)
|
||||
|
||||
@ -47,7 +47,7 @@ enum utf16_endian {
|
||||
/* nls_base.c */
|
||||
extern int __register_nls(struct nls_table *, struct module *);
|
||||
extern int unregister_nls(struct nls_table *);
|
||||
extern struct nls_table *load_nls(char *);
|
||||
extern struct nls_table *load_nls(const char *charset);
|
||||
extern void unload_nls(struct nls_table *);
|
||||
extern struct nls_table *load_nls_default(void);
|
||||
#define register_nls(nls) __register_nls((nls), THIS_MODULE)
|
||||
|
||||
@ -3764,6 +3764,18 @@ skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer)
|
||||
skb_headlen(skb), buffer);
|
||||
}
|
||||
|
||||
/* Variant of skb_header_pointer() where @offset is user-controlled
|
||||
* and potentially negative.
|
||||
*/
|
||||
static inline void * __must_check
|
||||
skb_header_pointer_careful(const struct sk_buff *skb, int offset,
|
||||
int len, void *buffer)
|
||||
{
|
||||
if (unlikely(offset < 0 && -offset > skb_headroom(skb)))
|
||||
return NULL;
|
||||
return skb_header_pointer(skb, offset, len, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* skb_needs_linearize - check if we need to linearize a given skb
|
||||
* depending on the given device features.
|
||||
|
||||
@ -150,6 +150,7 @@ extern bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check c
|
||||
extern bool arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *regs);
|
||||
extern void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
|
||||
void *src, unsigned long len);
|
||||
extern unsigned long arch_uprobe_get_xol_area(void);
|
||||
#else /* !CONFIG_UPROBES */
|
||||
struct uprobes_state {
|
||||
};
|
||||
|
||||
@ -70,7 +70,7 @@ struct netns_ipv4 {
|
||||
struct sock *mc_autojoin_sk;
|
||||
|
||||
struct inet_peer_base *peers;
|
||||
struct sock * __percpu *tcp_sk;
|
||||
RH_KABI_DEPRECATE(struct sock **, tcp_sk)
|
||||
struct netns_frags frags;
|
||||
#ifdef CONFIG_NETFILTER
|
||||
struct xt_table *iptable_filter;
|
||||
|
||||
@ -250,7 +250,8 @@ struct scsi_device {
|
||||
RH_KABI_USE(4, struct sbitmap *budget_map)
|
||||
|
||||
RH_KABI_USE_SPLIT(5, atomic_t iotmo_cnt) /* partial use */
|
||||
RH_KABI_RESERVE(6)
|
||||
RH_KABI_USE_SPLIT(6, atomic_t ua_new_media_ctr, /* Counter for New Media UNIT ATTENTIONs */
|
||||
atomic_t ua_por_ctr) /* Counter for Power On / Reset UAs */
|
||||
|
||||
unsigned long sdev_data[0];
|
||||
} __attribute__((aligned(sizeof(unsigned long))));
|
||||
@ -623,6 +624,10 @@ static inline int scsi_device_busy(struct scsi_device *sdev)
|
||||
return sbitmap_weight(sdev->budget_map);
|
||||
}
|
||||
|
||||
/* Macros to access the UNIT ATTENTION counters */
|
||||
#define scsi_get_ua_new_media_ctr(sdev) atomic_read(&sdev->ua_new_media_ctr)
|
||||
#define scsi_get_ua_por_ctr(sdev) atomic_read(&sdev->ua_por_ctr)
|
||||
|
||||
#define MODULE_ALIAS_SCSI_DEVICE(type) \
|
||||
MODULE_ALIAS("scsi:t-" __stringify(type) "*")
|
||||
#define SCSI_DEVICE_MODALIAS_FMT "scsi:t-0x%02x"
|
||||
|
||||
@ -1439,6 +1439,12 @@ void uprobe_munmap(struct vm_area_struct *vma, unsigned long start, unsigned lon
|
||||
set_bit(MMF_RECALC_UPROBES, &vma->vm_mm->flags);
|
||||
}
|
||||
|
||||
unsigned long __weak arch_uprobe_get_xol_area(void)
|
||||
{
|
||||
/* Try to map as high as possible, this is only a hint. */
|
||||
return get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE, PAGE_SIZE, 0, 0);
|
||||
}
|
||||
|
||||
/* Slot allocation for XOL */
|
||||
static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
|
||||
{
|
||||
@ -1454,9 +1460,7 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
|
||||
}
|
||||
|
||||
if (!area->vaddr) {
|
||||
/* Try to map as high as possible, this is only a hint. */
|
||||
area->vaddr = get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE,
|
||||
PAGE_SIZE, 0, 0);
|
||||
area->vaddr = arch_uprobe_get_xol_area();
|
||||
if (area->vaddr & ~PAGE_MASK) {
|
||||
ret = area->vaddr;
|
||||
goto fail;
|
||||
|
||||
111
mm/hugetlb.c
111
mm/hugetlb.c
@ -4058,7 +4058,6 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
|
||||
struct hstate *h = hstate_vma(vma);
|
||||
unsigned long sz = huge_page_size(h);
|
||||
struct mmu_notifier_range range;
|
||||
bool force_flush = false;
|
||||
|
||||
WARN_ON(!is_vm_hugetlb_page(vma));
|
||||
BUG_ON(start & ~huge_page_mask(h));
|
||||
@ -4085,10 +4084,8 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
|
||||
continue;
|
||||
|
||||
ptl = huge_pte_lock(h, mm, ptep);
|
||||
if (huge_pmd_unshare(mm, vma, &address, ptep)) {
|
||||
if (huge_pmd_unshare(tlb, vma, &address, ptep)) {
|
||||
spin_unlock(ptl);
|
||||
tlb_flush_pmd_range(tlb, address & PUD_MASK, PUD_SIZE);
|
||||
force_flush = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -4146,21 +4143,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
|
||||
mmu_notifier_invalidate_range_end(&range);
|
||||
tlb_end_vma(tlb, vma);
|
||||
|
||||
/*
|
||||
* If we unshared PMDs, the TLB flush was not recorded in mmu_gather. We
|
||||
* could defer the flush until now, since by holding i_mmap_rwsem we
|
||||
* guaranteed that the last refernece would not be dropped. But we must
|
||||
* do the flushing before we return, as otherwise i_mmap_rwsem will be
|
||||
* dropped and the last reference to the shared PMDs page might be
|
||||
* dropped as well.
|
||||
*
|
||||
* In theory we could defer the freeing of the PMD pages as well, but
|
||||
* huge_pmd_unshare() relies on the exact page_count for the PMD page to
|
||||
* detect sharing, so we cannot defer the release of the page either.
|
||||
* Instead, do flush now.
|
||||
*/
|
||||
if (force_flush)
|
||||
tlb_flush_mmu_tlbonly(tlb);
|
||||
huge_pmd_unshare_flush(tlb, vma);
|
||||
}
|
||||
|
||||
void __unmap_hugepage_range_final(struct mmu_gather *tlb,
|
||||
@ -5233,8 +5216,8 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
|
||||
pte_t pte;
|
||||
struct hstate *h = hstate_vma(vma);
|
||||
unsigned long pages = 0;
|
||||
bool shared_pmd = false;
|
||||
struct mmu_notifier_range range;
|
||||
struct mmu_gather tlb;
|
||||
|
||||
/*
|
||||
* In the case of shared PMDs, the area to flush could be beyond
|
||||
@ -5247,6 +5230,7 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
|
||||
|
||||
BUG_ON(address >= end);
|
||||
flush_cache_range(vma, range.start, range.end);
|
||||
tlb_gather_mmu_vma(&tlb, vma);
|
||||
|
||||
mmu_notifier_invalidate_range_start(&range);
|
||||
i_mmap_lock_write(vma->vm_file->f_mapping);
|
||||
@ -5256,10 +5240,9 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
|
||||
if (!ptep)
|
||||
continue;
|
||||
ptl = huge_pte_lock(h, mm, ptep);
|
||||
if (huge_pmd_unshare(mm, vma, &address, ptep)) {
|
||||
if (huge_pmd_unshare(&tlb, vma, &address, ptep)) {
|
||||
pages++;
|
||||
spin_unlock(ptl);
|
||||
shared_pmd = true;
|
||||
continue;
|
||||
}
|
||||
pte = huge_ptep_get(ptep);
|
||||
@ -5289,21 +5272,14 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
|
||||
pte = pte_mkhuge(huge_pte_modify(old_pte, newprot));
|
||||
pte = arch_make_huge_pte(pte, vma, NULL, 0);
|
||||
huge_ptep_modify_prot_commit(vma, address, ptep, old_pte, pte);
|
||||
tlb_remove_huge_tlb_entry(h, &tlb, ptep, address);
|
||||
pages++;
|
||||
}
|
||||
spin_unlock(ptl);
|
||||
}
|
||||
/*
|
||||
* Must flush TLB before releasing i_mmap_rwsem: x86's huge_pmd_unshare
|
||||
* may have cleared our pud entry and done put_page on the page table:
|
||||
* once we release i_mmap_rwsem, another task can do the final put_page
|
||||
* and that page table be reused and filled with junk. If we actually
|
||||
* did unshare a page of pmds, flush the range corresponding to the pud.
|
||||
*/
|
||||
if (shared_pmd)
|
||||
flush_hugetlb_tlb_range(vma, range.start, range.end);
|
||||
else
|
||||
flush_hugetlb_tlb_range(vma, start, end);
|
||||
|
||||
tlb_flush_mmu_tlbonly(&tlb);
|
||||
huge_pmd_unshare_flush(&tlb, vma);
|
||||
/*
|
||||
* No need to call mmu_notifier_invalidate_range() we are downgrading
|
||||
* page table protection not changing it to point to a new page.
|
||||
@ -5312,6 +5288,7 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,
|
||||
*/
|
||||
i_mmap_unlock_write(vma->vm_file->f_mapping);
|
||||
mmu_notifier_invalidate_range_end(&range);
|
||||
tlb_finish_mmu(&tlb);
|
||||
|
||||
return pages << h->order;
|
||||
}
|
||||
@ -5639,20 +5616,28 @@ out:
|
||||
}
|
||||
|
||||
/*
|
||||
* unmap huge page backed by shared pte.
|
||||
* huge_pmd_unshare - Unmap a pmd table if it is shared by multiple users
|
||||
* @tlb: the current mmu_gather.
|
||||
* @vma: the vma covering the pmd table.
|
||||
* @addr: the address we are trying to unshare.
|
||||
* @ptep: pointer into the (pmd) page table.
|
||||
*
|
||||
* Called with page table lock held and i_mmap_rwsem held in write mode.
|
||||
*
|
||||
* Note: The caller must call huge_pmd_unshare_flush() before dropping the
|
||||
* i_mmap_rwsem.
|
||||
*
|
||||
* Hugetlb pte page is ref counted at the time of mapping. If pte is shared
|
||||
* indicated by page_count > 1, unmap is achieved by clearing pud and
|
||||
* decrementing the ref count. If count == 1, the pte page is not shared.
|
||||
*
|
||||
* Called with page table lock held and i_mmap_rwsem held in write mode.
|
||||
*
|
||||
* returns: 1 successfully unmapped a shared pte page
|
||||
* 0 the underlying pte page is not shared, or it is the last user
|
||||
* Returns: 1 if it was a shared PMD table and it got unmapped, or 0 if it
|
||||
* was not a shared PMD table.
|
||||
*/
|
||||
int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma,
|
||||
unsigned long *addr, pte_t *ptep)
|
||||
int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma,
|
||||
unsigned long *addr, pte_t *ptep)
|
||||
{
|
||||
struct mm_struct *mm = vma->vm_mm;
|
||||
pgd_t *pgd = pgd_offset(mm, *addr);
|
||||
p4d_t *p4d = p4d_offset(pgd, *addr);
|
||||
pud_t *pud = pud_offset(p4d, *addr);
|
||||
@ -5663,20 +5648,8 @@ int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma,
|
||||
return 0;
|
||||
|
||||
pud_clear(pud);
|
||||
/*
|
||||
* Once our caller drops the rmap lock, some other process might be
|
||||
* using this page table as a normal, non-hugetlb page table.
|
||||
* Wait for pending gup_fast() in other threads to finish before letting
|
||||
* that happen.
|
||||
*
|
||||
* RHEL-120391: some customers reported severe interference/performance
|
||||
* degradation on particular database workloads, thus we are including
|
||||
* a waiving flag to allow for disabling this CVE mitigation
|
||||
*/
|
||||
if (likely(!is_rh_waived(CVE_2025_38085)))
|
||||
tlb_remove_table_sync_one();
|
||||
tlb_unshare_pmd_pte(tlb, ptep, *addr);
|
||||
|
||||
put_page(virt_to_page(ptep));
|
||||
mm_dec_nr_pmds(mm);
|
||||
/*
|
||||
* This update of passed address optimizes loops sequentially
|
||||
@ -5688,6 +5661,30 @@ int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma,
|
||||
*addr |= PUD_SIZE - PMD_SIZE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* huge_pmd_unshare_flush - Complete a sequence of huge_pmd_unshare() calls
|
||||
* @tlb: the current mmu_gather.
|
||||
* @vma: the vma covering the pmd table.
|
||||
*
|
||||
* Perform necessary TLB flushes or IPI broadcasts to synchronize PMD table
|
||||
* unsharing with concurrent page table walkers.
|
||||
*
|
||||
* This function must be called after a sequence of huge_pmd_unshare()
|
||||
* calls while still holding the i_mmap_rwsem.
|
||||
*/
|
||||
void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma)
|
||||
{
|
||||
/*
|
||||
* We must synchronize page table unsharing such that nobody will
|
||||
* try reusing a previously-shared page table while it might still
|
||||
* be in use by previous sharers (TLB, GUP_fast).
|
||||
*/
|
||||
i_mmap_assert_write_locked(vma->vm_file->f_mapping);
|
||||
|
||||
tlb_flush_unshared_tables(tlb);
|
||||
}
|
||||
|
||||
#define want_pmd_share() (1)
|
||||
#else /* !CONFIG_ARCH_WANT_HUGE_PMD_SHARE */
|
||||
pte_t *huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud)
|
||||
@ -5695,12 +5692,16 @@ pte_t *huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma,
|
||||
unsigned long *addr, pte_t *ptep)
|
||||
int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma,
|
||||
unsigned long *addr, pte_t *ptep)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma)
|
||||
{
|
||||
}
|
||||
|
||||
void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma,
|
||||
unsigned long *start, unsigned long *end)
|
||||
{
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/swap.h>
|
||||
#include <linux/hugetlb.h>
|
||||
|
||||
#include <asm/pgalloc.h>
|
||||
#include <asm/tlb.h>
|
||||
@ -277,6 +278,7 @@ static void __tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm,
|
||||
tlb->page_size = 0;
|
||||
#endif
|
||||
|
||||
tlb->fully_unshared_tables = 0;
|
||||
__tlb_reset_range(tlb);
|
||||
inc_tlb_flush_pending(tlb->mm);
|
||||
}
|
||||
@ -291,6 +293,30 @@ void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm)
|
||||
__tlb_gather_mmu(tlb, mm, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* tlb_gather_mmu_vma - initialize an mmu_gather structure for operating on a
|
||||
* single VMA
|
||||
* @tlb: the mmu_gather structure to initialize
|
||||
* @vma: the vm_area_struct
|
||||
*
|
||||
* Called to initialize an (on-stack) mmu_gather structure for operating on
|
||||
* a single VMA. In contrast to tlb_gather_mmu(), calling this function will
|
||||
* not require another call to tlb_start_vma(). In contrast to tlb_start_vma(),
|
||||
* this function will *not* call flush_cache_range().
|
||||
*
|
||||
* For hugetlb VMAs, this function will also initialize the mmu_gather
|
||||
* page_size accordingly, not requiring a separate call to
|
||||
* tlb_change_page_size().
|
||||
*/
|
||||
void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma)
|
||||
{
|
||||
tlb_gather_mmu(tlb, vma->vm_mm);
|
||||
tlb_update_vma_flags(tlb, vma);
|
||||
if (is_vm_hugetlb_page(vma))
|
||||
/* All entries have the same size. */
|
||||
tlb_change_page_size(tlb, huge_page_size(hstate_vma(vma)));
|
||||
}
|
||||
|
||||
/**
|
||||
* tlb_finish_mmu - finish an mmu_gather structure
|
||||
* @tlb: the mmu_gather structure to finish
|
||||
@ -300,6 +326,12 @@ void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm)
|
||||
*/
|
||||
void tlb_finish_mmu(struct mmu_gather *tlb)
|
||||
{
|
||||
/*
|
||||
* We expect an earlier huge_pmd_unshare_flush() call to sort this out,
|
||||
* due to complicated locking requirements with page table unsharing.
|
||||
*/
|
||||
VM_WARN_ON_ONCE(tlb->fully_unshared_tables);
|
||||
|
||||
/*
|
||||
* If there are parallel threads are doing PTE changes on same range
|
||||
* under non-exclusive lock (e.g., mmap_lock read-side) but defer TLB
|
||||
|
||||
12
mm/rmap.c
12
mm/rmap.c
@ -72,7 +72,7 @@
|
||||
#include <linux/memremap.h>
|
||||
#include <linux/userfaultfd_k.h>
|
||||
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/tlb.h>
|
||||
|
||||
#include <trace/events/tlb.h>
|
||||
|
||||
@ -1519,13 +1519,17 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
|
||||
address = pvmw.address;
|
||||
|
||||
if (PageHuge(page) && !PageAnon(page)) {
|
||||
struct mmu_gather tlb;
|
||||
|
||||
/*
|
||||
* To call huge_pmd_unshare, i_mmap_rwsem must be
|
||||
* held in write mode. Caller needs to explicitly
|
||||
* do this outside rmap routines.
|
||||
*/
|
||||
VM_BUG_ON(!(flags & TTU_RMAP_LOCKED));
|
||||
if (huge_pmd_unshare(mm, vma, &address, pvmw.pte)) {
|
||||
|
||||
tlb_gather_mmu_vma(&tlb, vma);
|
||||
if (huge_pmd_unshare(&tlb, vma, &address, pvmw.pte)) {
|
||||
/*
|
||||
* huge_pmd_unshare unmapped an entire PMD
|
||||
* page. There is no way of knowing exactly
|
||||
@ -1534,7 +1538,8 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
|
||||
* already adjusted above to cover this range.
|
||||
*/
|
||||
flush_cache_range(vma, range.start, range.end);
|
||||
flush_tlb_range(vma, range.start, range.end);
|
||||
huge_pmd_unshare_flush(&tlb, vma);
|
||||
tlb_finish_mmu(&tlb);
|
||||
mmu_notifier_invalidate_range(mm, range.start,
|
||||
range.end);
|
||||
|
||||
@ -1550,6 +1555,7 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
|
||||
page_vma_mapped_walk_done(&pvmw);
|
||||
break;
|
||||
}
|
||||
tlb_finish_mmu(&tlb);
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_MIGRATION) &&
|
||||
|
||||
@ -123,6 +123,7 @@ static unsigned char bus_mac[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
/* Device structures */
|
||||
static struct net_device *dev_lec[MAX_LEC_ITF];
|
||||
static DEFINE_MUTEX(lec_mutex);
|
||||
|
||||
#if IS_ENABLED(CONFIG_BRIDGE)
|
||||
static void lec_handle_bridge(struct sk_buff *skb, struct net_device *dev)
|
||||
@ -686,6 +687,7 @@ static int lec_vcc_attach(struct atm_vcc *vcc, void __user *arg)
|
||||
int bytes_left;
|
||||
struct atmlec_ioc ioc_data;
|
||||
|
||||
lockdep_assert_held(&lec_mutex);
|
||||
/* Lecd must be up in this case */
|
||||
bytes_left = copy_from_user(&ioc_data, arg, sizeof(struct atmlec_ioc));
|
||||
if (bytes_left != 0)
|
||||
@ -711,6 +713,7 @@ static int lec_vcc_attach(struct atm_vcc *vcc, void __user *arg)
|
||||
|
||||
static int lec_mcast_attach(struct atm_vcc *vcc, int arg)
|
||||
{
|
||||
lockdep_assert_held(&lec_mutex);
|
||||
if (arg < 0 || arg >= MAX_LEC_ITF)
|
||||
return -EINVAL;
|
||||
arg = array_index_nospec(arg, MAX_LEC_ITF);
|
||||
@ -726,6 +729,7 @@ static int lecd_attach(struct atm_vcc *vcc, int arg)
|
||||
int i;
|
||||
struct lec_priv *priv;
|
||||
|
||||
lockdep_assert_held(&lec_mutex);
|
||||
if (arg < 0)
|
||||
i = 0;
|
||||
else
|
||||
@ -745,6 +749,7 @@ static int lecd_attach(struct atm_vcc *vcc, int arg)
|
||||
snprintf(dev_lec[i]->name, IFNAMSIZ, "lec%d", i);
|
||||
if (register_netdev(dev_lec[i])) {
|
||||
free_netdev(dev_lec[i]);
|
||||
dev_lec[i] = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -912,7 +917,6 @@ static void *lec_itf_walk(struct lec_state *state, loff_t *l)
|
||||
v = (dev && netdev_priv(dev)) ?
|
||||
lec_priv_walk(state, l, netdev_priv(dev)) : NULL;
|
||||
if (!v && dev) {
|
||||
dev_put(dev);
|
||||
/* Partial state reset for the next time we get called */
|
||||
dev = NULL;
|
||||
}
|
||||
@ -936,6 +940,7 @@ static void *lec_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
{
|
||||
struct lec_state *state = seq->private;
|
||||
|
||||
mutex_lock(&lec_mutex);
|
||||
state->itf = 0;
|
||||
state->dev = NULL;
|
||||
state->locked = NULL;
|
||||
@ -953,8 +958,9 @@ static void lec_seq_stop(struct seq_file *seq, void *v)
|
||||
if (state->dev) {
|
||||
spin_unlock_irqrestore(&state->locked->lec_arp_lock,
|
||||
state->flags);
|
||||
dev_put(state->dev);
|
||||
state->dev = NULL;
|
||||
}
|
||||
mutex_unlock(&lec_mutex);
|
||||
}
|
||||
|
||||
static void *lec_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
||||
@ -1012,6 +1018,7 @@ static int lane_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
mutex_lock(&lec_mutex);
|
||||
switch (cmd) {
|
||||
case ATMLEC_CTRL:
|
||||
err = lecd_attach(vcc, (int)arg);
|
||||
@ -1026,6 +1033,7 @@ static int lane_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&lec_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@ -4076,6 +4076,12 @@ int tcp_abort(struct sock *sk, int err)
|
||||
/* Don't race with userspace socket closes such as tcp_close. */
|
||||
lock_sock(sk);
|
||||
|
||||
/* Avoid closing the same socket twice. */
|
||||
if (sk->sk_state == TCP_CLOSE) {
|
||||
release_sock(sk);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (sk->sk_state == TCP_LISTEN) {
|
||||
tcp_set_state(sk, TCP_CLOSE);
|
||||
inet_csk_listen_stop(sk);
|
||||
@ -4085,19 +4091,16 @@ int tcp_abort(struct sock *sk, int err)
|
||||
local_bh_disable();
|
||||
bh_lock_sock(sk);
|
||||
|
||||
if (!sock_flag(sk, SOCK_DEAD)) {
|
||||
sk->sk_err = err;
|
||||
/* This barrier is coupled with smp_rmb() in tcp_poll() */
|
||||
smp_wmb();
|
||||
sk_error_report(sk);
|
||||
if (tcp_need_reset(sk->sk_state))
|
||||
tcp_send_active_reset(sk, GFP_ATOMIC);
|
||||
tcp_done(sk);
|
||||
}
|
||||
sk->sk_err = err;
|
||||
/* This barrier is coupled with smp_rmb() in tcp_poll() */
|
||||
smp_wmb();
|
||||
sk_error_report(sk);
|
||||
if (tcp_need_reset(sk->sk_state))
|
||||
tcp_send_active_reset(sk, GFP_ATOMIC);
|
||||
tcp_done(sk);
|
||||
|
||||
bh_unlock_sock(sk);
|
||||
local_bh_enable();
|
||||
tcp_write_queue_purge(sk);
|
||||
release_sock(sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -96,6 +96,8 @@ static int tcp_v4_md5_hash_hdr(char *md5_hash, const struct tcp_md5sig_key *key,
|
||||
struct inet_hashinfo tcp_hashinfo;
|
||||
EXPORT_SYMBOL(tcp_hashinfo);
|
||||
|
||||
static DEFINE_PER_CPU(struct sock *, ipv4_tcp_sk);
|
||||
|
||||
static u32 tcp_v4_init_seq(const struct sk_buff *skb)
|
||||
{
|
||||
return secure_tcp_seq(ip_hdr(skb)->daddr,
|
||||
@ -802,7 +804,8 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
arg.tos = ip_hdr(skb)->tos;
|
||||
arg.uid = sock_net_uid(net, sk && sk_fullsock(sk) ? sk : NULL);
|
||||
local_bh_disable();
|
||||
ctl_sk = *this_cpu_ptr(net->ipv4.tcp_sk);
|
||||
ctl_sk = this_cpu_read(ipv4_tcp_sk);
|
||||
sock_net_set(ctl_sk, net);
|
||||
if (sk) {
|
||||
ctl_sk->sk_mark = (sk->sk_state == TCP_TIME_WAIT) ?
|
||||
inet_twsk(sk)->tw_mark : sk->sk_mark;
|
||||
@ -816,6 +819,7 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
txhash);
|
||||
|
||||
ctl_sk->sk_mark = 0;
|
||||
sock_net_set(ctl_sk, &init_net);
|
||||
__TCP_INC_STATS(net, TCP_MIB_OUTSEGS);
|
||||
__TCP_INC_STATS(net, TCP_MIB_OUTRSTS);
|
||||
local_bh_enable();
|
||||
@ -898,7 +902,8 @@ static void tcp_v4_send_ack(const struct sock *sk,
|
||||
arg.tos = tos;
|
||||
arg.uid = sock_net_uid(net, sk_fullsock(sk) ? sk : NULL);
|
||||
local_bh_disable();
|
||||
ctl_sk = *this_cpu_ptr(net->ipv4.tcp_sk);
|
||||
ctl_sk = this_cpu_read(ipv4_tcp_sk);
|
||||
sock_net_set(ctl_sk, net);
|
||||
if (sk)
|
||||
ctl_sk->sk_mark = (sk->sk_state == TCP_TIME_WAIT) ?
|
||||
inet_twsk(sk)->tw_mark : sk->sk_mark;
|
||||
@ -909,6 +914,7 @@ static void tcp_v4_send_ack(const struct sock *sk,
|
||||
txhash);
|
||||
|
||||
ctl_sk->sk_mark = 0;
|
||||
sock_net_set(ctl_sk, &init_net);
|
||||
__TCP_INC_STATS(net, TCP_MIB_OUTSEGS);
|
||||
local_bh_enable();
|
||||
}
|
||||
@ -2740,41 +2746,14 @@ EXPORT_SYMBOL(tcp_prot);
|
||||
|
||||
static void __net_exit tcp_sk_exit(struct net *net)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
if (net->ipv4.tcp_congestion_control)
|
||||
bpf_module_put(net->ipv4.tcp_congestion_control,
|
||||
net->ipv4.tcp_congestion_control->owner);
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
inet_ctl_sock_destroy(*per_cpu_ptr(net->ipv4.tcp_sk, cpu));
|
||||
free_percpu(net->ipv4.tcp_sk);
|
||||
}
|
||||
|
||||
static int __net_init tcp_sk_init(struct net *net)
|
||||
{
|
||||
int res, cpu, cnt;
|
||||
|
||||
net->ipv4.tcp_sk = alloc_percpu(struct sock *);
|
||||
if (!net->ipv4.tcp_sk)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct sock *sk;
|
||||
|
||||
res = inet_ctl_sock_create(&sk, PF_INET, SOCK_RAW,
|
||||
IPPROTO_TCP, net);
|
||||
if (res)
|
||||
goto fail;
|
||||
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
|
||||
|
||||
/* Please enforce IP_DF and IPID==0 for RST and
|
||||
* ACK sent in SYN-RECV and TIME-WAIT state.
|
||||
*/
|
||||
inet_sk(sk)->pmtudisc = IP_PMTUDISC_DO;
|
||||
|
||||
*per_cpu_ptr(net->ipv4.tcp_sk, cpu) = sk;
|
||||
}
|
||||
int cnt;
|
||||
|
||||
net->ipv4.sysctl_tcp_ecn = 2;
|
||||
net->ipv4.sysctl_tcp_ecn_fallback = 1;
|
||||
@ -2858,10 +2837,6 @@ static int __net_init tcp_sk_init(struct net *net)
|
||||
net->ipv4_sysctl_tcp_shrink_window = 0;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
tcp_sk_exit(net);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void __net_exit tcp_sk_exit_batch(struct list_head *net_exit_list)
|
||||
@ -2938,6 +2913,24 @@ static void __init bpf_iter_register(void)
|
||||
|
||||
void __init tcp_v4_init(void)
|
||||
{
|
||||
int cpu, res;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct sock *sk;
|
||||
|
||||
res = inet_ctl_sock_create(&sk, PF_INET, SOCK_RAW,
|
||||
IPPROTO_TCP, &init_net);
|
||||
if (res)
|
||||
panic("Failed to create the TCP control socket.\n");
|
||||
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
|
||||
|
||||
/* Please enforce IP_DF and IPID==0 for RST and
|
||||
* ACK sent in SYN-RECV and TIME-WAIT state.
|
||||
*/
|
||||
inet_sk(sk)->pmtudisc = IP_PMTUDISC_DO;
|
||||
|
||||
per_cpu(ipv4_tcp_sk, cpu) = sk;
|
||||
}
|
||||
if (register_pernet_subsys(&tcp_sk_ops))
|
||||
panic("Failed to create the TCP control socket.\n");
|
||||
|
||||
|
||||
@ -155,10 +155,8 @@ next_knode:
|
||||
int toff = off + key->off + (off2 & key->offmask);
|
||||
__be32 *data, hdata;
|
||||
|
||||
if (skb_headroom(skb) + toff > INT_MAX)
|
||||
goto out;
|
||||
|
||||
data = skb_header_pointer(skb, toff, 4, &hdata);
|
||||
data = skb_header_pointer_careful(skb, toff, 4,
|
||||
&hdata);
|
||||
if (!data)
|
||||
goto out;
|
||||
if ((*data ^ key->val) & key->mask) {
|
||||
@ -208,8 +206,9 @@ check_terminal:
|
||||
if (ht->divisor) {
|
||||
__be32 *data, hdata;
|
||||
|
||||
data = skb_header_pointer(skb, off + n->sel.hoff, 4,
|
||||
&hdata);
|
||||
data = skb_header_pointer_careful(skb,
|
||||
off + n->sel.hoff,
|
||||
4, &hdata);
|
||||
if (!data)
|
||||
goto out;
|
||||
sel = ht->divisor & u32_hash_fold(*data, &n->sel,
|
||||
@ -223,7 +222,7 @@ check_terminal:
|
||||
if (n->sel.flags & TC_U32_VAROFFSET) {
|
||||
__be16 *data, hdata;
|
||||
|
||||
data = skb_header_pointer(skb,
|
||||
data = skb_header_pointer_careful(skb,
|
||||
off + n->sel.offoff,
|
||||
2, &hdata);
|
||||
if (!data)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user