rsync and different UTF normalization in APFS vs HFS+ (macports-users Digest, Vol 167, Issue 3)

Jim DeLaHunt list+macports-users at
Sat Jul 4 00:42:56 UTC 2020

Ces VLC, Ryan:

On 2020-07-03 04:00, Ryan Schmidt wrote:
> On Jul 3, 2020, at 04:53, Ces VLC wrote:
>> …in filenames that have UTF international characters, I often hit the problem of rsync deleting a file and then rewriting it again, just because the UTF normalization is not the same in both disks….
>> …People suggest to use the --iconv flag, but... does this mean that you need to use different iconv settings depending on whether your transfer is APFS->HFS+ or HFS+->APFS? If affirmative, it would be a bit clumsy, IMHO (first detect the disk FS, then choose proper flags).
>> Isn't there some way for dealing with this more conveniently, in a way that you don't need to check the disk FS before invoking rsync?
> The issue I'm familiar with is that there can be several valid ways to represent certain strings of UTF-8 characters….

I don't know nearly enough about rsync, so I hope Ces VLC finds a good 
answer and that I can use it too.   I don't know nearly as much as Ryan 
about macports, and I am grateful for all Ryan's work on macports. 
However, I do know a bit about Unicode, and I have recently read up a 
bit on filenames in APFS, HFS+, and ext3/4 of Linux. Let me try to 
explain the difference between filenames which I suspect Ces is 
encountering. I will say something similar to Ryan, but with important 
differences in terminology related to Unicode. I may get details of the 
file systems wrong. And, none of my examples are tested, so some of them 
may be incorrect.

Fundamental question: when is a filename {Na} on file system A the 
"same" as filename {Nb} on filesystem B? The answer is complex.

Fundamental fact: different filesystems store filenames as different 
data structures, with different semantics attached to the data. 
Comparing filename {Na} to {Nb} requires converting {Na} to the data 
structures used in filesystem B, and doing the appropriate kind of 

  * HFS+ stores filenames as 16-bit code units with UTF-16BE semantics.
    The file system API receives filenames as an array of Unicode
    characters. It normalises the name to NFD(-ish) before writing. IIRC
    an HFS+ file system can be case-insensitive (more common) or
  * APFS stores filenames as 8-bit code units with UTF-8 semantics, and
    also as a 22-bit hash. The file system API receives filenames as an
    array of Unicode characters. It does not normalise the name when
    writing; the filename's characters are preserved in the filesystem.
    It also computes the 22-bit hash from the filename. However, the
    filesystem can be configured to normalise the filename before using
    it to compute the hash. Thus the filesystem API can do
    normalisation-insensitive comparison of filenames, by comparing
    their hash values but not the filename code units.  See
    section "j_drec_hashed_key_t".
  * ext 3/4 stores filenames as 8-bit code units with no semantics
    (except that byte values 0x00 and 0x2F '/' are special). The Posix
    file system API receives filenames as 8-bit code units and writes
    them as is. The filename's bytes are preserved in the filesystem.
    Filename comparisons are 8-bit code unit to code unit, with no
    interpretation as Unicode, or Unicode normalisation. Thus
    comparisons are normalisation-sensitive.
  * I suspect (but haven't confirmed) that rsync transmits filenames as
    sequences of bytes, possibly converted to UTF-8 code units via
    --iconv, but without any normalisation.


  * Unicode character: an abstract concept named by a integer value
    between 0 and about 1.1 million (0x10FFFF).
  * Code unit: a unit of storage for characters. Unicode defines 8-bit,
    16-bit, and 32-bit code units. The 16-bit and 32-bit code units have
    variants which map to bytes in "big-endian" (BE) and "little-endian"
    (LE) forms.
  * UTF (Unicode Transformation Format): an algorithm for mapping
    between Unicode characters and code unit sequences of various
    lengths. UTF-8 maps between Unicode characters and sequences of 1-4
    8-bit code units. UTF-16BE maps between Unicode characters and
    sequences of 1-2 16-bit big-endian code units. UTF-32BE maps between
    Unicode characters and single 32-bit big-endian code units.
  * Normalisation: an algorithm for taking arbitrary Unicode character
    sequences and removing some differences in representation, so that
    they are more useful for certain operations. One of these operations
    is comparison for equality. Background: Unicode provides multiple
    ways to represent the same user-perceived writing system unit. e.g.
    U+2128 Angstrom Sign (Å), U+00C5 Latin Capital Letter A with Ring
    Above (Å), and U+0041 Latin Capital Letter A  U+030A Combining Ring
    Above (Å, i.e. A˚) are different for some purposes, but the same
    for other purposes, including normalisation. See UAX #15 /Unicode
    Normalization Forms/ <>.
  * NFD: a normalisation algorithm which mostly decomposes compound
    characters: U+00C5 (Å) becomes U+0041 U+030A (Å, i.e. A˚).
  * Sensitive and insensitive: whether a difference between characters
    is significant or not significant when testing for "is the same".
    File systems can be case-sensitive, in which case Case.txt and
    cAsE.Txt are different; or they can be case-insensitive, in which
    case the two names are the same. Similarly, file systems can be
    normalisation-sensitive, in which case 5Å.svg and 5Å.svg are
    different, or they can be normalisation-insensitive, in which case
    they are the same.
  * Preserving and not-preserving: whether a difference between
    characters, present when names are written to a file system, is
    still present when the file names are read back out of the file
    system. DOS 8.3 FAT filesystems are case-insenstive and
    case-non-preserving: write "case.txt", and you get back "CASE.TXT".
    Similarly, file systems can be normalisation-preserving or
    normalisation-non-preserving. If you write 5Å.svg and 5Å.svg to
    HFS+, which is normalisation-insenstive and
    normalisation-non-preserving, you get back 5Å.svg. If you write
    them to APFS, which is normalisation-/preserving/, though
    normalisation-insenstive, you get back the same 5Å.svg and 5Å.svg.

So, the challenge which Ces VLC is giving to rsync is, read a filename 
{Na} from APFS filesystem A as Unicode characters from A's API, convert 
the name to UTF-8 code units, don't touch normalisation, convert to a 
name {Nb} on HFS+ filesystem B using B's API, and save the file in B. 
HFS+ on B normalises it to {Nb_norm}. Later, read filename {Na} from A, 
convert it to UTF-8, convert it to name {Nb} on B; is this the "same" as 
the existing filename {Nb_norm} on B?

There is an Rsync FAQ which might be relevant: "rsync recopies the same 
files" <>. it suggests fixing the 
problem using --iconv to specify filename conversions. I haven't looked 
into rsync enough to know if it will solve the problem. The impression I 
get is that rsync will not "first detect the disk FS, then choose proper 
flags". I suspect you will have to do that when you invoke rsync, using 
your knowledge of the source and destination filesystems.

I'm sorry this is so wordy, and I hope you see the distinctions I'm 
trying to explain. And, I hope this helps you figure out a solution. 
Please let the list know what you find out.

Best regards,
      —Jim DeLaHunt, software engineer, Vancouver, Canada

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the macports-users mailing list