<ulclass="crumbs"><li><ahref="../webs.html">★</a></li><li><ahref="index.html">foundation</a></li><li><ahref="index.html#3">Chapter 3: The Operating System</a></li><li><b>Case-Insensitive Filenames</b></li></ul><pclass="purpose">On some of the Unix-derived file systems on which Inform runs, filenames are case-sensitive, so that FISH and fish might be different files. This makes extension files, installed by the user, prone to being missed. The code in this section provides a routine to carry out file opening as if filenames are case-insensitive, and is used only for extensions.</p>
<ulclass="toc"><li><ahref="#SP3">§3. Use of POSIX</a></li><li><ahref="#SP4">§4. The routine</a></li><li><ahref="#SP4_1">§4.1. Looking for case-insensitive matches instead</a></li><li><ahref="#SP4_3">§4.3. Allocation and deallocation</a></li><li><ahref="#SP4_6">§4.6. Pathname hacking</a></li><li><ahref="#SP5">§5. Counting matches</a></li><li><ahref="#SP6">§6. Non-POSIX tail</a></li></ul><hrclass="tocbar">
<pclass="inwebparagraph"><aid="SP1"></a><b>§1. </b>This section contains a single utility routine, contributed by Adam
Thornton: a specialised, case-insensitive form of <codeclass="display"><spanclass="extract">fopen()</span></code>. It is specialised
in that it is designed for opening extensions, where the file path will be
case-correct up to the last two components of the path (the leafname and the
immediately containing directory), but where the casing may be wrong in those
last two components.
</p>
<pclass="inwebparagraph"><aid="SP2"></a><b>§2. </b>If the exact filename or extension directory (case-correct) exists,
<codeclass="display"><spanclass="extract">CIFilingSystem::fopen()</span></code> will choose it to open. If not, it will
use <codeclass="display"><spanclass="extract">strcasecmp()</span></code> to find a file or directory with the same name but
differing in case and use it instead. If it finds exactly one candidate file,
it will then attempt to <codeclass="display"><spanclass="extract">fopen()</span></code> it and return the result.
</p>
<pclass="inwebparagraph">If <codeclass="display"><spanclass="extract">CIFilingSystem::fopen()</span></code> succeeds, it returns a <codeclass="display"><spanclass="extract">FILE *</span></code>
(passed back to it from the underlying <codeclass="display"><spanclass="extract">fopen()</span></code>). If
<codeclass="display"><spanclass="extract">CIFilingSystem::fopen()</span></code> fails, it returns <codeclass="display"><spanclass="extract">NULL</span></code>, and
<codeclass="display"><spanclass="extract">errno</span></code> is set accordingly:
</p>
<ulclass="items"><li>(a) If no suitable file was found, <codeclass="display"><spanclass="extract">errno</span></code> is set to <codeclass="display"><spanclass="extract">ENOENT</span></code>.
</li><li>(b) If more than one possibility was found, but none of them exactly match
the supplied case, <codeclass="display"><spanclass="extract">errno</span></code> is set to <codeclass="display"><spanclass="extract">EBADF</span></code>.
</li><li>(c) Note that if multiple directories which match case-insensitively are
found, but none is an exact match, <codeclass="display"><spanclass="extract">EBADF</span></code> will be set regardless of the
contents of the directories.
</li><li>(d) If <codeclass="display"><spanclass="extract">CIFilingSystem::fopen()</span></code> fails during its allocation of
space to hold its intermediate strings for comparison, or for its various
data structures, <codeclass="display"><spanclass="extract">errno</span></code> is set to <codeclass="display"><spanclass="extract">ENOMEM</span></code>.
</li><li>(e) If an unambiguous filename is found but the <codeclass="display"><spanclass="extract">fopen()</span></code> fails, <codeclass="display"><spanclass="extract">errno</span></code> is
left at whatever value the underlying <codeclass="display"><spanclass="extract">fopen()</span></code> set it to.
</li></ul>
<pclass="inwebparagraph"><aid="SP3"></a><b>§3. Use of POSIX. </b>The routine is available only on POSIX platforms where <codeclass="display"><spanclass="extract">PLATFORM_POSIX</span></code>
is defined (see "Platform-Specific Definitions"). In practice this means
everywhere except Windows, but all Windows file systems are case-preserving
<spanclass="comment">for efficiency's sake, though it's logically equivalent, we try...</span>
<spanclass="identifier">handle</span><spanclass="plain"> = </span><spanclass="identifier">fopen</span><spanclass="plain">(</span><spanclass="identifier">path</span><spanclass="plain">, </span><spanclass="identifier">mode</span><spanclass="plain">); </span><spanclass="reserved">if</span><spanclass="plain"> (</span><spanclass="identifier">handle</span><spanclass="plain">) </span><<spanclass="cwebmacro">Happy ending to ci-fopen</span><spanclass="cwebmacronumber">4.4</span>><spanclass="plain">;</span>
<<spanclass="cwebmacro">Find the length of the path, giving an error if it is empty or NULL</span><spanclass="cwebmacronumber">4.6</span>><spanclass="plain">;</span>
<<spanclass="cwebmacro">Allocate memory for strings large enough to hold any subpath of the path</span><spanclass="cwebmacronumber">4.3</span>><spanclass="plain">;</span>
<<spanclass="cwebmacro">Parse the path to break it into topdir path, extension directory and leafname</span><spanclass="cwebmacronumber">4.7</span>><spanclass="plain">;</span>
<spanclass="identifier">topdir</span><spanclass="plain"> = </span><spanclass="identifier">opendir</span><spanclass="plain">(</span><spanclass="identifier">topdirpath</span><spanclass="plain">); </span><spanclass="comment">whose pathname is assumed case-correct...</span>
<spanclass="reserved">if</span><spanclass="plain"> (</span><spanclass="identifier">topdir</span><spanclass="plain"> == </span><spanclass="identifier">NULL</span><spanclass="plain">) </span><<spanclass="cwebmacro">Sad ending to ci-fopen</span><spanclass="cwebmacronumber">4.5</span>><spanclass="plain">; </span><spanclass="comment">...so that failure is fatal; <codeclass="display"><spanclass="extract">errno</span></code> is set by <codeclass="display"><spanclass="extract">opendir</span></code></span>
<spanclass="identifier">extdir</span><spanclass="plain"> = </span><spanclass="identifier">opendir</span><spanclass="plain">(</span><spanclass="identifier">workstring</span><spanclass="plain">); </span><spanclass="comment">try with supplied extension directory name</span>
<spanclass="reserved">if</span><spanclass="plain"> (</span><spanclass="identifier">extdir</span><spanclass="plain"> == </span><spanclass="identifier">NULL</span><spanclass="plain">) </span><<spanclass="cwebmacro">Try to find a unique insensitively matching directory name in topdir</span><spanclass="cwebmacronumber">4.1</span>>
<spanclass="identifier">handle</span><spanclass="plain"> = </span><spanclass="identifier">fopen</span><spanclass="plain">(</span><spanclass="identifier">workstring</span><spanclass="plain">, </span><spanclass="identifier">mode</span><spanclass="plain">); </span><spanclass="comment">try with supplied name</span>
<spanclass="reserved">if</span><spanclass="plain"> (</span><spanclass="identifier">handle</span><spanclass="plain">) </span><<spanclass="cwebmacro">Happy ending to ci-fopen</span><spanclass="cwebmacronumber">4.4</span>><spanclass="plain">;</span>
<<spanclass="cwebmacro">Try to find a unique insensitively matching entry in extdir</span><spanclass="cwebmacronumber">4.2</span>><spanclass="plain">;</span>
<spanclass="plain">}</span>
</pre>
<pclass="inwebparagraph"></p>
<pclass="endnote">The function CIFilingSystem::fopen is used in <ahref="#SP6">§6</a>, 3/fln (<ahref="3-fln.html#SP10">§10</a>).</p>
<pclass="inwebparagraph"><aid="SP4_1"></a><b>§4.1. Looking for case-insensitive matches instead. </b>We emerge from the following only in the happy case where a unique matching
directory name can be found.
</p>
<pclass="macrodefinition"><codeclass="display">
<<spanclass="cwebmacrodefn">Try to find a unique insensitively matching directory name in topdir</span><spanclass="cwebmacronumber">4.1</span>> =
<spanclass="identifier">errno</span><spanclass="plain"> = </span><spanclass="identifier">EBADF</span><spanclass="plain">; </span><<spanclass="cwebmacro">Sad ending to ci-fopen</span><spanclass="cwebmacronumber">4.5</span>><spanclass="plain">;</span>
<spanclass="plain">}</span>
</pre>
<pclass="inwebparagraph"></p>
<pclass="endnote">This code is used in <ahref="#SP4">§4</a>.</p>
<pclass="inwebparagraph"><aid="SP4_3"></a><b>§4.3. Allocation and deallocation. </b>We use six strings to hold full or partial pathnames.
</p>
<pclass="macrodefinition"><codeclass="display">
<<spanclass="cwebmacrodefn">Allocate memory for strings large enough to hold any subpath of the path</span><spanclass="cwebmacronumber">4.3</span>> =
<pclass="endnote">This code is used in <ahref="#SP4">§4</a> (twice), <ahref="#SP4_2">§4.2</a>.</p>
<pclass="inwebparagraph"><aid="SP4_5"></a><b>§4.5. </b>...and otherwise <codeclass="display"><spanclass="extract">NULL</span></code>, having already set <codeclass="display"><spanclass="extract">errno</span></code> with the reason why.
</p>
<pclass="macrodefinition"><codeclass="display">
<<spanclass="cwebmacrodefn">Sad ending to ci-fopen</span><spanclass="cwebmacronumber">4.5</span>> =
</code></p>
<preclass="displaydefn">
<<spanclass="cwebmacro">Prepare to exit ci-fopen cleanly</span><spanclass="cwebmacronumber">4.4.1</span>><spanclass="plain">;</span>
<pclass="endnote">This code is used in <ahref="#SP4">§4</a>, <ahref="#SP4_1">§4.1</a> (three times), <ahref="#SP4_2">§4.2</a> (three times), <ahref="#SP4_3">§4.3</a> (6 times).</p>
<spanclass="plain">topdirpath| is |/Users/bobama/Library/Inform/Extensions</span>
, and its casing is correct <spanclass="plain">ciextdirpath| is |Hillary Clinton</span>
, but its casing may not be correct <spanclass="plain">ciextname| is |Health Care.i7x</span>
, but its casing may not be correct</pre>
<pclass="inwebparagraph">The contents of <codeclass="display"><spanclass="extract">workstring</span></code> are not significant afterwards.
</p>
<pclass="macrodefinition"><codeclass="display">
<<spanclass="cwebmacrodefn">Parse the path to break it into topdir path, extension directory and leafname</span><spanclass="cwebmacronumber">4.7</span>> =
<pclass="endnote">This code is used in <ahref="#SP4">§4</a>.</p>
<pclass="inwebparagraph"><aid="SP5"></a><b>§5. Counting matches. </b>We count the number of names within the directory which case-insensitively
match against <codeclass="display"><spanclass="extract">name</span></code>, and copy the last which matches into <codeclass="display"><spanclass="extract">last_match</span></code>.
This must be at least as long as <codeclass="display"><spanclass="extract">name</span></code>. (We ought to be just a little careful
in case of improbable cases where the matched name contains a different
number of characters from <codeclass="display"><spanclass="extract">name</span></code>, for instance because on a strict reading
of Unicode "SS" is casing-equivalent to the eszet, but it's unlikely
that many contemporary implementations of <codeclass="display"><spanclass="extract">strcasecmp</span></code> are aware of this,
and in any case the code above contains much larger buffers than needed.)
<pclass="endnote">The function CIFilingSystem::match_in_directory is used in <ahref="#SP4_1">§4.1</a>, <ahref="#SP4_2">§4.2</a>.</p>
<pclass="inwebparagraph"><aid="SP6"></a><b>§6. Non-POSIX tail. </b>On platforms without POSIX directory handling, we revert to regular <codeclass="display"><spanclass="extract">fopen</span></code>.