Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/fs/smb/client/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 36 kB image not shown  

Quelle  reparse.c   Sprache: C

 
// 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 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,
       const char *symname);

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,
       const char *symname);

static int create_native_symlink(const unsigned int xid, struct inode *inode,
     struct dentry *dentry, struct cifs_tcon *tcon,
     const char *full_path, const char *symname);

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 create_reparse_symlink(const unsigned int xid, struct inode *inode,
    struct dentry *dentry, struct cifs_tcon *tcon,
    const char *full_path, const char *symname)
{
 switch (cifs_symlink_type(CIFS_SB(inode->i_sb))) {
 case CIFS_SYMLINK_TYPE_NATIVE:
  return create_native_symlink(xid, inode, dentry, tcon, full_path, symname);
 case CIFS_SYMLINK_TYPE_NFS:
  return mknod_nfs(xid, inode, dentry, tcon, full_path, S_IFLNK, 0, symname);
 case CIFS_SYMLINK_TYPE_WSL:
  return mknod_wsl(xid, inode, dentry, tcon, full_path, S_IFLNK, 0, symname);
 default:
  return -EOPNOTSUPP;
 }
}

static int create_native_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);
 const char *symroot = cifs_sb->ctx->symlinkroot;
 struct inode *new;
 struct kvec iov;
 __le16 *path = NULL;
 bool directory;
 char *symlink_target = NULL;
 char *sym = NULL;
 char sep = CIFS_DIR_SEP(cifs_sb);
 u16 len, plen, poff, slen;
 int rc = 0;

 if (strlen(symname) > REPARSE_SYM_PATH_MAX)
  return -ENAMETOOLONG;

 symlink_target = kstrdup(symname, GFP_KERNEL);
 if (!symlink_target) {
  rc = -ENOMEM;
  goto out;
 }

 data = (struct cifs_open_info_data) {
  .reparse_point = true,
  .reparse = { .tag = IO_REPARSE_TAG_SYMLINK, },
  .symlink_target = symlink_target,
 };

 if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) &&
     symroot && symname[0] == '/') {
  /*
 * This is a request to create an absolute symlink on the server
 * which does not support POSIX paths, and expects symlink in
 * NT-style path. So convert absolute Linux symlink target path
 * to the absolute NT-style path. Root of the NT-style path for
 * symlinks is specified in "symlinkroot" mount option. This will
 * ensure compatibility of this symlink stored in absolute form
 * on the SMB server.
 */

  if (!strstarts(symname, symroot)) {
   /*
 * If the absolute Linux symlink target path is not
 * inside "symlinkroot" location then there is no way
 * to convert such Linux symlink to NT-style path.
 */

   cifs_dbg(VFS,
     "absolute symlink '%s' cannot be converted to NT format "
     "because it is outside of symlinkroot='%s'\n",
     symname, symroot);
   rc = -EINVAL;
   goto out;
  }
  len = strlen(symroot);
  if (symroot[len - 1] != '/')
   len++;
  if (symname[len] >= 'a' && symname[len] <= 'z' &&
      (symname[len+1] == '/' || symname[len+1] == '\0')) {
   /*
 * Symlink points to Linux target /symlinkroot/x/path/...
 * where 'x' is the lowercase local Windows drive.
 * NT-style path for 'x' has common form \??\X:\path\...
 * with uppercase local Windows drive.
 */

   int common_path_len = strlen(symname+len+1)+1;
   sym = kzalloc(6+common_path_len, GFP_KERNEL);
   if (!sym) {
    rc = -ENOMEM;
    goto out;
   }
   memcpy(sym, "\\??\\", 4);
   sym[4] = symname[len] - ('a'-'A');
   sym[5] = ':';
   memcpy(sym+6, symname+len+1, common_path_len);
  } else {
   /* Unhandled absolute symlink. Report an error. */
   cifs_dbg(
     VFS,
     "absolute symlink '%s' cannot be converted to NT format "
     "because it points to unknown target\n",
     symname);
   rc = -EINVAL;
   goto out;
  }
 } else {
  /*
 * This is request to either create an absolute symlink on
 * server which expects POSIX paths or it is an request to
 * create a relative symlink from the current directory.
 * These paths have same format as relative SMB symlinks,
 * so no conversion is needed. So just take symname as-is.
 */

  sym = kstrdup(symname, GFP_KERNEL);
  if (!sym) {
   rc = -ENOMEM;
   goto out;
  }
 }

 if (sep == '\\')
  convert_delimiter(sym, sep);

 /*
 * For absolute NT symlinks it is required to pass also leading
 * backslash and to not mangle NT object prefix "\\??\\" and not to
 * mangle colon in drive letter. But cifs_convert_path_to_utf16()
 * removes leading backslash and replaces '?' and ':'. So temporary
 * mask these characters in NT object prefix by '_' and then change
 * them back.
 */

 if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/')
  sym[0] = sym[1] = sym[2] = sym[5] = '_';

 path = cifs_convert_path_to_utf16(sym, cifs_sb);
 if (!path) {
  rc = -ENOMEM;
  goto out;
 }

 if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') {
  sym[0] = '\\';
  sym[1] = sym[2] = '?';
  sym[5] = ':';
  path[0] = cpu_to_le16('\\');
  path[1] = path[2] = cpu_to_le16('?');
  path[5] = cpu_to_le16(':');
 }

 /*
 * 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;

 slen = 2 * UniStrnlen((wchar_t *)path, REPARSE_SYM_PATH_MAX);
 poff = 0;
 plen = slen;
 if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') {
  /*
 * For absolute NT symlinks skip leading "\\??\\" in PrintName as
 * PrintName is user visible location in DOS/Win32 format (not in NT format).
 */

  poff = 4;
  plen -= 2 * poff;
 }
 len = sizeof(*buf) + plen + slen;
 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(slen);
 memcpy(&buf->PathBuffer[plen], path, slen);

 buf->PrintNameOffset = 0;
 buf->PrintNameLength = cpu_to_le16(plen);
 memcpy(buf->PathBuffer, path+poff, plen);

 buf->Flags = cpu_to_le32(*symname != '/' ? SYMLINK_FLAG_RELATIVE : 0);

 iov.iov_base = buf;
 iov.iov_len = len;
 new = tcon->ses->server->ops->create_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(sym);
 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 determine
 * 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 create_native_socket(const unsigned int xid, struct inode *inode,
    struct dentry *dentry, struct cifs_tcon *tcon,
    const char *full_path)
{
 struct reparse_data_buffer buf = {
  .ReparseTag = cpu_to_le32(IO_REPARSE_TAG_AF_UNIX),
  .ReparseDataLength = cpu_to_le16(0),
 };
 struct cifs_open_info_data data = {
  .reparse_point = true,
  .reparse = { .tag = IO_REPARSE_TAG_AF_UNIX, .buf = &buf, },
 };
 struct kvec iov = {
  .iov_base = &buf,
  .iov_len = sizeof(buf),
 };
 struct inode *new;
 int rc = 0;

 new = tcon->ses->server->ops->create_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 nfs_set_reparse_buf(struct reparse_nfs_data_buffer *buf,
          mode_t mode, dev_t dev,
          __le16 *symname_utf16,
          int symname_utf16_len,
          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 = 2 * sizeof(__le32);
  ((__le32 *)buf->DataBuffer)[0] = cpu_to_le32(MAJOR(dev));
  ((__le32 *)buf->DataBuffer)[1] = cpu_to_le32(MINOR(dev));
  break;
 case NFS_SPECFILE_LNK:
  dlen = symname_utf16_len;
  memcpy(buf->DataBuffer, symname_utf16, symname_utf16_len);
  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));
 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,
       const char *symname)
{
 struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
 struct cifs_open_info_data data;
 struct reparse_nfs_data_buffer *p = NULL;
 __le16 *symname_utf16 = NULL;
 int symname_utf16_len = 0;
 struct inode *new;
 struct kvec iov;
 __u8 buf[sizeof(*p) + sizeof(__le64)];
 int rc;

 if (S_ISLNK(mode)) {
  symname_utf16 = cifs_strndup_to_utf16(symname, strlen(symname),
            &symname_utf16_len,
            cifs_sb->local_nls,
            NO_MAP_UNI_RSVD);
  if (!symname_utf16) {
   rc = -ENOMEM;
   goto out;
  }
  symname_utf16_len -= 2; /* symlink is without trailing wide-nul */
  p = kzalloc(sizeof(*p) + symname_utf16_len, GFP_KERNEL);
  if (!p) {
   rc = -ENOMEM;
   goto out;
  }
 } else {
  p = (struct reparse_nfs_data_buffer *)buf;
 }
 rc = nfs_set_reparse_buf(p, mode, dev, symname_utf16, symname_utf16_len, &iov);
 if (rc)
  goto out;

 data = (struct cifs_open_info_data) {
  .reparse_point = true,
  .reparse = { .tag = IO_REPARSE_TAG_NFS, .buf = (struct reparse_data_buffer *)p, },
  .symlink_target = kstrdup(symname, GFP_KERNEL),
 };

 new = tcon->ses->server->ops->create_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);
