2 * GNU ln(1) workalike that creates Windows links (hard and symbolic)
3 * instead of Cygwin ones.
5 * Copyright 2011-2013 by Daniel Colascione
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.
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.
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/>.
21 * See the COPYING file for full license information.
24 #define _WIN32_WINNT 0x0600 /* Vista, but will run on XP */
42 #include <sys/cygwin.h>
46 #define PRGNAME "winln"
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>"
53 (*XCreateSymbolicLinkW
)
54 (LPWSTR lpSymlinkFileName
,
55 LPWSTR lpTargetFileName
,
59 to_mbs(const wchar_t* wc
);
62 to_wcs(const char* mbs
);
69 PRGNAME
" [OPTION] TARGET LINKNAME: like ln(1) for native Windows links\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"
83 " Display this help message.\n"
86 PRGNAME
" --version\n"
88 " Display version information.\n"
96 PRGNAME
" " PRGVER
"\n"
102 /* Decode a Win32 error code to a localized string encoded according
103 to the current locale. Return a malloc()ed string. */
105 errmsg(DWORD errorcode
)
107 wchar_t* wcsmsg
= NULL
;
111 (FORMAT_MESSAGE_FROM_SYSTEM
|
112 FORMAT_MESSAGE_ALLOCATE_BUFFER
),
121 msg
= to_mbs(wcsmsg
);
123 if(msg
&& msg
[0] && msg
[strlen(msg
) - 1] == '\n') {
124 msg
[strlen(msg
) - 1] = '\0';
129 msg
= strdup("[unknown error]");
135 static const struct option longopts
[] =
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' },
150 /* Output information about link on stdout */
151 static int verbose
= 0;
153 /* Overwrite existing links */
154 static int force
= 0;
156 /* Create symbolic links */
157 static int symbolic
= 0;
159 /* Never treat last argument as a directory */
160 static int no_tgt_dir
= 0;
168 static enum type_mode mode
= MODE_AUTO
;
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. */
174 to_wcs(const char* mbs
)
176 size_t wcs_length
= mbstowcs(NULL
, mbs
, 0) + 1;
177 wchar_t* wcs
= malloc(wcs_length
* sizeof(*wcs
));
179 if(mbstowcs(wcs
, mbs
, wcs_length
) == (size_t) -1) {
188 /* Convert a wide-character string to a malloced multibyte string
189 encoded as specified in the current locale. Return NULL on
192 to_mbs(const wchar_t* wcs
)
194 size_t mbs_length
= wcstombs(NULL
, wcs
, 0) + 1;
195 char* mbs
= malloc(mbs_length
* sizeof(*mbs
));
197 if(wcstombs(mbs
, wcs
, mbs_length
) == (size_t) -1) {
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.
211 Return NULL on failure.
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:
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.
225 conv_path_to_win32(const char* posix_path
)
227 wchar_t* w32_path
= NULL
;
228 size_t posix_path_length
= strlen(posix_path
);
230 if(posix_path_length
< 1) {
235 if(posix_path
[0] != '/' &&
236 posix_path
[posix_path_length
- 1] != '.' &&
237 strcspn(posix_path
, "?<>\\:*|") == posix_path_length
)
239 char* tmp
= strdup(posix_path
);
242 for(tmp2
= tmp
; *tmp2
; ++tmp2
) {
248 w32_path
= to_wcs(tmp
);
252 if(w32_path
== NULL
) {
253 w32_path
= cygwin_create_path(
254 CCP_POSIX_TO_WIN_W
| CCP_RELATIVE
, posix_path
);
260 /* Make a link. Return 0 on success, something else on error. */
262 do_link(const char* target
, const char* link
)
264 /* Work around a bug that causes Cygwin to resolve the path if it
265 ends in a native symbolic link.
267 The bug is described on the Cygwin mailing list in message
268 <AANLkTi=98+M5sAsGp4vT09UN9uisqp0M=mgJi9WcSObG@mail.gmail.com>..
270 That this bug makes symlinks-to-symlinks point to the
271 ultimate target, and there's no good way around that.
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.
278 struct stat lstatbuf
;
279 int lstat_success
= 0;
282 int stat_success
= 0;
284 struct stat target_statbuf
;
285 int target_stat_success
= 0;
287 wchar_t* w32link
= NULL
;
288 wchar_t* w32target
= NULL
;
293 if(lstat(link
, &lstatbuf
) == 0) {
296 if(stat(link
, &statbuf
) == 0) {
303 PRGNAME
": cannot remove `%s': %s\n",
304 link
, strerror(errno
));
310 PRGNAME
": could not create link `%s': file exists\n",
317 if(stat(target
, &target_statbuf
) == 0) {
318 target_stat_success
= 1;
321 w32link
= conv_path_to_win32(link
);
322 if(w32link
== NULL
) {
323 fprintf(stderr
, PRGNAME
": could not convert `%s' to win32 path\n",
329 w32target
= conv_path_to_win32(target
);
330 if(w32target
== NULL
) {
331 fprintf(stderr
, PRGNAME
": could not convert `%s' to win32 path\n",
341 flags
= SYMBOLIC_LINK_FLAG_DIRECTORY
;
343 case MODE_FORCE_FILE
:
348 if(target_stat_success
&& S_ISDIR(target_statbuf
.st_mode
)) {
349 flags
|= SYMBOLIC_LINK_FLAG_DIRECTORY
;
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. */
360 if(XCreateSymbolicLinkW(w32link
, w32target
, flags
)) {
362 printf("`%s' -> `%s' [%s]\n", link
, target
,
363 flags
? "dir" : "file");
366 fprintf(stderr
, PRGNAME
": failed to create symbolic link `%s': %s\n",
367 link
, errmsg(GetLastError()));
372 if(CreateHardLinkW(w32link
, w32target
, 0)) {
374 printf("`%s' => `%s'\n", link
, target
);
377 fprintf(stderr
, PRGNAME
": failed to create hard link `%s': %s\n",
378 link
, errmsg(GetLastError()));
391 is_dir(const char* path
)
394 return stat(path
, &statbuf
) == 0 &&
395 S_ISDIR(statbuf
.st_mode
);
399 set_privilege_status (
400 const wchar_t* privname
,
401 BOOL bEnablePrivilege
)
403 /* After the MSDN example. */
413 if (!OpenProcessToken (GetCurrentProcess (),
415 TOKEN_ADJUST_PRIVILEGES
),
421 if ( !LookupPrivilegeValue (
422 NULL
, // lookup privilege on local system
423 privname
, // privilege to lookup
424 &luid
) ) // receives LUID of privilege
429 tp
.PrivilegeCount
= 1;
430 tp
.Privileges
[0].Luid
= luid
;
431 if (bEnablePrivilege
) {
432 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
434 tp
.Privileges
[0].Attributes
= 0;
437 // Enable the privilege or disable all privileges.
439 if ( !AdjustTokenPrivileges (
443 sizeof (TOKEN_PRIVILEGES
),
444 (PTOKEN_PRIVILEGES
) NULL
,
450 if (GetLastError () == ERROR_NOT_ALL_ASSIGNED
) {
459 CloseHandle (hToken
);
466 main(int argc
, char* argv
[])
469 char* tgt_dir
= NULL
;
472 setlocale(LC_ALL
, "");
477 while ((c
= getopt_long(argc
, argv
, "VvdfFsATt:", longopts
, 0)) != -1) {
483 mode
= MODE_FORCE_DIR
;
489 mode
= MODE_FORCE_FILE
;
501 tgt_dir
= strdup(optarg
);
512 fprintf(stderr
, PRGNAME
": use --help for usage\n");
519 HMODULE hKernel32
= LoadLibraryW(L
"kernel32");
520 if(hKernel32
== NULL
) {
521 fprintf(stderr
, PRGNAME
": could not load kernel32: %s\n",
522 errmsg(GetLastError()));
527 XCreateSymbolicLinkW
=
528 (void*)GetProcAddress(hKernel32
, "CreateSymbolicLinkW");
530 if(XCreateSymbolicLinkW
== NULL
) {
531 fprintf(stderr
, PRGNAME
": symbolic links not supported on this OS\n");
536 if(!set_privilege_status(L
"SeCreateSymbolicLinkPrivilege", TRUE
)) {
538 PRGNAME
": you don't permission to create symbolic links. Run,"
539 " as administrator,\n"
540 PRGNAME
": editrights -a SeCreateSymbolicLinkPrivilege -a $YOUR_USER\n"
552 fprintf(stderr
, PRGNAME
": no arguments. Use --help for usage\n");
559 fprintf(stderr
, PRGNAME
": must have exactly two args with -T\n");
564 ret
= do_link(argv
[0], argv
[1]);
568 if(tgt_dir
== NULL
&& argc
== 1) {
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]);
580 fprintf(stderr
, PRGNAME
": `%s': not a directory\n",
586 tgt_dir
= argv
[--argc
];
590 for(; *argv
; ++argv
) {
594 if(asprintf(&tgt
, "%s/%s", tgt_dir
, basename(*argv
)) == -1) {
595 fprintf(stderr
, PRGNAME
": asprintf: %s\n",
601 r
= do_link(*argv
, tgt
);