]> git.proxmox.com Git - libgit2.git/blob - tests/core/link.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / tests / core / link.c
1 #include "clar_libgit2.h"
2 #include "posix.h"
3
4 #ifdef GIT_WIN32
5 # include "win32/reparse.h"
6 #endif
7
8 void test_core_link__cleanup(void)
9 {
10 #ifdef GIT_WIN32
11 RemoveDirectory("lstat_junction");
12 RemoveDirectory("lstat_dangling");
13 RemoveDirectory("lstat_dangling_dir");
14 RemoveDirectory("lstat_dangling_junction");
15
16 RemoveDirectory("stat_junction");
17 RemoveDirectory("stat_dangling");
18 RemoveDirectory("stat_dangling_dir");
19 RemoveDirectory("stat_dangling_junction");
20 #endif
21 }
22
23 #ifdef GIT_WIN32
24 static bool should_run(void)
25 {
26 static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
27 PSID admin_sid;
28 BOOL is_admin;
29
30 cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid));
31 cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin));
32 FreeSid(admin_sid);
33
34 return is_admin ? true : false;
35 }
36 #else
37 static bool should_run(void)
38 {
39 return true;
40 }
41 #endif
42
43 static void do_symlink(const char *old, const char *new, int is_dir)
44 {
45 #ifndef GIT_WIN32
46 GIT_UNUSED(is_dir);
47
48 cl_must_pass(symlink(old, new));
49 #else
50 typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD);
51 HMODULE module;
52 create_symlink_func pCreateSymbolicLink;
53
54 cl_assert(module = GetModuleHandle("kernel32"));
55 cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA"));
56
57 cl_win32_pass(pCreateSymbolicLink(new, old, is_dir));
58 #endif
59 }
60
61 static void do_hardlink(const char *old, const char *new)
62 {
63 #ifndef GIT_WIN32
64 cl_must_pass(link(old, new));
65 #else
66 typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES);
67 HMODULE module;
68 create_hardlink_func pCreateHardLink;
69
70 cl_assert(module = GetModuleHandle("kernel32"));
71 cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA"));
72
73 cl_win32_pass(pCreateHardLink(new, old, 0));
74 #endif
75 }
76
77 #ifdef GIT_WIN32
78
79 static void do_junction(const char *old, const char *new)
80 {
81 GIT_REPARSE_DATA_BUFFER *reparse_buf;
82 HANDLE handle;
83 git_str unparsed_buf = GIT_STR_INIT;
84 wchar_t *subst_utf16, *print_utf16;
85 DWORD ioctl_ret;
86 int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret;
87 USHORT reparse_buflen;
88 size_t i;
89
90 /* Junction targets must be the unparsed name, starting with \??\, using
91 * backslashes instead of forward, and end in a trailing backslash.
92 * eg: \??\C:\Foo\
93 */
94 git_str_puts(&unparsed_buf, "\\??\\");
95
96 for (i = 0; i < strlen(old); i++)
97 git_str_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]);
98
99 git_str_putc(&unparsed_buf, '\\');
100
101 subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf));
102 subst_byte_len = subst_utf16_len * sizeof(WCHAR);
103
104 print_utf16_len = subst_utf16_len - 4;
105 print_byte_len = subst_byte_len - (4 * sizeof(WCHAR));
106
107 /* The junction must be an empty directory before the junction attribute
108 * can be added.
109 */
110 cl_win32_pass(CreateDirectoryA(new, NULL));
111
112 handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
113 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
114 cl_win32_pass(handle != INVALID_HANDLE_VALUE);
115
116 reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE +
117 REPARSE_DATA_MOUNTPOINT_HEADER_SIZE +
118 subst_byte_len + sizeof(WCHAR) +
119 print_byte_len + sizeof(WCHAR));
120
121 reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
122 cl_assert(reparse_buf);
123
124 subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer;
125 print_utf16 = subst_utf16 + subst_utf16_len + 1;
126
127 ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1,
128 git_str_cstr(&unparsed_buf));
129 cl_assert_equal_i(subst_utf16_len, ret);
130
131 ret = git__utf8_to_16(print_utf16,
132 print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4);
133 cl_assert_equal_i(print_utf16_len, ret);
134
135 reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
136 reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset = 0;
137 reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength = subst_byte_len;
138 reparse_buf->ReparseBuffer.MountPoint.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR));
139 reparse_buf->ReparseBuffer.MountPoint.PrintNameLength = print_byte_len;
140 reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE;
141
142 cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
143 reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL));
144
145 CloseHandle(handle);
146 LocalFree(reparse_buf);
147
148 git_str_dispose(&unparsed_buf);
149 }
150
151 static void do_custom_reparse(const char *path)
152 {
153 REPARSE_GUID_DATA_BUFFER *reparse_buf;
154 HANDLE handle;
155 DWORD ioctl_ret;
156
157 const char *reparse_data = "Reparse points are silly.";
158 size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE +
159 strlen(reparse_data) + 1;
160
161 reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
162 cl_assert(reparse_buf);
163
164 reparse_buf->ReparseTag = 42;
165 reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1);
166
167 reparse_buf->ReparseGuid.Data1 = 0xdeadbeef;
168 reparse_buf->ReparseGuid.Data2 = 0xdead;
169 reparse_buf->ReparseGuid.Data3 = 0xbeef;
170 reparse_buf->ReparseGuid.Data4[0] = 42;
171 reparse_buf->ReparseGuid.Data4[1] = 42;
172 reparse_buf->ReparseGuid.Data4[2] = 42;
173 reparse_buf->ReparseGuid.Data4[3] = 42;
174 reparse_buf->ReparseGuid.Data4[4] = 42;
175 reparse_buf->ReparseGuid.Data4[5] = 42;
176 reparse_buf->ReparseGuid.Data4[6] = 42;
177 reparse_buf->ReparseGuid.Data4[7] = 42;
178 reparse_buf->ReparseGuid.Data4[8] = 42;
179
180 memcpy(reparse_buf->GenericReparseBuffer.DataBuffer,
181 reparse_data, strlen(reparse_data) + 1);
182
183 handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
184 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
185 cl_win32_pass(handle != INVALID_HANDLE_VALUE);
186
187 cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
188 reparse_buf,
189 reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
190 NULL, 0, &ioctl_ret, NULL));
191
192 CloseHandle(handle);
193 LocalFree(reparse_buf);
194 }
195
196 #endif
197
198 void test_core_link__stat_regular_file(void)
199 {
200 struct stat st;
201
202 cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
203
204 cl_must_pass(p_stat("stat_regfile", &st));
205 cl_assert(S_ISREG(st.st_mode));
206 cl_assert_equal_i(24, st.st_size);
207 }
208
209 void test_core_link__lstat_regular_file(void)
210 {
211 struct stat st;
212
213 cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
214
215 cl_must_pass(p_stat("lstat_regfile", &st));
216 cl_assert(S_ISREG(st.st_mode));
217 cl_assert_equal_i(24, st.st_size);
218 }
219
220 void test_core_link__stat_symlink(void)
221 {
222 struct stat st;
223
224 if (!should_run())
225 clar__skip();
226
227 cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
228 do_symlink("stat_target", "stat_symlink", 0);
229
230 cl_must_pass(p_stat("stat_target", &st));
231 cl_assert(S_ISREG(st.st_mode));
232 cl_assert_equal_i(39, st.st_size);
233
234 cl_must_pass(p_stat("stat_symlink", &st));
235 cl_assert(S_ISREG(st.st_mode));
236 cl_assert_equal_i(39, st.st_size);
237 }
238
239 void test_core_link__stat_symlink_directory(void)
240 {
241 struct stat st;
242
243 if (!should_run())
244 clar__skip();
245
246 p_mkdir("stat_dirtarget", 0777);
247 do_symlink("stat_dirtarget", "stat_dirlink", 1);
248
249 cl_must_pass(p_stat("stat_dirtarget", &st));
250 cl_assert(S_ISDIR(st.st_mode));
251
252 cl_must_pass(p_stat("stat_dirlink", &st));
253 cl_assert(S_ISDIR(st.st_mode));
254 }
255
256 void test_core_link__stat_symlink_chain(void)
257 {
258 struct stat st;
259
260 if (!should_run())
261 clar__skip();
262
263 cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
264 do_symlink("stat_final_target", "stat_chain_3", 0);
265 do_symlink("stat_chain_3", "stat_chain_2", 0);
266 do_symlink("stat_chain_2", "stat_chain_1", 0);
267
268 cl_must_pass(p_stat("stat_chain_1", &st));
269 cl_assert(S_ISREG(st.st_mode));
270 cl_assert_equal_i(39, st.st_size);
271 }
272
273 void test_core_link__stat_dangling_symlink(void)
274 {
275 struct stat st;
276
277 if (!should_run())
278 clar__skip();
279
280 do_symlink("stat_nonexistent", "stat_dangling", 0);
281
282 cl_must_fail(p_stat("stat_nonexistent", &st));
283 cl_must_fail(p_stat("stat_dangling", &st));
284 }
285
286 void test_core_link__stat_dangling_symlink_directory(void)
287 {
288 struct stat st;
289
290 if (!should_run())
291 clar__skip();
292
293 do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
294
295 cl_must_fail(p_stat("stat_nonexistent_dir", &st));
296 cl_must_fail(p_stat("stat_dangling", &st));
297 }
298
299 void test_core_link__lstat_symlink(void)
300 {
301 git_str target_path = GIT_STR_INIT;
302 struct stat st;
303
304 if (!should_run())
305 clar__skip();
306
307 /* Windows always writes the canonical path as the link target, so
308 * write the full path on all platforms.
309 */
310 git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_target");
311
312 cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
313 do_symlink(git_str_cstr(&target_path), "lstat_symlink", 0);
314
315 cl_must_pass(p_lstat("lstat_target", &st));
316 cl_assert(S_ISREG(st.st_mode));
317 cl_assert_equal_i(39, st.st_size);
318
319 cl_must_pass(p_lstat("lstat_symlink", &st));
320 cl_assert(S_ISLNK(st.st_mode));
321 cl_assert_equal_i(git_str_len(&target_path), st.st_size);
322
323 git_str_dispose(&target_path);
324 }
325
326 void test_core_link__lstat_symlink_directory(void)
327 {
328 git_str target_path = GIT_STR_INIT;
329 struct stat st;
330
331 if (!should_run())
332 clar__skip();
333
334 git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget");
335
336 p_mkdir("lstat_dirtarget", 0777);
337 do_symlink(git_str_cstr(&target_path), "lstat_dirlink", 1);
338
339 cl_must_pass(p_lstat("lstat_dirtarget", &st));
340 cl_assert(S_ISDIR(st.st_mode));
341
342 cl_must_pass(p_lstat("lstat_dirlink", &st));
343 cl_assert(S_ISLNK(st.st_mode));
344 cl_assert_equal_i(git_str_len(&target_path), st.st_size);
345
346 git_str_dispose(&target_path);
347 }
348
349 void test_core_link__lstat_dangling_symlink(void)
350 {
351 struct stat st;
352
353 if (!should_run())
354 clar__skip();
355
356 do_symlink("lstat_nonexistent", "lstat_dangling", 0);
357
358 cl_must_fail(p_lstat("lstat_nonexistent", &st));
359
360 cl_must_pass(p_lstat("lstat_dangling", &st));
361 cl_assert(S_ISLNK(st.st_mode));
362 cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
363 }
364
365 void test_core_link__lstat_dangling_symlink_directory(void)
366 {
367 struct stat st;
368
369 if (!should_run())
370 clar__skip();
371
372 do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
373
374 cl_must_fail(p_lstat("lstat_nonexistent", &st));
375
376 cl_must_pass(p_lstat("lstat_dangling_dir", &st));
377 cl_assert(S_ISLNK(st.st_mode));
378 cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
379 }
380
381 void test_core_link__stat_junction(void)
382 {
383 #ifdef GIT_WIN32
384 git_str target_path = GIT_STR_INIT;
385 struct stat st;
386
387 git_str_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget");
388
389 p_mkdir("stat_junctarget", 0777);
390 do_junction(git_str_cstr(&target_path), "stat_junction");
391
392 cl_must_pass(p_stat("stat_junctarget", &st));
393 cl_assert(S_ISDIR(st.st_mode));
394
395 cl_must_pass(p_stat("stat_junction", &st));
396 cl_assert(S_ISDIR(st.st_mode));
397
398 git_str_dispose(&target_path);
399 #endif
400 }
401
402 void test_core_link__stat_dangling_junction(void)
403 {
404 #ifdef GIT_WIN32
405 git_str target_path = GIT_STR_INIT;
406 struct stat st;
407
408 git_str_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
409
410 p_mkdir("stat_nonexistent_junctarget", 0777);
411 do_junction(git_str_cstr(&target_path), "stat_dangling_junction");
412
413 RemoveDirectory("stat_nonexistent_junctarget");
414
415 cl_must_fail(p_stat("stat_nonexistent_junctarget", &st));
416 cl_must_fail(p_stat("stat_dangling_junction", &st));
417
418 git_str_dispose(&target_path);
419 #endif
420 }
421
422 void test_core_link__lstat_junction(void)
423 {
424 #ifdef GIT_WIN32
425 git_str target_path = GIT_STR_INIT;
426 struct stat st;
427
428 git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget");
429
430 p_mkdir("lstat_junctarget", 0777);
431 do_junction(git_str_cstr(&target_path), "lstat_junction");
432
433 cl_must_pass(p_lstat("lstat_junctarget", &st));
434 cl_assert(S_ISDIR(st.st_mode));
435
436 cl_must_pass(p_lstat("lstat_junction", &st));
437 cl_assert(S_ISLNK(st.st_mode));
438
439 git_str_dispose(&target_path);
440 #endif
441 }
442
443 void test_core_link__lstat_dangling_junction(void)
444 {
445 #ifdef GIT_WIN32
446 git_str target_path = GIT_STR_INIT;
447 struct stat st;
448
449 git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
450
451 p_mkdir("lstat_nonexistent_junctarget", 0777);
452 do_junction(git_str_cstr(&target_path), "lstat_dangling_junction");
453
454 RemoveDirectory("lstat_nonexistent_junctarget");
455
456 cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st));
457
458 cl_must_pass(p_lstat("lstat_dangling_junction", &st));
459 cl_assert(S_ISLNK(st.st_mode));
460 cl_assert_equal_i(git_str_len(&target_path), st.st_size);
461
462 git_str_dispose(&target_path);
463 #endif
464 }
465
466 void test_core_link__stat_hardlink(void)
467 {
468 struct stat st;
469
470 if (!should_run())
471 clar__skip();
472
473 cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
474 do_hardlink("stat_hardlink1", "stat_hardlink2");
475
476 cl_must_pass(p_stat("stat_hardlink1", &st));
477 cl_assert(S_ISREG(st.st_mode));
478 cl_assert_equal_i(26, st.st_size);
479
480 cl_must_pass(p_stat("stat_hardlink2", &st));
481 cl_assert(S_ISREG(st.st_mode));
482 cl_assert_equal_i(26, st.st_size);
483 }
484
485 void test_core_link__lstat_hardlink(void)
486 {
487 struct stat st;
488
489 if (!should_run())
490 clar__skip();
491
492 cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
493 do_hardlink("lstat_hardlink1", "lstat_hardlink2");
494
495 cl_must_pass(p_lstat("lstat_hardlink1", &st));
496 cl_assert(S_ISREG(st.st_mode));
497 cl_assert_equal_i(26, st.st_size);
498
499 cl_must_pass(p_lstat("lstat_hardlink2", &st));
500 cl_assert(S_ISREG(st.st_mode));
501 cl_assert_equal_i(26, st.st_size);
502 }
503
504 void test_core_link__stat_reparse_point(void)
505 {
506 #ifdef GIT_WIN32
507 struct stat st;
508
509 /* Generic reparse points should be treated as regular files, only
510 * symlinks and junctions should be treated as links.
511 */
512
513 cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
514 do_custom_reparse("stat_reparse");
515
516 cl_must_pass(p_lstat("stat_reparse", &st));
517 cl_assert(S_ISREG(st.st_mode));
518 cl_assert_equal_i(25, st.st_size);
519 #endif
520 }
521
522 void test_core_link__lstat_reparse_point(void)
523 {
524 #ifdef GIT_WIN32
525 struct stat st;
526
527 cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
528 do_custom_reparse("lstat_reparse");
529
530 cl_must_pass(p_lstat("lstat_reparse", &st));
531 cl_assert(S_ISREG(st.st_mode));
532 cl_assert_equal_i(25, st.st_size);
533 #endif
534 }
535
536 void test_core_link__readlink_nonexistent_file(void)
537 {
538 char buf[2048];
539
540 cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048));
541 cl_assert_equal_i(ENOENT, errno);
542 }
543
544 void test_core_link__readlink_normal_file(void)
545 {
546 char buf[2048];
547
548 cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
549 cl_must_fail(p_readlink("readlink_regfile", buf, 2048));
550 cl_assert_equal_i(EINVAL, errno);
551 }
552
553 void test_core_link__readlink_symlink(void)
554 {
555 git_str target_path = GIT_STR_INIT;
556 int len;
557 char buf[2048];
558
559 if (!should_run())
560 clar__skip();
561
562 git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_target");
563
564 cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
565 do_symlink(git_str_cstr(&target_path), "readlink_link", 0);
566
567 len = p_readlink("readlink_link", buf, 2048);
568 cl_must_pass(len);
569
570 buf[len] = 0;
571
572 cl_assert_equal_s(git_str_cstr(&target_path), buf);
573
574 git_str_dispose(&target_path);
575 }
576
577 void test_core_link__readlink_dangling(void)
578 {
579 git_str target_path = GIT_STR_INIT;
580 int len;
581 char buf[2048];
582
583 if (!should_run())
584 clar__skip();
585
586 git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent");
587
588 do_symlink(git_str_cstr(&target_path), "readlink_dangling", 0);
589
590 len = p_readlink("readlink_dangling", buf, 2048);
591 cl_must_pass(len);
592
593 buf[len] = 0;
594
595 cl_assert_equal_s(git_str_cstr(&target_path), buf);
596
597 git_str_dispose(&target_path);
598 }
599
600 void test_core_link__readlink_multiple(void)
601 {
602 git_str target_path = GIT_STR_INIT,
603 path3 = GIT_STR_INIT, path2 = GIT_STR_INIT, path1 = GIT_STR_INIT;
604 int len;
605 char buf[2048];
606
607 if (!should_run())
608 clar__skip();
609
610 git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_final");
611 git_str_join(&path3, '/', clar_sandbox_path(), "readlink_3");
612 git_str_join(&path2, '/', clar_sandbox_path(), "readlink_2");
613 git_str_join(&path1, '/', clar_sandbox_path(), "readlink_1");
614
615 do_symlink(git_str_cstr(&target_path), git_str_cstr(&path3), 0);
616 do_symlink(git_str_cstr(&path3), git_str_cstr(&path2), 0);
617 do_symlink(git_str_cstr(&path2), git_str_cstr(&path1), 0);
618
619 len = p_readlink("readlink_1", buf, 2048);
620 cl_must_pass(len);
621
622 buf[len] = 0;
623
624 cl_assert_equal_s(git_str_cstr(&path2), buf);
625
626 git_str_dispose(&path1);
627 git_str_dispose(&path2);
628 git_str_dispose(&path3);
629 git_str_dispose(&target_path);
630 }