diff --git a/dlls/ntdll/tests/path.c b/dlls/ntdll/tests/path.c index 5342d101b4f..9b6eecbfbbe 100644 --- a/dlls/ntdll/tests/path.c +++ b/dlls/ntdll/tests/path.c @@ -31,6 +31,7 @@ static BOOLEAN (WINAPI *pRtlIsNameLegalDOS8Dot3)(const UNICODE_STRING*,POEM_STRI static DWORD (WINAPI *pRtlGetFullPathName_U)(const WCHAR*,ULONG,WCHAR*,WCHAR**); static BOOLEAN (WINAPI *pRtlDosPathNameToNtPathName_U)(const WCHAR*, UNICODE_STRING*, WCHAR**, CURDIR*); static NTSTATUS (WINAPI *pRtlDosPathNameToNtPathName_U_WithStatus)(const WCHAR*, UNICODE_STRING*, WCHAR**, CURDIR*); +static NTSTATUS (WINAPI *pNtOpenFile)( HANDLE*, ACCESS_MASK, OBJECT_ATTRIBUTES*, IO_STATUS_BLOCK*, ULONG, ULONG ); static void test_RtlDetermineDosPathNameType_U(void) { @@ -619,6 +620,95 @@ static void test_RtlDosPathNameToNtPathName_U(void) SetCurrentDirectoryA(curdir); } +static void test_nt_names(void) +{ + static const struct { const WCHAR *root, *name; NTSTATUS expect; BOOL todo; } tests[] = + { + { NULL, L"\\??\\C:\\windows\\system32\\kernel32.dll", STATUS_SUCCESS }, + { NULL, L"\\??\\C:\\\\windows\\system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\\\windows\\system32\\", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\windows\\\\system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\windows\\system32\\.\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\windows\\system32\\..\\system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\.\\windows\\system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\windows\\system32\\kernel32.dll ", STATUS_OBJECT_NAME_NOT_FOUND }, + { NULL, L"\\??\\C:\\windows\\system32\\kernel32.dll..", STATUS_OBJECT_NAME_NOT_FOUND }, + { NULL, L"\\??\\C:\\windows \\system32 \\kernel32.dll", STATUS_OBJECT_PATH_NOT_FOUND }, + { NULL, L"\\??\\C:\\windows.\\system32.\\kernel32.dll", STATUS_OBJECT_PATH_NOT_FOUND }, + { NULL, L"\\??\\C:\\windows/system32/kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\windows\\system32\\kernel32.dll*", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"\\??\\C:\\windows\\system32?\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { NULL, L"C:\\windows\\system32?\\kernel32.dll", STATUS_OBJECT_PATH_SYNTAX_BAD }, + { NULL, L"/??\\C:\\windows\\system32\\kernel32.dll", STATUS_OBJECT_PATH_SYNTAX_BAD }, + { NULL, L"\\??" L"/C:\\windows\\system32\\kernel32.dll", STATUS_OBJECT_PATH_NOT_FOUND }, + { NULL, L"\\??\\C:/windows\\system32\\kernel32.dll", STATUS_OBJECT_PATH_NOT_FOUND }, + { NULL, L"\\??\\C:\\windows\\system32\\kernel32.dll\\", STATUS_OBJECT_NAME_INVALID, TRUE }, + { NULL, L"\\??\\C:\\windows\\system32\\kernel32.dll\\foo", STATUS_OBJECT_PATH_NOT_FOUND, TRUE }, + { NULL, L"\\??\\C:\\windows\\sys\001", STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\", NULL, STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\", NULL, STATUS_SUCCESS }, + { L"\\??\\C:\\\\", NULL, STATUS_OBJECT_NAME_INVALID }, + { L"/??\\C:\\", NULL, STATUS_OBJECT_PATH_SYNTAX_BAD }, + { L"\\??\\C:/", NULL, STATUS_OBJECT_NAME_NOT_FOUND }, + { L"\\??" L"/C:", NULL, STATUS_OBJECT_NAME_NOT_FOUND }, + { L"\\??" L"/C:\\", NULL, STATUS_OBJECT_PATH_NOT_FOUND }, + { L"\\??\\C:\\windows", NULL, STATUS_SUCCESS }, + { L"\\??\\C:\\windows\\", NULL, STATUS_SUCCESS }, + { L"\\??\\C:\\windows\\.", NULL, STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\.\\", NULL, STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\..", NULL, STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\..\\", NULL, STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\", L"windows\\system32\\kernel32.dll", STATUS_SUCCESS }, + { L"\\??\\C:\\windows", L"system32\\kernel32.dll", STATUS_SUCCESS }, + { L"\\??\\C:\\windows\\", L"system32\\kernel32.dll", STATUS_SUCCESS }, + { L"\\??\\C:\\windows\\", L"system32\\", STATUS_FILE_IS_A_DIRECTORY }, + { L"\\??\\C:\\windows\\", L"system32\\kernel32.dll\\", STATUS_OBJECT_NAME_INVALID, TRUE }, + { L"\\??\\C:\\windows\\", L"system32\\kernel32.dll\\foo", STATUS_OBJECT_PATH_NOT_FOUND, TRUE }, + { L"\\??\\C:\\windows\\", L"\\system32\\kernel32.dll", STATUS_INVALID_PARAMETER }, + { L"\\??\\C:\\windows\\", L"/system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\", L".\\system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\", L"..\\windows\\system32\\kernel32.dll", STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\", L".", STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\", L"..", STATUS_OBJECT_NAME_INVALID }, + { L"\\??\\C:\\windows\\", L"sys\001", STATUS_OBJECT_NAME_INVALID }, + { L"C:\\", L"windows\\system32\\kernel32.dll", STATUS_OBJECT_PATH_SYNTAX_BAD }, + }; + unsigned int i; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING nameW; + IO_STATUS_BLOCK io; + NTSTATUS status; + HANDLE handle; + + InitializeObjectAttributes( &attr, &nameW, OBJ_CASE_INSENSITIVE, 0, NULL ); + + for (i = 0; i < ARRAY_SIZE(tests); i++) + { + attr.RootDirectory = 0; + handle = 0; + status = STATUS_SUCCESS; + if (tests[i].root) + { + RtlInitUnicodeString( &nameW, tests[i].root ); + status = pNtOpenFile( &attr.RootDirectory, SYNCHRONIZE | FILE_LIST_DIRECTORY, &attr, &io, + FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT | + FILE_OPEN_FOR_BACKUP_INTENT | FILE_DIRECTORY_FILE ); + } + if (!status && tests[i].name) + { + RtlInitUnicodeString( &nameW, tests[i].name ); + status = pNtOpenFile( &handle, FILE_GENERIC_READ, &attr, &io, FILE_SHARE_READ, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE ); + } + if (attr.RootDirectory) NtClose( attr.RootDirectory ); + if (handle) NtClose( handle ); + todo_wine_if( tests[i].todo ) + ok( status == tests[i].expect, "%u: got %x / %x for %s + %s\n", i, status, tests[i].expect, + debugstr_w( tests[i].root ), debugstr_w( tests[i].name )); + } +} + + START_TEST(path) { HMODULE mod = GetModuleHandleA("ntdll.dll"); @@ -632,10 +722,12 @@ START_TEST(path) pRtlGetFullPathName_U = (void *)GetProcAddress(mod,"RtlGetFullPathName_U"); pRtlDosPathNameToNtPathName_U = (void *)GetProcAddress(mod, "RtlDosPathNameToNtPathName_U"); pRtlDosPathNameToNtPathName_U_WithStatus = (void *)GetProcAddress(mod, "RtlDosPathNameToNtPathName_U_WithStatus"); + pNtOpenFile = (void *)GetProcAddress(mod, "NtOpenFile"); test_RtlDetermineDosPathNameType_U(); test_RtlIsDosDeviceName_U(); test_RtlIsNameLegalDOS8Dot3(); test_RtlGetFullPathName_U(); test_RtlDosPathNameToNtPathName_U(); + test_nt_names(); } diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 9bf3b51ce8b..166ee85ebb7 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1228,13 +1228,9 @@ static BOOL is_hidden_file( const UNICODE_STRING *name ) if (show_dot_files) return FALSE; end = p = name->Buffer + name->Length/sizeof(WCHAR); - while (p > name->Buffer && IS_SEPARATOR(p[-1])) p--; - while (p > name->Buffer && !IS_SEPARATOR(p[-1])) p--; - if (p == end || *p != '.') return FALSE; - /* make sure it isn't '.' or '..' */ - if (p + 1 == end) return FALSE; - if (p[1] == '.' && p + 2 == end) return FALSE; - return TRUE; + while (p > name->Buffer && p[-1] == '\\') p--; + while (p > name->Buffer && p[-1] != '\\') p--; + return (p < end && *p == '.'); } @@ -2779,6 +2775,8 @@ static NTSTATUS get_dos_device( char **unix_name, int start_pos ) /* special case for drive devices */ if (dev[0] && dev[1] == ':' && !dev[2]) strcpy( dev + 1, "::" ); + if (strchr( dev, '/' )) goto failed; + for (;;) { if (!stat( *unix_name, &st )) @@ -2811,6 +2809,7 @@ static NTSTATUS get_dos_device( char **unix_name, int start_pos ) if (!new_name) return STATUS_BAD_DEVICE_TYPE; dev = NULL; /* last try */ } +failed: free( *unix_name ); *unix_name = NULL; return STATUS_BAD_DEVICE_TYPE; @@ -3090,22 +3089,42 @@ done: * Helper for nt_to_unix_file_name */ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer, int unix_len, int pos, - UINT disposition, BOOLEAN check_case ) + UINT disposition, BOOL is_unix ) { + static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, '/', 0 }; NTSTATUS status; int ret, len; struct stat st; char *unix_name = *buffer; + const WCHAR *ptr, *end; const BOOL redirect = NtCurrentTeb64() && !NtCurrentTeb64()->TlsSlots[WOW64_TLS_FILESYSREDIR]; - /* try a shortcut first */ + /* check syntax of individual components */ - while (name_len && IS_SEPARATOR(*name)) + for (ptr = name, end = name + name_len; ptr < end; ptr++) { - name++; - name_len--; + if (*ptr == '\\') return STATUS_OBJECT_NAME_INVALID; /* duplicate backslash */ + if (*ptr == '.') + { + if (ptr + 1 == end) return STATUS_OBJECT_NAME_INVALID; /* "." element */ + if (ptr[1] == '\\') return STATUS_OBJECT_NAME_INVALID; /* "." element */ + if (ptr[1] == '.') + { + if (ptr + 2 == end) return STATUS_OBJECT_NAME_INVALID; /* ".." element */ + if (ptr[2] == '\\') return STATUS_OBJECT_NAME_INVALID; /* ".." element */ + } + } + /* check for invalid characters (all chars except 0 are valid for unix) */ + for ( ; ptr < end && *ptr != '\\'; ptr++) + { + if (!*ptr) return STATUS_OBJECT_NAME_INVALID; + if (is_unix) continue; + if (*ptr < 32 || wcschr( invalid_charsW, *ptr )) return STATUS_OBJECT_NAME_INVALID; + } } + /* try a shortcut first */ + unix_name[pos] = '/'; ret = ntdll_wcstoumbs( name, name_len, unix_name + pos + 1, unix_len - pos - 1, TRUE ); if (ret >= 0 && ret < unix_len - pos - 1) @@ -3126,7 +3145,7 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer if (!name_len) /* empty name -> drive root doesn't exist */ return STATUS_OBJECT_PATH_NOT_FOUND; - if (check_case && !redirect && (disposition == FILE_OPEN || disposition == FILE_OVERWRITE)) + if (is_unix && !redirect && (disposition == FILE_OPEN || disposition == FILE_OVERWRITE)) return STATUS_OBJECT_NAME_NOT_FOUND; /* now do it component by component */ @@ -3137,9 +3156,9 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer BOOLEAN is_win_dir = FALSE; end = name; - while (end < name + name_len && !IS_SEPARATOR(*end)) end++; + while (end < name + name_len && *end != '\\') end++; next = end; - while (next < name + name_len && IS_SEPARATOR(*next)) next++; + if (next < name + name_len) next++; name_len -= next - name; /* grow the buffer if needed */ @@ -3153,7 +3172,7 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer } status = find_file_in_dir( unix_name, pos, name, end - name, - check_case, redirect ? &is_win_dir : NULL ); + is_unix, redirect ? &is_win_dir : NULL ); /* if this is the last element, not finding it is not necessarily fatal */ if (!name_len) @@ -3184,7 +3203,7 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer pos += strlen( unix_name + pos ); name = next; - if (is_win_dir && (len = get_redirect_path( unix_name, pos, name, name_len, check_case ))) + if (is_win_dir && (len = get_redirect_path( unix_name, pos, name, name_len, is_unix ))) { name += len; name_len -= len; @@ -3203,10 +3222,9 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer static NTSTATUS nt_to_unix_file_name_attr( const OBJECT_ATTRIBUTES *attr, char **name_ret, UNICODE_STRING *nt_name, UINT disposition ) { - static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 }; enum server_fd_type type; int old_cwd, root_fd, needs_close; - const WCHAR *name, *p; + const WCHAR *name; char *unix_name; int name_len, unix_len; NTSTATUS status; @@ -3217,11 +3235,7 @@ static NTSTATUS nt_to_unix_file_name_attr( const OBJECT_ATTRIBUTES *attr, char * name = attr->ObjectName->Buffer; name_len = attr->ObjectName->Length / sizeof(WCHAR); - if (name_len && IS_SEPARATOR(name[0])) return STATUS_INVALID_PARAMETER; - - /* check for invalid characters */ - for (p = name; p < name + name_len; p++) - if (*p < 32 || wcschr( invalid_charsW, *p )) return STATUS_OBJECT_NAME_INVALID; + if (name_len && name[0] == '\\') return STATUS_INVALID_PARAMETER; unix_len = name_len * 3 + MAX_DIR_ENTRY_LEN + 3; if (!(unix_name = malloc( unix_len ))) return STATUS_NO_MEMORY; @@ -3239,8 +3253,7 @@ static NTSTATUS nt_to_unix_file_name_attr( const OBJECT_ATTRIBUTES *attr, char * mutex_lock( &dir_mutex ); if ((old_cwd = open( ".", O_RDONLY )) != -1 && fchdir( root_fd ) != -1) { - status = lookup_unix_name( name, name_len, &unix_name, unix_len, 1, - disposition, FALSE ); + status = lookup_unix_name( name, name_len, &unix_name, unix_len, 1, disposition, FALSE ); if (fchdir( old_cwd ) == -1) chdir( "/" ); } else status = errno_to_status( errno ); @@ -3282,18 +3295,17 @@ NTSTATUS nt_to_unix_file_name( const UNICODE_STRING *nameW, char **unix_name_ret static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 }; NTSTATUS status = STATUS_SUCCESS; - const WCHAR *name, *p; + const WCHAR *name; struct stat st; char *unix_name; int pos, ret, name_len, unix_len, prefix_len; WCHAR prefix[MAX_DIR_ENTRY_LEN + 1]; - BOOLEAN check_case = FALSE; BOOLEAN is_unix = FALSE; name = nameW->Buffer; name_len = nameW->Length / sizeof(WCHAR); - if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_PATH_SYNTAX_BAD; + if (!name_len || name[0] != '\\') return STATUS_OBJECT_PATH_SYNTAX_BAD; if (!(pos = get_dos_prefix_len( nameW ))) return STATUS_BAD_DEVICE_TYPE; /* no DOS prefix, assume NT native name */ @@ -3306,30 +3318,22 @@ NTSTATUS nt_to_unix_file_name( const UNICODE_STRING *nameW, char **unix_name_ret /* check for sub-directory */ for (pos = 0; pos < name_len && pos <= MAX_DIR_ENTRY_LEN; pos++) { - if (IS_SEPARATOR(name[pos])) break; + if (name[pos] == '\\') break; if (name[pos] < 32 || wcschr( invalid_charsW, name[pos] )) return STATUS_OBJECT_NAME_INVALID; prefix[pos] = (name[pos] >= 'A' && name[pos] <= 'Z') ? name[pos] + 'a' - 'A' : name[pos]; } if (pos > MAX_DIR_ENTRY_LEN) return STATUS_OBJECT_NAME_INVALID; + if (pos >= 4 && !memcmp( prefix, unixW, sizeof(unixW) )) + { + /* allow slash for unix namespace */ + if (pos > 4 && prefix[4] == '/') pos = 4; + is_unix = pos == 4; + } prefix_len = pos; prefix[prefix_len] = 0; - /* check for invalid characters (all chars except 0 are valid for unix) */ - is_unix = (prefix_len == 4 && !memcmp( prefix, unixW, sizeof(unixW) )); - if (is_unix) - { - for (p = name + prefix_len; p < name + name_len; p++) - if (!*p) return STATUS_OBJECT_NAME_INVALID; - check_case = TRUE; - } - else - { - for (p = name + prefix_len; p < name + name_len; p++) - if (*p < 32 || wcschr( invalid_charsW, *p )) return STATUS_OBJECT_NAME_INVALID; - } - unix_len = name_len * 3 + MAX_DIR_ENTRY_LEN + 3; unix_len += strlen(config_dir) + sizeof("/dosdevices/"); if (!(unix_name = malloc( unix_len ))) return STATUS_NO_MEMORY; @@ -3353,6 +3357,7 @@ NTSTATUS nt_to_unix_file_name( const UNICODE_STRING *nameW, char **unix_name_ret /* check if prefix exists (except for DOS drives to avoid extra stat calls) */ + if (wcschr( prefix, '/' )) return STATUS_OBJECT_PATH_NOT_FOUND; if (prefix_len != 2 || prefix[1] != ':') { unix_name[pos] = 0; @@ -3367,10 +3372,10 @@ NTSTATUS nt_to_unix_file_name( const UNICODE_STRING *nameW, char **unix_name_ret } } - name += prefix_len; - name_len -= prefix_len; + name += prefix_len + 1; + name_len -= prefix_len + 1; - status = lookup_unix_name( name, name_len, &unix_name, unix_len, pos, disposition, check_case ); + status = lookup_unix_name( name, name_len, &unix_name, unix_len, pos, disposition, is_unix ); if (status == STATUS_SUCCESS || status == STATUS_NO_SUCH_FILE) { TRACE( "%s -> %s\n", debugstr_us(nameW), debugstr_a(unix_name) );