Attached is a small program that behaves very similarly to ln(1), but
that works with Windows hard and symbolic links instead of Cygwin ones.
Successful use of this program requires Vista or newer, a user with
SeCreateSymbolicLinkPrivilege, and a symlink evaluation policy that
allows the kind of symbolic link you'd like to create. If these
conditions are met, however, this program becomes useful because it can
create symbolic links that work equally well for Cygwin and non-Cygwin
programs. Because its argument handling is practically identical to
that of coreutils ln, winln can be used via a simple shell alias (or
PATH prefixing, if you're feeling bold).
#define _WIN32_WINNT 0x0500 /*Win2k*/
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/cygwin.h>
#include <sys/stat.h>
#include <libgen.h>
#define PRGNAME "winln"
#define PRGVER "1.0"
#define PRGAUTHOR "Daniel Colascione <dan.colasci...@gmail.com>"
#define PRGCOPY "Copyright (C) 2011 " PRGAUTHOR
#define PRGLICENSE "GPLv2 or later <http://www.gnu.org/licenses/gpl-2.0.html>"
/**
* ln(1) workalike that creates Windows links (hard and symbolic)
* instead of Cygwin ones.
*/
static BOOLEAN WINAPI
(*XCreateSymbolicLinkW)
(LPWSTR lpSymlinkFileName,
LPWSTR lpTargetFileName,
DWORD dwFlags);
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: verbose\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"
" -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. Return
a malloc()ed string. */
static char*
errmsg(DWORD errorcode)
{
char* msg = NULL;
FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
errorcode,
0,
(LPTSTR)&msg,
0,
NULL);
if(msg == NULL) {
msg = strdup("[unknown error]");
}
if (msg[strlen(msg) - 1] == '\n') {
msg[strlen(msg) - 1] = '\0';
}
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;
static wchar_t*
to_wc(const char* mb)
{
wchar_t* ret;
int bufsz = MultiByteToWideChar(
CP_THREAD_ACP, 0, mb, -1, 0, 0);
if(bufsz > 0) {
ret = malloc(bufsz*sizeof(*ret));
bufsz = MultiByteToWideChar(
CP_THREAD_ACP, 0, mb, -1, ret, bufsz);
if(bufsz == 0) {
free(ret);
ret = 0;
}
}
return ret;
}
/* Convert path to Win32.
If we're given an absolute path, use normal Cygwin conversion
functions. If we've given a relative path, hack it up. */
static wchar_t*
conv_path(const char* posixpath)
{
wchar_t* ret;
if(posixpath[0] == '/') {
ret = cygwin_create_path(
CCP_POSIX_TO_WIN_W | CCP_RELATIVE, posixpath);
} else {
char* tmp = strdup(posixpath);
char* tmp2;
for(tmp2 = tmp; *tmp2; ++tmp2) {
if(*tmp2 == '/') {
*tmp2 = '\\';
}
}
ret = to_wc(tmp);
free(tmp);
}
return ret;
}
/* 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.
Note that this bug makes symlinks-to-symlinks point to the ultimate
target, and there's no good way around that.
XXX: the workaround is 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(link);
if(w32link == NULL) {
fprintf(stderr, PRGNAME ": could not convert `%s' to win32
path\n",
link);
ret = 2;
goto out;
}
w32target = conv_path(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;
}
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);
}
int
main(int argc, char* argv[])
{
int c;
char* tgt_dir = NULL;
int ret = 0;
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 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;
}
}
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;
}
--
Problem reports: http://cygwin.com/problems.html
FAQ: http://cygwin.com/faq/
Documentation: http://cygwin.com/docs.html
Unsubscribe info: http://cygwin.com/ml/#unsubscribe-simple