]>
Commit | Line | Data |
---|---|---|
77d545a2 CW |
1 | /** |
2 | * GNU ln(1) workalike that creates Windows links (hard and symbolic) | |
3 | * instead of Cygwin ones. | |
4 | * | |
5 | * Copyright 2011-2013 by Daniel Colascione | |
6 | * All rights reserved. | |
7 | * | |
8 | * This program is free software: you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation, either version 3 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
20 | * | |
21 | * See the COPYING file for full license information. | |
22 | */ | |
23 | ||
24 | #define _WIN32_WINNT 0x0600 /* Vista, but will run on XP */ | |
25 | #define UNICODE 1 | |
26 | #define _UNICODE 1 | |
27 | ||
28 | #if HAVE_CONFIG_H | |
29 | #include "config.h" | |
30 | #endif | |
31 | #include "common.h" | |
32 | ||
33 | #include <windows.h> | |
34 | #include <stdio.h> | |
35 | #include <stdlib.h> | |
36 | #include <wchar.h> | |
37 | #include <getopt.h> | |
38 | #include <unistd.h> | |
39 | #include <string.h> | |
40 | #include <locale.h> | |
41 | #include <errno.h> | |
42 | #include <sys/cygwin.h> | |
43 | #include <sys/stat.h> | |
44 | #include <libgen.h> | |
45 | ||
46 | #define PRGNAME "winln" | |
47 | #define PRGVER "1.3" | |
48 | #define PRGAUTHOR "Daniel Colascione <dancol@dancol.org>" | |
49 | #define PRGCOPY "Copyright (C) 2011 " PRGAUTHOR | |
50 | #define PRGLICENSE "GPLv2 or later <http://www.gnu.org/licenses/gpl-2.0.html>" | |
51 | ||
52 | static BOOLEAN WINAPI | |
53 | (*XCreateSymbolicLinkW) | |
54 | (LPWSTR lpSymlinkFileName, | |
55 | LPWSTR lpTargetFileName, | |
56 | DWORD dwFlags); | |
57 | ||
58 | static char* | |
59 | to_mbs(const wchar_t* wc); | |
60 | ||
61 | static wchar_t* | |
62 | to_wcs(const char* mbs); | |
63 | ||
64 | static void | |
65 | usage() | |
66 | { | |
67 | fprintf( | |
68 | stdout, | |
69 | PRGNAME " [OPTION] TARGET LINKNAME: like ln(1) for native Windows links\n" | |
70 | "\n" | |
71 | " -s --symbolic: make symbolic links\n" | |
72 | " -v --verbose: print name of each linked file\n" | |
73 | " -f --force: replace existing links\n" | |
74 | " -d --directory: always treat TARGET as a directory\n" | |
75 | " -F --file: always treat TARGET as a file\n" | |
76 | " -T --target: treat LINKNAME as a normal file always\n" | |
77 | " -A --auto: guess type of TARGET [default]\n" | |
78 | " If TARGET does not exist, treat as file.\n" | |
79 | "\n" | |
80 | PRGNAME " -h\n" | |
81 | PRGNAME " --help\n" | |
82 | "\n" | |
83 | " Display this help message.\n" | |
84 | "\n" | |
85 | PRGNAME " -V\n" | |
86 | PRGNAME " --version\n" | |
87 | "\n" | |
88 | " Display version information.\n" | |
89 | ); | |
90 | } | |
91 | ||
92 | static void | |
93 | versinfo () | |
94 | { | |
95 | fprintf(stdout, | |
96 | PRGNAME " " PRGVER "\n" | |
97 | PRGCOPY "\n" | |
98 | PRGLICENSE "\n" | |
99 | ); | |
100 | } | |
101 | ||
102 | /* Decode a Win32 error code to a localized string encoded according | |
103 | to the current locale. Return a malloc()ed string. */ | |
104 | static char* | |
105 | errmsg(DWORD errorcode) | |
106 | { | |
107 | wchar_t* wcsmsg = NULL; | |
108 | char* msg = NULL; | |
109 | ||
110 | FormatMessageW( | |
111 | (FORMAT_MESSAGE_FROM_SYSTEM| | |
112 | FORMAT_MESSAGE_ALLOCATE_BUFFER), | |
113 | NULL, | |
114 | errorcode, | |
115 | 0, | |
116 | (LPWSTR)&wcsmsg, | |
117 | 0, | |
118 | NULL); | |
119 | ||
120 | if(wcsmsg != NULL) { | |
121 | msg = to_mbs(wcsmsg); | |
122 | LocalFree(wcsmsg); | |
123 | if(msg && msg[0] && msg[strlen(msg) - 1] == '\n') { | |
124 | msg[strlen(msg) - 1] = '\0'; | |
125 | } | |
126 | } | |
127 | ||
128 | if(msg == NULL) { | |
129 | msg = strdup("[unknown error]"); | |
130 | } | |
131 | ||
132 | return msg; | |
133 | } | |
134 | ||
135 | static const struct option longopts[] = | |
136 | { | |
137 | { "verbose", 0, 0, 'v' }, | |
138 | { "directory", 0, 0, 'd' }, | |
139 | { "file", 0, 0, 'F' }, | |
140 | { "symbolic", 0, 0, 's' }, | |
141 | { "force", 0, 0, 'f' }, | |
142 | { "auto", 0, 0, 'A' }, | |
143 | { "help", 0, 0, 'h' }, | |
144 | { "version", 0, 0, 'V' }, | |
145 | { "no-target-directory", 0, 0, 'T' }, | |
146 | { "target-directory", 1, 0, 't' }, | |
147 | { 0 } | |
148 | }; | |
149 | ||
150 | /* Output information about link on stdout */ | |
151 | static int verbose = 0; | |
152 | ||
153 | /* Overwrite existing links */ | |
154 | static int force = 0; | |
155 | ||
156 | /* Create symbolic links */ | |
157 | static int symbolic = 0; | |
158 | ||
159 | /* Never treat last argument as a directory */ | |
160 | static int no_tgt_dir = 0; | |
161 | ||
162 | enum type_mode { | |
163 | MODE_FORCE_FILE, | |
164 | MODE_FORCE_DIR, | |
165 | MODE_AUTO, | |
166 | }; | |
167 | ||
168 | static enum type_mode mode = MODE_AUTO; | |
169 | ||
170 | /* Convert the given string (which is encoded in the current locale) | |
171 | to a wide character string. The returned string is malloced. | |
172 | Return NULL on failure. */ | |
173 | static wchar_t* | |
174 | to_wcs(const char* mbs) | |
175 | { | |
176 | size_t wcs_length = mbstowcs(NULL, mbs, 0) + 1; | |
177 | wchar_t* wcs = malloc(wcs_length * sizeof(*wcs)); | |
178 | if(wcs != NULL) { | |
179 | if(mbstowcs(wcs, mbs, wcs_length) == (size_t) -1) { | |
180 | free(wcs); | |
181 | wcs = NULL; | |
182 | } | |
183 | } | |
184 | ||
185 | return wcs; | |
186 | } | |
187 | ||
188 | /* Convert a wide-character string to a malloced multibyte string | |
189 | encoded as specified in the current locale. Return NULL on | |
190 | failure. */ | |
191 | static char* | |
192 | to_mbs(const wchar_t* wcs) | |
193 | { | |
194 | size_t mbs_length = wcstombs(NULL, wcs, 0) + 1; | |
195 | char* mbs = malloc(mbs_length * sizeof(*mbs)); | |
196 | if(mbs != NULL) { | |
197 | if(wcstombs(mbs, wcs, mbs_length) == (size_t) -1) { | |
198 | free(mbs); | |
199 | mbs = NULL; | |
200 | } | |
201 | } | |
202 | ||
203 | return mbs; | |
204 | } | |
205 | ||
206 | /* Convert path to Win32. If we're given an absolute path, use normal | |
207 | Cygwin conversion functions. If we've given a relative path, work | |
208 | around the cygwin_conv_path deficiency described below by using a | |
209 | very simple filename transformation. | |
210 | ||
211 | Return NULL on failure. | |
212 | ||
213 | XXX: we treat relative paths specially because cygwin_create_path | |
214 | fails to actually return a relative path for a reference to the | |
215 | parent directory. Say we have this directory structure: | |
216 | ||
217 | dir/foo | |
218 | dir/subdir/ | |
219 | ||
220 | With CWD in dir/subdir, we run winln -sv ../foo. | |
221 | cygwin_create_path will actually yield the _absolute_ path to foo, | |
222 | not the correct relative Windows path, ..\foo. | |
223 | */ | |
224 | static wchar_t* | |
225 | conv_path_to_win32(const char* posix_path) | |
226 | { | |
227 | wchar_t* w32_path = NULL; | |
228 | size_t posix_path_length = strlen(posix_path); | |
229 | ||
230 | if(posix_path_length < 1) { | |
231 | errno = EINVAL; | |
232 | return NULL; | |
233 | } | |
234 | ||
235 | if(posix_path[0] != '/' && | |
236 | posix_path[posix_path_length - 1] != '.' && | |
237 | strcspn(posix_path, "?<>\\:*|") == posix_path_length) | |
238 | { | |
239 | char* tmp = strdup(posix_path); | |
240 | char* tmp2; | |
241 | ||
242 | for(tmp2 = tmp; *tmp2; ++tmp2) { | |
243 | if(*tmp2 == '/') { | |
244 | *tmp2 = '\\'; | |
245 | } | |
246 | } | |
247 | ||
248 | w32_path = to_wcs(tmp); | |
249 | free(tmp); | |
250 | } | |
251 | ||
252 | if(w32_path == NULL) { | |
253 | w32_path = cygwin_create_path( | |
254 | CCP_POSIX_TO_WIN_W | CCP_RELATIVE, posix_path); | |
255 | } | |
256 | ||
257 | return w32_path; | |
258 | } | |
259 | ||
260 | /* Make a link. Return 0 on success, something else on error. */ | |
261 | static int | |
262 | do_link(const char* target, const char* link) | |
263 | { | |
264 | /* Work around a bug that causes Cygwin to resolve the path if it | |
265 | ends in a native symbolic link. | |
266 | ||
267 | The bug is described on the Cygwin mailing list in message | |
268 | <AANLkTi=98+M5sAsGp4vT09UN9uisqp0M=mgJi9WcSObG@mail.gmail.com>.. | |
269 | ||
270 | That this bug makes symlinks-to-symlinks point to the | |
271 | ultimate target, and there's no good way around that. | |
272 | ||
273 | XXX: The workaround is here racy. The idea here is that if | |
274 | we're going to overwrite the link anyway, we can just | |
275 | remove the link first so that cygwin_conv_path doesn't | |
276 | follow the now non-existant symlink. | |
277 | */ | |
278 | struct stat lstatbuf; | |
279 | int lstat_success = 0; | |
280 | ||
281 | struct stat statbuf; | |
282 | int stat_success = 0; | |
283 | ||
284 | struct stat target_statbuf; | |
285 | int target_stat_success = 0; | |
286 | ||
287 | wchar_t* w32link = NULL; | |
288 | wchar_t* w32target = NULL; | |
289 | DWORD flags; | |
290 | ||
291 | int ret = 0; | |
292 | ||
293 | if(lstat(link, &lstatbuf) == 0) { | |
294 | lstat_success = 1; | |
295 | ||
296 | if(stat(link, &statbuf) == 0) { | |
297 | stat_success = 1; | |
298 | } | |
299 | ||
300 | if(force) { | |
301 | if(unlink(link)) { | |
302 | fprintf(stderr, | |
303 | PRGNAME ": cannot remove `%s': %s\n", | |
304 | link, strerror(errno)); | |
305 | ret = 5; | |
306 | goto out; | |
307 | } | |
308 | } else { | |
309 | fprintf(stderr, | |
310 | PRGNAME ": could not create link `%s': file exists\n", | |
311 | link); | |
312 | ret = 1; | |
313 | goto out; | |
314 | } | |
315 | } | |
316 | ||
317 | if(stat(target, &target_statbuf) == 0) { | |
318 | target_stat_success = 1; | |
319 | } | |
320 | ||
321 | w32link = conv_path_to_win32(link); | |
322 | if(w32link == NULL) { | |
323 | fprintf(stderr, PRGNAME ": could not convert `%s' to win32 path\n", | |
324 | link); | |
325 | ret = 2; | |
326 | goto out; | |
327 | } | |
328 | ||
329 | w32target = conv_path_to_win32(target); | |
330 | if(w32target == NULL) { | |
331 | fprintf(stderr, PRGNAME ": could not convert `%s' to win32 path\n", | |
332 | target); | |
333 | ret = 2; | |
334 | goto out; | |
335 | } | |
336 | ||
337 | ||
338 | switch(mode) | |
339 | { | |
340 | case MODE_FORCE_DIR: | |
341 | flags = SYMBOLIC_LINK_FLAG_DIRECTORY; | |
342 | break; | |
343 | case MODE_FORCE_FILE: | |
344 | flags = 0; | |
345 | break; | |
346 | default: | |
347 | flags = 0; | |
348 | if(target_stat_success && S_ISDIR(target_statbuf.st_mode)) { | |
349 | flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; | |
350 | } | |
351 | break; | |
352 | } | |
353 | ||
354 | /* Don't call link(2), even for hard links: we want to maintain | |
355 | * absolute parity between the hard and symbolic links made using | |
356 | * this tool. We don't want link targets to change just because | |
357 | * we change the link type. */ | |
358 | ||
359 | if(symbolic) { | |
360 | if(XCreateSymbolicLinkW(w32link, w32target, flags)) { | |
361 | if(verbose) { | |
362 | printf("`%s' -> `%s' [%s]\n", link, target, | |
363 | flags ? "dir" : "file"); | |
364 | } | |
365 | } else { | |
366 | fprintf(stderr, PRGNAME ": failed to create symbolic link `%s': %s\n", | |
367 | link, errmsg(GetLastError())); | |
368 | ret = 2; | |
369 | goto out; | |
370 | } | |
371 | } else { | |
372 | if(CreateHardLinkW(w32link, w32target, 0)) { | |
373 | if(verbose) { | |
374 | printf("`%s' => `%s'\n", link, target); | |
375 | } | |
376 | } else { | |
377 | fprintf(stderr, PRGNAME ": failed to create hard link `%s': %s\n", | |
378 | link, errmsg(GetLastError())); | |
379 | ret = 2; | |
380 | goto out; | |
381 | } | |
382 | } | |
383 | ||
384 | out: | |
385 | free(w32link); | |
386 | free(w32target); | |
387 | return ret; | |
388 | } | |
389 | ||
390 | static int | |
391 | is_dir(const char* path) | |
392 | { | |
393 | struct stat statbuf; | |
394 | return stat(path, &statbuf) == 0 && | |
395 | S_ISDIR(statbuf.st_mode); | |
396 | } | |
397 | ||
398 | static BOOL | |
399 | set_privilege_status ( | |
400 | const wchar_t* privname, | |
401 | BOOL bEnablePrivilege) | |
402 | { | |
403 | /* After the MSDN example. */ | |
404 | ||
405 | TOKEN_PRIVILEGES tp; | |
406 | LUID luid; | |
407 | HANDLE hToken; | |
408 | BOOL success; | |
409 | ||
410 | hToken = NULL; | |
411 | success = FALSE; | |
412 | ||
413 | if (!OpenProcessToken (GetCurrentProcess (), | |
414 | (TOKEN_QUERY | | |
415 | TOKEN_ADJUST_PRIVILEGES), | |
416 | &hToken)) | |
417 | { | |
418 | goto out; | |
419 | } | |
420 | ||
421 | if ( !LookupPrivilegeValue ( | |
422 | NULL, // lookup privilege on local system | |
423 | privname, // privilege to lookup | |
424 | &luid ) ) // receives LUID of privilege | |
425 | { | |
426 | goto out; | |
427 | } | |
428 | ||
429 | tp.PrivilegeCount = 1; | |
430 | tp.Privileges[0].Luid = luid; | |
431 | if (bEnablePrivilege) { | |
432 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
433 | } else { | |
434 | tp.Privileges[0].Attributes = 0; | |
435 | } | |
436 | ||
437 | // Enable the privilege or disable all privileges. | |
438 | ||
439 | if ( !AdjustTokenPrivileges ( | |
440 | hToken, | |
441 | FALSE, | |
442 | &tp, | |
443 | sizeof (TOKEN_PRIVILEGES), | |
444 | (PTOKEN_PRIVILEGES) NULL, | |
445 | (PDWORD) NULL) ) | |
446 | { | |
447 | goto out; | |
448 | } | |
449 | ||
450 | if (GetLastError () == ERROR_NOT_ALL_ASSIGNED) { | |
451 | goto out; | |
452 | } | |
453 | ||
454 | success = TRUE; | |
455 | ||
456 | out: | |
457 | ||
458 | if (hToken) { | |
459 | CloseHandle (hToken); | |
460 | } | |
461 | ||
462 | return success; | |
463 | } | |
464 | ||
465 | int | |
466 | main(int argc, char* argv[]) | |
467 | { | |
468 | int c; | |
469 | char* tgt_dir = NULL; | |
470 | int ret = 0; | |
471 | ||
472 | setlocale(LC_ALL, ""); | |
473 | ||
474 | to_mbs(L""); | |
475 | to_wcs(""); | |
476 | ||
477 | while ((c = getopt_long(argc, argv, "VvdfFsATt:", longopts, 0)) != -1) { | |
478 | switch(c) { | |
479 | case 'v': | |
480 | verbose = 1; | |
481 | break; | |
482 | case 'd': | |
483 | mode = MODE_FORCE_DIR; | |
484 | break; | |
485 | case 'f': | |
486 | force = 1; | |
487 | break; | |
488 | case 'F': | |
489 | mode = MODE_FORCE_FILE; | |
490 | break; | |
491 | case 's': | |
492 | symbolic = 1; | |
493 | break; | |
494 | case 'A': | |
495 | mode = MODE_AUTO; | |
496 | break; | |
497 | case 'T': | |
498 | no_tgt_dir = 1; | |
499 | break; | |
500 | case 't': | |
501 | tgt_dir = strdup(optarg); | |
502 | break; | |
503 | case 'h': | |
504 | usage(); | |
505 | ret = 0; | |
506 | goto out; | |
507 | case 'V': | |
508 | versinfo (); | |
509 | ret = 0; | |
510 | goto out; | |
511 | default: | |
512 | fprintf(stderr, PRGNAME ": use --help for usage\n"); | |
513 | ret = 4; | |
514 | goto out; | |
515 | } | |
516 | } | |
517 | ||
518 | if(symbolic) { | |
519 | HMODULE hKernel32 = LoadLibraryW(L"kernel32"); | |
520 | if(hKernel32 == NULL) { | |
521 | fprintf(stderr, PRGNAME ": could not load kernel32: %s\n", | |
522 | errmsg(GetLastError())); | |
523 | ret = 1; | |
524 | goto out; | |
525 | } | |
526 | ||
527 | XCreateSymbolicLinkW = | |
528 | (void*)GetProcAddress(hKernel32, "CreateSymbolicLinkW"); | |
529 | ||
530 | if(XCreateSymbolicLinkW == NULL) { | |
531 | fprintf(stderr, PRGNAME ": symbolic links not supported on this OS\n"); | |
532 | ret = 2; | |
533 | goto out; | |
534 | } | |
535 | ||
536 | if(!set_privilege_status(L"SeCreateSymbolicLinkPrivilege", TRUE)) { | |
537 | fprintf(stderr, | |
538 | PRGNAME ": you don't permission to create symbolic links. Run," | |
539 | " as administrator,\n" | |
540 | PRGNAME ": editrights -a SeCreateSymbolicLinkPrivilege -a $YOUR_USER\n" | |
541 | ); | |
542 | ||
543 | ret = 3; | |
544 | goto out; | |
545 | } | |
546 | } | |
547 | ||
548 | argc -= optind; | |
549 | argv += optind; | |
550 | ||
551 | if(argc == 0) { | |
552 | fprintf(stderr, PRGNAME ": no arguments. Use --help for usage\n"); | |
553 | ret = 1; | |
554 | goto out; | |
555 | } | |
556 | ||
557 | if(no_tgt_dir) { | |
558 | if(argc != 2) { | |
559 | fprintf(stderr, PRGNAME ": must have exactly two args with -T\n"); | |
560 | ret = 1; | |
561 | goto out; | |
562 | } | |
563 | ||
564 | ret = do_link(argv[0], argv[1]); | |
565 | goto out; | |
566 | } | |
567 | ||
568 | if(tgt_dir == NULL && argc == 1) { | |
569 | tgt_dir = "."; | |
570 | } | |
571 | ||
572 | if(tgt_dir == NULL) { | |
573 | int last_is_dir = is_dir(argv[argc - 1]); | |
574 | if(argc == 2 && !last_is_dir) { | |
575 | ret = do_link(argv[0], argv[1]); | |
576 | goto out; | |
577 | } | |
578 | ||
579 | if(!last_is_dir) { | |
580 | fprintf(stderr, PRGNAME ": `%s': not a directory\n", | |
581 | argv[argc - 1]); | |
582 | ret = 1; | |
583 | goto out; | |
584 | } | |
585 | ||
586 | tgt_dir = argv[--argc]; | |
587 | argv[argc] = NULL; | |
588 | } | |
589 | ||
590 | for(; *argv; ++argv) { | |
591 | char* tgt; | |
592 | int r; | |
593 | ||
594 | if(asprintf(&tgt, "%s/%s", tgt_dir, basename(*argv)) == -1) { | |
595 | fprintf(stderr, PRGNAME ": asprintf: %s\n", | |
596 | strerror(errno)); | |
597 | ret = 1; | |
598 | goto out; | |
599 | } | |
600 | ||
601 | r = do_link(*argv, tgt); | |
602 | if(r && ret == 0) { | |
603 | ret = r; | |
604 | } | |
605 | ||
606 | free(tgt); | |
607 | } | |
608 | ||
609 | out: | |
610 | return ret; | |
611 | } |