Skip to content

Commit 9b5a8e1

Browse files
dschoGit for Windows Build Agent
authored and
Git for Windows Build Agent
committed
Merge branch 'mingw-reserved-filenames'
This topic branch refuses to create files with reserved file names, such as `aux.c` or `nul.txt`. This addresses #679. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 36e2dca + 171871a commit 9b5a8e1

File tree

3 files changed

+122
-24
lines changed

3 files changed

+122
-24
lines changed

compat/mingw.c

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ int mingw_mkdir(const char *path, int mode)
416416
int ret;
417417
wchar_t wpath[MAX_PATH];
418418

419-
if (!is_valid_win32_path(path)) {
419+
if (!is_valid_win32_path(path, 0)) {
420420
errno = EINVAL;
421421
return -1;
422422
}
@@ -513,21 +513,21 @@ int mingw_open (const char *filename, int oflags, ...)
513513
mode = va_arg(args, int);
514514
va_end(args);
515515

516-
if (!is_valid_win32_path(filename)) {
516+
if (!is_valid_win32_path(filename, !create)) {
517517
errno = create ? EINVAL : ENOENT;
518518
return -1;
519519
}
520520

521-
if (filename && !strcmp(filename, "/dev/null"))
522-
filename = "nul";
523-
524521
if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
525522
open_fn = mingw_open_append;
526523
else
527524
open_fn = _wopen;
528525

529-
if (xutftowcs_path(wfilename, filename) < 0)
526+
if (filename && !strcmp(filename, "/dev/null"))
527+
wcscpy(wfilename, L"nul");
528+
else if (xutftowcs_path(wfilename, filename) < 0)
530529
return -1;
530+
531531
fd = open_fn(wfilename, oflags, mode);
532532

533533
if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
@@ -584,16 +584,18 @@ FILE *mingw_fopen (const char *filename, const char *otype)
584584
int hide = needs_hiding(filename);
585585
FILE *file;
586586
wchar_t wfilename[MAX_PATH], wotype[4];
587-
if (!is_valid_win32_path(filename)) {
587+
if (filename && !strcmp(filename, "/dev/null"))
588+
wcscpy(wfilename, L"nul");
589+
else if (!is_valid_win32_path(filename, 1)) {
588590
int create = otype && strchr(otype, 'w');
589591
errno = create ? EINVAL : ENOENT;
590592
return NULL;
591-
}
592-
if (filename && !strcmp(filename, "/dev/null"))
593-
filename = "nul";
594-
if (xutftowcs_path(wfilename, filename) < 0 ||
595-
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
593+
} else if (xutftowcs_path(wfilename, filename) < 0)
596594
return NULL;
595+
596+
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
597+
return NULL;
598+
597599
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
598600
error("could not unhide %s", filename);
599601
return NULL;
@@ -611,16 +613,18 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
611613
int hide = needs_hiding(filename);
612614
FILE *file;
613615
wchar_t wfilename[MAX_PATH], wotype[4];
614-
if (!is_valid_win32_path(filename)) {
616+
if (filename && !strcmp(filename, "/dev/null"))
617+
wcscpy(wfilename, L"nul");
618+
else if (!is_valid_win32_path(filename, 1)) {
615619
int create = otype && strchr(otype, 'w');
616620
errno = create ? EINVAL : ENOENT;
617621
return NULL;
618-
}
619-
if (filename && !strcmp(filename, "/dev/null"))
620-
filename = "nul";
621-
if (xutftowcs_path(wfilename, filename) < 0 ||
622-
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
622+
} else if (xutftowcs_path(wfilename, filename) < 0)
623+
return NULL;
624+
625+
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
623626
return NULL;
627+
624628
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
625629
error("could not unhide %s", filename);
626630
return NULL;
@@ -2741,14 +2745,16 @@ static void setup_windows_environment(void)
27412745
}
27422746
}
27432747

2744-
int is_valid_win32_path(const char *path)
2748+
int is_valid_win32_path(const char *path, int allow_literal_nul)
27452749
{
2750+
const char *p = path;
27462751
int preceding_space_or_period = 0, i = 0, periods = 0;
27472752

27482753
if (!protect_ntfs)
27492754
return 1;
27502755

27512756
skip_dos_drive_prefix((char **)&path);
2757+
goto segment_start;
27522758

27532759
for (;;) {
27542760
char c = *(path++);
@@ -2763,7 +2769,83 @@ int is_valid_win32_path(const char *path)
27632769
return 1;
27642770

27652771
i = periods = preceding_space_or_period = 0;
2766-
continue;
2772+
2773+
segment_start:
2774+
switch (*path) {
2775+
case 'a': case 'A': /* AUX */
2776+
if (((c = path[++i]) != 'u' && c != 'U') ||
2777+
((c = path[++i]) != 'x' && c != 'X')) {
2778+
not_a_reserved_name:
2779+
path += i;
2780+
continue;
2781+
}
2782+
break;
2783+
case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
2784+
if ((c = path[++i]) != 'o' && c != 'O')
2785+
goto not_a_reserved_name;
2786+
c = path[++i];
2787+
if (c == 'm' || c == 'M') { /* COM<N> */
2788+
if (!isdigit(path[++i]))
2789+
goto not_a_reserved_name;
2790+
} else if (c == 'n' || c == 'N') { /* CON */
2791+
c = path[i + 1];
2792+
if ((c == 'i' || c == 'I') &&
2793+
((c = path[i + 2]) == 'n' ||
2794+
c == 'N') &&
2795+
path[i + 3] == '$')
2796+
i += 3; /* CONIN$ */
2797+
else if ((c == 'o' || c == 'O') &&
2798+
((c = path[i + 2]) == 'u' ||
2799+
c == 'U') &&
2800+
((c = path[i + 3]) == 't' ||
2801+
c == 'T') &&
2802+
path[i + 4] == '$')
2803+
i += 4; /* CONOUT$ */
2804+
} else
2805+
goto not_a_reserved_name;
2806+
break;
2807+
case 'l': case 'L': /* LPT<N> */
2808+
if (((c = path[++i]) != 'p' && c != 'P') ||
2809+
((c = path[++i]) != 't' && c != 'T') ||
2810+
!isdigit(path[++i]))
2811+
goto not_a_reserved_name;
2812+
break;
2813+
case 'n': case 'N': /* NUL */
2814+
if (((c = path[++i]) != 'u' && c != 'U') ||
2815+
((c = path[++i]) != 'l' && c != 'L') ||
2816+
(allow_literal_nul &&
2817+
!path[i + 1] && p == path))
2818+
goto not_a_reserved_name;
2819+
break;
2820+
case 'p': case 'P': /* PRN */
2821+
if (((c = path[++i]) != 'r' && c != 'R') ||
2822+
((c = path[++i]) != 'n' && c != 'N'))
2823+
goto not_a_reserved_name;
2824+
break;
2825+
default:
2826+
continue;
2827+
}
2828+
2829+
/*
2830+
* So far, this looks like a reserved name. Let's see
2831+
* whether it actually is one: trailing spaces, a file
2832+
* extension, or an NTFS Alternate Data Stream do not
2833+
* matter, the name is still reserved if any of those
2834+
* follow immediately after the actual name.
2835+
*/
2836+
i++;
2837+
if (path[i] == ' ') {
2838+
preceding_space_or_period = 1;
2839+
while (path[++i] == ' ')
2840+
; /* skip all spaces */
2841+
}
2842+
2843+
c = path[i];
2844+
if (c && c != '.' && c != ':' && c != '/' && c != '\\')
2845+
goto not_a_reserved_name;
2846+
2847+
/* contains reserved name */
2848+
return 0;
27672849
case '.':
27682850
periods++;
27692851
/* fallthru */

compat/mingw.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,17 @@ char *mingw_query_user_email(void);
465465
*
466466
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
467467
*
468+
* - correspond to reserved names (such as `AUX`, `PRN`, etc)
469+
*
470+
* The `allow_literal_nul` parameter controls whether the path `NUL` should
471+
* be considered valid (this makes sense e.g. before opening files, as it is
472+
* perfectly legitimate to open `NUL` on Windows, just as it is to open
473+
* `/dev/null` on Unix/Linux).
474+
*
468475
* Returns 1 upon success, otherwise 0.
469476
*/
470-
int is_valid_win32_path(const char *path);
471-
#define is_valid_path(path) is_valid_win32_path(path)
477+
int is_valid_win32_path(const char *path, int allow_literal_nul);
478+
#define is_valid_path(path) is_valid_win32_path(path, 0)
472479

473480
/**
474481
* Converts UTF-8 encoded string to UTF-16LE.

t/t0060-path-utils.sh

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,19 +465,28 @@ test_expect_success 'match .gitmodules' '
465465
'
466466

467467
test_expect_success MINGW 'is_valid_path() on Windows' '
468-
test-tool path-utils is_valid_path \
468+
test-tool path-utils is_valid_path \
469469
win32 \
470470
"win32 x" \
471471
../hello.txt \
472472
C:\\git \
473+
comm \
474+
conout.c \
475+
lptN \
473476
\
474477
--not \
475478
"win32 " \
476479
"win32 /x " \
477480
"win32." \
478481
"win32 . ." \
479482
.../hello.txt \
480-
colon:test
483+
colon:test \
484+
"AUX.c" \
485+
"abc/conOut\$ .xyz/test" \
486+
lpt8 \
487+
"lpt*" \
488+
Nul \
489+
"PRN./abc"
481490
'
482491

483492
test_done

0 commit comments

Comments
 (0)