out:
 if (S_ISLNK(mode)) {
  kfree(symname_utf16);
  kfree(p);
 }
 return rc;
}

static int wsl_set_reparse_buf(struct reparse_data_buffer **buf,
          mode_t mode, const char *symname,
          struct cifs_sb_info *cifs_sb,
          struct kvec *iov)
{
 struct reparse_wsl_symlink_data_buffer *symlink_buf;
 __le16 *symname_utf16;
 int symname_utf16_len;
 int symname_utf8_maxlen;
 int symname_utf8_len;
 size_t buf_len;
 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:
  buf_len = sizeof(struct reparse_data_buffer);
  *buf = kzalloc(buf_len, GFP_KERNEL);
  if (!*buf)
   return -ENOMEM;
  break;
 case IO_REPARSE_TAG_LX_SYMLINK:
  symname_utf16 = cifs_strndup_to_utf16(symname, strlen(symname),
            &symname_utf16_len,
            cifs_sb->local_nls,
            NO_MAP_UNI_RSVD);
  if (!symname_utf16)
   return -ENOMEM;
  symname_utf8_maxlen = symname_utf16_len/2*3;
  symlink_buf = kzalloc(sizeof(struct reparse_wsl_symlink_data_buffer) +
          symname_utf8_maxlen, GFP_KERNEL);
  if (!symlink_buf) {
   kfree(symname_utf16);
   return -ENOMEM;
  }
  /* Version field must be set to 2 (MS-FSCC 2.1.2.7) */
  symlink_buf->Version = cpu_to_le32(2);
  /* Target for Version 2 is in UTF-8 but without trailing null-term byte */
  symname_utf8_len = utf16s_to_utf8s((wchar_t *)symname_utf16, symname_utf16_len/2,
         UTF16_LITTLE_ENDIAN,
         symlink_buf->Target,
         symname_utf8_maxlen);
  *buf = (struct reparse_data_buffer *)symlink_buf;
  buf_len = sizeof(struct reparse_wsl_symlink_data_buffer) + symname_utf8_len;
  kfree(symname_utf16);
  break;
 default:
  return -EOPNOTSUPP;
 }

 (*buf)->ReparseTag = cpu_to_le32(tag);
 (*buf)->Reserved = 0;
 (*buf)->ReparseDataLength = cpu_to_le16(buf_len - sizeof(struct reparse_data_buffer));
 iov->iov_base = *buf;
 iov->iov_len = buf_len;
 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 non-device files */
 if (!S_ISBLK(_mode) && !S_ISCHR(_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,
       const char *symname)
{
 struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
 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, symname, cifs_sb, &reparse_iov);
 if (rc)
  return rc;

 rc = wsl_set_xattrs(inode, mode, dev, &xattr_iov);
 if (rc) {
  kfree(buf);
  return rc;
 }

 data = (struct cifs_open_info_data) {
  .reparse_point = true,
  .reparse = { .tag = le32_to_cpu(buf->ReparseTag), .buf = buf, },
  .symlink_target = kstrdup(symname, GFP_KERNEL),
 };

 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 = tcon->ses->server->ops->create_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);
 kfree(buf);
 return rc;
}

