diff --git a/include/ntfs-3g/unistr.h b/include/ntfs-3g/unistr.h index 5f0b467b..63f57588 100644 --- a/include/ntfs-3g/unistr.h +++ b/include/ntfs-3g/unistr.h @@ -47,6 +47,9 @@ extern ntfschar *ntfs_ucsndup(const ntfschar *s, u32 maxlen); extern void ntfs_name_upcase(ntfschar *name, u32 name_len, const ntfschar *upcase, const u32 upcase_len); +extern void ntfs_name_locase(ntfschar *name, u32 name_len, + const ntfschar *locase, const u32 locase_len); + extern void ntfs_file_value_upcase(FILE_NAME_ATTR *file_name_attr, const ntfschar *upcase, const u32 upcase_len); @@ -59,7 +62,11 @@ extern int ntfs_ucstombs(const ntfschar *ins, const int ins_len, char **outs, int outs_len); extern int ntfs_mbstoucs(const char *ins, ntfschar **outs); +extern char *ntfs_uppercase_mbs(const char *low, + const ntfschar *upcase, u32 upcase_len); + extern void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len); +extern ntfschar *ntfs_locase_table_build(const ntfschar *uc, u32 uc_cnt); extern ntfschar *ntfs_str2ucs(const char *s, int *len); diff --git a/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h index 140c6948..3998e974 100644 --- a/include/ntfs-3g/volume.h +++ b/include/ntfs-3g/volume.h @@ -232,6 +232,9 @@ struct _ntfs_volume { FILE_UpCase. */ u32 upcase_len; /* Length in Unicode characters of the upcase table. */ + ntfschar *locase; /* Lower case equivalents of all 65536 2-byte + Unicode characters. Only if option + case_ignore is set. */ ATTR_DEF *attrdef; /* Attribute definitions. Obtained from FILE_AttrDef. */ @@ -289,6 +292,7 @@ extern int ntfs_volume_get_free_space(ntfs_volume *vol); extern int ntfs_set_shown_files(ntfs_volume *vol, BOOL show_sys_files, BOOL show_hid_files, BOOL hide_dot_files); extern int ntfs_set_locale(void); +extern int ntfs_set_ignore_case(ntfs_volume *vol); #endif /* defined _NTFS_VOLUME_H */ diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 17bdc7b1..ffc0e27b 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -253,6 +253,7 @@ u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, INDEX_ROOT *ir; INDEX_ENTRY *ie; INDEX_ALLOCATION *ia; + IGNORE_CASE_BOOL case_sensitivity; u8 *index_end; ntfs_attr *ia_na; int eo, rc; @@ -277,6 +278,7 @@ u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, "%lld", (unsigned long long)dir_ni->mft_no); goto put_err_out; } + case_sensitivity = (NVolCaseSensitive(vol) ? CASE_SENSITIVE : IGNORE_CASE); /* Get to the index root value. */ ir = (INDEX_ROOT*)((u8*)ctx->attr + le16_to_cpu(ctx->attr->value_offset)); @@ -324,7 +326,7 @@ u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, rc = ntfs_names_full_collate(uname, uname_len, (ntfschar*)&ie->key.file_name.file_name, ie->key.file_name.file_name_length, - CASE_SENSITIVE, vol->upcase, vol->upcase_len); + case_sensitivity, vol->upcase, vol->upcase_len); /* * If uname collates before the name of the current entry, there * is definitely no such name in this index but we might need to @@ -466,7 +468,7 @@ descend_into_child_node: rc = ntfs_names_full_collate(uname, uname_len, (ntfschar*)&ie->key.file_name.file_name, ie->key.file_name.file_name_length, - CASE_SENSITIVE, vol->upcase, vol->upcase_len); + case_sensitivity, vol->upcase, vol->upcase_len); /* * If uname collates before the name of the current entry, there * is definitely no such name in this index but we might need to @@ -545,51 +547,68 @@ u64 ntfs_inode_lookup_by_mbsname(ntfs_inode *dir_ni, const char *name) int uname_len; ntfschar *uname = (ntfschar*)NULL; u64 inum; + char *cached_name; + const char *const_name; + + if (!NVolCaseSensitive(dir_ni->vol)) { + cached_name = ntfs_uppercase_mbs(name, + dir_ni->vol->upcase, dir_ni->vol->upcase_len); + const_name = cached_name; + } else { + cached_name = (char*)NULL; + const_name = name; + } + if (const_name) { #if CACHE_LOOKUP_SIZE - struct CACHED_LOOKUP item; - struct CACHED_LOOKUP *cached; /* * fetch inode from cache */ - if (dir_ni->vol->lookup_cache) { - item.name = name; - item.namesize = strlen(name) + 1; - item.parent = dir_ni->mft_no; - cached = (struct CACHED_LOOKUP*)ntfs_fetch_cache( - dir_ni->vol->lookup_cache, GENERIC(&item), - lookup_cache_compare); - if (cached) { - inum = cached->inum; - if (inum == (u64)-1) - errno = ENOENT; - } else { - /* Generate unicode name. */ - uname_len = ntfs_mbstoucs(name, &uname); - if (uname_len >= 0) { - inum = ntfs_inode_lookup_by_name(dir_ni, - uname, uname_len); - item.inum = inum; + if (dir_ni->vol->lookup_cache) { + struct CACHED_LOOKUP item; + struct CACHED_LOOKUP *cached; + + item.name = const_name; + item.namesize = strlen(const_name) + 1; + item.parent = dir_ni->mft_no; + cached = (struct CACHED_LOOKUP*)ntfs_fetch_cache( + dir_ni->vol->lookup_cache, + GENERIC(&item), lookup_cache_compare); + if (cached) { + inum = cached->inum; + if (inum == (u64)-1) + errno = ENOENT; + } else { + /* Generate unicode name. */ + uname_len = ntfs_mbstoucs(name, &uname); + if (uname_len >= 0) { + inum = ntfs_inode_lookup_by_name(dir_ni, + uname, uname_len); + item.inum = inum; /* enter into cache, even if not found */ - ntfs_enter_cache(dir_ni->vol->lookup_cache, + ntfs_enter_cache(dir_ni->vol->lookup_cache, GENERIC(&item), lookup_cache_compare); - free(uname); - } else + free(uname); + } else + inum = (s64)-1; + } + } else +#endif + { + /* Generate unicode name. */ + uname_len = ntfs_mbstoucs(cached_name, &uname); + if (uname_len >= 0) + inum = ntfs_inode_lookup_by_name(dir_ni, + uname, uname_len); + else inum = (s64)-1; } + if (cached_name) + free(cached_name); } else -#endif - { - /* Generate unicode name. */ - uname_len = ntfs_mbstoucs(name, &uname); - if (uname_len >= 0) - inum = ntfs_inode_lookup_by_name(dir_ni, - uname, uname_len); - else - inum = (s64)-1; - } + inum = (s64)-1; return (inum); } @@ -604,17 +623,29 @@ void ntfs_inode_update_mbsname(ntfs_inode *dir_ni, const char *name, u64 inum) #if CACHE_LOOKUP_SIZE struct CACHED_LOOKUP item; struct CACHED_LOOKUP *cached; + char *cached_name; if (dir_ni->vol->lookup_cache) { - item.name = name; - item.namesize = strlen(name) + 1; - item.parent = dir_ni->mft_no; - item.inum = inum; - cached = (struct CACHED_LOOKUP*)ntfs_enter_cache( + if (!NVolCaseSensitive(dir_ni->vol)) { + cached_name = ntfs_uppercase_mbs(name, + dir_ni->vol->upcase, dir_ni->vol->upcase_len); + item.name = cached_name; + } else { + cached_name = (char*)NULL; + item.name = name; + } + if (item.name) { + item.namesize = strlen(item.name) + 1; + item.parent = dir_ni->mft_no; + item.inum = inum; + cached = (struct CACHED_LOOKUP*)ntfs_enter_cache( dir_ni->vol->lookup_cache, GENERIC(&item), lookup_cache_compare); - if (cached) - cached->inum = inum; + if (cached) + cached->inum = inum; + if (cached_name) + free(cached_name); + } } #endif } @@ -859,6 +890,7 @@ static int ntfs_filldir(ntfs_inode *dir_ni, s64 *pos, u8 ivcn_bits, FILE_NAME_ATTR *fn = &ie->key.file_name; unsigned dt_type; BOOL metadata; + ntfschar *loname; int res; MFT_REF mref; @@ -888,9 +920,27 @@ static int ntfs_filldir(ntfs_inode *dir_ni, s64 *pos, u8 ivcn_bits, || !(fn->file_attributes & FILE_ATTR_HIDDEN))) || (NVolShowSysFiles(dir_ni->vol) && (NVolShowHidFiles(dir_ni->vol) || metadata))) { - res = filldir(dirent, fn->file_name, fn->file_name_length, - fn->file_name_type, *pos, - mref, dt_type); + if (NVolCaseSensitive(dir_ni->vol)) { + res = filldir(dirent, fn->file_name, + fn->file_name_length, + fn->file_name_type, *pos, + mref, dt_type); + } else { + loname = (ntfschar*)ntfs_malloc(2*fn->file_name_length); + if (loname) { + memcpy(loname, fn->file_name, + 2*fn->file_name_length); + ntfs_name_locase(loname, fn->file_name_length, + dir_ni->vol->locase, + dir_ni->vol->upcase_len); + res = filldir(dirent, loname, + fn->file_name_length, + fn->file_name_type, *pos, + mref, dt_type); + free(loname); + } else + res = -1; + } } else res = 0; return (res); diff --git a/libntfs-3g/unistr.c b/libntfs-3g/unistr.c index e65df992..5d16294d 100644 --- a/libntfs-3g/unistr.c +++ b/libntfs-3g/unistr.c @@ -389,6 +389,21 @@ void ntfs_name_upcase(ntfschar *name, u32 name_len, const ntfschar *upcase, name[i] = upcase[u]; } +/** + * ntfs_name_locase - Map a Unicode name to its lowercase equivalent + */ +void ntfs_name_locase(ntfschar *name, u32 name_len, const ntfschar *locase, + const u32 locase_len) +{ + u32 i; + u16 u; + + if (locase) + for (i = 0; i < name_len; i++) + if ((u = le16_to_cpu(name[i])) < locase_len) + name[i] = locase[u]; +} + /** * ntfs_file_value_upcase - Convert a filename to upper case * @file_name_attr: @@ -1035,6 +1050,61 @@ err_out: return -1; } +/* + * Turn a UTF8 name uppercase + * + * Returns an allocated uppercase name which has to be freed by caller + * or NULL if there is an error (described by errno) + */ + +char *ntfs_uppercase_mbs(const char *low, + const ntfschar *upcase, u32 upcase_size) +{ + int size; + char *upp; + u32 wc; + int n; + const char *s; + char *t; + + size = strlen(low); + upp = (char*)ntfs_malloc(3*size + 1); + if (upp) { + s = low; + t = upp; + do { + n = utf8_to_unicode(&wc, s); + if (n > 0) { + if (wc < upcase_size) + wc = le16_to_cpu(upcase[wc]); + if (wc < 0x80) + *t++ = wc; + else if (wc < 0x800) { + *t++ = (0xc0 | ((wc >> 6) & 0x3f)); + *t++ = 0x80 | (wc & 0x3f); + } else if (wc < 0x10000) { + *t++ = 0xe0 | (wc >> 12); + *t++ = 0x80 | ((wc >> 6) & 0x3f); + *t++ = 0x80 | (wc & 0x3f); + } else { + *t++ = 0xf0 | ((wc >> 18) & 7); + *t++ = 0x80 | ((wc >> 12) & 63); + *t++ = 0x80 | ((wc >> 6) & 0x3f); + *t++ = 0x80 | (wc & 0x3f); + } + s += n; + } + } while (n > 0); + if (n < 0) { + free(upp); + upp = (char*)NULL; + errno = EILSEQ; + } + *t = 0; + } + return (upp); +} + /** * ntfs_upcase_table_build - build the default upcase table for NTFS * @uc: destination buffer where to store the built table @@ -1106,6 +1176,38 @@ void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len) } } +/* + * Build a table for converting to lower case + * + * This is only meaningful when there is a single lower case + * character leading to an upper case one, and currently the + * only exception is the greek letter sigma which has a single + * upper case glyph (code U+03A3), but two lower case glyphs + * (code U+03C3 and U+03C2, the latter to be used at the end + * of a word). In the following implementation the upper case + * sigma will be lowercased as U+03C3. + */ + +ntfschar *ntfs_locase_table_build(const ntfschar *uc, u32 uc_cnt) +{ + ntfschar *lc; + u32 upp; + u32 i; + + lc = (ntfschar*)ntfs_malloc(uc_cnt*sizeof(ntfschar)); + if (lc) { + for (i=0; ivol_name); free(v->upcase); + if (v->locase) free(v->locase); free(v->attrdef); free(v); @@ -488,6 +489,9 @@ ntfs_volume *ntfs_volume_startup(struct ntfs_device *dev, unsigned long flags) ntfs_upcase_table_build(vol->upcase, vol->upcase_len * sizeof(ntfschar)); + /* Default with no locase table and case sensitive file names */ + vol->locase = (ntfschar*)NULL; + NVolSetCaseSensitive(vol); /* by default, all files are shown and not marked hidden */ NVolSetShowSysFiles(vol); @@ -1229,6 +1233,28 @@ int ntfs_set_shown_files(ntfs_volume *vol, return (res); } +/* + * Set ignore case mode + */ + +int ntfs_set_ignore_case(ntfs_volume *vol) +{ + int res; + + res = -1; + if (vol && vol->upcase) { + vol->locase = ntfs_locase_table_build(vol->upcase, + vol->upcase_len); + if (vol->locase) { + NVolClearCaseSensitive(vol); + res = 0; + } + } + if (res) + ntfs_log_error("Failed to set ignore_case mode\n"); + return (res); +} + /** * ntfs_mount - open ntfs volume * @name: name of device/file to open diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 58f7cf76..856a3311 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -211,6 +211,7 @@ typedef struct { BOOL show_sys_files; BOOL show_hid_files; BOOL hide_dot_files; + BOOL ignore_case; BOOL silent; BOOL recover; BOOL hiberfile; @@ -3630,6 +3631,7 @@ static int ntfs_fuse_init(void) static int ntfs_open(const char *device) { unsigned long flags = 0; + ntfs_volume *vol; if (!ctx->blkdev) flags |= MS_EXCLUSIVE; @@ -3640,28 +3642,31 @@ static int ntfs_open(const char *device) if (ctx->hiberfile) flags |= MS_IGNORE_HIBERFILE; - ctx->vol = ntfs_mount(device, flags); - if (!ctx->vol) { + ctx->vol = vol = ntfs_mount(device, flags); + if (!vol) { ntfs_log_perror("Failed to mount '%s'", device); goto err_out; } - if (ntfs_set_shown_files(ctx->vol, ctx->show_sys_files, + if (ntfs_set_shown_files(vol, ctx->show_sys_files, ctx->show_hid_files, ctx->hide_dot_files)) goto err_out; + + if (ctx->ignore_case && ntfs_set_ignore_case(vol)) + goto err_out; - ctx->vol->free_clusters = ntfs_attr_get_free_bits(ctx->vol->lcnbmp_na); - if (ctx->vol->free_clusters < 0) { + vol->free_clusters = ntfs_attr_get_free_bits(vol->lcnbmp_na); + if (vol->free_clusters < 0) { ntfs_log_perror("Failed to read NTFS $Bitmap"); goto err_out; } - ctx->vol->free_mft_records = ntfs_get_nr_free_mft_records(ctx->vol); - if (ctx->vol->free_mft_records < 0) { + vol->free_mft_records = ntfs_get_nr_free_mft_records(vol); + if (vol->free_mft_records < 0) { ntfs_log_perror("Failed to calculate free MFT records"); goto err_out; } - if (ctx->hiberfile && ntfs_volume_check_hiberfile(ctx->vol, 0)) { + if (ctx->hiberfile && ntfs_volume_check_hiberfile(vol, 0)) { if (errno != EPERM) goto err_out; if (ntfs_fuse_rm((fuse_req_t)NULL,FILE_root,"hiberfil.sys")) @@ -3820,6 +3825,10 @@ static char *parse_mount_options(const char *orig_opts) if (bogus_option_value(val, "hide_dot_files")) goto err_exit; ctx->hide_dot_files = TRUE; + } else if (!strcmp(opt, "ignore_case")) { + if (bogus_option_value(val, "ignore_case")) + goto err_exit; + ctx->ignore_case = TRUE; } else if (!strcmp(opt, "silent")) { if (bogus_option_value(val, "silent")) goto err_exit; diff --git a/src/ntfs-3g.8.in b/src/ntfs-3g.8.in index a99fd55c..cf891c05 100644 --- a/src/ntfs-3g.8.in +++ b/src/ntfs-3g.8.in @@ -150,6 +150,11 @@ Force the mounting even if the NTFS logfile is unclean. The logfile will be unconditionally cleared. Use this option with caution and for your own responsibility. .TP +.B ignore_case +(only with lowntfs-3g) Ignore character case when accessing a file +(\fBFOO\fR, \fBFoo\fR, \fBfoo\fR, etc. designate the same file). All +files are displayed with lower case in directory listings. +.TP .B remove_hiberfile Unlike in case of read-only mount, the read-write mount is denied if the NTFS volume is hibernated. One needs either to resume Windows and