--- zzzz-none-000/linux-3.10.107/fs/cifs/link.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/fs/cifs/link.c 2021-02-04 17:41:59.000000000 +0000 @@ -28,6 +28,14 @@ #include "cifsproto.h" #include "cifs_debug.h" #include "cifs_fs_sb.h" +#include "cifs_unicode.h" +#ifdef CONFIG_CIFS_SMB2 +#include "smb2proto.h" +#endif + +/* + * M-F Symlink Functions - Begin + */ #define CIFS_MF_SYMLINK_LEN_OFFSET (4+1) #define CIFS_MF_SYMLINK_MD5_OFFSET (CIFS_MF_SYMLINK_LEN_OFFSET+(4+1)) @@ -91,10 +99,8 @@ } static int -CIFSParseMFSymlink(const u8 *buf, - unsigned int buf_len, - unsigned int *_link_len, - char **_link_str) +parse_mf_symlink(const u8 *buf, unsigned int buf_len, unsigned int *_link_len, + char **_link_str) { int rc; unsigned int link_len; @@ -137,7 +143,7 @@ } static int -CIFSFormatMFSymlink(u8 *buf, unsigned int buf_len, const char *link_str) +format_mf_symlink(u8 *buf, unsigned int buf_len, const char *link_str) { int rc; unsigned int link_len; @@ -180,210 +186,355 @@ return 0; } +bool +couldbe_mf_symlink(const struct cifs_fattr *fattr) +{ + if (!S_ISREG(fattr->cf_mode)) + /* it's not a symlink */ + return false; + + if (fattr->cf_eof != CIFS_MF_SYMLINK_FILE_SIZE) + /* it's not a symlink */ + return false; + + return true; +} + static int -CIFSCreateMFSymLink(const unsigned int xid, struct cifs_tcon *tcon, - const char *fromName, const char *toName, - struct cifs_sb_info *cifs_sb) +create_mf_symlink(const unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const char *fromName, + const char *toName) { int rc; - int oplock = 0; - int remap; - int create_options = CREATE_NOT_DIR; - __u16 netfid = 0; u8 *buf; unsigned int bytes_written = 0; - struct cifs_io_parms io_parms; - struct nls_table *nls_codepage; - nls_codepage = cifs_sb->local_nls; - remap = cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR; + buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rc = format_mf_symlink(buf, CIFS_MF_SYMLINK_FILE_SIZE, toName); + if (rc) + goto out; + + if (tcon->ses->server->ops->create_mf_symlink) + rc = tcon->ses->server->ops->create_mf_symlink(xid, tcon, + cifs_sb, fromName, buf, &bytes_written); + else + rc = -EOPNOTSUPP; + + if (rc) + goto out; + + if (bytes_written != CIFS_MF_SYMLINK_FILE_SIZE) + rc = -EIO; +out: + kfree(buf); + return rc; +} + +static int +query_mf_symlink(const unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const unsigned char *path, + char **symlinkinfo) +{ + int rc; + u8 *buf = NULL; + unsigned int link_len = 0; + unsigned int bytes_read = 0; buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; - rc = CIFSFormatMFSymlink(buf, CIFS_MF_SYMLINK_FILE_SIZE, toName); - if (rc != 0) { - kfree(buf); - return rc; - } + if (tcon->ses->server->ops->query_mf_symlink) + rc = tcon->ses->server->ops->query_mf_symlink(xid, tcon, + cifs_sb, path, buf, &bytes_read); + else + rc = -ENOSYS; - if (backup_cred(cifs_sb)) - create_options |= CREATE_OPEN_BACKUP_INTENT; + if (rc) + goto out; - rc = CIFSSMBOpen(xid, tcon, fromName, FILE_CREATE, GENERIC_WRITE, - create_options, &netfid, &oplock, NULL, - nls_codepage, remap); - if (rc != 0) { - kfree(buf); - return rc; + if (bytes_read == 0) { /* not a symlink */ + rc = -EINVAL; + goto out; } - io_parms.netfid = netfid; - io_parms.pid = current->tgid; - io_parms.tcon = tcon; - io_parms.offset = 0; - io_parms.length = CIFS_MF_SYMLINK_FILE_SIZE; - - rc = CIFSSMBWrite(xid, &io_parms, &bytes_written, buf, NULL, 0); - CIFSSMBClose(xid, tcon, netfid); + rc = parse_mf_symlink(buf, bytes_read, &link_len, symlinkinfo); +out: kfree(buf); - if (rc != 0) - return rc; + return rc; +} - if (bytes_written != CIFS_MF_SYMLINK_FILE_SIZE) - return -EIO; +int +check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, struct cifs_fattr *fattr, + const unsigned char *path) +{ + int rc; + u8 *buf = NULL; + unsigned int link_len = 0; + unsigned int bytes_read = 0; - return 0; + if (!couldbe_mf_symlink(fattr)) + /* it's not a symlink */ + return 0; + + buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (tcon->ses->server->ops->query_mf_symlink) + rc = tcon->ses->server->ops->query_mf_symlink(xid, tcon, + cifs_sb, path, buf, &bytes_read); + else + rc = -ENOSYS; + + if (rc) + goto out; + + if (bytes_read == 0) /* not a symlink */ + goto out; + + rc = parse_mf_symlink(buf, bytes_read, &link_len, NULL); + if (rc == -EINVAL) { + /* it's not a symlink */ + rc = 0; + goto out; + } + + if (rc != 0) + goto out; + + /* it is a symlink */ + fattr->cf_eof = link_len; + fattr->cf_mode &= ~S_IFMT; + fattr->cf_mode |= S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; + fattr->cf_dtype = DT_LNK; +out: + kfree(buf); + return rc; } -static int -CIFSQueryMFSymLink(const unsigned int xid, struct cifs_tcon *tcon, - const unsigned char *searchName, char **symlinkinfo, - const struct nls_table *nls_codepage, int remap) +/* + * SMB 1.0 Protocol specific functions + */ + +int +cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const unsigned char *path, + char *pbuf, unsigned int *pbytes_read) { int rc; int oplock = 0; - __u16 netfid = 0; - u8 *buf; - char *pbuf; - unsigned int bytes_read = 0; - int buf_type = CIFS_NO_BUFFER; - unsigned int link_len = 0; + struct cifs_fid fid; + struct cifs_open_parms oparms; struct cifs_io_parms io_parms; + int buf_type = CIFS_NO_BUFFER; FILE_ALL_INFO file_info; - rc = CIFSSMBOpen(xid, tcon, searchName, FILE_OPEN, GENERIC_READ, - CREATE_NOT_DIR, &netfid, &oplock, &file_info, - nls_codepage, remap); - if (rc != 0) + oparms.tcon = tcon; + oparms.cifs_sb = cifs_sb; + oparms.desired_access = GENERIC_READ; + oparms.create_options = CREATE_NOT_DIR; + oparms.disposition = FILE_OPEN; + oparms.path = path; + oparms.fid = &fid; + oparms.reconnect = false; + + rc = CIFS_open(xid, &oparms, &oplock, &file_info); + if (rc) return rc; if (file_info.EndOfFile != cpu_to_le64(CIFS_MF_SYMLINK_FILE_SIZE)) { - CIFSSMBClose(xid, tcon, netfid); + rc = -ENOENT; /* it's not a symlink */ - return -EINVAL; + goto out; } - buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; - pbuf = buf; - io_parms.netfid = netfid; + io_parms.netfid = fid.netfid; io_parms.pid = current->tgid; io_parms.tcon = tcon; io_parms.offset = 0; io_parms.length = CIFS_MF_SYMLINK_FILE_SIZE; - rc = CIFSSMBRead(xid, &io_parms, &bytes_read, &pbuf, &buf_type); - CIFSSMBClose(xid, tcon, netfid); - if (rc != 0) { - kfree(buf); - return rc; - } + rc = CIFSSMBRead(xid, &io_parms, pbytes_read, &pbuf, &buf_type); +out: + CIFSSMBClose(xid, tcon, fid.netfid); + return rc; +} - rc = CIFSParseMFSymlink(buf, bytes_read, &link_len, symlinkinfo); - kfree(buf); - if (rc != 0) +int +cifs_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const unsigned char *path, + char *pbuf, unsigned int *pbytes_written) +{ + int rc; + int oplock = 0; + struct cifs_fid fid; + struct cifs_open_parms oparms; + struct cifs_io_parms io_parms; + int create_options = CREATE_NOT_DIR; + + if (backup_cred(cifs_sb)) + create_options |= CREATE_OPEN_BACKUP_INTENT; + + oparms.tcon = tcon; + oparms.cifs_sb = cifs_sb; + oparms.desired_access = GENERIC_WRITE; + oparms.create_options = create_options; + oparms.disposition = FILE_CREATE; + oparms.path = path; + oparms.fid = &fid; + oparms.reconnect = false; + + rc = CIFS_open(xid, &oparms, &oplock, NULL); + if (rc) return rc; - return 0; + io_parms.netfid = fid.netfid; + io_parms.pid = current->tgid; + io_parms.tcon = tcon; + io_parms.offset = 0; + io_parms.length = CIFS_MF_SYMLINK_FILE_SIZE; + + rc = CIFSSMBWrite(xid, &io_parms, pbytes_written, pbuf, NULL, 0); + CIFSSMBClose(xid, tcon, fid.netfid); + return rc; } -bool -CIFSCouldBeMFSymlink(const struct cifs_fattr *fattr) +/* + * SMB 2.1/SMB3 Protocol specific functions + */ +#ifdef CONFIG_CIFS_SMB2 +int +smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const unsigned char *path, + char *pbuf, unsigned int *pbytes_read) { - if (!(fattr->cf_mode & S_IFREG)) - /* it's not a symlink */ - return false; + int rc; + struct cifs_fid fid; + struct cifs_open_parms oparms; + struct cifs_io_parms io_parms; + int buf_type = CIFS_NO_BUFFER; + __le16 *utf16_path; + __u8 oplock = SMB2_OPLOCK_LEVEL_II; + struct smb2_file_all_info *pfile_info = NULL; + + oparms.tcon = tcon; + oparms.cifs_sb = cifs_sb; + oparms.desired_access = GENERIC_READ; + oparms.create_options = CREATE_NOT_DIR; + if (backup_cred(cifs_sb)) + oparms.create_options |= CREATE_OPEN_BACKUP_INTENT; + oparms.disposition = FILE_OPEN; + oparms.fid = &fid; + oparms.reconnect = false; - if (fattr->cf_eof != CIFS_MF_SYMLINK_FILE_SIZE) + utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); + if (utf16_path == NULL) + return -ENOMEM; + + pfile_info = kzalloc(sizeof(struct smb2_file_all_info) + PATH_MAX * 2, + GFP_KERNEL); + + if (pfile_info == NULL) { + kfree(utf16_path); + return -ENOMEM; + } + + rc = SMB2_open(xid, &oparms, utf16_path, &oplock, pfile_info, NULL); + if (rc) + goto qmf_out_open_fail; + + if (pfile_info->EndOfFile != cpu_to_le64(CIFS_MF_SYMLINK_FILE_SIZE)) { /* it's not a symlink */ - return false; + rc = -ENOENT; /* Is there a better rc to return? */ + goto qmf_out; + } - return true; + io_parms.netfid = fid.netfid; + io_parms.pid = current->tgid; + io_parms.tcon = tcon; + io_parms.offset = 0; + io_parms.length = CIFS_MF_SYMLINK_FILE_SIZE; + io_parms.persistent_fid = fid.persistent_fid; + io_parms.volatile_fid = fid.volatile_fid; + rc = SMB2_read(xid, &io_parms, pbytes_read, &pbuf, &buf_type); +qmf_out: + SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); +qmf_out_open_fail: + kfree(utf16_path); + kfree(pfile_info); + return rc; } int -CIFSCheckMFSymlink(struct cifs_fattr *fattr, - const unsigned char *path, - struct cifs_sb_info *cifs_sb, unsigned int xid) +smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const unsigned char *path, + char *pbuf, unsigned int *pbytes_written) { int rc; - int oplock = 0; - __u16 netfid = 0; - struct tcon_link *tlink; - struct cifs_tcon *pTcon; + struct cifs_fid fid; + struct cifs_open_parms oparms; struct cifs_io_parms io_parms; - u8 *buf; - char *pbuf; - unsigned int bytes_read = 0; - int buf_type = CIFS_NO_BUFFER; - unsigned int link_len = 0; - FILE_ALL_INFO file_info; + int create_options = CREATE_NOT_DIR; + __le16 *utf16_path; + __u8 oplock = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + struct kvec iov[2]; - if (!CIFSCouldBeMFSymlink(fattr)) - /* it's not a symlink */ - return 0; + if (backup_cred(cifs_sb)) + create_options |= CREATE_OPEN_BACKUP_INTENT; - tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) - return PTR_ERR(tlink); - pTcon = tlink_tcon(tlink); + cifs_dbg(FYI, "%s: path: %s\n", __func__, path); - rc = CIFSSMBOpen(xid, pTcon, path, FILE_OPEN, GENERIC_READ, - CREATE_NOT_DIR, &netfid, &oplock, &file_info, - cifs_sb->local_nls, - cifs_sb->mnt_cifs_flags & - CIFS_MOUNT_MAP_SPECIAL_CHR); - if (rc != 0) - goto out; + utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); + if (!utf16_path) + return -ENOMEM; - if (file_info.EndOfFile != cpu_to_le64(CIFS_MF_SYMLINK_FILE_SIZE)) { - CIFSSMBClose(xid, pTcon, netfid); - /* it's not a symlink */ - goto out; - } + oparms.tcon = tcon; + oparms.cifs_sb = cifs_sb; + oparms.desired_access = GENERIC_WRITE; + oparms.create_options = create_options; + oparms.disposition = FILE_CREATE; + oparms.fid = &fid; + oparms.reconnect = false; - buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL); - if (!buf) { - rc = -ENOMEM; - goto out; + rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL); + if (rc) { + kfree(utf16_path); + return rc; } - pbuf = buf; - io_parms.netfid = netfid; + + io_parms.netfid = fid.netfid; io_parms.pid = current->tgid; - io_parms.tcon = pTcon; + io_parms.tcon = tcon; io_parms.offset = 0; io_parms.length = CIFS_MF_SYMLINK_FILE_SIZE; + io_parms.persistent_fid = fid.persistent_fid; + io_parms.volatile_fid = fid.volatile_fid; - rc = CIFSSMBRead(xid, &io_parms, &bytes_read, &pbuf, &buf_type); - CIFSSMBClose(xid, pTcon, netfid); - if (rc != 0) { - kfree(buf); - goto out; - } + /* iov[0] is reserved for smb header */ + iov[1].iov_base = pbuf; + iov[1].iov_len = CIFS_MF_SYMLINK_FILE_SIZE; - rc = CIFSParseMFSymlink(buf, bytes_read, &link_len, NULL); - kfree(buf); - if (rc == -EINVAL) { - /* it's not a symlink */ - rc = 0; - goto out; - } + rc = SMB2_write(xid, &io_parms, pbytes_written, iov, 1); - if (rc != 0) - goto out; + /* Make sure we wrote all of the symlink data */ + if ((rc == 0) && (*pbytes_written != CIFS_MF_SYMLINK_FILE_SIZE)) + rc = -EIO; - /* it is a symlink */ - fattr->cf_eof = link_len; - fattr->cf_mode &= ~S_IFMT; - fattr->cf_mode |= S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; - fattr->cf_dtype = DT_LNK; -out: - cifs_put_tlink(tlink); + SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); + + kfree(utf16_path); return rc; } +#endif /* CONFIG_CIFS_SMB2 */ + +/* + * M-F Symlink Functions - End + */ int cifs_hardlink(struct dentry *old_file, struct inode *inode, @@ -416,12 +567,13 @@ if (tcon->unix_ext) rc = CIFSUnixCreateHardLink(xid, tcon, from_name, to_name, cifs_sb->local_nls, - cifs_sb->mnt_cifs_flags & - CIFS_MOUNT_MAP_SPECIAL_CHR); + cifs_remap(cifs_sb)); else { server = tcon->ses->server; - if (!server->ops->create_hardlink) - return -ENOSYS; + if (!server->ops->create_hardlink) { + rc = -ENOSYS; + goto cifs_hl_exit; + } rc = server->ops->create_hardlink(xid, tcon, from_name, to_name, cifs_sb); if ((rc == -EIO) || (rc == -EINVAL)) @@ -434,17 +586,13 @@ * if source file is cached (oplocked) revalidate will not go to server * until the file is closed or oplock broken so update nlinks locally */ - if (old_file->d_inode) { - cifsInode = CIFS_I(old_file->d_inode); + if (d_really_is_positive(old_file)) { + cifsInode = CIFS_I(d_inode(old_file)); if (rc == 0) { - spin_lock(&old_file->d_inode->i_lock); - inc_nlink(old_file->d_inode); - spin_unlock(&old_file->d_inode->i_lock); - /* - * BB should we make this contingent on superblock flag - * NOATIME? - */ - /* old_file->d_inode->i_ctime = CURRENT_TIME; */ + spin_lock(&d_inode(old_file)->i_lock); + inc_nlink(d_inode(old_file)); + spin_unlock(&d_inode(old_file)->i_lock); + /* * parent dir timestamps will update from srv within a * second, would it really be worth it to set the parent @@ -454,7 +602,9 @@ } /* * if not oplocked will force revalidate to get info on source - * file from srv + * file from srv. Note Samba server prior to 4.2 has bug - + * not updating src file ctime on hardlinks but Windows servers + * handle it properly */ cifsInode->time = 0; @@ -476,10 +626,10 @@ return rc; } -void * -cifs_follow_link(struct dentry *direntry, struct nameidata *nd) +const char * +cifs_follow_link(struct dentry *direntry, void **cookie) { - struct inode *inode = direntry->d_inode; + struct inode *inode = d_inode(direntry); int rc = -ENOMEM; unsigned int xid; char *full_path = NULL; @@ -487,39 +637,24 @@ struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct tcon_link *tlink = NULL; struct cifs_tcon *tcon; + struct TCP_Server_Info *server; xid = get_xid(); tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) { - rc = PTR_ERR(tlink); - tlink = NULL; - goto out; + free_xid(xid); + return ERR_CAST(tlink); } tcon = tlink_tcon(tlink); - - /* - * For now, we just handle symlinks with unix extensions enabled. - * Eventually we should handle NTFS reparse points, and MacOS - * symlink support. For instance... - * - * rc = CIFSSMBQueryReparseLinkInfo(...) - * - * For now, just return -EACCES when the server doesn't support posix - * extensions. Note that we still allow querying symlinks when posix - * extensions are manually disabled. We could disable these as well - * but there doesn't seem to be any harm in allowing the client to - * read them. - */ - if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) && - !cap_unix(tcon->ses)) { - rc = -EACCES; - goto out; - } + server = tcon->ses->server; full_path = build_path_from_dentry(direntry); - if (!full_path) - goto out; + if (!full_path) { + free_xid(xid); + cifs_put_tlink(tlink); + return ERR_PTR(-ENOMEM); + } cifs_dbg(FYI, "Full path: %s inode = 0x%p\n", full_path, inode); @@ -529,27 +664,21 @@ * and fallback to UNIX Extensions Symlinks. */ if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) - rc = CIFSQueryMFSymLink(xid, tcon, full_path, &target_path, - cifs_sb->local_nls, - cifs_sb->mnt_cifs_flags & - CIFS_MOUNT_MAP_SPECIAL_CHR); - - if ((rc != 0) && cap_unix(tcon->ses)) - rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path, - cifs_sb->local_nls); + rc = query_mf_symlink(xid, tcon, cifs_sb, full_path, + &target_path); + + if (rc != 0 && server->ops->query_symlink) + rc = server->ops->query_symlink(xid, tcon, full_path, + &target_path, cifs_sb); kfree(full_path); -out: + free_xid(xid); + cifs_put_tlink(tlink); if (rc != 0) { kfree(target_path); - target_path = ERR_PTR(rc); + return ERR_PTR(rc); } - - free_xid(xid); - if (tlink) - cifs_put_tlink(tlink); - nd_set_link(nd, target_path); - return NULL; + return *cookie = target_path; } int @@ -583,11 +712,11 @@ /* BB what if DFS and this volume is on different share? BB */ if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) - rc = CIFSCreateMFSymLink(xid, pTcon, full_path, symname, - cifs_sb); + rc = create_mf_symlink(xid, pTcon, cifs_sb, full_path, symname); else if (pTcon->unix_ext) rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname, - cifs_sb->local_nls); + cifs_sb->local_nls, + cifs_remap(cifs_sb)); /* else rc = CIFSCreateReparseSymLink(xid, pTcon, fromName, toName, cifs_sb_target->local_nls); */ @@ -613,10 +742,3 @@ free_xid(xid); return rc; } - -void cifs_put_link(struct dentry *direntry, struct nameidata *nd, void *cookie) -{ - char *p = nd_get_link(nd); - if (!IS_ERR(p)) - kfree(p); -}