/**
* GNU ln(1) workalike that creates Windows links (hard and symbolic)
* instead of Cygwin ones.
*
* Copyright 2011-2013 by Daniel Colascione
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* See the COPYING file for full license information.
*/
#define _WIN32_WINNT 0x0600 /* Vista, but will run on XP */
#define UNICODE 1
#define _UNICODE 1
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include "common.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PRGNAME "winln"
#define PRGVER "1.3"
#define PRGAUTHOR "Daniel Colascione "
#define PRGCOPY "Copyright (C) 2011 " PRGAUTHOR
#define PRGLICENSE "GPLv2 or later "
static BOOLEAN WINAPI
(*XCreateSymbolicLinkW)
(LPWSTR lpSymlinkFileName,
LPWSTR lpTargetFileName,
DWORD dwFlags);
static char*
to_mbs(const wchar_t* wc);
static wchar_t*
to_wcs(const char* mbs);
static void
usage()
{
fprintf(
stdout,
PRGNAME " [OPTION] TARGET LINKNAME: like ln(1) for native Windows links\n"
"\n"
" -s --symbolic: make symbolic links\n"
" -v --verbose: print name of each linked file\n"
" -f --force: replace existing links\n"
" -d --directory: always treat TARGET as a directory\n"
" -F --file: always treat TARGET as a file\n"
" -T --target: treat LINKNAME as a normal file always\n"
" -A --auto: guess type of TARGET [default]\n"
" If TARGET does not exist, treat as file.\n"
"\n"
PRGNAME " -h\n"
PRGNAME " --help\n"
"\n"
" Display this help message.\n"
"\n"
PRGNAME " -V\n"
PRGNAME " --version\n"
"\n"
" Display version information.\n"
);
}
static void
versinfo ()
{
fprintf(stdout,
PRGNAME " " PRGVER "\n"
PRGCOPY "\n"
PRGLICENSE "\n"
);
}
/* Decode a Win32 error code to a localized string encoded according
to the current locale. Return a malloc()ed string. */
static char*
errmsg(DWORD errorcode)
{
wchar_t* wcsmsg = NULL;
char* msg = NULL;
FormatMessageW(
(FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_ALLOCATE_BUFFER),
NULL,
errorcode,
0,
(LPWSTR)&wcsmsg,
0,
NULL);
if(wcsmsg != NULL) {
msg = to_mbs(wcsmsg);
LocalFree(wcsmsg);
if(msg && msg[0] && msg[strlen(msg) - 1] == '\n') {
msg[strlen(msg) - 1] = '\0';
}
}
if(msg == NULL) {
msg = strdup("[unknown error]");
}
return msg;
}
static const struct option longopts[] =
{
{ "verbose", 0, 0, 'v' },
{ "directory", 0, 0, 'd' },
{ "file", 0, 0, 'F' },
{ "symbolic", 0, 0, 's' },
{ "force", 0, 0, 'f' },
{ "auto", 0, 0, 'A' },
{ "help", 0, 0, 'h' },
{ "version", 0, 0, 'V' },
{ "no-target-directory", 0, 0, 'T' },
{ "target-directory", 1, 0, 't' },
{ 0 }
};
/* Output information about link on stdout */
static int verbose = 0;
/* Overwrite existing links */
static int force = 0;
/* Create symbolic links */
static int symbolic = 0;
/* Never treat last argument as a directory */
static int no_tgt_dir = 0;
enum type_mode {
MODE_FORCE_FILE,
MODE_FORCE_DIR,
MODE_AUTO,
};
static enum type_mode mode = MODE_AUTO;
/* Convert the given string (which is encoded in the current locale)
to a wide character string. The returned string is malloced.
Return NULL on failure. */
static wchar_t*
to_wcs(const char* mbs)
{
size_t wcs_length = mbstowcs(NULL, mbs, 0) + 1;
wchar_t* wcs = malloc(wcs_length * sizeof(*wcs));
if(wcs != NULL) {
if(mbstowcs(wcs, mbs, wcs_length) == (size_t) -1) {
free(wcs);
wcs = NULL;
}
}
return wcs;
}
/* Convert a wide-character string to a malloced multibyte string
encoded as specified in the current locale. Return NULL on
failure. */
static char*
to_mbs(const wchar_t* wcs)
{
size_t mbs_length = wcstombs(NULL, wcs, 0) + 1;
char* mbs = malloc(mbs_length * sizeof(*mbs));
if(mbs != NULL) {
if(wcstombs(mbs, wcs, mbs_length) == (size_t) -1) {
free(mbs);
mbs = NULL;
}
}
return mbs;
}
/* Convert path to Win32. If we're given an absolute path, use normal
Cygwin conversion functions. If we've given a relative path, work
around the cygwin_conv_path deficiency described below by using a
very simple filename transformation.
Return NULL on failure.
XXX: we treat relative paths specially because cygwin_create_path
fails to actually return a relative path for a reference to the
parent directory. Say we have this directory structure:
dir/foo
dir/subdir/
With CWD in dir/subdir, we run winln -sv ../foo.
cygwin_create_path will actually yield the _absolute_ path to foo,
not the correct relative Windows path, ..\foo.
*/
static wchar_t*
conv_path_to_win32(const char* posix_path)
{
wchar_t* w32_path = NULL;
size_t posix_path_length = strlen(posix_path);
if(posix_path_length < 1) {
errno = EINVAL;
return NULL;
}
if(posix_path[0] != '/' &&
posix_path[posix_path_length - 1] != '.' &&
strcspn(posix_path, "?<>\\:*|") == posix_path_length)
{
char* tmp = strdup(posix_path);
char* tmp2;
for(tmp2 = tmp; *tmp2; ++tmp2) {
if(*tmp2 == '/') {
*tmp2 = '\\';
}
}
w32_path = to_wcs(tmp);
free(tmp);
}
if(w32_path == NULL) {
w32_path = cygwin_create_path(
CCP_POSIX_TO_WIN_W | CCP_RELATIVE, posix_path);
}
return w32_path;
}
/* Make a link. Return 0 on success, something else on error. */
static int
do_link(const char* target, const char* link)
{
/* Work around a bug that causes Cygwin to resolve the path if it
ends in a native symbolic link.
The bug is described on the Cygwin mailing list in message
..
That this bug makes symlinks-to-symlinks point to the
ultimate target, and there's no good way around that.
XXX: The workaround is here racy. The idea here is that if
we're going to overwrite the link anyway, we can just
remove the link first so that cygwin_conv_path doesn't
follow the now non-existant symlink.
*/
struct stat lstatbuf;
int lstat_success = 0;
struct stat statbuf;
int stat_success = 0;
struct stat target_statbuf;
int target_stat_success = 0;
wchar_t* w32link = NULL;
wchar_t* w32target = NULL;
DWORD flags;
int ret = 0;
if(lstat(link, &lstatbuf) == 0) {
lstat_success = 1;
if(stat(link, &statbuf) == 0) {
stat_success = 1;
}
if(force) {
if(unlink(link)) {
fprintf(stderr,
PRGNAME ": cannot remove `%s': %s\n",
link, strerror(errno));
ret = 5;
goto out;
}
} else {
fprintf(stderr,
PRGNAME ": could not create link `%s': file exists\n",
link);
ret = 1;
goto out;
}
}
if(stat(target, &target_statbuf) == 0) {
target_stat_success = 1;
}
w32link = conv_path_to_win32(link);
if(w32link == NULL) {
fprintf(stderr, PRGNAME ": could not convert `%s' to win32 path\n",
link);
ret = 2;
goto out;
}
w32target = conv_path_to_win32(target);
if(w32target == NULL) {
fprintf(stderr, PRGNAME ": could not convert `%s' to win32 path\n",
target);
ret = 2;
goto out;
}
switch(mode)
{
case MODE_FORCE_DIR:
flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
break;
case MODE_FORCE_FILE:
flags = 0;
break;
default:
flags = 0;
if(target_stat_success && S_ISDIR(target_statbuf.st_mode)) {
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
break;
}
/* Don't call link(2), even for hard links: we want to maintain
* absolute parity between the hard and symbolic links made using
* this tool. We don't want link targets to change just because
* we change the link type. */
if(symbolic) {
if(XCreateSymbolicLinkW(w32link, w32target, flags)) {
if(verbose) {
printf("`%s' -> `%s' [%s]\n", link, target,
flags ? "dir" : "file");
}
} else {
fprintf(stderr, PRGNAME ": failed to create symbolic link `%s': %s\n",
link, errmsg(GetLastError()));
ret = 2;
goto out;
}
} else {
if(CreateHardLinkW(w32link, w32target, 0)) {
if(verbose) {
printf("`%s' => `%s'\n", link, target);
}
} else {
fprintf(stderr, PRGNAME ": failed to create hard link `%s': %s\n",
link, errmsg(GetLastError()));
ret = 2;
goto out;
}
}
out:
free(w32link);
free(w32target);
return ret;
}
static int
is_dir(const char* path)
{
struct stat statbuf;
return stat(path, &statbuf) == 0 &&
S_ISDIR(statbuf.st_mode);
}
static BOOL
set_privilege_status (
const wchar_t* privname,
BOOL bEnablePrivilege)
{
/* After the MSDN example. */
TOKEN_PRIVILEGES tp;
LUID luid;
HANDLE hToken;
BOOL success;
hToken = NULL;
success = FALSE;
if (!OpenProcessToken (GetCurrentProcess (),
(TOKEN_QUERY |
TOKEN_ADJUST_PRIVILEGES),
&hToken))
{
goto out;
}
if ( !LookupPrivilegeValue (
NULL, // lookup privilege on local system
privname, // privilege to lookup
&luid ) ) // receives LUID of privilege
{
goto out;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege) {
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
} else {
tp.Privileges[0].Attributes = 0;
}
// Enable the privilege or disable all privileges.
if ( !AdjustTokenPrivileges (
hToken,
FALSE,
&tp,
sizeof (TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
goto out;
}
if (GetLastError () == ERROR_NOT_ALL_ASSIGNED) {
goto out;
}
success = TRUE;
out:
if (hToken) {
CloseHandle (hToken);
}
return success;
}
int
main(int argc, char* argv[])
{
int c;
char* tgt_dir = NULL;
int ret = 0;
setlocale(LC_ALL, "");
to_mbs(L"");
to_wcs("");
while ((c = getopt_long(argc, argv, "VvdfFsATt:", longopts, 0)) != -1) {
switch(c) {
case 'v':
verbose = 1;
break;
case 'd':
mode = MODE_FORCE_DIR;
break;
case 'f':
force = 1;
break;
case 'F':
mode = MODE_FORCE_FILE;
break;
case 's':
symbolic = 1;
break;
case 'A':
mode = MODE_AUTO;
break;
case 'T':
no_tgt_dir = 1;
break;
case 't':
tgt_dir = strdup(optarg);
break;
case 'h':
usage();
ret = 0;
goto out;
case 'V':
versinfo ();
ret = 0;
goto out;
default:
fprintf(stderr, PRGNAME ": use --help for usage\n");
ret = 4;
goto out;
}
}
if(symbolic) {
HMODULE hKernel32 = LoadLibraryW(L"kernel32");
if(hKernel32 == NULL) {
fprintf(stderr, PRGNAME ": could not load kernel32: %s\n",
errmsg(GetLastError()));
ret = 1;
goto out;
}
XCreateSymbolicLinkW =
(void*)GetProcAddress(hKernel32, "CreateSymbolicLinkW");
if(XCreateSymbolicLinkW == NULL) {
fprintf(stderr, PRGNAME ": symbolic links not supported on this OS\n");
ret = 2;
goto out;
}
if(!set_privilege_status(L"SeCreateSymbolicLinkPrivilege", TRUE)) {
fprintf(stderr,
PRGNAME ": you don't permission to create symbolic links. Run,"
" as administrator,\n"
PRGNAME ": editrights -a SeCreateSymbolicLinkPrivilege -a $YOUR_USER\n"
);
ret = 3;
goto out;
}
}
argc -= optind;
argv += optind;
if(argc == 0) {
fprintf(stderr, PRGNAME ": no arguments. Use --help for usage\n");
ret = 1;
goto out;
}
if(no_tgt_dir) {
if(argc != 2) {
fprintf(stderr, PRGNAME ": must have exactly two args with -T\n");
ret = 1;
goto out;
}
ret = do_link(argv[0], argv[1]);
goto out;
}
if(tgt_dir == NULL && argc == 1) {
tgt_dir = ".";
}
if(tgt_dir == NULL) {
int last_is_dir = is_dir(argv[argc - 1]);
if(argc == 2 && !last_is_dir) {
ret = do_link(argv[0], argv[1]);
goto out;
}
if(!last_is_dir) {
fprintf(stderr, PRGNAME ": `%s': not a directory\n",
argv[argc - 1]);
ret = 1;
goto out;
}
tgt_dir = argv[--argc];
argv[argc] = NULL;
}
for(; *argv; ++argv) {
char* tgt;
int r;
if(asprintf(&tgt, "%s/%s", tgt_dir, basename(*argv)) == -1) {
fprintf(stderr, PRGNAME ": asprintf: %s\n",
strerror(errno));
ret = 1;
goto out;
}
r = do_link(*argv, tgt);
if(r && ret == 0) {
ret = r;
}
free(tgt);
}
out:
return ret;
}