| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2024 Paulo Alcantara <pc@manguebit.com> | |
| */ | |
| #include <linux/fs.h> | |
| #include <linux/stat.h> | |
| #include <linux/slab.h> | |
| #include "cifsglob.h" | |
| #include "smb2proto.h" | |
| #include "cifsproto.h" | |
| #include "cifs_unicode.h" | |
| #include "cifs_debug.h" | |
| #include "fs_context.h" | |
| #include "reparse.h" | |
| static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb, | |
| const unsigned int xid, | |
| const char *full_path, | |
| const char *symname, | |
| bool *directory); | |
| int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, | |
| struct dentry *dentry, struct cifs_tcon *tcon, | |
| const char *full_path, const char *symname) | |
| { | |
| struct reparse_symlink_data_buffer *buf = NULL; | |
| struct cifs_open_info_data data; | |
| struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); | |
| struct inode *new; | |
| struct kvec iov; | |
| __le16 *path; | |
| bool directory; | |
| char *sym, sep = CIFS_DIR_SEP(cifs_sb); | |
| u16 len, plen; | |
| int rc = 0; | |
| sym = kstrdup(symname, GFP_KERNEL); | |
| if (!sym) | |
| return -ENOMEM; | |
| data = (struct cifs_open_info_data) { | |
| .reparse_point = true, | |
| .reparse = { .tag = IO_REPARSE_TAG_SYMLINK, }, | |
| .symlink_target = sym, | |
| }; | |
| convert_delimiter(sym, sep); | |
| path = cifs_convert_path_to_utf16(sym, cifs_sb); | |
| if (!path) { | |
| rc = -ENOMEM; | |
| goto out; | |
| } | |
| /* | |
| * SMB distinguish between symlink to directory and symlink to file. | |
| * They cannot be exchanged (symlink of file type which points to | |
| * directory cannot be resolved and vice-versa). Try to detect if | |
| * the symlink target could be a directory or not. When detection | |
| * fails then treat symlink as a file (non-directory) symlink. | |
| */ | |
| directory = false; | |
| rc = detect_directory_symlink_target(cifs_sb, xid, full_path, symname, &directory); | |
| if (rc < 0) | |
| goto out; | |
| plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX); | |
| len = sizeof(*buf) + plen * 2; | |
| buf = kzalloc(len, GFP_KERNEL); | |
| if (!buf) { | |
| rc = -ENOMEM; | |
| goto out; | |
| } | |
| buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_SYMLINK); | |
| buf->ReparseDataLength = cpu_to_le16(len - sizeof(struct reparse_data_buffer)); | |
| buf->SubstituteNameOffset = cpu_to_le16(plen); | |
| buf->SubstituteNameLength = cpu_to_le16(plen); | |
| memcpy(&buf->PathBuffer[plen], path, plen); | |
| buf->PrintNameOffset = 0; | |
| buf->PrintNameLength = cpu_to_le16(plen); | |
| memcpy(buf->PathBuffer, path, plen); | |
| buf->Flags = cpu_to_le32(*symname != '/' ? SYMLINK_FLAG_RELATIVE : 0); | |
| if (*sym != sep) | |
| buf->Flags = cpu_to_le32(SYMLINK_FLAG_RELATIVE); | |
| convert_delimiter(sym, '/'); | |
| iov.iov_base = buf; | |
| iov.iov_len = len; | |
| new = smb2_get_reparse_inode(&data, inode->i_sb, xid, | |
| tcon, full_path, directory, | |
| &iov, NULL); | |
| if (!IS_ERR(new)) | |
| d_instantiate(dentry, new); | |
| else | |
| rc = PTR_ERR(new); | |
| out: | |
| kfree(path); | |
| cifs_free_open_info(&data); | |
| kfree(buf); | |
| return rc; | |
| } | |
| static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb, | |
| const unsigned int xid, | |
| const char *full_path, | |
| const char *symname, | |
| bool *directory) | |
| { | |
| char sep = CIFS_DIR_SEP(cifs_sb); | |
| struct cifs_open_parms oparms; | |
| struct tcon_link *tlink; | |
| struct cifs_tcon *tcon; | |
| const char *basename; | |
| struct cifs_fid fid; | |
| char *resolved_path; | |
| int full_path_len; | |
| int basename_len; | |
| int symname_len; | |
| char *path_sep; | |
| __u32 oplock; | |
| int open_rc; | |
| /* | |
| * First do some simple check. If the original Linux symlink target ends | |
| * with slash, or last path component is dot or dot-dot then it is for | |
| * sure symlink to the directory. | |
| */ | |
| basename = kbasename(symname); | |
| basename_len = strlen(basename); | |
| if (basename_len == 0 || /* symname ends with slash */ | |
| (basename_len == 1 && basename[0] == '.') || /* last component is "." */ | |
| (basename_len == 2 && basename[0] == '.' && basename[1] == '.')) { /* or ".." */ | |
| *directory = true; | |
| return 0; | |
| } | |
| /* | |
| * For absolute symlinks it is not possible to determinate | |
| * if it should point to directory or file. | |
| */ | |
| if (symname[0] == '/') { | |
| cifs_dbg(FYI, | |
| "%s: cannot determinate if the symlink target path '%s' " | |
| "is directory or not, creating '%s' as file symlink\n", | |
| __func__, symname, full_path); | |
| return 0; | |
| } | |
| /* | |
| * If it was not detected as directory yet and the symlink is relative | |
| * then try to resolve the path on the SMB server, check if the path | |
| * exists and determinate if it is a directory or not. | |
| */ | |
| full_path_len = strlen(full_path); | |
| symname_len = strlen(symname); | |
| tlink = cifs_sb_tlink(cifs_sb); | |
| if (IS_ERR(tlink)) | |
| return PTR_ERR(tlink); | |
| resolved_path = kzalloc(full_path_len + symname_len + 1, GFP_KERNEL); | |
| if (!resolved_path) { | |
| cifs_put_tlink(tlink); | |
| return -ENOMEM; | |
| } | |
| /* | |
| * Compose the resolved SMB symlink path from the SMB full path | |
| * and Linux target symlink path. | |
| */ | |
| memcpy(resolved_path, full_path, full_path_len+1); | |
| path_sep = strrchr(resolved_path, sep); | |
| if (path_sep) | |
| path_sep++; | |
| else | |
| path_sep = resolved_path; | |
| memcpy(path_sep, symname, symname_len+1); | |
| if (sep == '\\') | |
| convert_delimiter(path_sep, sep); | |
| tcon = tlink_tcon(tlink); | |
| oparms = CIFS_OPARMS(cifs_sb, tcon, resolved_path, | |
| FILE_READ_ATTRIBUTES, FILE_OPEN, 0, ACL_NO_MODE); | |
| oparms.fid = &fid; | |
| /* Try to open as a directory (NOT_FILE) */ | |
| oplock = 0; | |
| oparms.create_options = cifs_create_options(cifs_sb, | |
| CREATE_NOT_FILE | OPEN_REPARSE_POINT); | |
| open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL); | |
| if (open_rc == 0) { | |
| /* Successful open means that the target path is definitely a directory. */ | |
| *directory = true; | |
| tcon->ses->server->ops->close(xid, tcon, &fid); | |
| } else if (open_rc == -ENOTDIR) { | |
| /* -ENOTDIR means that the target path is definitely a file. */ | |
| *directory = false; | |
| } else if (open_rc == -ENOENT) { | |
| /* -ENOENT means that the target path does not exist. */ | |
| cifs_dbg(FYI, | |
| "%s: symlink target path '%s' does not exist, " | |
| "creating '%s' as file symlink\n", | |
| __func__, symname, full_path); | |
| } else { | |
| /* Try to open as a file (NOT_DIR) */ | |
| oplock = 0; | |
| oparms.create_options = cifs_create_options(cifs_sb, | |
| CREATE_NOT_DIR | OPEN_REPARSE_POINT); | |
| open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL); | |
| if (open_rc == 0) { | |
| /* Successful open means that the target path is definitely a file. */ | |
| *directory = false; | |
| tcon->ses->server->ops->close(xid, tcon, &fid); | |
| } else if (open_rc == -EISDIR) { | |
| /* -EISDIR means that the target path is definitely a directory. */ | |
| *directory = true; | |
| } else { | |
| /* | |
| * This code branch is called when we do not have a permission to | |
| * open the resolved_path or some other client/process denied | |
| * opening the resolved_path. | |
| * | |
| * TODO: Try to use ops->query_dir_first on the parent directory | |
| * of resolved_path, search for basename of resolved_path and | |
| * check if the ATTR_DIRECTORY is set in fi.Attributes. In some | |
| * case this could work also when opening of the path is denied. | |
| */ | |
| cifs_dbg(FYI, | |
| "%s: cannot determinate if the symlink target path '%s' " | |
| "is directory or not, creating '%s' as file symlink\n", | |
| __func__, symname, full_path); | |
| } | |
| } | |
| kfree(resolved_path); | |
| cifs_put_tlink(tlink); | |
| return 0; | |
| } | |
| static int nfs_set_reparse_buf(struct reparse_posix_data *buf, | |
| mode_t mode, dev_t dev, | |
| struct kvec *iov) | |
| { | |
| u64 type; | |
| u16 len, dlen; | |
| len = sizeof(*buf); | |
| switch ((type = reparse_mode_nfs_type(mode))) { | |
| case NFS_SPECFILE_BLK: | |
| case NFS_SPECFILE_CHR: | |
| dlen = sizeof(__le64); | |
| break; | |
| case NFS_SPECFILE_FIFO: | |
| case NFS_SPECFILE_SOCK: | |
| dlen = 0; | |
| break; | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_NFS); | |
| buf->Reserved = 0; | |
| buf->InodeType = cpu_to_le64(type); | |
| buf->ReparseDataLength = cpu_to_le16(len + dlen - | |
| sizeof(struct reparse_data_buffer)); | |
| *(__le64 *)buf->DataBuffer = cpu_to_le64(((u64)MINOR(dev) << 32) | | |
| MAJOR(dev)); | |
| iov->iov_base = buf; | |
| iov->iov_len = len + dlen; | |
| return 0; | |
| } | |
| static int mknod_nfs(unsigned int xid, struct inode *inode, | |
| struct dentry *dentry, struct cifs_tcon *tcon, | |
| const char *full_path, umode_t mode, dev_t dev) | |
| { | |
| struct cifs_open_info_data data; | |
| struct reparse_posix_data *p; | |
| struct inode *new; | |
| struct kvec iov; | |
| __u8 buf[sizeof(*p) + sizeof(__le64)]; | |
| int rc; | |
| p = (struct reparse_posix_data *)buf; | |
| rc = nfs_set_reparse_buf(p, mode, dev, &iov); | |
| if (rc) | |
| return rc; | |
| data = (struct cifs_open_info_data) { | |
| .reparse_point = true, | |
| .reparse = { .tag = IO_REPARSE_TAG_NFS, .posix = p, }, | |
| }; | |
| new = smb2_get_reparse_inode(&data, inode->i_sb, xid, | |
| tcon, full_path, false, &iov, NULL); | |
| if (!IS_ERR(new)) | |
| d_instantiate(dentry, new); | |
| else | |
| rc = PTR_ERR(new); | |
| cifs_free_open_info(&data); | |
| return rc; | |
| } | |
| static int wsl_set_reparse_buf(struct reparse_data_buffer *buf, | |
| mode_t mode, struct kvec *iov) | |
| { | |
| u32 tag; | |
| switch ((tag = reparse_mode_wsl_tag(mode))) { | |
| case IO_REPARSE_TAG_LX_BLK: | |
| case IO_REPARSE_TAG_LX_CHR: | |
| case IO_REPARSE_TAG_LX_FIFO: | |
| case IO_REPARSE_TAG_AF_UNIX: | |
| break; | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| buf->ReparseTag = cpu_to_le32(tag); | |
| buf->Reserved = 0; | |
| buf->ReparseDataLength = 0; | |
| iov->iov_base = buf; | |
| iov->iov_len = sizeof(*buf); | |
| return 0; | |
| } | |
| static struct smb2_create_ea_ctx *ea_create_context(u32 dlen, size_t *cc_len) | |
| { | |
| struct smb2_create_ea_ctx *cc; | |
| *cc_len = round_up(sizeof(*cc) + dlen, 8); | |
| cc = kzalloc(*cc_len, GFP_KERNEL); | |
| if (!cc) | |
| return ERR_PTR(-ENOMEM); | |
| cc->ctx.NameOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx, | |
| name)); | |
| cc->ctx.NameLength = cpu_to_le16(4); | |
| memcpy(cc->name, SMB2_CREATE_EA_BUFFER, strlen(SMB2_CREATE_EA_BUFFER)); | |
| cc->ctx.DataOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx, ea)); | |
| cc->ctx.DataLength = cpu_to_le32(dlen); | |
| return cc; | |
| } | |
| struct wsl_xattr { | |
| const char *name; | |
| __le64 value; | |
| u16 size; | |
| u32 next; | |
| }; | |
| static int wsl_set_xattrs(struct inode *inode, umode_t _mode, | |
| dev_t _dev, struct kvec *iov) | |
| { | |
| struct smb2_file_full_ea_info *ea; | |
| struct smb2_create_ea_ctx *cc; | |
| struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx; | |
| __le64 uid = cpu_to_le64(from_kuid(current_user_ns(), ctx->linux_uid)); | |
| __le64 gid = cpu_to_le64(from_kgid(current_user_ns(), ctx->linux_gid)); | |
| __le64 dev = cpu_to_le64(((u64)MINOR(_dev) << 32) | MAJOR(_dev)); | |
| __le64 mode = cpu_to_le64(_mode); | |
| struct wsl_xattr xattrs[] = { | |
| { .name = SMB2_WSL_XATTR_UID, .value = uid, .size = SMB2_WSL_XATTR_UID_SIZE, }, | |
| { .name = SMB2_WSL_XATTR_GID, .value = gid, .size = SMB2_WSL_XATTR_GID_SIZE, }, | |
| { .name = SMB2_WSL_XATTR_MODE, .value = mode, .size = SMB2_WSL_XATTR_MODE_SIZE, }, | |
| { .name = SMB2_WSL_XATTR_DEV, .value = dev, .size = SMB2_WSL_XATTR_DEV_SIZE, }, | |
| }; | |
| size_t cc_len; | |
| u32 dlen = 0, next = 0; | |
| int i, num_xattrs; | |
| u8 name_size = SMB2_WSL_XATTR_NAME_LEN + 1; | |
| memset(iov, 0, sizeof(*iov)); | |
| /* Exclude $LXDEV xattr for sockets and fifos */ | |
| if (S_ISSOCK(_mode) || S_ISFIFO(_mode)) | |
| num_xattrs = ARRAY_SIZE(xattrs) - 1; | |
| else | |
| num_xattrs = ARRAY_SIZE(xattrs); | |
| for (i = 0; i < num_xattrs; i++) { | |
| xattrs[i].next = ALIGN(sizeof(*ea) + name_size + | |
| xattrs[i].size, 4); | |
| dlen += xattrs[i].next; | |
| } | |
| cc = ea_create_context(dlen, &cc_len); | |
| if (IS_ERR(cc)) | |
| return PTR_ERR(cc); | |
| ea = &cc->ea; | |
| for (i = 0; i < num_xattrs; i++) { | |
| ea = (void *)((u8 *)ea + next); | |
| next = xattrs[i].next; | |
| ea->next_entry_offset = cpu_to_le32(next); | |
| ea->ea_name_length = name_size - 1; | |
| ea->ea_value_length = cpu_to_le16(xattrs[i].size); | |
| memcpy(ea->ea_data, xattrs[i].name, name_size); | |
| memcpy(&ea->ea_data[name_size], | |
| &xattrs[i].value, xattrs[i].size); | |
| } | |
| ea->next_entry_offset = 0; | |
| iov->iov_base = cc; | |
| iov->iov_len = cc_len; | |
| return 0; | |
| } | |
| static int mknod_wsl(unsigned int xid, struct inode *inode, | |
| struct dentry *dentry, struct cifs_tcon *tcon, | |
| const char *full_path, umode_t mode, dev_t dev) | |
| { | |
| struct cifs_open_info_data data; | |
| struct reparse_data_buffer buf; | |
| struct smb2_create_ea_ctx *cc; | |
| struct inode *new; | |
| unsigned int len; | |
| struct kvec reparse_iov, xattr_iov; | |
| int rc; | |
| rc = wsl_set_reparse_buf(&buf, mode, &reparse_iov); | |
| if (rc) | |
| return rc; | |
| rc = wsl_set_xattrs(inode, mode, dev, &xattr_iov); | |
| if (rc) | |
| return rc; | |
| data = (struct cifs_open_info_data) { | |
| .reparse_point = true, | |
| .reparse = { .tag = le32_to_cpu(buf.ReparseTag), .buf = &buf, }, | |
| }; | |
| cc = xattr_iov.iov_base; | |
| len = le32_to_cpu(cc->ctx.DataLength); | |
| memcpy(data.wsl.eas, &cc->ea, len); | |
| data.wsl.eas_len = len; | |
| new = smb2_get_reparse_inode(&data, inode->i_sb, | |
| xid, tcon, full_path, false, | |
| &reparse_iov, &xattr_iov); | |
| if (!IS_ERR(new)) | |
| d_instantiate(dentry, new); | |
| else | |
| rc = PTR_ERR(new); | |
| cifs_free_open_info(&data); | |
| kfree(xattr_iov.iov_base); | |
| return rc; | |
| } | |
| int smb2_mknod_reparse(unsigned int xid, struct inode *inode, | |
| struct dentry *dentry, struct cifs_tcon *tcon, | |
| const char *full_path, umode_t mode, dev_t dev) | |
| { | |
| struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx; | |
| int rc = -EOPNOTSUPP; | |
| switch (ctx->reparse_type) { | |
| case CIFS_REPARSE_TYPE_NFS: | |
| rc = mknod_nfs(xid, inode, dentry, tcon, full_path, mode, dev); | |
| break; | |
| case CIFS_REPARSE_TYPE_WSL: | |
| rc = mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev); | |
| break; | |
| } | |
| return rc; | |
| } | |
| /* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */ | |
| static int parse_reparse_posix(struct reparse_posix_data *buf, | |
| struct cifs_sb_info *cifs_sb, | |
| struct cifs_open_info_data *data) | |
| { | |
| unsigned int len; | |
| u64 type; | |
| len = le16_to_cpu(buf->ReparseDataLength); | |
| if (len < sizeof(buf->InodeType)) { | |
| cifs_dbg(VFS, "srv returned malformed nfs buffer\n"); | |
| return -EIO; | |
| } | |
| len -= sizeof(buf->InodeType); | |
| switch ((type = le64_to_cpu(buf->InodeType))) { | |
| case NFS_SPECFILE_LNK: | |
| if (len == 0 || (len % 2)) { | |
| cifs_dbg(VFS, "srv returned malformed nfs symlink buffer\n"); | |
| return -EIO; | |
| } | |
| /* | |
| * Check that buffer does not contain UTF-16 null codepoint | |
| * because Linux cannot process symlink with null byte. | |
| */ | |
| if (UniStrnlen((wchar_t *)buf->DataBuffer, len/2) != len/2) { | |
| cifs_dbg(VFS, "srv returned null byte in nfs symlink target location\n"); | |
| return -EIO; | |
| } | |
| data->symlink_target = cifs_strndup_from_utf16(buf->DataBuffer, | |
| len, true, | |
| cifs_sb->local_nls); | |
| if (!data->symlink_target) | |
| return -ENOMEM; | |
| cifs_dbg(FYI, "%s: target path: %s\n", | |
| __func__, data->symlink_target); | |
| break; | |
| case NFS_SPECFILE_CHR: | |
| case NFS_SPECFILE_BLK: | |
| /* DataBuffer for block and char devices contains two 32-bit numbers */ | |
| if (len != 8) { | |
| cifs_dbg(VFS, "srv returned malformed nfs buffer for type: 0x%llx\n", type); | |
| return -EIO; | |
| } | |
| break; | |
| case NFS_SPECFILE_FIFO: | |
| case NFS_SPECFILE_SOCK: | |
| /* DataBuffer for fifos and sockets is empty */ | |
| if (len != 0) { | |
| cifs_dbg(VFS, "srv returned malformed nfs buffer for type: 0x%llx\n", type); | |
| return -EIO; | |
| } | |
| break; | |
| default: | |
| cifs_dbg(VFS, "%s: unhandled inode type: 0x%llx\n", | |
| __func__, type); | |
| return -EOPNOTSUPP; | |
| } | |
| return 0; | |
| } | |
| static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, | |
| u32 plen, bool unicode, | |
| struct cifs_sb_info *cifs_sb, | |
| struct cifs_open_info_data *data) | |
| { | |
| unsigned int len; | |
| unsigned int offs; | |
| /* We handle Symbolic Link reparse tag here. See: MS-FSCC 2.1.2.4 */ | |
| offs = le16_to_cpu(sym->SubstituteNameOffset); | |
| len = le16_to_cpu(sym->SubstituteNameLength); | |
| if (offs + 20 > plen || offs + len + 20 > plen) { | |
| cifs_dbg(VFS, "srv returned malformed symlink buffer\n"); | |
| return -EIO; | |
| } | |
| data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs, | |
| len, unicode, | |
| cifs_sb->local_nls); | |
| if (!data->symlink_target) | |
| return -ENOMEM; | |
| convert_delimiter(data->symlink_target, '/'); | |
| cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target); | |
| return 0; | |
| } | |
| int parse_reparse_point(struct reparse_data_buffer *buf, | |
| u32 plen, struct cifs_sb_info *cifs_sb, | |
| bool unicode, struct cifs_open_info_data *data) | |
| { | |
| struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); | |
| data->reparse.buf = buf; | |
| /* See MS-FSCC 2.1.2 */ | |
| switch (le32_to_cpu(buf->ReparseTag)) { | |
| case IO_REPARSE_TAG_NFS: | |
| return parse_reparse_posix((struct reparse_posix_data *)buf, | |
| cifs_sb, data); | |
| case IO_REPARSE_TAG_SYMLINK: | |
| return parse_reparse_symlink( | |
| (struct reparse_symlink_data_buffer *)buf, | |
| plen, unicode, cifs_sb, data); | |
| case IO_REPARSE_TAG_LX_SYMLINK: | |
| case IO_REPARSE_TAG_AF_UNIX: | |
| case IO_REPARSE_TAG_LX_FIFO: | |
| case IO_REPARSE_TAG_LX_CHR: | |
| case IO_REPARSE_TAG_LX_BLK: | |
| break; | |
| default: | |
| cifs_tcon_dbg(VFS | ONCE, "unhandled reparse tag: 0x%08x\n", | |
| le32_to_cpu(buf->ReparseTag)); | |
| break; | |
| } | |
| return 0; | |
| } | |
| int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, | |
| struct kvec *rsp_iov, | |
| struct cifs_open_info_data *data) | |
| { | |
| struct reparse_data_buffer *buf; | |
| struct smb2_ioctl_rsp *io = rsp_iov->iov_base; | |
| u32 plen = le32_to_cpu(io->OutputCount); | |
| buf = (struct reparse_data_buffer *)((u8 *)io + | |
| le32_to_cpu(io->OutputOffset)); | |
| return parse_reparse_point(buf, plen, cifs_sb, true, data); | |
| } | |
| static void wsl_to_fattr(struct cifs_open_info_data *data, | |
| struct cifs_sb_info *cifs_sb, | |
| u32 tag, struct cifs_fattr *fattr) | |
| { | |
| struct smb2_file_full_ea_info *ea; | |
| u32 next = 0; | |
| switch (tag) { | |
| case IO_REPARSE_TAG_LX_SYMLINK: | |
| fattr->cf_mode |= S_IFLNK; | |
| break; | |
| case IO_REPARSE_TAG_LX_FIFO: | |
| fattr->cf_mode |= S_IFIFO; | |
| break; | |
| case IO_REPARSE_TAG_AF_UNIX: | |
| fattr->cf_mode |= S_IFSOCK; | |
| break; | |
| case IO_REPARSE_TAG_LX_CHR: | |
| fattr->cf_mode |= S_IFCHR; | |
| break; | |
| case IO_REPARSE_TAG_LX_BLK: | |
| fattr->cf_mode |= S_IFBLK; | |
| break; | |
| } | |
| if (!data->wsl.eas_len) | |
| goto out; | |
| ea = (struct smb2_file_full_ea_info *)data->wsl.eas; | |
| do { | |
| const char *name; | |
| void *v; | |
| u8 nlen; | |
| ea = (void *)((u8 *)ea + next); | |
| next = le32_to_cpu(ea->next_entry_offset); | |
| if (!le16_to_cpu(ea->ea_value_length)) | |
| continue; | |
| name = ea->ea_data; | |
| nlen = ea->ea_name_length; | |
| v = (void *)((u8 *)ea->ea_data + ea->ea_name_length + 1); | |
| if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) | |
| fattr->cf_uid = wsl_make_kuid(cifs_sb, v); | |
| else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) | |
| fattr->cf_gid = wsl_make_kgid(cifs_sb, v); | |
| else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) | |
| fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v); | |
| else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) | |
| fattr->cf_rdev = reparse_mkdev(v); | |
| } while (next); | |
| out: | |
| fattr->cf_dtype = S_DT(fattr->cf_mode); | |
| } | |
| bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, | |
| struct cifs_fattr *fattr, | |
| struct cifs_open_info_data *data) | |
| { | |
| struct reparse_posix_data *buf = data->reparse.posix; | |
| u32 tag = data->reparse.tag; | |
| if (tag == IO_REPARSE_TAG_NFS && buf) { | |
| if (le16_to_cpu(buf->ReparseDataLength) < sizeof(buf->InodeType)) | |
| return false; | |
| switch (le64_to_cpu(buf->InodeType)) { | |
| case NFS_SPECFILE_CHR: | |
| if (le16_to_cpu(buf->ReparseDataLength) != sizeof(buf->InodeType) + 8) | |
| return false; | |
| fattr->cf_mode |= S_IFCHR; | |
| fattr->cf_rdev = reparse_mkdev(buf->DataBuffer); | |
| break; | |
| case NFS_SPECFILE_BLK: | |
| if (le16_to_cpu(buf->ReparseDataLength) != sizeof(buf->InodeType) + 8) | |
| return false; | |
| fattr->cf_mode |= S_IFBLK; | |
| fattr->cf_rdev = reparse_mkdev(buf->DataBuffer); | |
| break; | |
| case NFS_SPECFILE_FIFO: | |
| fattr->cf_mode |= S_IFIFO; | |
| break; | |
| case NFS_SPECFILE_SOCK: | |
| fattr->cf_mode |= S_IFSOCK; | |
| break; | |
| case NFS_SPECFILE_LNK: | |
| fattr->cf_mode |= S_IFLNK; | |
| break; | |
| default: | |
| WARN_ON_ONCE(1); | |
| return false; | |
| } | |
| goto out; | |
| } | |
| switch (tag) { | |
| case IO_REPARSE_TAG_INTERNAL: | |
| if (!(fattr->cf_cifsattrs & ATTR_DIRECTORY)) | |
| return false; | |
| fallthrough; | |
| case IO_REPARSE_TAG_DFS: | |
| case IO_REPARSE_TAG_DFSR: | |
| case IO_REPARSE_TAG_MOUNT_POINT: | |
| /* See cifs_create_junction_fattr() */ | |
| fattr->cf_mode = S_IFDIR | 0711; | |
| break; | |
| case IO_REPARSE_TAG_LX_SYMLINK: | |
| case IO_REPARSE_TAG_LX_FIFO: | |
| case IO_REPARSE_TAG_AF_UNIX: | |
| case IO_REPARSE_TAG_LX_CHR: | |
| case IO_REPARSE_TAG_LX_BLK: | |
| wsl_to_fattr(data, cifs_sb, tag, fattr); | |
| break; | |
| case 0: /* SMB1 symlink */ | |
| case IO_REPARSE_TAG_SYMLINK: | |
| case IO_REPARSE_TAG_NFS: | |
| fattr->cf_mode |= S_IFLNK; | |
| break; | |
| default: | |
| return false; | |
| } | |
| out: | |
| fattr->cf_dtype = S_DT(fattr->cf_mode); | |
| return true; | |
| } |