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