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 is_administrator(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;
40 static void do_symlink(const char *old
, const char *new, int is_dir
)
45 cl_must_pass(symlink(old
, new));
47 typedef DWORD (WINAPI
*create_symlink_func
)(LPCTSTR
, LPCTSTR
, DWORD
);
49 create_symlink_func pCreateSymbolicLink
;
51 if (!is_administrator())
54 cl_assert(module
= GetModuleHandle("kernel32"));
55 cl_assert(pCreateSymbolicLink
= (create_symlink_func
)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 if (!is_administrator())
73 cl_assert(module
= GetModuleHandle("kernel32"));
74 cl_assert(pCreateHardLink
= (create_hardlink_func
)GetProcAddress(module
, "CreateHardLinkA"));
76 cl_win32_pass(pCreateHardLink(new, old
, 0));
82 static void do_junction(const char *old
, const char *new)
84 GIT_REPARSE_DATA_BUFFER
*reparse_buf
;
86 git_buf unparsed_buf
= GIT_BUF_INIT
;
87 wchar_t *subst_utf16
, *print_utf16
;
89 int subst_utf16_len
, subst_byte_len
, print_utf16_len
, print_byte_len
, ret
;
90 USHORT reparse_buflen
;
93 /* Junction targets must be the unparsed name, starting with \??\, using
94 * backslashes instead of forward, and end in a trailing backslash.
97 git_buf_puts(&unparsed_buf
, "\\??\\");
99 for (i
= 0; i
< strlen(old
); i
++)
100 git_buf_putc(&unparsed_buf
, old
[i
] == '/' ? '\\' : old
[i
]);
102 git_buf_putc(&unparsed_buf
, '\\');
104 subst_utf16_len
= git__utf8_to_16(NULL
, 0, git_buf_cstr(&unparsed_buf
));
105 subst_byte_len
= subst_utf16_len
* sizeof(WCHAR
);
107 print_utf16_len
= subst_utf16_len
- 4;
108 print_byte_len
= subst_byte_len
- (4 * sizeof(WCHAR
));
110 /* The junction must be an empty directory before the junction attribute
113 cl_win32_pass(CreateDirectoryA(new, NULL
));
115 handle
= CreateFileA(new, GENERIC_WRITE
, 0, NULL
, OPEN_EXISTING
,
116 FILE_FLAG_OPEN_REPARSE_POINT
| FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
117 cl_win32_pass(handle
!= INVALID_HANDLE_VALUE
);
119 reparse_buflen
= (USHORT
)(REPARSE_DATA_HEADER_SIZE
+
120 REPARSE_DATA_MOUNTPOINT_HEADER_SIZE
+
121 subst_byte_len
+ sizeof(WCHAR
) +
122 print_byte_len
+ sizeof(WCHAR
));
124 reparse_buf
= LocalAlloc(LMEM_FIXED
|LMEM_ZEROINIT
, reparse_buflen
);
125 cl_assert(reparse_buf
);
127 subst_utf16
= reparse_buf
->MountPointReparseBuffer
.PathBuffer
;
128 print_utf16
= subst_utf16
+ subst_utf16_len
+ 1;
130 ret
= git__utf8_to_16(subst_utf16
, subst_utf16_len
+ 1,
131 git_buf_cstr(&unparsed_buf
));
132 cl_assert_equal_i(subst_utf16_len
, ret
);
134 ret
= git__utf8_to_16(print_utf16
,
135 print_utf16_len
+ 1, git_buf_cstr(&unparsed_buf
) + 4);
136 cl_assert_equal_i(print_utf16_len
, ret
);
138 reparse_buf
->ReparseTag
= IO_REPARSE_TAG_MOUNT_POINT
;
139 reparse_buf
->MountPointReparseBuffer
.SubstituteNameOffset
= 0;
140 reparse_buf
->MountPointReparseBuffer
.SubstituteNameLength
= subst_byte_len
;
141 reparse_buf
->MountPointReparseBuffer
.PrintNameOffset
= (USHORT
)(subst_byte_len
+ sizeof(WCHAR
));
142 reparse_buf
->MountPointReparseBuffer
.PrintNameLength
= print_byte_len
;
143 reparse_buf
->ReparseDataLength
= reparse_buflen
- REPARSE_DATA_HEADER_SIZE
;
145 cl_win32_pass(DeviceIoControl(handle
, FSCTL_SET_REPARSE_POINT
,
146 reparse_buf
, reparse_buflen
, NULL
, 0, &ioctl_ret
, NULL
));
149 LocalFree(reparse_buf
);
152 static void do_custom_reparse(const char *path
)
154 REPARSE_GUID_DATA_BUFFER
*reparse_buf
;
158 const char *reparse_data
= "Reparse points are silly.";
159 size_t reparse_buflen
= REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
+
160 strlen(reparse_data
) + 1;
162 reparse_buf
= LocalAlloc(LMEM_FIXED
|LMEM_ZEROINIT
, reparse_buflen
);
163 cl_assert(reparse_buf
);
165 reparse_buf
->ReparseTag
= 42;
166 reparse_buf
->ReparseDataLength
= (WORD
)(strlen(reparse_data
) + 1);
168 reparse_buf
->ReparseGuid
.Data1
= 0xdeadbeef;
169 reparse_buf
->ReparseGuid
.Data2
= 0xdead;
170 reparse_buf
->ReparseGuid
.Data3
= 0xbeef;
171 reparse_buf
->ReparseGuid
.Data4
[0] = 42;
172 reparse_buf
->ReparseGuid
.Data4
[1] = 42;
173 reparse_buf
->ReparseGuid
.Data4
[2] = 42;
174 reparse_buf
->ReparseGuid
.Data4
[3] = 42;
175 reparse_buf
->ReparseGuid
.Data4
[4] = 42;
176 reparse_buf
->ReparseGuid
.Data4
[5] = 42;
177 reparse_buf
->ReparseGuid
.Data4
[6] = 42;
178 reparse_buf
->ReparseGuid
.Data4
[7] = 42;
179 reparse_buf
->ReparseGuid
.Data4
[8] = 42;
181 memcpy(reparse_buf
->GenericReparseBuffer
.DataBuffer
,
182 reparse_data
, strlen(reparse_data
) + 1);
184 handle
= CreateFileA(path
, GENERIC_WRITE
, 0, NULL
, OPEN_EXISTING
,
185 FILE_FLAG_OPEN_REPARSE_POINT
| FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
186 cl_win32_pass(handle
!= INVALID_HANDLE_VALUE
);
188 cl_win32_pass(DeviceIoControl(handle
, FSCTL_SET_REPARSE_POINT
,
190 reparse_buf
->ReparseDataLength
+ REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
,
191 NULL
, 0, &ioctl_ret
, NULL
));
194 LocalFree(reparse_buf
);
199 git_buf
*unslashify(git_buf
*buf
)
204 for (i
= 0; i
< buf
->size
; i
++)
205 if (buf
->ptr
[i
] == '/')
212 void test_core_link__stat_regular_file(void)
216 cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
218 cl_must_pass(p_stat("stat_regfile", &st
));
219 cl_assert(S_ISREG(st
.st_mode
));
220 cl_assert_equal_i(24, st
.st_size
);
223 void test_core_link__lstat_regular_file(void)
227 cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
229 cl_must_pass(p_stat("lstat_regfile", &st
));
230 cl_assert(S_ISREG(st
.st_mode
));
231 cl_assert_equal_i(24, st
.st_size
);
234 void test_core_link__stat_symlink(void)
238 cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
239 do_symlink("stat_target", "stat_symlink", 0);
241 cl_must_pass(p_stat("stat_target", &st
));
242 cl_assert(S_ISREG(st
.st_mode
));
243 cl_assert_equal_i(39, st
.st_size
);
245 cl_must_pass(p_stat("stat_symlink", &st
));
246 cl_assert(S_ISREG(st
.st_mode
));
247 cl_assert_equal_i(39, st
.st_size
);
250 void test_core_link__stat_symlink_directory(void)
254 p_mkdir("stat_dirtarget", 0777);
255 do_symlink("stat_dirtarget", "stat_dirlink", 1);
257 cl_must_pass(p_stat("stat_dirtarget", &st
));
258 cl_assert(S_ISDIR(st
.st_mode
));
260 cl_must_pass(p_stat("stat_dirlink", &st
));
261 cl_assert(S_ISDIR(st
.st_mode
));
264 void test_core_link__stat_symlink_chain(void)
268 cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
269 do_symlink("stat_final_target", "stat_chain_3", 0);
270 do_symlink("stat_chain_3", "stat_chain_2", 0);
271 do_symlink("stat_chain_2", "stat_chain_1", 0);
273 cl_must_pass(p_stat("stat_chain_1", &st
));
274 cl_assert(S_ISREG(st
.st_mode
));
275 cl_assert_equal_i(39, st
.st_size
);
278 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)
292 do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
294 cl_must_fail(p_stat("stat_nonexistent_dir", &st
));
295 cl_must_fail(p_stat("stat_dangling", &st
));
298 void test_core_link__lstat_symlink(void)
300 git_buf target_path
= GIT_BUF_INIT
;
303 /* Windows always writes the canonical path as the link target, so
304 * write the full path on all platforms.
306 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_target");
308 cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
309 do_symlink(git_buf_cstr(&target_path
), "lstat_symlink", 0);
311 cl_must_pass(p_lstat("lstat_target", &st
));
312 cl_assert(S_ISREG(st
.st_mode
));
313 cl_assert_equal_i(39, st
.st_size
);
315 cl_must_pass(p_lstat("lstat_symlink", &st
));
316 cl_assert(S_ISLNK(st
.st_mode
));
317 cl_assert_equal_i(git_buf_len(&target_path
), st
.st_size
);
319 git_buf_free(&target_path
);
322 void test_core_link__lstat_symlink_directory(void)
324 git_buf target_path
= GIT_BUF_INIT
;
327 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_dirtarget");
329 p_mkdir("lstat_dirtarget", 0777);
330 do_symlink(git_buf_cstr(&target_path
), "lstat_dirlink", 1);
332 cl_must_pass(p_lstat("lstat_dirtarget", &st
));
333 cl_assert(S_ISDIR(st
.st_mode
));
335 cl_must_pass(p_lstat("lstat_dirlink", &st
));
336 cl_assert(S_ISLNK(st
.st_mode
));
337 cl_assert_equal_i(git_buf_len(&target_path
), st
.st_size
);
339 git_buf_free(&target_path
);
342 void test_core_link__lstat_dangling_symlink(void)
346 do_symlink("lstat_nonexistent", "lstat_dangling", 0);
348 cl_must_fail(p_lstat("lstat_nonexistent", &st
));
350 cl_must_pass(p_lstat("lstat_dangling", &st
));
351 cl_assert(S_ISLNK(st
.st_mode
));
352 cl_assert_equal_i(strlen("lstat_nonexistent"), st
.st_size
);
355 void test_core_link__lstat_dangling_symlink_directory(void)
359 do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
361 cl_must_fail(p_lstat("lstat_nonexistent", &st
));
363 cl_must_pass(p_lstat("lstat_dangling_dir", &st
));
364 cl_assert(S_ISLNK(st
.st_mode
));
365 cl_assert_equal_i(strlen("lstat_nonexistent"), st
.st_size
);
368 void test_core_link__stat_junction(void)
371 git_buf target_path
= GIT_BUF_INIT
;
374 git_buf_join(&target_path
, '/', clar_sandbox_path(), "stat_junctarget");
376 p_mkdir("stat_junctarget", 0777);
377 do_junction(git_buf_cstr(&target_path
), "stat_junction");
379 cl_must_pass(p_stat("stat_junctarget", &st
));
380 cl_assert(S_ISDIR(st
.st_mode
));
382 cl_must_pass(p_stat("stat_junction", &st
));
383 cl_assert(S_ISDIR(st
.st_mode
));
385 git_buf_free(&target_path
);
389 void test_core_link__stat_dangling_junction(void)
392 git_buf target_path
= GIT_BUF_INIT
;
395 git_buf_join(&target_path
, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
397 p_mkdir("stat_nonexistent_junctarget", 0777);
398 do_junction(git_buf_cstr(&target_path
), "stat_dangling_junction");
400 RemoveDirectory("stat_nonexistent_junctarget");
402 cl_must_fail(p_stat("stat_nonexistent_junctarget", &st
));
403 cl_must_fail(p_stat("stat_dangling_junction", &st
));
405 git_buf_free(&target_path
);
409 void test_core_link__lstat_junction(void)
412 git_buf target_path
= GIT_BUF_INIT
;
415 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_junctarget");
417 p_mkdir("lstat_junctarget", 0777);
418 do_junction(git_buf_cstr(&target_path
), "lstat_junction");
420 cl_must_pass(p_lstat("lstat_junctarget", &st
));
421 cl_assert(S_ISDIR(st
.st_mode
));
423 cl_must_pass(p_lstat("lstat_junction", &st
));
424 cl_assert(S_ISLNK(st
.st_mode
));
426 git_buf_free(&target_path
);
430 void test_core_link__lstat_dangling_junction(void)
433 git_buf target_path
= GIT_BUF_INIT
;
436 git_buf_join(&target_path
, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
438 p_mkdir("lstat_nonexistent_junctarget", 0777);
439 do_junction(git_buf_cstr(&target_path
), "lstat_dangling_junction");
441 RemoveDirectory("lstat_nonexistent_junctarget");
443 cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st
));
445 cl_must_pass(p_lstat("lstat_dangling_junction", &st
));
446 cl_assert(S_ISLNK(st
.st_mode
));
447 cl_assert_equal_i(git_buf_len(&target_path
), st
.st_size
);
449 git_buf_free(&target_path
);
453 void test_core_link__stat_hardlink(void)
457 cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
458 do_hardlink("stat_hardlink1", "stat_hardlink2");
460 cl_must_pass(p_stat("stat_hardlink1", &st
));
461 cl_assert(S_ISREG(st
.st_mode
));
462 cl_assert_equal_i(26, st
.st_size
);
464 cl_must_pass(p_stat("stat_hardlink2", &st
));
465 cl_assert(S_ISREG(st
.st_mode
));
466 cl_assert_equal_i(26, st
.st_size
);
469 void test_core_link__lstat_hardlink(void)
473 cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
474 do_hardlink("lstat_hardlink1", "lstat_hardlink2");
476 cl_must_pass(p_lstat("lstat_hardlink1", &st
));
477 cl_assert(S_ISREG(st
.st_mode
));
478 cl_assert_equal_i(26, st
.st_size
);
480 cl_must_pass(p_lstat("lstat_hardlink2", &st
));
481 cl_assert(S_ISREG(st
.st_mode
));
482 cl_assert_equal_i(26, st
.st_size
);
485 void test_core_link__stat_reparse_point(void)
490 /* Generic reparse points should be treated as regular files, only
491 * symlinks and junctions should be treated as links.
494 cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
495 do_custom_reparse("stat_reparse");
497 cl_must_pass(p_lstat("stat_reparse", &st
));
498 cl_assert(S_ISREG(st
.st_mode
));
499 cl_assert_equal_i(25, st
.st_size
);
503 void test_core_link__lstat_reparse_point(void)
508 cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
509 do_custom_reparse("lstat_reparse");
511 cl_must_pass(p_lstat("lstat_reparse", &st
));
512 cl_assert(S_ISREG(st
.st_mode
));
513 cl_assert_equal_i(25, st
.st_size
);
517 void test_core_link__readlink_nonexistent_file(void)
521 cl_must_fail(p_readlink("readlink_nonexistent", buf
, 2048));
522 cl_assert_equal_i(ENOENT
, errno
);
525 void test_core_link__readlink_normal_file(void)
529 cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
530 cl_must_fail(p_readlink("readlink_regfile", buf
, 2048));
531 cl_assert_equal_i(EINVAL
, errno
);
534 void test_core_link__readlink_symlink(void)
536 git_buf target_path
= GIT_BUF_INIT
;
540 git_buf_join(&target_path
, '/', clar_sandbox_path(), "readlink_target");
542 cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
543 do_symlink(git_buf_cstr(&target_path
), "readlink_link", 0);
545 len
= p_readlink("readlink_link", buf
, 2048);
550 cl_assert_equal_s(git_buf_cstr(unslashify(&target_path
)), buf
);
552 git_buf_free(&target_path
);
555 void test_core_link__readlink_dangling(void)
557 git_buf target_path
= GIT_BUF_INIT
;
561 git_buf_join(&target_path
, '/', clar_sandbox_path(), "readlink_nonexistent");
563 do_symlink(git_buf_cstr(&target_path
), "readlink_dangling", 0);
565 len
= p_readlink("readlink_dangling", buf
, 2048);
570 cl_assert_equal_s(git_buf_cstr(unslashify(&target_path
)), buf
);
572 git_buf_free(&target_path
);
575 void test_core_link__readlink_multiple(void)
577 git_buf target_path
= GIT_BUF_INIT
,
578 path3
= GIT_BUF_INIT
, path2
= GIT_BUF_INIT
, path1
= GIT_BUF_INIT
;
582 git_buf_join(&target_path
, '/', clar_sandbox_path(), "readlink_final");
583 git_buf_join(&path3
, '/', clar_sandbox_path(), "readlink_3");
584 git_buf_join(&path2
, '/', clar_sandbox_path(), "readlink_2");
585 git_buf_join(&path1
, '/', clar_sandbox_path(), "readlink_1");
587 do_symlink(git_buf_cstr(&target_path
), git_buf_cstr(&path3
), 0);
588 do_symlink(git_buf_cstr(&path3
), git_buf_cstr(&path2
), 0);
589 do_symlink(git_buf_cstr(&path2
), git_buf_cstr(&path1
), 0);
591 len
= p_readlink("readlink_1", buf
, 2048);
596 cl_assert_equal_s(git_buf_cstr(unslashify(&path2
)), buf
);
598 git_buf_free(&path1
);
599 git_buf_free(&path2
);
600 git_buf_free(&path3
);
601 git_buf_free(&target_path
);