int 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;

 if (S_ISSOCK(mode) && !ctx->nonativesocket && ctx->reparse_type != CIFS_REPARSE_TYPE_NONE)
  return create_native_socket(xid, inode, dentry, tcon, full_path);

 switch (ctx->reparse_type) {
 case CIFS_REPARSE_TYPE_NFS:
  return mknod_nfs(xid, inode, dentry, tcon, full_path, mode, dev, NULL);
 case CIFS_REPARSE_TYPE_WSL:
  return mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev, NULL);
 default:
  return -EOPNOTSUPP;
 }
}

/* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */
static int parse_reparse_nfs(struct reparse_nfs_data_buffer *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;
}

int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
         bool relative,
         const char *full_path,
         struct cifs_sb_info *cifs_sb)
{
 const char *symroot = cifs_sb->ctx->symlinkroot;
 char sep = CIFS_DIR_SEP(cifs_sb);
 char *linux_target = NULL;
 char *smb_target = NULL;
 int symlinkroot_len;
 int abs_path_len;
 char *abs_path;
 int levels;
 int rc;
 int i;

 /* Check that length it valid */
 if (!len || (len % 2)) {
  cifs_dbg(VFS, "srv returned malformed symlink buffer\n");
  rc = -EIO;
  goto out;
 }

