1 #include "clar_libgit2.h"
5 # include "win32/reparse.h"
8 void test_core_link__cleanup(void)
11 RemoveDirectory("lstat_junction");
12 RemoveDirectory("lstat_dangling");
13 RemoveDirectory("lstat_dangling_dir");
14 RemoveDirectory("lstat_dangling_junction");
16 RemoveDirectory("stat_junction");
17 RemoveDirectory("stat_dangling");
18 RemoveDirectory("stat_dangling_dir");
19 RemoveDirectory("stat_dangling_junction");
24 static bool should_run(void)
26 static SID_IDENTIFIER_AUTHORITY authority
= { SECURITY_NT_AUTHORITY
};
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
));
34 return is_admin
? true : false;
37 static bool should_run(void)
43 static void do_symlink(const char *old
, const char *new, int is_dir
)
48 cl_must_pass(symlink(old
, new));
50 typedef DWORD (WINAPI
*create_symlink_func
)(LPCTSTR
, LPCTSTR
, DWORD
);
52 create_symlink_func pCreateSymbolicLink
;
54 cl_assert(module
= GetModuleHandle("kernel32"));
55 cl_assert(pCreateSymbolicLink
= (create_symlink_func
)(void *)GetProcAddress(module
, "CreateSymbolicLinkA"));
57 cl_win32_pass(pCreateSymbolicLink(new, old
, is_dir
));
61 static void do_hardlink(const char *old
, const char *new)
64 cl_must_pass(link(old
, new));
66 typedef DWORD (WINAPI
*create_hardlink_func
)(LPCTSTR
, LPCTSTR
, LPSECURITY_ATTRIBUTES
);
68 create_hardlink_func pCreateHardLink
;
70 cl_assert(module
= GetModuleHandle("kernel32"));
71 cl_assert(pCreateHardLink
= (create_hardlink_func
)(void *)GetProcAddress(module
, "CreateHardLinkA"));
73 cl_win32_pass(pCreateHardLink(new, old
, 0));
79 static void do_junction(const char *old
, const char *new)
81 GIT_REPARSE_DATA_BUFFER
*reparse_buf
;
83 git_str unparsed_buf
= GIT_STR_INIT
;
84 wchar_t *subst_utf16
, *print_utf16
;
86 int subst_utf16_len
, subst_byte_len
, print_utf16_len
, print_byte_len
, ret
;
87 USHORT reparse_buflen
;
90 /* Junction targets must be the unparsed name, starting with \??\, using
91 * backslashes instead of forward, and end in a trailing backslash.
94 git_str_puts(&unparsed_buf
, "\\??\\");
96 for (i
= 0; i
< strlen(old
); i
++)
97 git_str_putc(&unparsed_buf
, old
[i
] == '/' ? '\\' : old
[i
]);
99 git_str_putc(&unparsed_buf
, '\\');
101 subst_utf16_len
= git__utf8_to_16(NULL
, 0, git_str_cstr(&unparsed_buf
));
102 subst_byte_len
= subst_utf16_len
* sizeof(WCHAR
);
104 print_utf16_len
= subst_utf16_len
- 4;
105 print_byte_len
= subst_byte_len
- (4 * sizeof(WCHAR
));
107 /* The junction must be an empty directory before the junction attribute
110 cl_win32_pass(CreateDirectoryA(new, NULL
));
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
);
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
));
121 reparse_buf
= LocalAlloc(LMEM_FIXED
|LMEM_ZEROINIT
, reparse_buflen
);
122 cl_assert(reparse_buf
);
124 subst_utf16
= reparse_buf
->ReparseBuffer
.MountPoint
.PathBuffer
;
125 print_utf16
= subst_utf16
+ subst_utf16_len
+ 1;
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
);
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
);
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
;
142 cl_win32_pass(DeviceIoControl(handle
, FSCTL_SET_REPARSE_POINT
,
143 reparse_buf
, reparse_buflen
, NULL
, 0, &ioctl_ret
, NULL
));
146 LocalFree(reparse_buf
);
148 git_str_dispose(&unparsed_buf
);
151 static void do_custom_reparse(const char *path
)
153 REPARSE_GUID_DATA_BUFFER
*reparse_buf
;
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;
161 reparse_buf
= LocalAlloc(LMEM_FIXED
|LMEM_ZEROINIT
, reparse_buflen
);
162 cl_assert(reparse_buf
);
164 reparse_buf
->ReparseTag
= 42;
165 reparse_buf
->ReparseDataLength
= (WORD
)(strlen(reparse_data
) + 1);
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;
180 memcpy(reparse_buf
->GenericReparseBuffer
.DataBuffer
,
181 reparse_data
, strlen(reparse_data
) + 1);
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
);
187 cl_win32_pass(DeviceIoControl(handle
, FSCTL_SET_REPARSE_POINT
,
189 reparse_buf
->ReparseDataLength
+ REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
,
190 NULL
, 0, &ioctl_ret
, NULL
));
193 LocalFree(reparse_buf
);
198 void test_core_link__stat_regular_file(void)
202 cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
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
);
209 void test_core_link__lstat_regular_file(void)
213 cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
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
);
220 void test_core_link__stat_symlink(void)
227 cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
228 do_symlink("stat_target", "stat_symlink", 0);
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
);
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
);
239 void test_core_link__stat_symlink_directory(void)
246 p_mkdir("stat_dirtarget", 0777);
247 do_symlink("stat_dirtarget", "stat_dirlink", 1);
249 cl_must_pass(p_stat("stat_dirtarget", &st
));
250 cl_assert(S_ISDIR(st
.st_mode
));
252 cl_must_pass(p_stat("stat_dirlink", &st
));
253 cl_assert(S_ISDIR(st
.st_mode
));
256 void test_core_link__stat_symlink_chain(void)
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);
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
);
273 void test_core_link__stat_dangling_symlink(void)
280 do_symlink("stat_nonexistent", "stat_dangling", 0);
282 cl_must_fail(p_stat("stat_nonexistent", &st
));
283 cl_must_fail(p_stat("stat_dangling", &st
));
286 void test_core_link__stat_dangling_symlink_directory(void)
293 do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
295 cl_must_fail(p_stat("stat_nonexistent_dir", &st
));
296 cl_must_fail(p_stat("stat_dangling", &st
));
299 void test_core_link__lstat_symlink(void)
301 git_str target_path
= GIT_STR_INIT
;
307 /* Windows always writes the canonical path as the link target, so
308 * write the full path on all platforms.
310 git_str_join(&target_path
, '/', clar_sandbox_path(), "lstat_target");
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);
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
);
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
);
323 git_str_dispose(&target_path
);
326 void test_core_link__lstat_symlink_directory(void)
328 git_str target_path
= GIT_STR_INIT
;
334 git_str_join(&target_path
, '/', clar_sandbox_path(), "lstat_dirtarget");
336 p_mkdir("lstat_dirtarget", 0777);
337 do_symlink(git_str_cstr(&target_path
), "lstat_dirlink", 1);
339 cl_must_pass(p_lstat("lstat_dirtarget", &st
));
340 cl_assert(S_ISDIR(st
.st_mode
));
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
);
346 git_str_dispose(&target_path
);
349 void test_core_link__lstat_dangling_symlink(void)
356 do_symlink("lstat_nonexistent", "lstat_dangling", 0);
358 cl_must_fail(p_lstat("lstat_nonexistent", &st
));
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
);
365 void test_core_link__lstat_dangling_symlink_directory(void)
372 do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
374 cl_must_fail(p_lstat("lstat_nonexistent", &st
));
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
);
381 void test_core_link__stat_junction(void)
384 git_str target_path
= GIT_STR_INIT
;
387 git_str_join(&target_path
, '/', clar_sandbox_path(), "stat_junctarget");
389 p_mkdir("stat_junctarget", 0777);
390 do_junction(git_str_cstr(&target_path
), "stat_junction");
392 cl_must_pass(p_stat("stat_junctarget", &st
));
393 cl_assert(S_ISDIR(st
.st_mode
));
395 cl_must_pass(p_stat("stat_junction", &st
));
396 cl_assert(S_ISDIR(st
.st_mode
));
398 git_str_dispose(&target_path
);
402 void test_core_link__stat_dangling_junction(void)
405 git_str target_path
= GIT_STR_INIT
;
408 git_str_join(&target_path
, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
410 p_mkdir("stat_nonexistent_junctarget", 0777);
411 do_junction(git_str_cstr(&target_path
), "stat_dangling_junction");
413 RemoveDirectory("stat_nonexistent_junctarget");
415 cl_must_fail(p_stat("stat_nonexistent_junctarget", &st
));
416 cl_must_fail(p_stat("stat_dangling_junction", &st
));
418 git_str_dispose(&target_path
);
422 void test_core_link__lstat_junction(void)
425 git_str target_path
= GIT_STR_INIT
;
428 git_str_join(&target_path
, '/', clar_sandbox_path(), "lstat_junctarget");
430 p_mkdir("lstat_junctarget", 0777);
431 do_junction(git_str_cstr(&target_path
), "lstat_junction");
433 cl_must_pass(p_lstat("lstat_junctarget", &st
));
434 cl_assert(S_ISDIR(st
.st_mode
));
436 cl_must_pass(p_lstat("lstat_junction", &st
));
437 cl_assert(S_ISLNK(st
.st_mode
));
439 git_str_dispose(&target_path
);
443 void test_core_link__lstat_dangling_junction(void)
446 git_str target_path
= GIT_STR_INIT
;
449 git_str_join(&target_path
, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
451 p_mkdir("lstat_nonexistent_junctarget", 0777);
452 do_junction(git_str_cstr(&target_path
), "lstat_dangling_junction");
454 RemoveDirectory("lstat_nonexistent_junctarget");
456 cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st
));
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
);
462 git_str_dispose(&target_path
);
466 void test_core_link__stat_hardlink(void)
473 cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
474 do_hardlink("stat_hardlink1", "stat_hardlink2");
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
);
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
);
485 void test_core_link__lstat_hardlink(void)
492 cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
493 do_hardlink("lstat_hardlink1", "lstat_hardlink2");
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
);
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
);
504 void test_core_link__stat_reparse_point(void)
509 /* Generic reparse points should be treated as regular files, only
510 * symlinks and junctions should be treated as links.
513 cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
514 do_custom_reparse("stat_reparse");
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
);
522 void test_core_link__lstat_reparse_point(void)
527 cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
528 do_custom_reparse("lstat_reparse");
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
);
536 void test_core_link__readlink_nonexistent_file(void)
540 cl_must_fail(p_readlink("readlink_nonexistent", buf
, 2048));
541 cl_assert_equal_i(ENOENT
, errno
);
544 void test_core_link__readlink_normal_file(void)
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
);
553 void test_core_link__readlink_symlink(void)
555 git_str target_path
= GIT_STR_INIT
;
562 git_str_join(&target_path
, '/', clar_sandbox_path(), "readlink_target");
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);
567 len
= p_readlink("readlink_link", buf
, 2048);
572 cl_assert_equal_s(git_str_cstr(&target_path
), buf
);
574 git_str_dispose(&target_path
);
577 void test_core_link__readlink_dangling(void)
579 git_str target_path
= GIT_STR_INIT
;
586 git_str_join(&target_path
, '/', clar_sandbox_path(), "readlink_nonexistent");
588 do_symlink(git_str_cstr(&target_path
), "readlink_dangling", 0);
590 len
= p_readlink("readlink_dangling", buf
, 2048);
595 cl_assert_equal_s(git_str_cstr(&target_path
), buf
);
597 git_str_dispose(&target_path
);
600 void test_core_link__readlink_multiple(void)
602 git_str target_path
= GIT_STR_INIT
,
603 path3
= GIT_STR_INIT
, path2
= GIT_STR_INIT
, path1
= GIT_STR_INIT
;
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");
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);
619 len
= p_readlink("readlink_1", buf
, 2048);
624 cl_assert_equal_s(git_str_cstr(&path2
), buf
);
626 git_str_dispose(&path1
);
627 git_str_dispose(&path2
);
628 git_str_dispose(&path3
);
629 git_str_dispose(&target_path
);