]> git.proxmox.com Git - libgit2.git/blob - tests/core/link.c
Merge branch 'cmn/zlib-update' into cmn/update-zlib
[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 is_administrator(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 #endif
39
40 static void do_symlink(const char *old, const char *new, int is_dir)
41 {
42 #ifndef GIT_WIN32
43 GIT_UNUSED(is_dir);
44
45 cl_must_pass(symlink(old, new));
46 #else
47 typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD);
48 HMODULE module;
49 create_symlink_func pCreateSymbolicLink;
50
51 if (!is_administrator())
52 clar__skip();
53
54 cl_assert(module = GetModuleHandle("kernel32"));
55 cl_assert(pCreateSymbolicLink = (create_symlink_func)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 if (!is_administrator())
71 clar__skip();
72
73 cl_assert(module = GetModuleHandle("kernel32"));
74 cl_assert(pCreateHardLink = (create_hardlink_func)GetProcAddress(module, "CreateHardLinkA"));
75
76 cl_win32_pass(pCreateHardLink(new, old, 0));
77 #endif
78 }
79
80 #ifdef GIT_WIN32
81
82 static void do_junction(const char *old, const char *new)
83 {
84 GIT_REPARSE_DATA_BUFFER *reparse_buf;
85 HANDLE handle;
86 git_buf unparsed_buf = GIT_BUF_INIT;
87 wchar_t *subst_utf16, *print_utf16;
88 DWORD ioctl_ret;
89 int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret;
90 USHORT reparse_buflen;
91 size_t i;
92
93 /* Junction targets must be the unparsed name, starting with \??\, using
94 * backslashes instead of forward, and end in a trailing backslash.
95 * eg: \??\C:\Foo\
96 */
97 git_buf_puts(&unparsed_buf, "\\??\\");
98
99 for (i = 0; i < strlen(old); i++)
100 git_buf_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]);
101
102 git_buf_putc(&unparsed_buf, '\\');
103
104 subst_utf16_len = git__utf8_to_16(NULL, 0, git_buf_cstr(&unparsed_buf));
105 subst_byte_len = subst_utf16_len * sizeof(WCHAR);
106
107 print_utf16_len = subst_utf16_len - 4;
108 print_byte_len = subst_byte_len - (4 * sizeof(WCHAR));
109
110 /* The junction must be an empty directory before the junction attribute
111 * can be added.
112 */
113 cl_win32_pass(CreateDirectoryA(new, NULL));
114
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);
118
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));
123
124 reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
125 cl_assert(reparse_buf);
126
127 subst_utf16 = reparse_buf->MountPointReparseBuffer.PathBuffer;
128 print_utf16 = subst_utf16 + subst_utf16_len + 1;
129
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);
133
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);
137
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;
144
145 cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
146 reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL));
147
148 CloseHandle(handle);
149 LocalFree(reparse_buf);
150 }
151
152 static void do_custom_reparse(const char *path)
153 {
154 REPARSE_GUID_DATA_BUFFER *reparse_buf;
155 HANDLE handle;
156 DWORD ioctl_ret;
157
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;
161
162 reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
163 cl_assert(reparse_buf);
164
165 reparse_buf->ReparseTag = 42;
166 reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1);
167
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;
180
181 memcpy(reparse_buf->GenericReparseBuffer.DataBuffer,
182 reparse_data, strlen(reparse_data) + 1);
183
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);
187
188 cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
189 reparse_buf,
190 reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
191 NULL, 0, &ioctl_ret, NULL));
192
193 CloseHandle(handle);
194 LocalFree(reparse_buf);
195 }
196
197 #endif
198
199 git_buf *unslashify(git_buf *buf)
200 {
201 #ifdef GIT_WIN32
202 size_t i;
203
204 for (i = 0; i < buf->size; i++)
205 if (buf->ptr[i] == '/')
206 buf->ptr[i] = '\\';
207 #endif
208
209 return buf;
210 }
211
212 void test_core_link__stat_regular_file(void)
213 {
214 struct stat st;
215
216 cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
217
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);
221 }
222
223 void test_core_link__lstat_regular_file(void)
224 {
225 struct stat st;
226
227 cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
228
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);
232 }
233
234 void test_core_link__stat_symlink(void)
235 {
236 struct stat st;
237
238 cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
239 do_symlink("stat_target", "stat_symlink", 0);
240
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);
244
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);
248 }
249
250 void test_core_link__stat_symlink_directory(void)
251 {
252 struct stat st;
253
254 p_mkdir("stat_dirtarget", 0777);
255 do_symlink("stat_dirtarget", "stat_dirlink", 1);
256
257 cl_must_pass(p_stat("stat_dirtarget", &st));
258 cl_assert(S_ISDIR(st.st_mode));
259
260 cl_must_pass(p_stat("stat_dirlink", &st));
261 cl_assert(S_ISDIR(st.st_mode));
262 }
263
264 void test_core_link__stat_symlink_chain(void)
265 {
266 struct stat st;
267
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);
272
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);
276 }
277
278 void test_core_link__stat_dangling_symlink(void)
279 {
280 struct stat st;
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 do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
293
294 cl_must_fail(p_stat("stat_nonexistent_dir", &st));
295 cl_must_fail(p_stat("stat_dangling", &st));
296 }
297
298 void test_core_link__lstat_symlink(void)
299 {
300 git_buf target_path = GIT_BUF_INIT;
301 struct stat st;
302
303 /* Windows always writes the canonical path as the link target, so
304 * write the full path on all platforms.
305 */
306 git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_target");
307
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);
310
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);
314
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);
318
319 git_buf_free(&target_path);
320 }
321
322 void test_core_link__lstat_symlink_directory(void)
323 {
324 git_buf target_path = GIT_BUF_INIT;
325 struct stat st;
326
327 git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget");
328
329 p_mkdir("lstat_dirtarget", 0777);
330 do_symlink(git_buf_cstr(&target_path), "lstat_dirlink", 1);
331
332 cl_must_pass(p_lstat("lstat_dirtarget", &st));
333 cl_assert(S_ISDIR(st.st_mode));
334
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);
338
339 git_buf_free(&target_path);
340 }
341
342 void test_core_link__lstat_dangling_symlink(void)
343 {
344 struct stat st;
345
346 do_symlink("lstat_nonexistent", "lstat_dangling", 0);
347
348 cl_must_fail(p_lstat("lstat_nonexistent", &st));
349
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);
353 }
354
355 void test_core_link__lstat_dangling_symlink_directory(void)
356 {
357 struct stat st;
358
359 do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
360
361 cl_must_fail(p_lstat("lstat_nonexistent", &st));
362
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);
366 }
367
368 void test_core_link__stat_junction(void)
369 {
370 #ifdef GIT_WIN32
371 git_buf target_path = GIT_BUF_INIT;
372 struct stat st;
373
374 git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget");
375
376 p_mkdir("stat_junctarget", 0777);
377 do_junction(git_buf_cstr(&target_path), "stat_junction");
378
379 cl_must_pass(p_stat("stat_junctarget", &st));
380 cl_assert(S_ISDIR(st.st_mode));
381
382 cl_must_pass(p_stat("stat_junction", &st));
383 cl_assert(S_ISDIR(st.st_mode));
384
385 git_buf_free(&target_path);
386 #endif
387 }
388
389 void test_core_link__stat_dangling_junction(void)
390 {
391 #ifdef GIT_WIN32
392 git_buf target_path = GIT_BUF_INIT;
393 struct stat st;
394
395 git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
396
397 p_mkdir("stat_nonexistent_junctarget", 0777);
398 do_junction(git_buf_cstr(&target_path), "stat_dangling_junction");
399
400 RemoveDirectory("stat_nonexistent_junctarget");
401
402 cl_must_fail(p_stat("stat_nonexistent_junctarget", &st));
403 cl_must_fail(p_stat("stat_dangling_junction", &st));
404
405 git_buf_free(&target_path);
406 #endif
407 }
408
409 void test_core_link__lstat_junction(void)
410 {
411 #ifdef GIT_WIN32
412 git_buf target_path = GIT_BUF_INIT;
413 struct stat st;
414
415 git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget");
416
417 p_mkdir("lstat_junctarget", 0777);
418 do_junction(git_buf_cstr(&target_path), "lstat_junction");
419
420 cl_must_pass(p_lstat("lstat_junctarget", &st));
421 cl_assert(S_ISDIR(st.st_mode));
422
423 cl_must_pass(p_lstat("lstat_junction", &st));
424 cl_assert(S_ISLNK(st.st_mode));
425
426 git_buf_free(&target_path);
427 #endif
428 }
429
430 void test_core_link__lstat_dangling_junction(void)
431 {
432 #ifdef GIT_WIN32
433 git_buf target_path = GIT_BUF_INIT;
434 struct stat st;
435
436 git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
437
438 p_mkdir("lstat_nonexistent_junctarget", 0777);
439 do_junction(git_buf_cstr(&target_path), "lstat_dangling_junction");
440
441 RemoveDirectory("lstat_nonexistent_junctarget");
442
443 cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st));
444
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);
448
449 git_buf_free(&target_path);
450 #endif
451 }
452
453 void test_core_link__stat_hardlink(void)
454 {
455 struct stat st;
456
457 cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
458 do_hardlink("stat_hardlink1", "stat_hardlink2");
459
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);
463
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);
467 }
468
469 void test_core_link__lstat_hardlink(void)
470 {
471 struct stat st;
472
473 cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
474 do_hardlink("lstat_hardlink1", "lstat_hardlink2");
475
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);
479
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);
483 }
484
485 void test_core_link__stat_reparse_point(void)
486 {
487 #ifdef GIT_WIN32
488 struct stat st;
489
490 /* Generic reparse points should be treated as regular files, only
491 * symlinks and junctions should be treated as links.
492 */
493
494 cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
495 do_custom_reparse("stat_reparse");
496
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);
500 #endif
501 }
502
503 void test_core_link__lstat_reparse_point(void)
504 {
505 #ifdef GIT_WIN32
506 struct stat st;
507
508 cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
509 do_custom_reparse("lstat_reparse");
510
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);
514 #endif
515 }
516
517 void test_core_link__readlink_nonexistent_file(void)
518 {
519 char buf[2048];
520
521 cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048));
522 cl_assert_equal_i(ENOENT, errno);
523 }
524
525 void test_core_link__readlink_normal_file(void)
526 {
527 char buf[2048];
528
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);
532 }
533
534 void test_core_link__readlink_symlink(void)
535 {
536 git_buf target_path = GIT_BUF_INIT;
537 int len;
538 char buf[2048];
539
540 git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_target");
541
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);
544
545 len = p_readlink("readlink_link", buf, 2048);
546 cl_must_pass(len);
547
548 buf[len] = 0;
549
550 cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
551
552 git_buf_free(&target_path);
553 }
554
555 void test_core_link__readlink_dangling(void)
556 {
557 git_buf target_path = GIT_BUF_INIT;
558 int len;
559 char buf[2048];
560
561 git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent");
562
563 do_symlink(git_buf_cstr(&target_path), "readlink_dangling", 0);
564
565 len = p_readlink("readlink_dangling", buf, 2048);
566 cl_must_pass(len);
567
568 buf[len] = 0;
569
570 cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
571
572 git_buf_free(&target_path);
573 }
574
575 void test_core_link__readlink_multiple(void)
576 {
577 git_buf target_path = GIT_BUF_INIT,
578 path3 = GIT_BUF_INIT, path2 = GIT_BUF_INIT, path1 = GIT_BUF_INIT;
579 int len;
580 char buf[2048];
581
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");
586
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);
590
591 len = p_readlink("readlink_1", buf, 2048);
592 cl_must_pass(len);
593
594 buf[len] = 0;
595
596 cl_assert_equal_s(git_buf_cstr(unslashify(&path2)), buf);
597
598 git_buf_free(&path1);
599 git_buf_free(&path2);
600 git_buf_free(&path3);
601 git_buf_free(&target_path);
602 }