 /*
 * Check that buffer does not contain UTF-16 null codepoint
 * because Linux cannot process symlink with null byte.
 */

 if (UniStrnlen((wchar_t *)buf, len/2) != len/2) {
  cifs_dbg(VFS, "srv returned null byte in native symlink target location\n");
  rc = -EIO;
  goto out;
 }

 smb_target = cifs_strndup_from_utf16(buf, len, true, cifs_sb->local_nls);
 if (!smb_target) {
  rc = -ENOMEM;
  goto out;
 }

 if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) &&
     symroot && !relative) {
  /*
 * This is an absolute symlink from the server which does not
 * support POSIX paths, so the symlink is in NT-style path.
 * So convert it to absolute Linux symlink target path. Root of
 * the NT-style path for symlinks is specified in "symlinkroot"
 * mount option.
 *
 * Root of the DOS and Win32 paths is at NT path \??\
 * It means that DOS/Win32 path C:\folder\file.txt is
 * NT path \??\C:\folder\file.txt
 *
 * NT systems have some well-known object symlinks in their NT
 * hierarchy, which is needed to take into account when resolving
 * other symlinks. Most commonly used symlink paths are:
 * \?? -> \GLOBAL??
 * \DosDevices -> \??
 * \GLOBAL??\GLOBALROOT -> \
 * \GLOBAL??\Global -> \GLOBAL??
 * \GLOBAL??\NUL -> \Device\Null
 * \GLOBAL??\UNC -> \Device\Mup
 * \GLOBAL??\PhysicalDrive0 -> \Device\Harddisk0\DR0 (for each harddisk)
 * \GLOBAL??\A: -> \Device\Floppy0 (if A: is the first floppy)
 * \GLOBAL??\C: -> \Device\HarddiskVolume1 (if C: is the first harddisk)
 * \GLOBAL??\D: -> \Device\CdRom0 (if D: is first cdrom)
 * \SystemRoot -> \Device\Harddisk0\Partition1\WINDOWS (or where is NT system installed)
 * \Volume{...} -> \Device\HarddiskVolume1 (where ... is system generated guid)
 *
 * In most common cases, absolute NT symlinks points to path on
 * DOS/Win32 drive letter, system-specific Volume or on UNC share.
 * Here are few examples of commonly used absolute NT symlinks
 * created by mklink.exe tool:
 * \??\C:\folder\file.txt
 * \??\\C:\folder\file.txt
 * \??\UNC\server\share\file.txt
 * \??\\UNC\server\share\file.txt
 * \??\Volume{b75e2c83-0000-0000-0000-602f00000000}\folder\file.txt
 *
 * It means that the most common path prefix \??\ is also NT path
 * symlink (to \GLOBAL??). It is less common that second path
 * separator is double backslash, but it is valid.
 *
 * Volume guid is randomly generated by the target system and so
 * only the target system knows the mapping between guid and the
 * hardisk number. Over SMB it is not possible to resolve this
 * mapping, therefore symlinks pointing to target location of
 * volume guids are totally unusable over SMB.
 *
 * For now parse only symlink paths available for DOS and Win32.
 * Those are paths with \??\ prefix or paths which points to \??\
 * via other NT symlink (\DosDevices\, \GLOBAL??\, ...).
 */

  abs_path = smb_target;
globalroot:
  if (strstarts(abs_path, "\\??\\"))
   abs_path += sizeof("\\??\\")-1;
  else if (strstarts(abs_path, "\\DosDevices\\"))
   abs_path += sizeof("\\DosDevices\\")-1;
  else if (strstarts(abs_path, "\\GLOBAL??\\"))
   abs_path += sizeof("\\GLOBAL??\\")-1;
  else
   goto out_unhandled_target;

  /* Sometimes path separator after \?? is double backslash */
  if (abs_path[0] == '\\')
   abs_path++;

  while (strstarts(abs_path, "Global\\"))
   abs_path += sizeof("Global\\")-1;

  if (strstarts(abs_path, "GLOBALROOT\\")) {
   /* Label globalroot requires path with leading '\\', so do not trim '\\' */
   abs_path += sizeof("GLOBALROOT")-1;
   goto globalroot;
  }

  /* For now parse only paths to drive letters */
  if (((abs_path[0] >= 'A' && abs_path[0] <= 'Z') ||
       (abs_path[0] >= 'a' && abs_path[0] <= 'z')) &&
      abs_path[1] == ':' &&
      (abs_path[2] == '\\' || abs_path[2] == '\0')) {
   /* Convert drive letter to lowercase and drop colon */
   char drive_letter = abs_path[0];
   if (drive_letter >= 'A' && drive_letter <= 'Z')
    drive_letter += 'a'-'A';
   abs_path++;
   abs_path[0] = drive_letter;
  } else {
   goto out_unhandled_target;
  }

  abs_path_len = strlen(abs_path)+1;
  symlinkroot_len = strlen(symroot);
  if (symroot[symlinkroot_len - 1] == '/')
   symlinkroot_len--;
  linux_target = kmalloc(symlinkroot_len + 1 + abs_path_len, GFP_KERNEL);
  if (!linux_target) {
   rc = -ENOMEM;
   goto out;
  }
  memcpy(linux_target, symroot, symlinkroot_len);
  linux_target[symlinkroot_len] = '/';
  memcpy(linux_target + symlinkroot_len + 1, abs_path, abs_path_len);
 } else if (smb_target[0] == sep && relative) {
  /*
 * This is a relative SMB symlink from the top of the share,
 * which is the top level directory of the Linux mount point.
 * Linux does not support such relative symlinks, so convert
 * it to the relative symlink from the current directory.
 * full_path is the SMB path to the symlink (from which is
 * extracted current directory) and smb_target is the SMB path
 * where symlink points, therefore full_path must always be on
 * the SMB share.
 */

  int smb_target_len = strlen(smb_target)+1;
  levels = 0;
  for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */
   if (full_path[i] == sep)
    levels++;
  }
  linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL);
  if (!linux_target) {
   rc = -ENOMEM;
   goto out;
  }
  for (i = 0; i < levels; i++) {
   linux_target[i*3 + 0] = '.';
   linux_target[i*3 + 1] = '.';
   linux_target[i*3 + 2] = sep;
  }
  memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */
 } else {
  /*
 * This is either an absolute symlink in POSIX-style format
 * or relative SMB symlink from the current directory.
 * These paths have same format as Linux symlinks, so no
 * conversion is needed.
 */

out_unhandled_target:
  linux_target = smb_target;
  smb_target = NULL;
 }

 if (sep == '\\')
  convert_delimiter(linux_target, '/');

 rc = 0;
 *target = linux_target;

 cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target);

