Allow conversion of characters in Mac remap range. Part 1
authorSteve French <smfrench@gmail.com>
Thu, 25 Sep 2014 18:20:05 +0000 (13:20 -0500)
committerSteve French <smfrench@gmail.com>
Thu, 16 Oct 2014 20:20:20 +0000 (15:20 -0500)
This allows directory listings to Mac to display filenames
correctly which have been created with illegal (to Windows)
characters in their filename. It does not allow
converting the other direction yet ie opening files with
these characters (followon patch).

There are seven reserved characters that need to be remapped when
mounting to Windows, Mac (or any server without Unix Extensions) which
are valid in POSIX but not in the other OS.

: \ < > ? * |

We used the normal UCS-2 remap range for this in order to convert this
to/from UTF8 as did Windows Services for Unix (basically add 0xF000 to
any of the 7 reserved characters), at least when the "mapchars" mount
option was specified.

Mac used a very slightly different "Services for Mac" remap range
0xF021 through 0xF027.  The attached patch allows cifs.ko (the kernel
client) to read directories on macs containing files with these
characters and display their names properly.  In theory this even
might be useful on mounts to Samba when the vfs_catia or new
"vfs_fruit" module is loaded.

Currently the 7 reserved characters look very strange in directory
listings from cifs.ko to Mac server.  This patch allows these file
name characters to be read (requires specifying mapchars on mount).

Two additional changes are needed:
1) Make it more automatic: a way of detecting enough info so that
we know to try to always remap these characters or not. Various
have suggested that the SFM approach be made the default when
the server does not support POSIX Unix extensions (cifs mounts
to Samba for example) so need to make SFM remapping the default
unless mapchars (SFU style mapping) specified on mount or no
mapping explicitly requested or no mapping needed (cifs mounts to Samba).

2) Adding a patch to map the characters the other direction
(ie UTF-8 to UCS-2 on open).  This patch does it for translating
readdir entries (ie UCS-2 to UTF-8)

Signed-off-by: Steve French <smfrench@gmail.com>
Reviewed-by: Shirish Pargaonkar <shirishpargaonkar@gmail.com>
fs/cifs/cifs_fs_sb.h
fs/cifs/cifs_unicode.c
fs/cifs/cifs_unicode.h
fs/cifs/cifsencrypt.c
fs/cifs/readdir.c

index 9409fa1..3182273 100644 (file)
@@ -45,6 +45,7 @@
 #define CIFS_MOUNT_POSIXACL    0x100000 /* mirror of MS_POSIXACL in mnt_cifs_flags */
 #define CIFS_MOUNT_CIFS_BACKUPUID 0x200000 /* backup intent bit for a user */
 #define CIFS_MOUNT_CIFS_BACKUPGID 0x400000 /* backup intent bit for a group */
