diff options
Diffstat (limited to 'fs/smb/client/smb2inode.c')
| -rw-r--r-- | fs/smb/client/smb2inode.c | 750 |
1 files changed, 475 insertions, 275 deletions
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 826b57a5a2a8..6c9c229b91f6 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -13,7 +13,6 @@ #include <linux/pagemap.h> #include <asm/div64.h> #include "cifsfs.h" -#include "cifspdu.h" #include "cifsglob.h" #include "cifsproto.h" #include "cifs_debug.h" @@ -21,26 +20,30 @@ #include "cifs_unicode.h" #include "fscache.h" #include "smb2glob.h" -#include "smb2pdu.h" #include "smb2proto.h" #include "cached_dir.h" #include "../common/smb2status.h" +#include "../common/smbfsctl.h" static struct reparse_data_buffer *reparse_buf_ptr(struct kvec *iov) { struct reparse_data_buffer *buf; struct smb2_ioctl_rsp *io = iov->iov_base; u32 off, count, len; + u16 rdlen; count = le32_to_cpu(io->OutputCount); off = le32_to_cpu(io->OutputOffset); if (check_add_overflow(off, count, &len) || len > iov->iov_len) - return ERR_PTR(-EIO); + return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_overlong, + off, count)); buf = (struct reparse_data_buffer *)((u8 *)io + off); len = sizeof(*buf); - if (count < len || count < le16_to_cpu(buf->ReparseDataLength) + len) - return ERR_PTR(-EIO); + rdlen = le16_to_cpu(buf->ReparseDataLength); + + if (count < len || count < rdlen + len) + return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, rdlen)); return buf; } @@ -50,7 +53,7 @@ static inline __u32 file_create_options(struct dentry *dentry) if (dentry) { ci = CIFS_I(d_inode(dentry)); - if (ci->cifsAttrs & ATTR_REPARSE) + if (ci->cifsAttrs & ATTR_REPARSE_POINT) return OPEN_REPARSE_POINT; } return 0; @@ -108,7 +111,7 @@ static int check_wsl_eas(struct kvec *rsp_iov) u32 outlen, next; u16 vlen; u8 nlen; - u8 *end; + u8 *ea_end, *iov_end; outlen = le32_to_cpu(rsp->OutputBufferLength); if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || @@ -117,15 +120,19 @@ static int check_wsl_eas(struct kvec *rsp_iov) ea = (void *)((u8 *)rsp_iov->iov_base + le16_to_cpu(rsp->OutputBufferOffset)); - end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + ea_end = (u8 *)ea + outlen; + iov_end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + if (ea_end > iov_end) + return -EINVAL; + for (;;) { - if ((u8 *)ea > end - sizeof(*ea)) + if ((u8 *)ea > ea_end - sizeof(*ea)) return -EINVAL; nlen = ea->ea_name_length; vlen = le16_to_cpu(ea->ea_value_length); if (nlen != SMB2_WSL_XATTR_NAME_LEN || - (u8 *)ea + nlen + 1 + vlen > end) + (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) return -EINVAL; switch (vlen) { @@ -162,6 +169,27 @@ static int check_wsl_eas(struct kvec *rsp_iov) } /* + * If @cfile is NULL, then need to account for trailing CLOSE request in the + * compound chain. + */ +static void set_next_compound(struct cifs_tcon *tcon, + struct cifsFileInfo *cfile, + int i, int num_cmds, + struct smb_rqst *rqst, int *num_rqst) +{ + int k = !cfile ? 1 : 0; + + if (i + 1 < num_cmds + k) + smb2_set_next_command(tcon, &rqst[*num_rqst]); + if (i + k > 0) + smb2_set_related(&rqst[*num_rqst]); + (*num_rqst)++; +} + +#define COMP_PID(cfile) ((cfile) ? (cfile)->fid.persistent_fid : COMPOUND_FID) +#define COMP_VID(cfile) ((cfile) ? (cfile)->fid.volatile_fid : COMPOUND_FID) + +/* * note: If cfile is passed, the reference to it is dropped here. * So make sure that you do not reuse cfile after return from this func. * @@ -176,6 +204,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, struct kvec *out_iov, int *out_buftype, struct dentry *dentry) { + struct smb2_create_rsp *create_rsp = NULL; struct smb2_query_info_rsp *qi_rsp = NULL; struct smb2_compound_vars *vars = NULL; __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; @@ -184,7 +213,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, struct reparse_data_buffer *rbuf; struct TCP_Server_Info *server; int resp_buftype[MAX_COMPOUND]; - int retries = 0, cur_sleep = 1; + int retries = 0, cur_sleep = 0; __u8 delete_pending[8] = {1,}; struct kvec *rsp_iov, *iov; struct inode *inode = NULL; @@ -205,9 +234,11 @@ replay_again: num_rqst = 0; server = cifs_pick_channel(ses); - vars = kzalloc(sizeof(*vars), GFP_ATOMIC); - if (vars == NULL) - return -ENOMEM; + vars = kzalloc_obj(*vars, GFP_KERNEL); + if (vars == NULL) { + rc = -ENOMEM; + goto out; + } rqst = &vars->rqst[0]; rsp_iov = &vars->rsp_iov[0]; @@ -265,39 +296,29 @@ replay_again: num_rqst++; rc = 0; - for (i = 0; i < num_cmds; i++) { + i = 0; + + /* Skip the leading explicit OPEN operation */ + if (num_cmds > 0 && cmds[0] == SMB2_OP_OPEN_QUERY) + i++; + + for (; i < num_cmds; i++) { /* Operation */ switch (cmds[i]) { case SMB2_OP_QUERY_INFO: rqst[num_rqst].rq_iov = &vars->qi_iov; rqst[num_rqst].rq_nvec = 1; - if (cfile) { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - FILE_ALL_INFORMATION, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb2_file_all_info) + - PATH_MAX * 2, 0, NULL); - } else { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - FILE_ALL_INFORMATION, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb2_file_all_info) + - PATH_MAX * 2, 0, NULL); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + FILE_ALL_INFORMATION, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb2_file_all_info) + + PATH_MAX * 2, 0, NULL); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_query_info_compound_enter(xid, tcon->tid, ses->Suid, full_path); break; @@ -305,41 +326,21 @@ replay_again: rqst[num_rqst].rq_iov = &vars->qi_iov; rqst[num_rqst].rq_nvec = 1; - if (cfile) { - /* TBD: fix following to allow for longer SIDs */ - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - SMB_FIND_FILE_POSIX_INFO, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb311_posix_qinfo *) + - (PATH_MAX * 2) + - (sizeof(struct smb_sid) * 2), 0, NULL); - } else { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - SMB_FIND_FILE_POSIX_INFO, - SMB2_O_INFO_FILE, 0, - sizeof(struct smb311_posix_qinfo *) + - (PATH_MAX * 2) + - (sizeof(struct smb_sid) * 2), 0, NULL); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + /* TBD: fix following to allow for longer SIDs */ + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + SMB_FIND_FILE_POSIX_INFO, + SMB2_O_INFO_FILE, 0, + sizeof(struct smb311_posix_qinfo) + + (PATH_MAX * 2) + + (sizeof(struct smb_sid) * 2), 0, NULL); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_posix_query_info_compound_enter(xid, tcon->tid, ses->Suid, full_path); break; - case SMB2_OP_DELETE: - trace_smb3_delete_enter(xid, tcon->tid, ses->Suid, full_path); - break; case SMB2_OP_MKDIR: /* * Directories are created through parameters in the @@ -347,23 +348,23 @@ replay_again: */ trace_smb3_mkdir_enter(xid, tcon->tid, ses->Suid, full_path); break; - case SMB2_OP_RMDIR: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; + case SMB2_OP_UNLINK: + rqst[num_rqst].rq_iov = vars->unlink_iov; rqst[num_rqst].rq_nvec = 1; size[0] = 1; /* sizeof __u8 See MS-FSCC section 2.4.11 */ data[0] = &delete_pending[0]; rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], COMPOUND_FID, - COMPOUND_FID, current->tgid, - FILE_DISPOSITION_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + current->tgid, FILE_DISPOSITION_INFORMATION, + SMB2_O_INFO_FILE, 0, + data, size); if (rc) goto finished; - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst++]); - trace_smb3_rmdir_enter(xid, tcon->tid, ses->Suid, full_path); + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); + trace_smb3_unlink_enter(xid, tcon->tid, ses->Suid, full_path); break; case SMB2_OP_SET_EOF: rqst[num_rqst].rq_iov = &vars->si_iov[0]; @@ -372,32 +373,15 @@ replay_again: size[0] = in_iov[i].iov_len; data[0] = in_iov[i].iov_base; - if (cfile) { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - current->tgid, - FILE_END_OF_FILE_INFORMATION, - SMB2_O_INFO_FILE, 0, - data, size); - } else { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - current->tgid, - FILE_END_OF_FILE_INFORMATION, - SMB2_O_INFO_FILE, 0, - data, size); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + current->tgid, FILE_END_OF_FILE_INFORMATION, + SMB2_O_INFO_FILE, 0, + data, size); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_set_eof_enter(xid, tcon->tid, ses->Suid, full_path); break; case SMB2_OP_SET_INFO: @@ -407,33 +391,19 @@ replay_again: size[0] = in_iov[i].iov_len; data[0] = in_iov[i].iov_base; - if (cfile) { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, current->tgid, - FILE_BASIC_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - } else { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, current->tgid, - FILE_BASIC_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + current->tgid, FILE_BASIC_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_set_info_compound_enter(xid, tcon->tid, ses->Suid, full_path); break; case SMB2_OP_RENAME: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_iov = vars->rename_iov; rqst[num_rqst].rq_nvec = 2; len = in_iov[i].iov_len; @@ -448,31 +418,19 @@ replay_again: size[1] = len + 2 /* null */; data[1] = in_iov[i].iov_base; - if (cfile) { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - current->tgid, FILE_RENAME_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - } else { - rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, COMPOUND_FID, - current->tgid, FILE_RENAME_INFORMATION, - SMB2_O_INFO_FILE, 0, data, size); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_set_info_init(tcon, server, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + current->tgid, FILE_RENAME_INFORMATION, + SMB2_O_INFO_FILE, 0, data, size); + + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_rename_enter(xid, tcon->tid, ses->Suid, full_path); break; case SMB2_OP_HARDLINK: - rqst[num_rqst].rq_iov = &vars->si_iov[0]; + rqst[num_rqst].rq_iov = vars->hl_iov; rqst[num_rqst].rq_nvec = 2; len = in_iov[i].iov_len; @@ -488,41 +446,27 @@ replay_again: data[1] = in_iov[i].iov_base; rc = SMB2_set_info_init(tcon, server, - &rqst[num_rqst], COMPOUND_FID, - COMPOUND_FID, current->tgid, - FILE_LINK_INFORMATION, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + current->tgid, FILE_LINK_INFORMATION, SMB2_O_INFO_FILE, 0, data, size); if (rc) goto finished; - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst++]); + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_hardlink_enter(xid, tcon->tid, ses->Suid, full_path); break; case SMB2_OP_SET_REPARSE: rqst[num_rqst].rq_iov = vars->io_iov; rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov); - if (cfile) { - rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - FSCTL_SET_REPARSE_POINT, - in_iov[i].iov_base, - in_iov[i].iov_len, 0); - } else { - rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], - COMPOUND_FID, COMPOUND_FID, - FSCTL_SET_REPARSE_POINT, - in_iov[i].iov_base, - in_iov[i].iov_len, 0); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + FSCTL_SET_REPARSE_POINT, + in_iov[i].iov_base, + in_iov[i].iov_len, 0); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_set_reparse_compound_enter(xid, tcon->tid, ses->Suid, full_path); break; @@ -530,25 +474,13 @@ replay_again: rqst[num_rqst].rq_iov = vars->io_iov; rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov); - if (cfile) { - rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - FSCTL_GET_REPARSE_POINT, - NULL, 0, CIFSMaxBufSize); - } else { - rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], - COMPOUND_FID, COMPOUND_FID, - FSCTL_GET_REPARSE_POINT, - NULL, 0, CIFSMaxBufSize); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + FSCTL_GET_REPARSE_POINT, + NULL, 0, CIFSMaxBufSize); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_get_reparse_compound_enter(xid, tcon->tid, ses->Suid, full_path); break; @@ -556,34 +488,17 @@ replay_again: rqst[num_rqst].rq_iov = &vars->ea_iov; rqst[num_rqst].rq_nvec = 1; - if (cfile) { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - cfile->fid.persistent_fid, - cfile->fid.volatile_fid, - FILE_FULL_EA_INFORMATION, - SMB2_O_INFO_FILE, 0, - SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, - sizeof(wsl_query_eas), - (void *)wsl_query_eas); - } else { - rc = SMB2_query_info_init(tcon, server, - &rqst[num_rqst], - COMPOUND_FID, - COMPOUND_FID, - FILE_FULL_EA_INFORMATION, - SMB2_O_INFO_FILE, 0, - SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, - sizeof(wsl_query_eas), - (void *)wsl_query_eas); - } - if (!rc && (!cfile || num_rqst > 1)) { - smb2_set_next_command(tcon, &rqst[num_rqst]); - smb2_set_related(&rqst[num_rqst]); - } else if (rc) { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMP_PID(cfile), COMP_VID(cfile), + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, 0, + SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, + sizeof(wsl_query_eas), + (void *)wsl_query_eas); + if (rc) goto finished; - } - num_rqst++; + set_next_compound(tcon, cfile, i, num_cmds, rqst, &num_rqst); trace_smb3_query_wsl_ea_compound_enter(xid, tcon->tid, ses->Suid, full_path); break; @@ -612,18 +527,26 @@ replay_again: num_rqst++; if (cfile) { - if (retries) + if (retries) { + /* Back-off before retry */ + if (cur_sleep) + msleep(cur_sleep); for (i = 1; i < num_rqst - 2; i++) smb2_set_replay(server, &rqst[i]); + } rc = compound_send_recv(xid, ses, server, flags, num_rqst - 2, &rqst[1], &resp_buftype[1], &rsp_iov[1]); } else { - if (retries) + if (retries) { + /* Back-off before retry */ + if (cur_sleep) + msleep(cur_sleep); for (i = 0; i < num_rqst; i++) smb2_set_replay(server, &rqst[i]); + } rc = compound_send_recv(xid, ses, server, flags, num_rqst, @@ -640,8 +563,31 @@ finished: } tmp_rc = rc; + + if (rc == 0 && num_cmds > 0 && cmds[0] == SMB2_OP_OPEN_QUERY) { + create_rsp = rsp_iov[0].iov_base; + idata = in_iov[0].iov_base; + idata->fi.CreationTime = create_rsp->CreationTime; + idata->fi.LastAccessTime = create_rsp->LastAccessTime; + idata->fi.LastWriteTime = create_rsp->LastWriteTime; + idata->fi.ChangeTime = create_rsp->ChangeTime; + idata->fi.Attributes = create_rsp->FileAttributes; + idata->fi.AllocationSize = create_rsp->AllocationSize; + idata->fi.EndOfFile = create_rsp->EndofFile; + if (le32_to_cpu(idata->fi.NumberOfLinks) == 0) + idata->fi.NumberOfLinks = cpu_to_le32(1); /* dummy value */ + idata->fi.DeletePending = 0; /* successful open = not delete pending */ + idata->fi.Directory = !!(le32_to_cpu(create_rsp->FileAttributes) & ATTR_DIRECTORY); + + /* smb2_parse_contexts() fills idata->fi.IndexNumber */ + rc = smb2_parse_contexts(server, &rsp_iov[0], &oparms->fid->epoch, + oparms->fid->lease_key, &oplock, &idata->fi, NULL); + if (rc) + cifs_dbg(VFS, "rc: %d parsing context of compound op\n", rc); + } + for (i = 0; i < num_cmds; i++) { - char *buf = rsp_iov[i + i].iov_base; + char *buf = rsp_iov[i + 1].iov_base; if (buf && resp_buftype[i + 1] != CIFS_NO_BUFFER) rc = server->ops->map_error(buf, false); @@ -700,19 +646,6 @@ finished: trace_smb3_posix_query_info_compound_done(xid, tcon->tid, ses->Suid); break; - case SMB2_OP_DELETE: - if (rc) - trace_smb3_delete_err(xid, tcon->tid, ses->Suid, rc); - else { - /* - * If dentry (hence, inode) is NULL, lease break is going to - * take care of degrading leases on handles for deleted files. - */ - if (inode) - cifs_mark_open_handles_for_deleted_file(inode, full_path); - trace_smb3_delete_done(xid, tcon->tid, ses->Suid); - } - break; case SMB2_OP_MKDIR: if (rc) trace_smb3_mkdir_err(xid, tcon->tid, ses->Suid, rc); @@ -733,11 +666,11 @@ finished: trace_smb3_rename_done(xid, tcon->tid, ses->Suid); SMB2_set_info_free(&rqst[num_rqst++]); break; - case SMB2_OP_RMDIR: - if (rc) - trace_smb3_rmdir_err(xid, tcon->tid, ses->Suid, rc); + case SMB2_OP_UNLINK: + if (!rc) + trace_smb3_unlink_done(xid, tcon->tid, ses->Suid); else - trace_smb3_rmdir_done(xid, tcon->tid, ses->Suid); + trace_smb3_unlink_err(xid, tcon->tid, ses->Suid, rc); SMB2_set_info_free(&rqst[num_rqst++]); break; case SMB2_OP_SET_EOF: @@ -834,6 +767,7 @@ finished: smb2_should_replay(tcon, &retries, &cur_sleep)) goto replay_again; +out: if (cfile) cifsFileInfo_put(cfile); @@ -978,6 +912,43 @@ int smb2_query_path_info(const unsigned int xid, case 0: rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]); break; + case -EACCES: + /* + * If SMB2_OP_QUERY_INFO (called when POSIX extensions are not used) failed with + * STATUS_ACCESS_DENIED then it means that caller does not have permission to + * open the path with FILE_READ_ATTRIBUTES access and therefore cannot issue + * SMB2_OP_QUERY_INFO command. + * + * There is an alternative way how to query limited information about path but still + * suitable for stat() syscall. SMB2 OPEN/CREATE operation returns in its successful + * response subset of query information. + * + * So try to open the path without FILE_READ_ATTRIBUTES but with MAXIMUM_ALLOWED + * access which will grant the maximum possible access to the file and the response + * will contain required query information for stat() syscall. + */ + + if (tcon->posix_extensions) + break; + + num_cmds = 1; + cmds[0] = SMB2_OP_OPEN_QUERY; + in_iov[0].iov_base = data; + in_iov[0].iov_len = sizeof(*data); + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, MAXIMUM_ALLOWED, + FILE_OPEN, create_options, ACL_NO_MODE); + free_rsp_iov(out_iov, out_buftype, ARRAY_SIZE(out_iov)); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, + &oparms, in_iov, cmds, num_cmds, + cfile, out_iov, out_buftype, NULL); + + hdr = out_iov[0].iov_base; + if (!hdr || out_buftype[0] == CIFS_NO_BUFFER) + goto out; + + if (!rc) + rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]); + break; case -EOPNOTSUPP: /* * BB TODO: When support for special files added to Samba @@ -991,10 +962,11 @@ int smb2_query_path_info(const unsigned int xid, * Skip SMB2_OP_GET_REPARSE if symlink already parsed in create * response. */ - if (data->reparse.tag != IO_REPARSE_TAG_SYMLINK) + if (data->reparse.tag != IO_REPARSE_TAG_SYMLINK) { cmds[num_cmds++] = SMB2_OP_GET_REPARSE; - if (!tcon->posix_extensions) - cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA; + if (!tcon->posix_extensions) + cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA; + } oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_READ_ATTRIBUTES | @@ -1073,7 +1045,7 @@ smb2_mkdir_setinfo(struct inode *inode, const char *name, cifs_i = CIFS_I(inode); dosattrs = cifs_i->cifsAttrs | ATTR_READONLY; data.Attributes = cpu_to_le32(dosattrs); - cifs_get_writable_path(tcon, name, FIND_WR_ANY, &cfile); + cifs_get_writable_path(tcon, name, inode, FIND_ANY, &cfile); oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES, FILE_CREATE, CREATE_NOT_FILE, ACL_NO_MODE); tmprc = smb2_compound_op(xid, tcon, cifs_sb, name, @@ -1095,7 +1067,7 @@ smb2_rmdir(const unsigned int xid, struct cifs_tcon *tcon, const char *name, FILE_OPEN, CREATE_NOT_FILE, ACL_NO_MODE); return smb2_compound_op(xid, tcon, cifs_sb, name, &oparms, NULL, - &(int){SMB2_OP_RMDIR}, 1, + &(int){SMB2_OP_UNLINK}, 1, NULL, NULL, NULL, NULL); } @@ -1103,21 +1075,112 @@ int smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct cifs_sb_info *cifs_sb, struct dentry *dentry) { + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; + __le16 *utf16_path __free(kfree) = NULL; + int retries = 0, cur_sleep = 0; + struct TCP_Server_Info *server; struct cifs_open_parms oparms; + struct smb2_create_req *creq; + struct inode *inode = NULL; + struct smb_rqst rqst[2]; + struct kvec rsp_iov[2]; + struct kvec close_iov; + int resp_buftype[2]; + struct cifs_fid fid; + int flags = 0; + __u8 oplock; + int rc; - oparms = CIFS_OPARMS(cifs_sb, tcon, name, - DELETE, FILE_OPEN, - CREATE_DELETE_ON_CLOSE | OPEN_REPARSE_POINT, - ACL_NO_MODE); - int rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms, - NULL, &(int){SMB2_OP_DELETE}, 1, - NULL, NULL, NULL, dentry); - if (rc == -EINVAL) { - cifs_dbg(FYI, "invalid lease key, resending request without lease"); - rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms, - NULL, &(int){SMB2_OP_DELETE}, 1, - NULL, NULL, NULL, NULL); + utf16_path = cifs_convert_path_to_utf16(name, cifs_sb); + if (!utf16_path) + return -ENOMEM; + + if (smb3_encryption_required(tcon)) + flags |= CIFS_TRANSFORM_REQ; +again: + oplock = SMB2_OPLOCK_LEVEL_NONE; + server = cifs_pick_channel(tcon->ses); + + memset(rqst, 0, sizeof(rqst)); + memset(resp_buftype, 0, sizeof(resp_buftype)); + memset(rsp_iov, 0, sizeof(rsp_iov)); + + memset(open_iov, 0, sizeof(open_iov)); + rqst[0].rq_iov = open_iov; + rqst[0].rq_nvec = ARRAY_SIZE(open_iov); + + oparms = CIFS_OPARMS(cifs_sb, tcon, name, DELETE | FILE_READ_ATTRIBUTES, + FILE_OPEN, CREATE_DELETE_ON_CLOSE | + OPEN_REPARSE_POINT, ACL_NO_MODE); + oparms.fid = &fid; + + if (dentry) { + inode = d_inode(dentry); + if (CIFS_I(inode)->lease_granted && server->ops->get_lease_key) { + oplock = SMB2_OPLOCK_LEVEL_LEASE; + server->ops->get_lease_key(inode, &fid); + } + } + + rc = SMB2_open_init(tcon, server, + &rqst[0], &oplock, &oparms, utf16_path); + if (rc) + goto err_free; + smb2_set_next_command(tcon, &rqst[0]); + creq = rqst[0].rq_iov[0].iov_base; + creq->ShareAccess = FILE_SHARE_DELETE_LE; + + memset(&close_iov, 0, sizeof(close_iov)); + rqst[1].rq_iov = &close_iov; + rqst[1].rq_nvec = 1; + + rc = SMB2_close_init(tcon, server, &rqst[1], + COMPOUND_FID, COMPOUND_FID, false); + if (rc) + goto err_free; + smb2_set_related(&rqst[1]); + + if (retries) { + /* Back-off before retry */ + if (cur_sleep) + msleep(cur_sleep); + for (int i = 0; i < ARRAY_SIZE(rqst); i++) + smb2_set_replay(server, &rqst[i]); + } + + rc = compound_send_recv(xid, tcon->ses, server, flags, + ARRAY_SIZE(rqst), rqst, + resp_buftype, rsp_iov); + SMB2_open_free(&rqst[0]); + SMB2_close_free(&rqst[1]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto again; + + /* Retry compound request without lease */ + if (rc == -EINVAL && dentry) { + dentry = NULL; + retries = 0; + cur_sleep = 0; + goto again; } + /* + * If dentry (hence, inode) is NULL, lease break is going to + * take care of degrading leases on handles for deleted files. + */ + if (!rc && inode) + cifs_mark_open_handles_for_deleted_file(inode, name); + + return rc; + +err_free: + SMB2_open_free(&rqst[0]); + SMB2_close_free(&rqst[1]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); return rc; } @@ -1136,6 +1199,8 @@ static int smb2_set_path_attr(const unsigned int xid, struct cifs_tcon *tcon, smb2_to_name = cifs_convert_path_to_utf16(to_name, cifs_sb); if (smb2_to_name == NULL) { rc = -ENOMEM; + if (cfile) + cifsFileInfo_put(cfile); goto smb2_rename_path; } in_iov.iov_base = smb2_to_name; @@ -1156,35 +1221,62 @@ int smb2_rename_path(const unsigned int xid, const char *from_name, const char *to_name, struct cifs_sb_info *cifs_sb) { + struct inode *inode = source_dentry ? d_inode(source_dentry) : NULL; struct cifsFileInfo *cfile; __u32 co = file_create_options(source_dentry); drop_cached_dir_by_name(xid, tcon, from_name, cifs_sb); - cifs_get_writable_path(tcon, from_name, FIND_WR_WITH_DELETE, &cfile); + cifs_get_writable_path(tcon, from_name, inode, + FIND_WITH_DELETE, &cfile); int rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, co, DELETE, SMB2_OP_RENAME, cfile, source_dentry); if (rc == -EINVAL) { cifs_dbg(FYI, "invalid lease key, resending request without lease"); - cifs_get_writable_path(tcon, from_name, - FIND_WR_WITH_DELETE, &cfile); + cifs_get_writable_path(tcon, from_name, inode, + FIND_WITH_DELETE, &cfile); rc = smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, co, DELETE, SMB2_OP_RENAME, cfile, NULL); } return rc; } +static int clear_tmpfile_attr(const unsigned int xid, struct cifs_tcon *tcon, + struct inode *inode, const char *full_path) +{ + struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses); + struct cifsInodeInfo *cinode = CIFS_I(inode); + FILE_BASIC_INFO fi; + + cinode->cifsAttrs &= ~(ATTR_TEMPORARY | ATTR_HIDDEN); + fi = (FILE_BASIC_INFO) { + .Attributes = cpu_to_le32(cinode->cifsAttrs), + }; + return server->ops->set_file_info(inode, full_path, &fi, xid); +} + int smb2_create_hardlink(const unsigned int xid, struct cifs_tcon *tcon, struct dentry *source_dentry, const char *from_name, const char *to_name, struct cifs_sb_info *cifs_sb) { + struct inode *inode = source_dentry ? d_inode(source_dentry) : NULL; __u32 co = file_create_options(source_dentry); + struct cifsFileInfo *cfile; + int rc; + + if (inode && test_bit(CIFS_INO_TMPFILE, &CIFS_I(inode)->flags)) { + rc = clear_tmpfile_attr(xid, tcon, inode, from_name); + if (rc) + return rc; + } + cifs_get_writable_path(tcon, from_name, inode, + FIND_WITH_DELETE, &cfile); return smb2_set_path_attr(xid, tcon, from_name, to_name, cifs_sb, co, FILE_READ_ATTRIBUTES, - SMB2_OP_HARDLINK, NULL, NULL); + SMB2_OP_HARDLINK, cfile, NULL); } int @@ -1193,15 +1285,16 @@ smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, bool set_alloc, struct dentry *dentry) { + struct inode *inode = dentry ? d_inode(dentry) : NULL; + __le64 eof = cpu_to_le64(size); struct cifs_open_parms oparms; struct cifsFileInfo *cfile; struct kvec in_iov; - __le64 eof = cpu_to_le64(size); int rc; in_iov.iov_base = &eof; in_iov.iov_len = sizeof(eof); - cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + cifs_get_writable_path(tcon, full_path, inode, FIND_ANY, &cfile); oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_DATA, FILE_OPEN, 0, ACL_NO_MODE); @@ -1211,7 +1304,8 @@ smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon, cfile, NULL, NULL, dentry); if (rc == -EINVAL) { cifs_dbg(FYI, "invalid lease key, resending request without lease"); - cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + cifs_get_writable_path(tcon, full_path, + inode, FIND_ANY, &cfile); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, &in_iov, &(int){SMB2_OP_SET_EOF}, 1, @@ -1224,36 +1318,39 @@ int smb2_set_file_info(struct inode *inode, const char *full_path, FILE_BASIC_INFO *buf, const unsigned int xid) { - struct cifs_open_parms oparms; + struct kvec in_iov = { .iov_base = buf, .iov_len = sizeof(*buf), }; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + struct cifsFileInfo *cfile = NULL; + struct cifs_open_parms oparms; struct tcon_link *tlink; struct cifs_tcon *tcon; - struct cifsFileInfo *cfile; - struct kvec in_iov = { .iov_base = buf, .iov_len = sizeof(*buf), }; - int rc; - - if ((buf->CreationTime == 0) && (buf->LastAccessTime == 0) && - (buf->LastWriteTime == 0) && (buf->ChangeTime == 0) && - (buf->Attributes == 0)) - return 0; /* would be a no op, no sense sending this */ + int rc = 0; tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) return PTR_ERR(tlink); tcon = tlink_tcon(tlink); - cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + if ((buf->CreationTime == 0) && (buf->LastAccessTime == 0) && + (buf->LastWriteTime == 0) && (buf->ChangeTime == 0)) { + if (buf->Attributes == 0) + goto out; /* would be a no op, no sense sending this */ + cifs_get_writable_path(tcon, full_path, + inode, FIND_ANY, &cfile); + } + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_ATTRIBUTES, FILE_OPEN, 0, ACL_NO_MODE); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, &in_iov, &(int){SMB2_OP_SET_INFO}, 1, cfile, NULL, NULL, NULL); +out: cifs_put_tlink(tlink); return rc; } -struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, +struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data, struct super_block *sb, const unsigned int xid, struct cifs_tcon *tcon, @@ -1273,6 +1370,14 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, int rc; int i; + /* + * If server filesystem does not support reparse points then do not + * attempt to create reparse point. This will prevent creating unusable + * empty object on the server. + */ + if (!CIFS_REPARSE_SUPPORT(tcon)) + return ERR_PTR(-EOPNOTSUPP); + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, SYNCHRONIZE | DELETE | FILE_READ_ATTRIBUTES | @@ -1290,7 +1395,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, if (tcon->posix_extensions) { cmds[1] = SMB2_OP_POSIX_QUERY_INFO; - cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + cifs_get_writable_path(tcon, full_path, NULL, FIND_ANY, &cfile); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, in_iov, cmds, 2, cfile, out_iov, out_buftype, NULL); if (!rc) { @@ -1299,7 +1404,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, } } else { cmds[1] = SMB2_OP_QUERY_INFO; - cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile); + cifs_get_writable_path(tcon, full_path, NULL, FIND_ANY, &cfile); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, in_iov, cmds, 2, cfile, out_iov, out_buftype, NULL); if (!rc) { @@ -1362,3 +1467,98 @@ out: cifs_free_open_info(&data); return rc; } + +static inline __le16 *utf16_smb2_path(struct cifs_sb_info *cifs_sb, + const char *name, size_t namelen) +{ + int len; + + if (*name == '\\' || + (cifs_sb_master_tlink(cifs_sb) && + cifs_sb_master_tcon(cifs_sb)->posix_extensions && *name == '/')) + name++; + return cifs_strndup_to_utf16(name, namelen, &len, + cifs_sb->local_nls, + cifs_remap(cifs_sb)); +} + +int smb2_rename_pending_delete(const char *full_path, + struct dentry *dentry, + const unsigned int xid) +{ + struct cifsInodeInfo *cinode = CIFS_I(d_inode(dentry)); + struct cifs_sb_info *cifs_sb = CIFS_SB(dentry); + __le16 *utf16_path __free(kfree) = NULL; + __u32 co = file_create_options(dentry); + int cmds[] = { + SMB2_OP_SET_INFO, + SMB2_OP_RENAME, + SMB2_OP_UNLINK, + }; + const int num_cmds = ARRAY_SIZE(cmds); + char *to_name __free(kfree) = NULL; + __u32 attrs = cinode->cifsAttrs; + struct cifs_open_parms oparms; + struct cifsFileInfo *cfile; + struct tcon_link *tlink; + struct cifs_tcon *tcon; + struct kvec iov[2]; + int rc; + + tlink = cifs_sb_tlink(cifs_sb); + if (IS_ERR(tlink)) + return PTR_ERR(tlink); + tcon = tlink_tcon(tlink); + + to_name = cifs_silly_fullpath(dentry); + if (IS_ERR(to_name)) { + rc = PTR_ERR(to_name); + to_name = NULL; + goto out; + } + + utf16_path = utf16_smb2_path(cifs_sb, to_name, strlen(to_name)); + if (!utf16_path) { + rc = -ENOMEM; + goto out; + } + + drop_cached_dir_by_name(xid, tcon, full_path, cifs_sb); + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, + DELETE | FILE_WRITE_ATTRIBUTES, + FILE_OPEN, co, ACL_NO_MODE); + + attrs &= ~ATTR_READONLY; + if (!attrs) + attrs = ATTR_NORMAL; + if (d_inode(dentry)->i_nlink <= 1) + attrs |= ATTR_HIDDEN; + iov[0].iov_base = &(FILE_BASIC_INFO) { + .Attributes = cpu_to_le32(attrs), + }; + iov[0].iov_len = sizeof(FILE_BASIC_INFO); + iov[1].iov_base = utf16_path; + iov[1].iov_len = sizeof(*utf16_path) * UniStrlen((wchar_t *)utf16_path); + + cifs_get_writable_path(tcon, full_path, d_inode(dentry), + FIND_WITH_DELETE, &cfile); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, iov, + cmds, num_cmds, cfile, NULL, NULL, dentry); + if (rc == -EINVAL) { + cifs_dbg(FYI, "invalid lease key, resending request without lease\n"); + cifs_get_writable_path(tcon, full_path, d_inode(dentry), + FIND_WITH_DELETE, &cfile); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, iov, + cmds, num_cmds, cfile, NULL, NULL, NULL); + } + if (!rc) { + set_bit(CIFS_INO_DELETE_PENDING, &cinode->flags); + } else { + cifs_tcon_dbg(FYI, "%s: failed to rename '%s' to '%s': %d\n", + __func__, full_path, to_name, rc); + rc = smb_EIO1(smb_eio_trace_pend_del_fail, rc); + } +out: + cifs_put_tlink(tlink); + return rc; +} |