out:
 if (rc != 0)
  kfree(linux_target);
 kfree(smb_target);
 return rc;
}

static int parse_reparse_native_symlink(struct reparse_symlink_data_buffer *sym,
     u32 plen,
     struct cifs_sb_info *cifs_sb,
     const char *full_path,
     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;
 }

 return smb2_parse_native_symlink(&data->symlink_target,
      sym->PathBuffer + offs,
      len,
      le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
      full_path,
      cifs_sb);
}

static int parse_reparse_wsl_symlink(struct reparse_wsl_symlink_data_buffer *buf,
         struct cifs_sb_info *cifs_sb,
         struct cifs_open_info_data *data)
{
 int len = le16_to_cpu(buf->ReparseDataLength);
 int data_offset = offsetof(typeof(*buf), Target) - offsetof(typeof(*buf), Version);
 int symname_utf8_len;
 __le16 *symname_utf16;
 int symname_utf16_len;

 if (len <= data_offset) {
  cifs_dbg(VFS, "srv returned malformed wsl symlink buffer\n");
  return -EIO;
 }

 /* MS-FSCC 2.1.2.7 defines layout of the Target field only for Version 2. */
 if (le32_to_cpu(buf->Version) != 2) {
  cifs_dbg(VFS, "srv returned unsupported wsl symlink version %u\n", le32_to_cpu(buf->Version));
  return -EIO;
 }