+#define CIFS_MOUNT_MAP_SFM_CHR 0x800000 /* SFM/MAC mapping for illegal chars */
 
 struct cifs_sb_info {
        struct rb_root tlink_tree;
index 15e9505..a479cc5 100644 (file)
@@ -61,26 +61,10 @@ cifs_utf16_bytes(const __le16 *from, int maxbytes,
        return outlen;
 }
 
-/*
- * cifs_mapchar - convert a host-endian char to proper char in codepage
- * @target - where converted character should be copied
- * @src_char - 2 byte host-endian source character
- * @cp - codepage to which character should be converted
- * @mapchar - should character be mapped according to mapchars mount option?
- *
- * This function handles the conversion of a single character. It is the
- * responsibility of the caller to ensure that the target buffer is large
- * enough to hold the result of the conversion (at least NLS_MAX_CHARSET_SIZE).
- */
-static int
-cifs_mapchar(char *target, const __u16 src_char, const struct nls_table *cp,
-            bool mapchar)
+/* Convert character using the SFU - "Services for Unix" remapping range */
+static bool
+convert_sfu_char(const __u16 src_char, char *target)
 {
-       int len = 1;
-
-       if (!mapchar)
-               goto cp_convert;
-
        /*
         * BB: Cannot handle remapping UNI_SLASH until all the calls to
         *     build_path_from_dentry are modified, as they use slash as
@@ -106,19 +90,74 @@ cifs_mapchar(char *target, const __u16 src_char, const struct nls_table *cp,
                *target = '<';
                break;
        default:
-               goto cp_convert;
+               return false;
        }
+       return true;
+}
 
-out:
-       return len;
+/* Convert character using the SFM - "Services for Mac" remapping range */
+static bool
+convert_sfm_char(const __u16 src_char, char *target)
+{
+       switch (src_char) {
+       case SFM_COLON:
+               *target = ':';
+               break;
+       case SFM_ASTERISK:
+               *target = '*';
+               break;
+       case SFM_QUESTION:
+               *target = '?';
+               break;
+       case SFM_PIPE:
+               *target = '|';
+               break;
+       case SFM_GRTRTHAN:
+               *target = '>';
+               break;
+       case SFM_LESSTHAN:
+               *target = '<';
+               break;
+       case SFM_SLASH:
+               *target = '\\';
+               break;
+       default:
+               return false;
+       }
+       return true;
+}
+
+
+/*
+ * cifs_mapchar - convert a host-endian char to proper char in codepage
+ * @target - where converted character should be copied
+ * @src_char - 2 byte host-endian source character
+ * @cp - codepage to which character should be converted
+ * @map_type - How should the 7 NTFS/SMB reserved characters be mapped to UCS2?
+ *
+ * This function handles the conversion of a single character. It is the
+ * responsibility of the caller to ensure that the target buffer is large
+ * enough to hold the result of the conversion (at least NLS_MAX_CHARSET_SIZE).
+ */
+static int
+cifs_mapchar(char *target, const __u16 src_char, const struct nls_table *cp,
+            int maptype)
+{
+       int len = 1;
+
+       if ((maptype == SFM_MAP_UNI_RSVD) && convert_sfm_char(src_char, target))
+               return len;
+       else if ((maptype == SFU_MAP_UNI_RSVD) &&
+                 convert_sfu_char(src_char, target))
+               return len;
 
-cp_convert:
+       /* if character not one of seven in special remap set */
        len = cp->uni2char(src_char, target, NLS_MAX_CHARSET_SIZE);
        if (len <= 0) {
                *target = '?';
                len = 1;
        }
-       goto out;
+       return len;
 }
 
 /*
@@ -145,7 +184,7 @@ cp_convert:
  */
 int
 cifs_from_utf16(char *to, const __le16 *from, int tolen, int fromlen,
-                const struct nls_table *codepage, bool mapchar)
+               const struct nls_table *codepage, int map_type)
 {
        int i, charlen, safelen;
        int outlen = 0;
@@ -172,13 +211,13 @@ cifs_from_utf16(char *to, const __le16 *from, int tolen, int fromlen,
                 * conversion bleed into the null terminator
                 */
                if (outlen >= safelen) {
-                       charlen = cifs_mapchar(tmp, ftmp, codepage, mapchar);
+                       charlen = cifs_mapchar(tmp, ftmp, codepage, map_type);
                        if ((outlen + charlen) > (tolen - nullsize))
                                break;
                }
 
                /* put converted char into 'to' buffer */
-               charlen = cifs_mapchar(&to[outlen], ftmp, codepage, mapchar);
+               charlen = cifs_mapchar(&to[outlen], ftmp, codepage, map_type);
                outlen += charlen;
        }
 
@@ -267,7 +306,7 @@ cifs_strndup_from_utf16(const char *src, const int maxlen,
                if (!dst)
                        return NULL;
                cifs_from_utf16(dst, (__le16 *) src, len, maxlen, codepage,
-                              false);
+                              NO_MAP_UNI_RSVD);
        } else {
                len = strnlen(src, maxlen);
                len++;
index d8eac3b..30af32b 100644 (file)
 #define UNI_PIPE        (__u16) ('|' + 0xF000)
 #define UNI_SLASH       (__u16) ('\\' + 0xF000)
 
+/*
+ * Macs use an older "SFM" mapping of the symbols above. Fortunately it does
+ * not conflict (although almost does) with the mapping above.
+ */
+
+#define SFM_ASTERISK    ((__u16) 0xF021)
+#define SFM_QUESTION    ((__u16) 0xF025)
+#define SFM_COLON       ((__u16) 0xF022)
+#define SFM_GRTRTHAN    ((__u16) 0xF024)
+#define SFM_LESSTHAN    ((__u16) 0xF023)
+#define SFM_PIPE        ((__u16) 0xF027)
+#define SFM_SLASH       ((__u16) 0xF026)
+
+/*
+ * Mapping mechanism to use when one of the seven reserved characters is
+ * encountered.  We can only map using one of the mechanisms at a time
+ * since otherwise readdir could return directory entries which we would
+ * not be able to open
+ *
+ * NO_MAP_UNI_RSVD  = do not perform any remapping of the character
+ * SFM_MAP_UNI_RSVD = map reserved characters using SFM scheme (MAC compatible)
+ * SFU_MAP_UNI_RSVD = map reserved characters ala SFU ("mapchars" option)
+ *
+ */
+#define NO_MAP_UNI_RSVD                0
+#define SFM_MAP_UNI_RSVD       1
+#define SFU_MAP_UNI_RSVD       2
+
 /* Just define what we want from uniupr.h.  We don't want to define the tables
  * in each source file.
  */
@@ -75,7 +103,7 @@ extern const struct UniCaseRange CifsUniLowerRange[];
 
 #ifdef __KERNEL__
 int cifs_from_utf16(char *to, const __le16 *from, int tolen, int fromlen,
-                   const struct nls_table *codepage, bool mapchar);
+                   const struct nls_table *cp, int map_type);
 int cifs_utf16_bytes(const __le16 *from, int maxbytes,
                     const struct nls_table *codepage);
 int cifs_strtoUTF16(__le16 *, const char *, int, const struct nls_table *);
index 4934347..4ac7445 100644 (file)
@@ -431,7 +431,7 @@ find_domain_name(struct cifs_ses *ses, const struct nls_table *nls_cp)
                                                return -ENOMEM;
                                cifs_from_utf16(ses->domainName,
                                        (__le16 *)blobptr, attrsize, attrsize,
-                                       nls_cp, false);
+                                       nls_cp, NO_MAP_UNI_RSVD);
                                break;
                        }
                }
index d2141f1..5bf3d0a 100644 (file)
@@ -704,15 +704,21 @@ static int cifs_filldir(char *find_entry, struct file *file,
 
        if (file_info->srch_inf.unicode) {
                struct nls_table *nlt = cifs_sb->local_nls;
+               int map_type;
+
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SFM_CHR)
+                       map_type = SFM_MAP_UNI_RSVD;
+               else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR)
+                       map_type = SFU_MAP_UNI_RSVD;
+               else
+                       map_type = NO_MAP_UNI_RSVD;
 
                name.name = scratch_buf;
                name.len =
                        cifs_from_utf16((char *)name.name, (__le16 *)de.name,
                                        UNICODE_NAME_MAX,
                                        min_t(size_t, de.namelen,
-                                             (size_t)max_len), nlt,
-                                       cifs_sb->mnt_cifs_flags &
-                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+                                             (size_t)max_len), nlt, map_type);
                name.len -= nls_nullsize(nlt);
        } else {
                name.name = de.name;