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

Reply via email to