 /* Target for Version 2 is in UTF-8 but without trailing null-term byte */
 symname_utf8_len = len - data_offset;
 /*
 * Check that buffer does not contain null byte
 * because Linux cannot process symlink with null byte.
 */

 if (strnlen(buf->Target, symname_utf8_len) != symname_utf8_len) {
  cifs_dbg(VFS, "srv returned null byte in wsl symlink target location\n");
  return -EIO;
 }
 symname_utf16 = kzalloc(symname_utf8_len * 2, GFP_KERNEL);
 if (!symname_utf16)
  return -ENOMEM;
 symname_utf16_len = utf8s_to_utf16s(buf->Target, symname_utf8_len,
         UTF16_LITTLE_ENDIAN,
         (wchar_t *) symname_utf16, symname_utf8_len * 2);
 if (symname_utf16_len < 0) {
  kfree(symname_utf16);
  return symname_utf16_len;
 }
 symname_utf16_len *= 2; /* utf8s_to_utf16s() returns number of u16 items, not byte length */

 data->symlink_target = cifs_strndup_from_utf16((u8 *)symname_utf16,
             symname_utf16_len, true,
             cifs_sb->local_nls);
 kfree(symname_utf16);
 if (!data->symlink_target)
  return -ENOMEM;

 return 0;
}

int parse_reparse_point(struct reparse_data_buffer *buf,
   u32 plen, struct cifs_sb_info *cifs_sb,
   const char *full_path,
   struct cifs_open_info_data *data)
{
 data->reparse.buf = buf;

