Fossil

Check-in [9919dfbbaa]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Enhance the function to find case-preserved filenames on Windows to deal with non-ASCII filenames.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 9919dfbbaa2019e7eb7bbfdb2f821058e25553478e27079f95122d672a6fbdff
User & Date: florian 2024-10-15 05:18:00.000
References
2024-10-19
14:03
Another update for[9919dfbbaa]: Make sure reallocated buffers always grow, guard all buffer writes by overflow checks (next time use blobs), make sure the `case-sensitive' setting and command-line option are followed, and use brute force to achieve binary (vs.linguistic) file name comparison (only on older versions of Windows). check-in: fe6ef89f5f user: florian tags: trunk
2024-10-16
05:16
Amend [9919dfbbaa], again: Include an optional directory separator in buffer size calculation, guard against the unlikely (impossible?) case that a case-adjusted filename component round-tripping from UTF-8 to UTF-16 and back get longer, plus some unrelated white space fix. check-in: 49262642f4 user: florian tags: trunk
2024-10-15
05:36
Amend [9919dfbbaa]: fix a comment typo and rename a variable. check-in: d7d106227f user: florian tags: trunk
Context
2024-10-15
05:21
Mention the comment formatter updates in the change as a hint to users encountering problems with timeline output. check-in: 3c6e5a1e4c user: florian tags: trunk
05:18
Enhance the function to find case-preserved filenames on Windows to deal with non-ASCII filenames. check-in: 9919dfbbaa user: florian tags: trunk
2024-10-14
19:00
When building with tcl8.7 or higher, eliminate the call to Tcl_MakeSafe(), which does not exist in those versions (8.7 includes it in their headers but not their lib). Building with tcl8.7+ reveals an unrelated function-type conversion error caused (apparently) by changes in tcl8.7+, and that's still unresolved. check-in: 2d5a23e919 user: stephan tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/file.c.
1370
1371
1372
1373
1374
1375
1376

1377
1378
1379
1380
1381
1382
1383
**
** For case-sensitive filesystems, such as on Linux, this routine is
** just fossil_strdup().  But for case-insenstiive but "case preserving"
** filesystems, such as on MacOS or Windows, we want the filename to be
** in the preserved casing.  That's what this routine does.
*/
char *file_case_preferred_name(const char *zDir, const char *zPath){

  DIR *d;
  int i;
  char *zResult = 0;
  void *zNative = 0;

  if( filenames_are_case_sensitive() ){
    return fossil_strdup(zPath);







>







1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
**
** For case-sensitive filesystems, such as on Linux, this routine is
** just fossil_strdup().  But for case-insenstiive but "case preserving"
** filesystems, such as on MacOS or Windows, we want the filename to be
** in the preserved casing.  That's what this routine does.
*/
char *file_case_preferred_name(const char *zDir, const char *zPath){
#ifndef _WIN32 /* Call win32_file_case_preferred_name() on Windows. */
  DIR *d;
  int i;
  char *zResult = 0;
  void *zNative = 0;

  if( filenames_are_case_sensitive() ){
    return fossil_strdup(zPath);
1405
1406
1407
1408
1409
1410
1411



1412
1413
1414
1415
1416
1417
1418
      fossil_path_free(zUtf8);
    }
    closedir(d);
  }
  fossil_path_free(zNative);
  if( zResult==0 ) zResult = fossil_strdup(zPath);
  return zResult;



}

/*
** COMMAND: test-case-filename
**
** Usage: fossil test-case-filename DIRECTORY PATH PATH PATH ....
**







>
>
>







1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
      fossil_path_free(zUtf8);
    }
    closedir(d);
  }
  fossil_path_free(zNative);
  if( zResult==0 ) zResult = fossil_strdup(zPath);
  return zResult;
#else /* _WIN32 */
  return win32_file_case_preferred_name(zDir,zPath);
#endif /* _WIN32 */
}

/*
** COMMAND: test-case-filename
**
** Usage: fossil test-case-filename DIRECTORY PATH PATH PATH ....
**
Changes to src/winfile.c.
288
289
290
291
292
293
294






















































































































295
  }
  zUtf8 = fossil_path_to_utf8(zWide);
  fossil_free(zWide);
  for(i=0; zUtf8[i]; i++) if( zUtf8[i]=='\\' ) zUtf8[i] = '/';
  strncpy(zBuf, zUtf8, nBuf);
  fossil_path_free(zUtf8);
}






















































































































#endif /* _WIN32  -- This code is for win32 only */







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
  }
  zUtf8 = fossil_path_to_utf8(zWide);
  fossil_free(zWide);
  for(i=0; zUtf8[i]; i++) if( zUtf8[i]=='\\' ) zUtf8[i] = '/';
  strncpy(zBuf, zUtf8, nBuf);
  fossil_path_free(zUtf8);
}

/* Perform case-insensitive comparison of two UTF-16 file names. Try to load the
** CompareStringOrdinal() function on Windows Vista and newer, and resort to the
** lstrcmpiW() function on Windows XP.
*/
int win32_compare_filenames_nocase(
  const wchar_t *fn1,
  const wchar_t *fn2
){
  static FARPROC fnCompareStringOrdinal;
  static int try_fnCompareStringOrdinal;
  if( !try_fnCompareStringOrdinal ){
    fnCompareStringOrdinal =
      GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal");
    try_fnCompareStringOrdinal = 1;
  }
  if( fnCompareStringOrdinal ){
    return -2 + fnCompareStringOrdinal(fn1,-1,fn2,-1,1);
  }else{
    return lstrcmpiW(fn1,fn2);
  }
}

/* Helper macros to deal with directory separators. */
#define IS_DIRSEP(s,i) ( s[i]=='/' || s[i]=='\\' )
#define NEXT_DIRSEP(s,i) while( s[i] && s[i]!='/' && s[i]!='\\' ){i++;}

/* The Win32 version of file_case_preferred_name() from file.c, which is able to
** find case-preserved file names containing non-ASCII characters. The result is
** allocated by fossil_malloc() and *should* be free'd by tha caller. While this
** function usually gets canonicalized paths, it is able to handle any input and
** figure out more cases than the original:
**
**    fossil test-case-filename C:/ .//..\WINDOWS\/.//.\SYSTEM32\.\NOTEPAD.EXE
**    → Original:   .//..\WINDOWS\/.//.\SYSTEM32\.\NOTEPAD.EXE
**    → Modified:   .//..\Windows\/.//.\System32\.\notepad.exe
**
**    md ÄÖÜ
**    fossil test-case-filename ./\ .\äöü\/[empty]\\/
**    → Original:   ./äöü\/[empty]\\/
**    → Modified:   .\ÄÖÜ\/[empty]\\/
**
** The function preserves slashes and backslashes: only single file or directory
** components without directory separators ("basenames") are converted to UTF-16
** using fossil_utf8_to_path(), so bypassing its slash ↔ backslash translations.
** Note that the original function doesn't preserve all slashes and backslashes,
** for example in the second example above.
**
** NOTE: As of Windows 10, version 1803, case sensitivity may be enabled on a
** per-directory basis, as returned by NtQueryInformationFile() with the file
** information class FILE_CASE_SENSITIVE_INFORMATION. So this function may be
** changed to act like fossil_strdup() for files located in such directories.
*/
char *win32_file_case_preferred_name(
  const char *zBase,
  const char *zPath
){
  int cchBase = strlen(zBase);
  int cchPath = strlen(zPath);
  int cchBuf = cchBase + cchPath + 1;
  int cchRes = cchPath + 1;
  char *zBuf = fossil_malloc(cchBuf);
  char *zRes = fossil_malloc(cchRes);
  int i, j;
  memcpy(zBuf,zBase,cchBase);
  cchRes = 0;
  if( !IS_DIRSEP(zBuf,cchBase-1) ){
    zBuf[cchBase++]=L'/';
  }
  memcpy(zBuf+cchBase,zPath,cchPath+1);
  i = j = cchBase;
  while( 1 ){
    WIN32_FIND_DATAW fd;
    HANDLE hFind;
    wchar_t *wzBuf;
    char *zCompBuf = 0;
    char *zComp = &zBuf[i];
    int cchComp;
    char chSep;
    int fDone;
    if( IS_DIRSEP(zBuf,i) ){
      zRes[cchRes++] = zBuf[i];
      i = j = i+1;
      continue;
    }
    NEXT_DIRSEP(zBuf,j);
    fDone = zBuf[j]==0;
    chSep = zBuf[j];
    zBuf[j] = 0;                /* Truncate working buffer. */
    wzBuf = fossil_utf8_to_path(zBuf,0);
    hFind = FindFirstFileW(wzBuf,&fd);
    if( hFind!= INVALID_HANDLE_VALUE ){
      wchar_t *wzComp = fossil_utf8_to_path(zComp,0);
      FindClose(hFind);
      /* Test fd.cFileName, not fd.cAlternateFileName (classic 8.3 format). */
      if( win32_compare_filenames_nocase(wzComp,fd.cFileName)==0 ){
        zCompBuf = fossil_path_to_utf8(fd.cFileName);
        zComp = zCompBuf;
      }
      fossil_path_free(wzComp);
    }
    fossil_path_free(wzBuf);
    cchComp = strlen(zComp);
    memcpy(zRes+cchRes,zComp,cchComp);
    cchRes += cchComp;
    if( zCompBuf ){
      fossil_path_free(zCompBuf);
    }
    if( fDone ){
      zRes[cchRes] = 0;
      break;
    }
    zBuf[j] = chSep;            /* Undo working buffer truncation. */
    i = j;
  }
  fossil_free(zBuf);
  return zRes;
}
#endif /* _WIN32  -- This code is for win32 only */