]> cygwin.com Git - cygwin-apps/cygutils.git/blob - src/winln/winln.c
Add winln
[cygwin-apps/cygutils.git] / src / winln / winln.c
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 }
This page took 0.062698 seconds and 5 git commands to generate.