 /* See MS-FSCC 2.1.2 */
 switch (le32_to_cpu(buf->ReparseTag)) {
 case IO_REPARSE_TAG_NFS:
  return parse_reparse_nfs((struct reparse_nfs_data_buffer *)buf,
        cifs_sb, data);
 case IO_REPARSE_TAG_SYMLINK:
  return parse_reparse_native_symlink(
   (struct reparse_symlink_data_buffer *)buf,
   plen, cifs_sb, full_path, data);
 case IO_REPARSE_TAG_LX_SYMLINK:
  return parse_reparse_wsl_symlink(
   (struct reparse_wsl_symlink_data_buffer *)buf,
   cifs_sb, data);
 case IO_REPARSE_TAG_AF_UNIX:
 case IO_REPARSE_TAG_LX_FIFO:
 case IO_REPARSE_TAG_LX_CHR:
 case IO_REPARSE_TAG_LX_BLK:
  if (le16_to_cpu(buf->ReparseDataLength) != 0) {
   cifs_dbg(VFS, "srv returned malformed buffer for reparse point: 0x%08x\n",
     le32_to_cpu(buf->ReparseTag));
   return -EIO;
  }
  return 0;
 default:
  return -EOPNOTSUPP;
 }
}

struct reparse_data_buffer *smb2_get_reparse_point_buffer(const struct kvec *rsp_iov,
         u32 *plen)
{
 struct smb2_ioctl_rsp *io = rsp_iov->iov_base;
 *plen = le32_to_cpu(io->OutputCount);
 return (struct reparse_data_buffer *)((u8 *)io +
           le32_to_cpu(io->OutputOffset));
}

static bool 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;
 bool have_xattr_dev = false;
 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)) {
   /* File type in reparse point tag and in xattr mode must match. */
   if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v)))
    return false;
   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);
   have_xattr_dev = true;
  }
 } while (next);
out:

 /* Major and minor numbers for char and block devices are mandatory. */
 if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK))
  return false;

 return true;
}

static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb,
       struct cifs_fattr *fattr,
       struct cifs_open_info_data *data)
{
 struct reparse_nfs_data_buffer *buf = (struct reparse_nfs_data_buffer *)data->reparse.buf;

 if (buf == NULL)
  return true;

 if (le16_to_cpu(buf->ReparseDataLength) < sizeof(buf->InodeType)) {
  WARN_ON_ONCE(1);
  return false;
 }

 switch (le64_to_cpu(buf->InodeType)) {
 case NFS_SPECFILE_CHR:
  if (le16_to_cpu(buf->ReparseDataLength) != sizeof(buf->InodeType) + 8) {
   WARN_ON_ONCE(1);
   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) {
   WARN_ON_ONCE(1);
   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;
 }
 return true;
}

bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
     struct cifs_fattr *fattr,
     struct cifs_open_info_data *data)
{
 u32 tag = data->reparse.tag;
 bool ok;

 switch (tag) {
 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:
  ok = wsl_to_fattr(data, cifs_sb, tag, fattr);
  if (!ok)
   return false;
  break;
 case IO_REPARSE_TAG_NFS:
  ok = posix_reparse_to_fattr(cifs_sb, fattr, data);
  if (!ok)
   return false;
  break;
 case 0: /* SMB1 symlink */
 case IO_REPARSE_TAG_SYMLINK:
  fattr->cf_mode |= S_IFLNK;
  break;
 default:
  if (!(fattr->cf_cifsattrs & ATTR_DIRECTORY))
   return false;
  if (!IS_REPARSE_TAG_NAME_SURROGATE(tag) &&
      tag != IO_REPARSE_TAG_INTERNAL)
   return false;
  /* See cifs_create_junction_fattr() */
  fattr->cf_mode = S_IFDIR | 0711;
  break;
 }

 fattr->cf_dtype = S_DT(fattr->cf_mode);
 return true;
}

Messung V0.5
C=97 H=94 G=95

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.