Reactos
1/*
2 * Unit test suite for file functions
3 *
4 * Copyright 2024 Eric Pouech for CodeWeavers
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21#include <errno.h>
22#include <direct.h>
23#include <stdarg.h>
24#include <locale.h>
25#include <process.h>
26#include <share.h>
27#include <sys/stat.h>
28
29#include <windef.h>
30#include <winbase.h>
31#include <winnls.h>
32#include "wine/test.h"
33#include <corecrt_io.h>
34
35#define STDIN_FILENO 0
36#define STDOUT_FILENO 1
37#define STDERR_FILENO 2
38
39static void test_std_stream_buffering(void)
40{
41 int dup_fd, ret, pos;
42 FILE *file;
43 char ch;
44
45 dup_fd = _dup(STDOUT_FILENO);
46 ok(dup_fd != -1, "_dup failed\n");
47
48 file = freopen("std_stream_test.tmp", "w", stdout);
49 ok(file != NULL, "freopen failed\n");
50
51 ret = fprintf(stdout, "test");
52 pos = _telli64(STDOUT_FILENO);
53
54 fflush(stdout);
55 _dup2(dup_fd, STDOUT_FILENO);
56 close(dup_fd);
57 setvbuf(stdout, NULL, _IONBF, 0);
58
59 ok(ret == 4, "fprintf(stdout) returned %d\n", ret);
60 ok(!pos, "expected stdout to be buffered\n");
61
62 dup_fd = _dup(STDERR_FILENO);
63 ok(dup_fd != -1, "_dup failed\n");
64
65 file = freopen("std_stream_test.tmp", "w", stderr);
66 ok(file != NULL, "freopen failed\n");
67
68 ret = fprintf(stderr, "test");
69 ok(ret == 4, "fprintf(stderr) returned %d\n", ret);
70 pos = _telli64(STDERR_FILENO);
71 if (broken(!GetProcAddress(GetModuleHandleA("ucrtbase"), "__CxxFrameHandler4") && !pos))
72 trace("stderr is buffered\n");
73 else
74 ok(pos == 4, "expected stderr to be unbuffered (%d)\n", pos);
75
76 fflush(stderr);
77 _dup2(dup_fd, STDERR_FILENO);
78 close(dup_fd);
79
80 dup_fd = _dup(STDIN_FILENO);
81 ok(dup_fd != -1, "_dup failed\n");
82
83 file = freopen("std_stream_test.tmp", "r", stdin);
84 ok(file != NULL, "freopen failed\n");
85
86 ch = 0;
87 ret = fscanf(stdin, "%c", &ch);
88 ok(ret == 1, "fscanf returned %d\n", ret);
89 ok(ch == 't', "ch = 0x%x\n", (unsigned char)ch);
90 pos = _telli64(STDIN_FILENO);
91 ok(pos == 4, "pos = %d\n", pos);
92
93 fflush(stdin);
94 _dup2(dup_fd, STDIN_FILENO);
95 close(dup_fd);
96
97 ok(DeleteFileA("std_stream_test.tmp"), "DeleteFile failed\n");
98}
99
100int CDECL _get_stream_buffer_pointers(FILE*,char***,char***,int**);
101static void test_iobuf_layout(void)
102{
103 union
104 {
105 FILE *f;
106 struct
107 {
108 char* _ptr;
109 char* _base;
110 int _cnt;
111 int _flag;
112 int _file;
113 int _charbuf;
114 int _bufsiz;
115 char* _tmpfname;
116 CRITICAL_SECTION _crit;
117 } *iobuf;
118 } fp;
119 char *tempf, *ptr, **file_ptr, **file_base;
120 int cnt, r, *file_cnt;
121
122 tempf = _tempnam(".","wne");
123 fp.f = fopen(tempf, "wb");
124 ok(fp.f != NULL, "fopen failed with error: %d\n", errno);
125
126 ok(!(fp.iobuf->_flag & 0x440), "fp.iobuf->_flag = %x\n", fp.iobuf->_flag);
127 r = fprintf(fp.f, "%s", "init");
128 ok(r == 4, "fprintf returned %d\n", r);
129 ok(fp.iobuf->_flag & 0x40, "fp.iobuf->_flag = %x\n", fp.iobuf->_flag);
130 ok(fp.iobuf->_cnt + 4 == fp.iobuf->_bufsiz, "_cnt = %d, _bufsiz = %d\n",
131 fp.iobuf->_cnt, fp.iobuf->_bufsiz);
132
133 ptr = fp.iobuf->_ptr;
134 cnt = fp.iobuf->_cnt;
135 r = fprintf(fp.f, "%s", "hello");
136 ok(r == 5, "fprintf returned %d\n", r);
137 ok(ptr + 5 == fp.iobuf->_ptr, "fp.iobuf->_ptr = %p, expected %p\n", fp.iobuf->_ptr, ptr + 5);
138 ok(cnt - 5 == fp.iobuf->_cnt, "fp.iobuf->_cnt = %d, expected %d\n", fp.iobuf->_cnt, cnt - 5);
139 ok(fp.iobuf->_ptr + fp.iobuf->_cnt == fp.iobuf->_base + fp.iobuf->_bufsiz,
140 "_ptr = %p, _cnt = %d, _base = %p, _bufsiz = %d\n",
141 fp.iobuf->_ptr, fp.iobuf->_cnt, fp.iobuf->_base, fp.iobuf->_bufsiz);
142
143 _get_stream_buffer_pointers(fp.f, &file_base, &file_ptr, &file_cnt);
144 ok(file_base == &fp.iobuf->_base, "_base = %p, expected %p\n", file_base, &fp.iobuf->_base);
145 ok(file_ptr == &fp.iobuf->_ptr, "_ptr = %p, expected %p\n", file_ptr, &fp.iobuf->_ptr);
146 ok(file_cnt == &fp.iobuf->_cnt, "_cnt = %p, expected %p\n", file_cnt, &fp.iobuf->_cnt);
147
148 r = setvbuf(fp.f, NULL, _IONBF, 0);
149 ok(!r, "setvbuf returned %d\n", r);
150 ok(fp.iobuf->_flag & 0x400, "fp.iobuf->_flag = %x\n", fp.iobuf->_flag);
151
152 ok(TryEnterCriticalSection(&fp.iobuf->_crit), "TryEnterCriticalSection section returned FALSE\n");
153 LeaveCriticalSection(&fp.iobuf->_crit);
154
155 fclose(fp.f);
156 unlink(tempf);
157}
158
159static void test_std_stream_open(void)
160{
161 FILE *f;
162 int fd;
163
164 fd = _dup(STDIN_FILENO);
165 ok(fd != -1, "_dup failed\n");
166
167 ok(!fclose(stdin), "fclose failed\n");
168 f = fopen("nul", "r");
169 ok(f != stdin, "f = %p, stdin = %p\n", f, stdin);
170 ok(_fileno(f) == STDIN_FILENO, "_fileno(f) = %d\n", _fileno(f));
171 ok(!fclose(f), "fclose failed\n");
172
173 f = freopen("nul", "r", stdin);
174 ok(f == stdin, "f = %p, expected %p\n", f, stdin);
175 ok(_fileno(f) == STDIN_FILENO, "_fileno(f) = %d\n", _fileno(f));
176
177 _dup2(fd, STDIN_FILENO);
178 close(fd);
179}
180
181static void test_fopen(void)
182{
183 int i;
184 FILE *f;
185 wchar_t wpath[MAX_PATH];
186 static const struct {
187 const char *loc;
188 const char *path;
189 } tests[] = {
190 { "German.utf8", "t\xc3\xa4\xc3\x8f\xc3\xb6\xc3\x9f.txt" },
191 { "Polish.utf8", "t\xc4\x99\xc5\x9b\xc4\x87.txt" },
192 { "Turkish.utf8", "t\xc3\x87\xc4\x9e\xc4\xb1\xc4\xb0\xc5\x9e.txt" },
193 { "Arabic.utf8", "t\xd8\xaa\xda\x86.txt" },
194 { "Japanese.utf8", "t\xe3\x82\xaf\xe3\x83\xa4.txt" },
195 { "Chinese.utf8", "t\xe4\xb8\x82\xe9\xbd\xab.txt" },
196 { "Japanese", "t\xb8\xd5.txt" },
197
198 };
199
200 for(i=0; i<ARRAY_SIZE(tests); i++) {
201 if(!setlocale(LC_ALL, tests[i].loc)) {
202 win_skip("skipping locale %s\n", tests[i].loc);
203 continue;
204 }
205
206 if(!MultiByteToWideChar(___lc_codepage_func() == CP_UTF8 ? CP_UTF8 : CP_ACP,
207 MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, tests[i].path, -1, wpath, MAX_PATH))
208 continue;
209
210 f = _fsopen(tests[i].path, "w", SH_DENYNO);
211 ok(!!f, "failed to create %s with locale %s\n",
212 debugstr_a(tests[i].path), tests[i].loc);
213 fclose(f);
214
215 f = _wfsopen(wpath, L"r", SH_DENYNO);
216 ok(!!f, "failed to open %s with locale %s\n",
217 debugstr_a(tests[i].path), tests[i].loc);
218 if(f) fclose(f);
219
220 ok(!unlink(tests[i].path), "failed to unlink %s with locale %s\n",
221 tests[i].path, tests[i].loc);
222 }
223 setlocale(LC_ALL, "C");
224}
225
226static void test_utf8(const char *argv0)
227{
228 const char file[] = "file\xc4\x99\xc5\x9b\xc4\x87.a";
229 const char dir[] = "dir\xc4\x99\xc5\x9b\xc4\x87";
230 const WCHAR fileW[] = L"file\x0119\x015b\x0107.a";
231 const WCHAR dirW[] = L"dir\x0119\x015b\x0107";
232
233 char file2[32], buf[256], *p, *q, *env[2];
234 struct _finddata64i32_t fdata64i32;
235 struct _finddata32_t fdata32;
236 struct _finddata64_t fdata64;
237 intptr_t hfind, hproc;
238 WCHAR bufW[256], *pW;
239 struct _stat64 stat;
240 FILE *f;
241 int ret;
242
243 if (!setlocale(LC_ALL, ".utf8"))
244 {
245 win_skip("utf-8 tests\n");
246 return;
247 }
248
249 ret = _mkdir(dir);
250 if (ret == -1 && errno == ENOENT)
251 {
252 skip("can't create test environment\n");
253 return;
254 }
255 ok(!ret, "_mkdir returned %d, error %d\n", ret, errno);
256
257 ret = _chdir(dir);
258 ok(!ret, "_chdir returned %d, error %d\n", ret, errno);
259
260 p = _getcwd(buf, sizeof(buf));
261 ok(p == buf, "_getcwd returned %p, errno %d\n", p, errno);
262 p = strrchr(p, '\\');
263 ok(!!p, "strrchr returned NULL, buf = %s\n", debugstr_a(buf));
264 ok(!strcmp(p + 1, dir), "unexpected working directory: %s\n", debugstr_a(buf));
265
266 p = _getdcwd(_getdrive(), buf, sizeof(buf));
267 ok(p == buf, "_getdcwd returned %p, errno %d\n", p, errno);
268 p = strrchr(p, '\\');
269 ok(!!p, "strrchr returned NULL, buf = %s\n", debugstr_a(buf));
270 ok(!strcmp(p + 1, dir), "unexpected working directory: %s\n", debugstr_a(buf));
271
272 p = _fullpath(buf, NULL, sizeof(buf));
273 ok(p == buf, "_fulpath returned %p, errno %d\n", p, errno);
274 p = strrchr(p, '\\');
275 ok(!!p, "strrchr returned NULL, buf = %s\n", debugstr_a(buf));
276 ok(!strcmp(p + 1, dir), "unexpected working directory: %s\n", debugstr_a(buf));
277
278 f = fopen(file, "w");
279 ok(!!f, "fopen returned %d, error %d\n", ret, errno);
280 fclose(f);
281
282 ret = access(file, 0);
283 ok(!ret, "access returned %d, error %d\n", ret, errno);
284
285 ret = _stat64(file, &stat);
286 ok(!ret, "_stat64 returned %d, error %d\n", ret, errno);
287
288 ret = _chmod(file, _S_IREAD | _S_IWRITE);
289 ok(!ret, "_chmod returned %d, error %d\n", ret, errno);
290
291 strcpy(file2, file);
292 strcat(file2, "XXXXXX");
293 p = _mktemp(file2);
294 ok(p == file2, "_mktemp returned %p, file2 %p, errno %d\n", p, file2, errno);
295 ok(!memcmp(file2, file, sizeof(file) - 1), "file2 = %s\n", debugstr_a(file2));
296 ok(p[ARRAY_SIZE(file) - 1] == 'a', "p = %s\n", debugstr_a(p));
297 f = fopen(p, "w");
298 ok(!!f, "fopen returned %d, error %d\n", ret, errno);
299 fclose(f);
300
301 strcpy(buf, file);
302 strcat(buf, "XXXXXX");
303 p = _mktemp(buf);
304 ok(p == buf, "_mktemp returned %p, buf %p, errno %d\n", p, buf, errno);
305 ok(!memcmp(buf, file, sizeof(file) - 1), "buf = %s\n", debugstr_a(buf));
306 ok(p[ARRAY_SIZE(file) - 1] == 'b', "p = %s\n", debugstr_a(p));
307
308 strcpy(buf, file);
309 strcat(buf, "XXXXXX");
310 ret = _mktemp_s(buf, sizeof(buf));
311 ok(!memcmp(buf, file, sizeof(file) - 1), "buf = %s\n", debugstr_a(buf));
312 ok(buf[ARRAY_SIZE(file) - 1] == 'b', "buf = %s\n", debugstr_a(buf));
313
314 strcpy(buf, file);
315 strcat(buf, "*");
316 fdata32.name[0] = 'x';
317 hfind = _findfirst32(buf, &fdata32);
318 ok(hfind != -1, "_findfirst32 returned %Id, errno %d\n", hfind, errno);
319 ok(!memcmp(file, fdata32.name, sizeof(file) - 1), "fdata32.name = %s\n", debugstr_a(fdata32.name));
320
321 fdata32.name[0] = 'x';
322 ret = _findnext32(hfind, &fdata32);
323 ok(!ret, "_findnext32 returned %d, errno %d\n", ret, errno);
324 ok(!memcmp(file, fdata32.name, sizeof(file) - 1), "fdata32.name = %s\n", debugstr_a(fdata32.name));
325 ret = _findclose(hfind);
326 ok(!ret, "_findclose returned %d, errno %d\n", ret, errno);
327
328
329 strcpy(buf, file);
330 strcat(buf, "*");
331 fdata64.name[0] = 'x';
332 hfind = _findfirst64(buf, &fdata64);
333 ok(hfind != -1, "_findfirst64 returned %Id, errno %d\n", hfind, errno);
334 ok(!memcmp(file, fdata64.name, sizeof(file) - 1), "fdata64.name = %s\n", debugstr_a(fdata64.name));
335
336 fdata64.name[0] = 'x';
337 ret = _findnext64(hfind, &fdata64);
338 ok(!ret, "_findnext64 returned %d, errno %d\n", ret, errno);
339 ok(!memcmp(file, fdata64.name, sizeof(file) - 1), "fdata64.name = %s\n", debugstr_a(fdata64.name));
340 ret = _findclose(hfind);
341 ok(!ret, "_findclose returned %d, errno %d\n", ret, errno);
342
343 strcpy(buf, file);
344 strcat(buf, "*");
345 fdata64i32.name[0] = 'x';
346 hfind = _findfirst64i32(buf, &fdata64i32);
347 ok(hfind != -1, "_findfirst64i32 returned %Id, errno %d\n", hfind, errno);
348 ok(!memcmp(file, fdata64i32.name, sizeof(file) - 1), "fdata64i32.name = %s\n", debugstr_a(fdata64i32.name));
349
350 fdata64i32.name[0] = 'x';
351 ret = _findnext64i32(hfind, &fdata64i32);
352 ok(!ret, "_findnext64i32 returned %d, errno %d\n", ret, errno);
353 ok(!memcmp(file, fdata64i32.name, sizeof(file) - 1), "fdata64i32.name = %s\n", debugstr_a(fdata64i32.name));
354 ret = _findclose(hfind);
355 ok(!ret, "_findclose returned %d, errno %d\n", ret, errno);
356
357 ret = remove(file2);
358 ok(!ret, "remove returned %d, errno %d\n", ret, errno);
359
360 buf[0] = 'x';
361 _searchenv(file, "env", buf);
362 p = strrchr(buf, '\\');
363 ok(!!p, "buf = %s\n", debugstr_a(buf));
364 ok(!strcmp(p + 1, file), "buf = %s\n", debugstr_a(buf));
365
366 ret = _wunlink(fileW);
367 ok(!ret, "_wunlink returned %d, errno %d\n", ret, errno);
368
369 ret = _chdir("..");
370 ok(!ret, "_chdir returned %d, error %d\n", ret, errno);
371
372 ret = _wrmdir(dirW);
373 ok(!ret, "_wrmdir returned %d, errno %d\n", ret, errno);
374
375 p = _tempnam(NULL, file);
376 ok(!!p, "_tempnam returned NULL, error %d\n", errno);
377 q = strrchr(p, '\\');
378 ok(!!q, "_tempnam returned %s\n", debugstr_a(p));
379 todo_wine ok(!memcmp(q + 1, file, ARRAY_SIZE(file) - 1),
380 "incorrect file prefix: %s\n", debugstr_a(p));
381 free(p);
382
383 /* native implementation mixes CP_UTF8 and CP_ACP */
384 if (GetACP() != CP_UTF8)
385 {
386 /* make sure wide environment is initialized (works around bug in native) */
387 ret = _putenv("__wine_env_test=test");
388 ok(!ret, "_putenv returned %d, errno %d\n", ret, errno);
389 _wgetenv(L"__wine_env_test");
390
391 strcpy(buf, file);
392 strcat(buf, "=test");
393 ret = _putenv(buf);
394 ok(!ret, "_putenv returned %d, errno %d\n", ret, errno);
395 /* bug in native _wgetenv/_putenv implementation */
396 pW = _wgetenv(fileW);
397 ok(!pW, "environment variable name was converted\n");
398 bufW[0] = 0;
399 ret = GetEnvironmentVariableW(fileW, bufW, ARRAY_SIZE(bufW));
400 todo_wine ok(ret, "GetEnvironmentVariableW returned error %lu\n", GetLastError());
401 todo_wine ok(!wcscmp(bufW, L"test"), "bufW = %s\n", debugstr_w(bufW));
402 strcpy(buf, file);
403 strcat(buf, "=");
404 ret = _putenv(buf);
405 ok(!ret, "_putenv returned %d, errno %d\n", ret, errno);
406
407 strcpy(buf, "__wine_env_test=");
408 strcat(buf, file);
409 ret = _putenv(buf);
410 ok(!ret, "_putenv returned %d, errno %d\n", ret, errno);
411 /* bug in native _wgetenv/_putenv implementation */
412 pW = _wgetenv(L"__wine_env_test");
413 ok(wcscmp(pW, fileW), "pW = %s\n", debugstr_w(pW));
414 ret = GetEnvironmentVariableW(L"__wine_env_test", bufW, ARRAY_SIZE(bufW));
415 ok(ret, "GetEnvironmentVariableW returned error %lu\n", GetLastError());
416 todo_wine ok(!wcscmp(bufW, fileW), "bufW = %s\n", debugstr_w(bufW));
417
418 wcscpy(bufW, L"__wine_env_test=");
419 wcscat(bufW, fileW);
420 ret = _wputenv(bufW);
421 ok(!ret, "_wputenv returned %d, errno %d\n", ret, errno);
422 p = getenv("__wine_env_test");
423 ok(strcmp(p, file), "environment variable was converted\n");
424 strcpy(buf, "__wine_env_test=");
425 ret = _putenv(buf);
426 ok(!ret, "_putenv returned %d, errno %d\n", ret, errno);
427 }
428
429 strcpy(buf, "__wine_env_test=");
430 strcat(buf, file);
431 env[0] = buf;
432 env[1] = NULL;
433 hproc = _spawnle(_P_NOWAIT, argv0, argv0, "file", "utf8", file, NULL, env);
434 ok(hproc != -1, "_spawnl returned %Id, errno %d\n", hproc, errno);
435 wait_child_process((HANDLE)hproc);
436 CloseHandle((HANDLE)hproc);
437
438 setlocale(LC_ALL, "C");
439}
440
441static void test_utf8_argument(void)
442{
443 static const WCHAR nameW[] = L"file\x0119\x015b\x0107.a";
444 const WCHAR *cmdline = GetCommandLineW(), *p;
445 WCHAR buf[256];
446 DWORD ret;
447
448 p = wcsrchr(cmdline, ' ');
449 ok(!!p, "cmdline = %s\n", debugstr_w(cmdline));
450 ok(!wcscmp(p + 1, nameW), "cmdline = %s\n", debugstr_w(cmdline));
451
452 ret = GetEnvironmentVariableW(L"__wine_env_test", buf, ARRAY_SIZE(buf));
453 ok(ret, "GetEnvironmentVariableW returned error %lu\n", GetLastError());
454 if (GetACP() == CP_UTF8)
455 ok(!wcscmp(buf, nameW), "__wine_env_test = %s\n", debugstr_w(buf));
456 else
457 ok(wcscmp(buf, nameW), "environment was converted\n");
458}
459
460START_TEST(file)
461{
462 int arg_c;
463 char** arg_v;
464
465 arg_c = winetest_get_mainargs(&arg_v);
466 if(arg_c == 4 && !strcmp(arg_v[2], "utf8"))
467 {
468 test_utf8_argument();
469 return;
470 }
471
472 test_std_stream_buffering();
473 test_iobuf_layout();
474 test_std_stream_open();
475 test_fopen();
476 test_utf8(arg_v[0]);
477}