1 #include "clar_libgit2.h"
7 # include "win32/reparse.h"
10 void test_core_link__cleanup(void)
13 RemoveDirectory("lstat_junction");
14 RemoveDirectory("lstat_dangling");
15 RemoveDirectory("lstat_dangling_dir");
16 RemoveDirectory("lstat_dangling_junction");
18 RemoveDirectory("stat_junction");
19 RemoveDirectory("stat_dangling");
20 RemoveDirectory("stat_dangling_dir");
21 RemoveDirectory("stat_dangling_junction");
26 static bool should_run(void)
28 static SID_IDENTIFIER_AUTHORITY authority
= { SECURITY_NT_AUTHORITY
};
32 cl_win32_pass(AllocateAndInitializeSid(&authority
, 2, SECURITY_BUILTIN_DOMAIN_RID
, DOMAIN_ALIAS_RID_ADMINS
, 0, 0, 0, 0, 0, 0, &admin_sid
));
33 cl_win32_pass(CheckTokenMembership(NULL
, admin_sid
, &is_admin
));
36 return is_admin
? true : false;
39 static bool should_run(void)
45 static void do_symlink(const char *old
, const char *new, int is_dir
)
50 cl_must_pass(symlink(old
, new));
52 typedef DWORD (WINAPI
*create_symlink_func
)(LPCTSTR
, LPCTSTR
, DWORD
);
54 create_symlink_func pCreateSymbolicLink
;
56 cl_assert(module
= GetModuleHandle("kernel32"));
57 cl_assert(pCreateSymbolicLink
= (create_symlink_func
)(void *)GetProcAddress(module
, "CreateSymbolicLinkA"));
59 cl_win32_pass(pCreateSymbolicLink(new, old
, is_dir
));
63 static void do_hardlink(const char *old
, const char *new)
66 cl_must_pass(link(old
, new));
68 typedef DWORD (WINAPI
*create_hardlink_func
)(LPCTSTR
, LPCTSTR
, LPSECURITY_ATTRIBUTES
);
70 create_hardlink_func pCreateHardLink
;
72 cl_assert(module
= GetModuleHandle("kernel32"));
73 cl_assert(pCreateHardLink
= (create_hardlink_func
)(void *)GetProcAddress(module
, "CreateHardLinkA"));
75 cl_win32_pass(pCreateHardLink(new, old
, 0));
81 static void do_junction(const char *old
, const char *new)
83 GIT_REPARSE_DATA_BUFFER
*reparse_buf
;
85 git_buf unparsed_buf
= GIT_BUF_INIT
;
86 wchar_t *subst_utf16
, *print_utf16
;
88 int subst_utf16_len
, subst_byte_len
, print_utf16_len
, print_byte_len
, ret
;
89 USHORT reparse_buflen
;
92 /* Junction targets must be the unparsed name, starting with \??\, using
93 * backslashes instead of forward, and end in a trailing backslash.
96 git_buf_puts(&unparsed_buf
, "\\??\\");
98 for (i
= 0; i
< strlen(old
); i
++)
99 git_buf_putc(&unparsed_buf
, old
[i
] == '/' ? '\\' : old
[i
]);
101 git_buf_putc(&unparsed_buf
, '\\');
103 subst_utf16_len
= git__utf8_to_16(NULL
, 0, git_buf_cstr(&unparsed_buf
));
104 subst_byte_len
= subst_utf16_len
* sizeof(WCHAR
);
106 print_utf16_len
= subst_utf16_len
- 4;
107 print_byte_len
= subst_byte_len
- (4 * sizeof(WCHAR
));
109 /* The junction must be an empty directory before the junction attribute
112 cl_win32_pass(CreateDirectoryA(new, NULL
));
114 handle
= CreateFileA(new, GENERIC_WRITE
, 0, NULL
, OPEN_EXISTING
,
115 FILE_FLAG_OPEN_REPARSE_POINT
| FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
116 cl_win32_pass(handle
!= INVALID_HANDLE_VALUE
);
118 reparse_buflen
= (USHORT
)(REPARSE_DATA_HEADER_SIZE
+
119 REPARSE_DATA_MOUNTPOINT_HEADER_SIZE
+
120 subst_byte_len
+ sizeof(WCHAR
) +
121 print_byte_len
+ sizeof(WCHAR
));
123 reparse_buf
= LocalAlloc(LMEM_FIXED
|LMEM_ZEROINIT
, reparse_buflen
);
124 cl_assert(reparse_buf
);
126 subst_utf16
= reparse_buf
->MountPointReparseBuffer
.PathBuffer
;
127 print_utf16
= subst_utf16
+ subst_utf16_len
+ 1;
129 ret
= git__utf8_to_16(subst_utf16
, subst_utf16_len
+ 1,
130 git_buf_cstr(&unparsed_buf
));
131 cl_assert_equal_i(subst_utf16_len
, ret
);
133 ret
= git__utf8_to_16(print_utf16
,
134 print_utf16_len
+ 1, git_buf_cstr(&unparsed_buf
) + 4);
135 cl_assert_equal_i(print_utf16_len
, ret
);
137 reparse_buf
->ReparseTag
= IO_REPARSE_TAG_MOUNT_POINT
;
138 reparse_buf
->MountPointReparseBuffer
.SubstituteNameOffset
= 0;
139 reparse_buf
->MountPointReparseBuffer
.SubstituteNameLength
= subst_byte_len
;
140 reparse_buf
->MountPointReparseBuffer
.PrintNameOffset
= (USHORT
)(subst_byte_len
+ sizeof(WCHAR
));
141 reparse_buf
->MountPointReparseBuffer
.PrintNameLength
= print_byte_len
;
142 reparse_buf
->ReparseDataLength
= reparse_buflen
- REPARSE_DATA_HEADER_SIZE
;
144 cl_win32_pass(DeviceIoControl(handle
, FSCTL_SET_REPARSE_POINT
,
145 reparse_buf
, reparse_buflen
, NULL
, 0, &ioctl_ret
, NULL
));
148 LocalFree(reparse_buf
);
150 git_buf_dispose(&unparsed_buf
);
153 static void do_custom_reparse(const char *path
)
155 REPARSE_GUID_DATA_BUFFER
*reparse_buf
;
159 const char *reparse_data
= "Reparse points are silly.";
160 size_t reparse_buflen
= REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
+
161 strlen(reparse_data
) + 1;
163 reparse_buf
= LocalAlloc(LMEM_FIXED
|LMEM_ZEROINIT
, reparse_buflen
);
164 cl_assert(reparse_buf
);
166 reparse_buf
->ReparseTag
= 42;
167 reparse_buf
->ReparseDataLength
= (WORD
)(strlen(reparse_data
) + 1);
169 reparse_buf
->ReparseGuid
.Data1
= 0xdeadbeef;
170 reparse_buf
->ReparseGuid
.Data2
= 0xdead;
171 reparse_buf
->ReparseGuid
.Data3
= 0xbeef;
172 reparse_buf
->ReparseGuid
.Data4
[0] = 42;
173 reparse_buf
->ReparseGuid
.Data4
[1] = 42;
174 reparse_buf
->ReparseGuid
.Data4
[2] = 42;
175 reparse_buf
->ReparseGuid
.Data4
[3] = 42;
176 reparse_buf
->ReparseGuid
.Data4
[4] = 42;
177 reparse_buf
->ReparseGuid
.Data4
[5] = 42;
178 reparse_buf
->ReparseGuid
.Data4
[6] = 42;
179 reparse_buf
->ReparseGuid
.Data4
[7] = 42;
180 reparse_buf
->ReparseGuid
.Data4
[8] = 42;
182 memcpy(reparse_buf
->GenericReparseBuffer
.DataBuffer
,
183 reparse_data
, strlen(reparse_data
) + 1);
185 handle
= CreateFileA(path
, GENERIC_WRITE
, 0, NULL
, OPEN_EXISTING
,
186 FILE_FLAG_OPEN_REPARSE_POINT
| FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
187 cl_win32_pass(handle
!= INVALID_HANDLE_VALUE
);
189 cl_win32_pass(DeviceIoControl(handle
, FSCTL_SET_REPARSE_POINT
,
191 reparse_buf
->ReparseDataLength
+ REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
,
192 NULL
, 0, &ioctl_ret
, NULL
));
195 LocalFree(reparse_buf
);
200 void test_core_link__stat_regular_file(void)
204 cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
206 cl_must_pass(p_stat("stat_regfile", &st
));
207 cl_assert(S_ISREG(st
.st_mode
));
208 cl_assert_equal_i(24, st
.st_size
);
211 void test_core_link__lstat_regular_file(void)
215 cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
217 cl_must_pass(p_stat("lstat_regfile", &st
));
218 cl_assert(S_ISREG(st
.st_mode
));
219 cl_assert_equal_i(24, st
.st_size
);
222 void test_core_link__stat_symlink(void)
229 cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
230 do_symlink("stat_target", "stat_symlink", 0);
232 cl_must_pass(p_stat("stat_target", &st
));
233 cl_assert(S_ISREG(st
.st_mode
));
234 cl_assert_equal_i(39, st
.st_size
);
236 cl_must_pass(p_stat("stat_symlink", &st
));
237 cl_assert(S_ISREG(st
.st_mode
));
238 cl_assert_equal_i(39, st
.st_size
);
241 void test_core_link__stat_symlink_directory(void)
248 p_mkdir("stat_dirtarget", 0777);
249 do_symlink("stat_dirtarget", "stat_dirlink", 1);
251 cl_must_pass(p_stat("stat_dirtarget", &st
));
252 cl_assert(S_ISDIR(st
.st_mode
));
254 cl_must_pass(p_stat("stat_dirlink", &st
));
255 cl_assert(S_ISDIR(st
.st_mode
));
258 void test_core_link__stat_symlink_chain(void)
265 cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
266 do_symlink("stat_final_target", "stat_chain_3", 0);
267 do_symlink("stat_chain_3", "stat_chain_2", 0);
268 do_symlink("stat_chain_2", "stat_chain_1", 0);
270 cl_must_pass(p_stat("stat_chain_1", &st
));
271 cl_assert(S_ISREG(st
.st_mode
));
272 cl_assert_equal_i(39, st
.st_size
);
275 void test_core_link__stat_dangling_symlink(void)
282 do_symlink("stat_nonexistent", "stat_dangling", 0);
284 cl_must_fail(p_stat("stat_nonexistent", &st
));
285 cl_must_fail(p_stat("stat_dangling", &st
));
288 void test_core_link__stat_dangling_symlink_directory(void)
295 do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
297 cl_must_fail(p_stat("stat_nonexistent_dir", &st
));
298 cl_must_fail(p_stat("stat_dangling", &st
));
301 void test_core_link__lstat_symlink(void)
303 git_buf target_path
= GIT_BUF_INIT
;
309 /* Windows always writes the canonical path as the link target, so
310 * write the full path on all platforms.
312 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_target");
314 cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
315 do_symlink(git_buf_cstr(&target_path
), "lstat_symlink", 0);
317 cl_must_pass(p_lstat("lstat_target", &st
));
318 cl_assert(S_ISREG(st
.st_mode
));
319 cl_assert_equal_i(39, st
.st_size
);
321 cl_must_pass(p_lstat("lstat_symlink", &st
));
322 cl_assert(S_ISLNK(st
.st_mode
));
323 cl_assert_equal_i(git_buf_len(&target_path
), st
.st_size
);
325 git_buf_dispose(&target_path
);
328 void test_core_link__lstat_symlink_directory(void)
330 git_buf target_path
= GIT_BUF_INIT
;
336 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_dirtarget");
338 p_mkdir("lstat_dirtarget", 0777);
339 do_symlink(git_buf_cstr(&target_path
), "lstat_dirlink", 1);
341 cl_must_pass(p_lstat("lstat_dirtarget", &st
));
342 cl_assert(S_ISDIR(st
.st_mode
));
344 cl_must_pass(p_lstat("lstat_dirlink", &st
));
345 cl_assert(S_ISLNK(st
.st_mode
));
346 cl_assert_equal_i(git_buf_len(&target_path
), st
.st_size
);
348 git_buf_dispose(&target_path
);
351 void test_core_link__lstat_dangling_symlink(void)
358 do_symlink("lstat_nonexistent", "lstat_dangling", 0);
360 cl_must_fail(p_lstat("lstat_nonexistent", &st
));
362 cl_must_pass(p_lstat("lstat_dangling", &st
));
363 cl_assert(S_ISLNK(st
.st_mode
));
364 cl_assert_equal_i(strlen("lstat_nonexistent"), st
.st_size
);
367 void test_core_link__lstat_dangling_symlink_directory(void)
374 do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
376 cl_must_fail(p_lstat("lstat_nonexistent", &st
));
378 cl_must_pass(p_lstat("lstat_dangling_dir", &st
));
379 cl_assert(S_ISLNK(st
.st_mode
));
380 cl_assert_equal_i(strlen("lstat_nonexistent"), st
.st_size
);
383 void test_core_link__stat_junction(void)
386 git_buf target_path
= GIT_BUF_INIT
;
389 git_buf_join(&target_path
, '/', clar_sandbox_path(), "stat_junctarget");
391 p_mkdir("stat_junctarget", 0777);
392 do_junction(git_buf_cstr(&target_path
), "stat_junction");
394 cl_must_pass(p_stat("stat_junctarget", &st
));
395 cl_assert(S_ISDIR(st
.st_mode
));
397 cl_must_pass(p_stat("stat_junction", &st
));
398 cl_assert(S_ISDIR(st
.st_mode
));
400 git_buf_dispose(&target_path
);
404 void test_core_link__stat_dangling_junction(void)
407 git_buf target_path
= GIT_BUF_INIT
;
410 git_buf_join(&target_path
, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
412 p_mkdir("stat_nonexistent_junctarget", 0777);
413 do_junction(git_buf_cstr(&target_path
), "stat_dangling_junction");
415 RemoveDirectory("stat_nonexistent_junctarget");
417 cl_must_fail(p_stat("stat_nonexistent_junctarget", &st
));
418 cl_must_fail(p_stat("stat_dangling_junction", &st
));
420 git_buf_dispose(&target_path
);
424 void test_core_link__lstat_junction(void)
427 git_buf target_path
= GIT_BUF_INIT
;
430 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_junctarget");
432 p_mkdir("lstat_junctarget", 0777);
433 do_junction(git_buf_cstr(&target_path
), "lstat_junction");
435 cl_must_pass(p_lstat("lstat_junctarget", &st
));
436 cl_assert(S_ISDIR(st
.st_mode
));
438 cl_must_pass(p_lstat("lstat_junction", &st
));
439 cl_assert(S_ISLNK(st
.st_mode
));
441 git_buf_dispose(&target_path
);
445 void test_core_link__lstat_dangling_junction(void)
448 git_buf target_path
= GIT_BUF_INIT
;
451 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
453 p_mkdir("lstat_nonexistent_junctarget", 0777);
454 do_junction(git_buf_cstr(&target_path
), "lstat_dangling_junction");
456 RemoveDirectory("lstat_nonexistent_junctarget");
458 cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st
));
460 cl_must_pass(p_lstat("lstat_dangling_junction", &st
));
461 cl_assert(S_ISLNK(st
.st_mode
));
462 cl_assert_equal_i(git_buf_len(&target_path
), st
.st_size
);
464 git_buf_dispose(&target_path
);
468 void test_core_link__stat_hardlink(void)
475 cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
476 do_hardlink("stat_hardlink1", "stat_hardlink2");
478 cl_must_pass(p_stat("stat_hardlink1", &st
));
479 cl_assert(S_ISREG(st
.st_mode
));
480 cl_assert_equal_i(26, st
.st_size
);
482 cl_must_pass(p_stat("stat_hardlink2", &st
));
483 cl_assert(S_ISREG(st
.st_mode
));
484 cl_assert_equal_i(26, st
.st_size
);
487 void test_core_link__lstat_hardlink(void)
494 cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
495 do_hardlink("lstat_hardlink1", "lstat_hardlink2");
497 cl_must_pass(p_lstat("lstat_hardlink1", &st
));
498 cl_assert(S_ISREG(st
.st_mode
));
499 cl_assert_equal_i(26, st
.st_size
);
501 cl_must_pass(p_lstat("lstat_hardlink2", &st
));
502 cl_assert(S_ISREG(st
.st_mode
));
503 cl_assert_equal_i(26, st
.st_size
);
506 void test_core_link__stat_reparse_point(void)
511 /* Generic reparse points should be treated as regular files, only
512 * symlinks and junctions should be treated as links.
515 cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
516 do_custom_reparse("stat_reparse");
518 cl_must_pass(p_lstat("stat_reparse", &st
));
519 cl_assert(S_ISREG(st
.st_mode
));
520 cl_assert_equal_i(25, st
.st_size
);
524 void test_core_link__lstat_reparse_point(void)
529 cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
530 do_custom_reparse("lstat_reparse");
532 cl_must_pass(p_lstat("lstat_reparse", &st
));
533 cl_assert(S_ISREG(st
.st_mode
));
534 cl_assert_equal_i(25, st
.st_size
);
538 void test_core_link__readlink_nonexistent_file(void)
542 cl_must_fail(p_readlink("readlink_nonexistent", buf
, 2048));
543 cl_assert_equal_i(ENOENT
, errno
);
546 void test_core_link__readlink_normal_file(void)
550 cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
551 cl_must_fail(p_readlink("readlink_regfile", buf
, 2048));
552 cl_assert_equal_i(EINVAL
, errno
);
555 void test_core_link__readlink_symlink(void)
557 git_buf target_path
= GIT_BUF_INIT
;
564 git_buf_join(&target_path
, '/', clar_sandbox_path(), "readlink_target");
566 cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
567 do_symlink(git_buf_cstr(&target_path
), "readlink_link", 0);
569 len
= p_readlink("readlink_link", buf
, 2048);
574 cl_assert_equal_s(git_buf_cstr(&target_path
), buf
);
576 git_buf_dispose(&target_path
);
579 void test_core_link__readlink_dangling(void)
581 git_buf target_path
= GIT_BUF_INIT
;
588 git_buf_join(&target_path
, '/', clar_sandbox_path(), "readlink_nonexistent");
590 do_symlink(git_buf_cstr(&target_path
), "readlink_dangling", 0);
592 len
= p_readlink("readlink_dangling", buf
, 2048);
597 cl_assert_equal_s(git_buf_cstr(&target_path
), buf
);
599 git_buf_dispose(&target_path
);
602 void test_core_link__readlink_multiple(void)
604 git_buf target_path
= GIT_BUF_INIT
,
605 path3
= GIT_BUF_INIT
, path2
= GIT_BUF_INIT
, path1
= GIT_BUF_INIT
;
612 git_buf_join(&target_path
, '/', clar_sandbox_path(), "readlink_final");
613 git_buf_join(&path3
, '/', clar_sandbox_path(), "readlink_3");
614 git_buf_join(&path2
, '/', clar_sandbox_path(), "readlink_2");
615 git_buf_join(&path1
, '/', clar_sandbox_path(), "readlink_1");
617 do_symlink(git_buf_cstr(&target_path
), git_buf_cstr(&path3
), 0);
618 do_symlink(git_buf_cstr(&path3
), git_buf_cstr(&path2
), 0);
619 do_symlink(git_buf_cstr(&path2
), git_buf_cstr(&path1
), 0);
621 len
= p_readlink("readlink_1", buf
, 2048);
626 cl_assert_equal_s(git_buf_cstr(&path2
), buf
);
628 git_buf_dispose(&path1
);
629 git_buf_dispose(&path2
);
630 git_buf_dispose(&path3
);
631 git_buf_dispose(&target_path
);