cifs: plumb smb2 POSIX dir enumeration
authorAurelien Aptel <aaptel@suse.com>
Sat, 8 Feb 2020 14:50:58 +0000 (15:50 +0100)
committerSteve French <stfrench@microsoft.com>
Mon, 23 Mar 2020 03:49:09 +0000 (22:49 -0500)
* add code to request POSIX info level
* parse dir entries and fill cifs_fattr to get correct inode data

since the POSIX payload is variable size the number of entries in a
FIND response needs to be computed differently.

Dirs and regular files are properly reported along with mode bits,
hardlink number, c/m/atime. No special files yet (see below).

Current experimental version of Samba with the extension unfortunately
has issues with wildcards and needs the following patch:

> --- i/source3/smbd/smb2_query_directory.c
> +++ w/source3/smbd/smb2_query_directory.c
> @@ -397,9 +397,7 @@ smbd_smb2_query_directory_send(TALLOC_CTX
> *mem_ctx,
>  }
>  }
>
> -       if (!state->smbreq->posix_pathnames) {
>  wcard_has_wild = ms_has_wild(state->in_file_name);
> -       }
>
>  /* Ensure we've canonicalized any search path if not a wildcard. */
>  if (!wcard_has_wild) {
>

Also for special files despite reporting them as reparse point samba
doesn't set the reparse tag field. This patch will mark them as needing
re-evaluation but the re-evaluate code doesn't deal with it yet.

Signed-off-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/readdir.c
fs/cifs/smb2pdu.c

index ba9dadf..19e4a5d 100644 (file)
@@ -32,6 +32,7 @@
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
 #include "cifsfs.h"
+#include "smb2proto.h"
 
 /*
  * To be safe - for UCS to UTF-8 with strings loaded with the rare long
@@ -217,6 +218,60 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
        }
 }
 
+/* Fill a cifs_fattr struct with info from SMB_FIND_FILE_POSIX_INFO. */
+static void
+cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info,
+                   struct cifs_sb_info *cifs_sb)
+{
+       struct smb2_posix_info_parsed parsed;
+
+       posix_info_parse(info, NULL, &parsed);
+
+       memset(fattr, 0, sizeof(*fattr));
+       fattr->cf_uniqueid = le64_to_cpu(info->Inode);
+       fattr->cf_bytes = le64_to_cpu(info->AllocationSize);
+       fattr->cf_eof = le64_to_cpu(info->EndOfFile);
+
+       fattr->cf_atime = cifs_NTtimeToUnix(info->LastAccessTime);
+       fattr->cf_mtime = cifs_NTtimeToUnix(info->LastWriteTime);
+       fattr->cf_ctime = cifs_NTtimeToUnix(info->CreationTime);
+
+       fattr->cf_nlink = le32_to_cpu(info->HardLinks);
+       fattr->cf_cifsattrs = le32_to_cpu(info->DosAttributes);
+
+       /*
+        * Since we set the inode type below we need to mask off
+        * to avoid strange results if bits set above.
+        * XXX: why not make server&client use the type bits?
+        */
+       fattr->cf_mode = le32_to_cpu(info->Mode) & ~S_IFMT;
+
+       cifs_dbg(VFS, "XXX dev %d, reparse %d, mode %o",
+                le32_to_cpu(info->DeviceId),
+                le32_to_cpu(info->ReparseTag),
+                le32_to_cpu(info->Mode));
+
+       if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
+               fattr->cf_mode |= S_IFDIR;
+               fattr->cf_dtype = DT_DIR;
+       } else {
+               /*
+                * mark anything that is not a dir as regular
+                * file. special files should have the REPARSE
+                * attribute and will be marked as needing revaluation
+                */
+               fattr->cf_mode |= S_IFREG;
+               fattr->cf_dtype = DT_REG;
+       }
+
+       if (reparse_file_needs_reval(fattr))
+               fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
+
+       /* TODO map SIDs */
+       fattr->cf_uid = cifs_sb->mnt_uid;
+       fattr->cf_gid = cifs_sb->mnt_gid;
+}
+
 static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info)
 {
        const FILE_DIRECTORY_INFO *fi = info;
@@ -359,6 +414,8 @@ ffirst_retry:
        /* if (cap_unix(tcon->ses) { */
        if (tcon->unix_ext)
                cifsFile->srch_inf.info_level = SMB_FIND_FILE_UNIX;
+       else if (tcon->posix_extensions)
+               cifsFile->srch_inf.info_level = SMB_FIND_FILE_POSIX_INFO;
        else if ((tcon->ses->capabilities &
                  tcon->ses->server->vals->cap_nt_find) == 0) {
                cifsFile->srch_inf.info_level = SMB_FIND_FILE_INFO_STANDARD;
@@ -451,6 +508,23 @@ struct cifs_dirent {
        u64             ino;
 };
 
+static void cifs_fill_dirent_posix(struct cifs_dirent *de,
+                                  const struct smb2_posix_info *info)
+{
+       struct smb2_posix_info_parsed parsed;
+
+       /* payload should have already been checked at this point */
+       if (posix_info_parse(info, NULL, &parsed) < 0) {
+               cifs_dbg(VFS, "invalid POSIX info payload");
+               return;
+       }
+
+       de->name = parsed.name;
+       de->namelen = parsed.name_len;
+       de->resume_key = info->Ignored;
+       de->ino = le64_to_cpu(info->Inode);
+}
+
 static void cifs_fill_dirent_unix(struct cifs_dirent *de,
                const FILE_UNIX_INFO *info, bool is_unicode)
 {
@@ -511,6 +585,9 @@ static int cifs_fill_dirent(struct cifs_dirent *de, const void *info,
        memset(de, 0, sizeof(*de));
 
        switch (level) {
+       case SMB_FIND_FILE_POSIX_INFO:
+               cifs_fill_dirent_posix(de, info);
+               break;
        case SMB_FIND_FILE_UNIX:
                cifs_fill_dirent_unix(de, info, is_unicode);
                break;
@@ -786,6 +863,11 @@ static int cifs_filldir(char *find_entry, struct file *file,
        }
 
        switch (file_info->srch_inf.info_level) {
+       case SMB_FIND_FILE_POSIX_INFO:
+               cifs_posix_to_fattr(&fattr,
+                                   (struct smb2_posix_info *)find_entry,
+                                   cifs_sb);
+               break;
        case SMB_FIND_FILE_UNIX:
                cifs_unix_basic_to_fattr(&fattr,
                                         &((FILE_UNIX_INFO *)find_entry)->basic,
index 08d6a13..7356017 100644 (file)
@@ -4398,7 +4398,8 @@ static int posix_info_extra_size(const void *beg, const void *end)
 }
 
 static unsigned int
-num_entries(char *bufstart, char *end_of_buf, char **lastentry, size_t size)
+num_entries(int infotype, char *bufstart, char *end_of_buf, char **lastentry,
+           size_t size)
 {
        int len;
        unsigned int entrycount = 0;
@@ -4422,8 +4423,13 @@ num_entries(char *bufstart, char *end_of_buf, char **lastentry, size_t size)
                entryptr = entryptr + next_offset;
                dir_info = (FILE_DIRECTORY_INFO *)entryptr;
 
-               len = le32_to_cpu(dir_info->FileNameLength);
-               if (entryptr + len < entryptr ||
+               if (infotype == SMB_FIND_FILE_POSIX_INFO)
+                       len = posix_info_extra_size(entryptr, end_of_buf);
+               else
+                       len = le32_to_cpu(dir_info->FileNameLength);
+
+               if (len < 0 ||
+                   entryptr + len < entryptr ||
                    entryptr + len > end_of_buf ||
                    entryptr + len + size > end_of_buf) {
                        cifs_dbg(VFS, "directory entry name would overflow frame end of buf %p\n",
@@ -4473,6 +4479,9 @@ int SMB2_query_directory_init(const unsigned int xid,
        case SMB_FIND_FILE_ID_FULL_DIR_INFO:
                req->FileInformationClass = FILEID_FULL_DIRECTORY_INFORMATION;
                break;
+       case SMB_FIND_FILE_POSIX_INFO:
+               req->FileInformationClass = SMB_FIND_FILE_POSIX_INFO;
+               break;
        default:
                cifs_tcon_dbg(VFS, "info level %u isn't supported\n",
                        info_level);
@@ -4538,6 +4547,10 @@ smb2_parse_query_directory(struct cifs_tcon *tcon,
        case SMB_FIND_FILE_ID_FULL_DIR_INFO:
                info_buf_size = sizeof(SEARCH_ID_FULL_DIR_INFO) - 1;
                break;
+       case SMB_FIND_FILE_POSIX_INFO:
+               /* note that posix payload are variable size */
+               info_buf_size = sizeof(struct smb2_posix_info);
+               break;
        default:
                cifs_tcon_dbg(VFS, "info level %u isn't supported\n",
                         srch_inf->info_level);
@@ -4547,8 +4560,10 @@ smb2_parse_query_directory(struct cifs_tcon *tcon,
        rc = smb2_validate_iov(le16_to_cpu(rsp->OutputBufferOffset),
                               le32_to_cpu(rsp->OutputBufferLength), rsp_iov,
                               info_buf_size);
-       if (rc)
+       if (rc) {
+               cifs_tcon_dbg(VFS, "bad info payload");
                return rc;
+       }
 
        srch_inf->unicode = true;
 
@@ -4562,9 +4577,14 @@ smb2_parse_query_directory(struct cifs_tcon *tcon,
        srch_inf->srch_entries_start = srch_inf->last_entry =
                (char *)rsp + le16_to_cpu(rsp->OutputBufferOffset);
        end_of_smb = rsp_iov->iov_len + (char *)rsp;
-       srch_inf->entries_in_buffer =
-                       num_entries(srch_inf->srch_entries_start, end_of_smb,
-                                   &srch_inf->last_entry, info_buf_size);
+
+       srch_inf->entries_in_buffer = num_entries(
+               srch_inf->info_level,
+               srch_inf->srch_entries_start,
+               end_of_smb,
+               &srch_inf->last_entry,
+               info_buf_size);
+
        srch_inf->index_of_last_entry += srch_inf->entries_in_buffer;
        cifs_dbg(FYI, "num entries %d last_index %lld srch start %p srch end %p\n",
                 srch_inf->entries_in_buffer, srch_inf->index_of_last_entry,