delorie.com/archives/browse.cgi   search  
Mail Archives: cygwin/2013/04/03/03:18:23

X-Recipient: archive-cygwin AT delorie DOT com
DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id
:list-unsubscribe:list-subscribe:list-archive:list-post
:list-help:sender:message-id:date:from:mime-version:to:subject
:content-type; q=dns; s=default; b=ldZ4FuXfnLwahAwa200w7o9lMlub8
WBNTS82yGD5CyE3zFbviRyB0s/KFT3oSlw155edvndoFg2zrfDu38fx9Wi0yF/6a
rZJI7EMkyBoRhqzJsO43HxAkdIsyv+LNDuNoCpzltdTlprfnzRijxyrxgKT4m5H1
c9LFYHDK/a1ZsA=
DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id
:list-unsubscribe:list-subscribe:list-archive:list-post
:list-help:sender:message-id:date:from:mime-version:to:subject
:content-type; s=default; bh=8wQ7nsoiZqXRX1sMK4Ye5G1UlNM=; b=yEO
blhG6CxSIH1KWdPZ0dwCkHufqmacBTqVGNBSqPBr6i8XLB/fdErox2eSIvhwpe4B
JmBywIqJR6JsCmWpbQFS3tvO49KK+if0kPb1Ec6ioGbDZrSoUXp26mzLq0swYQ39
/cKFzsgB7nI+elm9RKc/prKSnsKTPIjcZh7IfO4w=
Mailing-List: contact cygwin-help AT cygwin DOT com; run by ezmlm
List-Id: <cygwin.cygwin.com>
List-Subscribe: <mailto:cygwin-subscribe AT cygwin DOT com>
List-Archive: <http://sourceware.org/ml/cygwin/>
List-Post: <mailto:cygwin AT cygwin DOT com>
List-Help: <mailto:cygwin-help AT cygwin DOT com>, <http://sourceware.org/ml/#faqs>
Sender: cygwin-owner AT cygwin DOT com
Mail-Followup-To: cygwin AT cygwin DOT com
Delivered-To: mailing list cygwin AT cygwin DOT com
X-Spam-SWARE-Status: No, score=-7.9 required=5.0 tests=AWL,BAYES_00,KHOP_PGP_SIGNED,RP_MATCHES_RCVD,SPF_HELO_PASS,TW_NL,TW_RG autolearn=ham version=3.3.1
Message-ID: <515BD718.8080605@dancol.org>
Date: Wed, 03 Apr 2013 00:15:36 -0700
From: Daniel Colascione <dancol AT dancol DOT org>
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20130328 Thunderbird/17.0.5
MIME-Version: 1.0
To: cygwin AT cygwin DOT com
Subject: winln for native symlinks
X-Virus-Found: No

------enig2RBKIBRFBFOLOGAMKHTEQ
Content-Type: multipart/mixed;
 boundary="------------020704010003050809020108"

This is a multi-part message in MIME format.
--------------020704010003050809020108
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable

In light of the recent discussion on the developers list about native symli=
nks,
I'd like to suggest including my winln program (which I posted a while ago =
on
this list, and which I've attached to this message) in the cygutils package.
It's a drop-in replacement for GNU ld.

--------------020704010003050809020108
Content-Type: text/plain; charset=windows-1252;
 name="winln.c"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
 filename="winln.c"

/**
 * GNU ln(1) workalike that creates Windows links (hard and symbolic)
 * instead of Cygwin ones.
 *
 * Revision History:
 *
 * Version 1.2 - TBD
 *
 *  - Fix off-by-one error in to_wcs that caused it to sometimes return
 *    an unterminated string.
 *
 * Version 1.1 - 2011-12-04
 *
 *  - Use Cygwin functions to convert between character encodings,
 *    correctly respecting locale.
 *
 *  - Explain bugs worked around in the code.
 *
 *  - Ensure that we don't create relative symlinks to invalid
 *    filenames.
 *
 *  - Print message when user lacks SeCreateSymbolicLinkPrivilege and
 *    suggest a way to enable the privilege.
 *
 * Version 1.0 - 2011-04-06
 *
 *  - Initial release
 *
 */

#define _WIN32_WINNT 0x0500 /*Win2k*/
#define STRICT
#define UNICODE 1
#define _UNICODE 1

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <locale.h>
#include <errno.h>
#include <sys/cygwin.h>
#include <sys/stat.h>
#include <libgen.h>

#define PRGNAME "winln"
#define PRGVER "1.2"
#define PRGAUTHOR "Daniel Colascione <dan DOT colascione AT gmail DOT com>"
#define PRGCOPY "Copyright (C) 2011 " PRGAUTHOR
#define PRGLICENSE "GPLv2 or later <http://www.gnu.org/licenses/gpl-2.0.htm=
l>"

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 l=
inks\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 encoded according
   to the current locale.  Return a malloc()ed string. */
static char*
errmsg(DWORD errorcode)
{
    wchar_t* wcsmsg =3D NULL;
    char* msg =3D NULL;

    FormatMessageW(
        (FORMAT_MESSAGE_FROM_SYSTEM|
         FORMAT_MESSAGE_ALLOCATE_BUFFER),
        NULL,
        errorcode,
        0,
        (LPWSTR)&wcsmsg,
        0,
        NULL);

    if(wcsmsg !=3D NULL) {
        msg =3D to_mbs(wcsmsg);
        LocalFree(wcsmsg);
        if(msg && msg[0] && msg[strlen(msg) - 1] =3D=3D '\n') {
            msg[strlen(msg) - 1] =3D '\0';
        }
    }

    if(msg =3D=3D NULL) {
        msg =3D strdup("[unknown error]");
    }

    return msg;
}

static const struct option longopts[] =3D
{
    { "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    =3D 0;

/* Overwrite existing links */
static int force      =3D 0;

/* Create symbolic links */
static int symbolic   =3D 0;

/* Never treat last argument as a directory */
static int no_tgt_dir =3D 0;

enum type_mode {
    MODE_FORCE_FILE,
    MODE_FORCE_DIR,
    MODE_AUTO,
};

static enum type_mode mode =3D 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 =3D mbstowcs(NULL, mbs, 0) + 1;
    wchar_t* wcs =3D malloc(wcs_length * sizeof(*wcs));
    if(wcs !=3D NULL) {
        if(mbstowcs(wcs, mbs, wcs_length) =3D=3D (size_t) -1) {
            free(wcs);
            wcs =3D 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 =3D wcstombs(NULL, wcs, 0) + 1;
    char* mbs =3D malloc(mbs_length * sizeof(*mbs));
    if(mbs !=3D NULL) {
        if(wcstombs(mbs, wcs, mbs_length) =3D=3D (size_t) -1) {
            free(mbs);
            mbs =3D 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 =3D NULL;
    size_t posix_path_length =3D strlen(posix_path);

    if(posix_path_length < 1) {
        errno =3D EINVAL;
        return NULL;
    }

    if(posix_path[0] !=3D '/' &&
       posix_path[posix_path_length - 1] !=3D '.' &&
       strcspn(posix_path, "?<>\\:*|") =3D=3D posix_path_length)
    {
        char* tmp =3D strdup(posix_path);
        char* tmp2;

        for(tmp2 =3D tmp; *tmp2; ++tmp2) {
            if(*tmp2 =3D=3D '/') {
                *tmp2 =3D '\\';
            }
        }

        w32_path =3D to_wcs(tmp);
        free(tmp);
    }

    if(w32_path =3D=3D NULL) {
        w32_path =3D 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
       <AANLkTi=3D98+M5sAsGp4vT09UN9uisqp0M=3DmgJi9WcSObG AT mail DOT gmail DOT com>..

       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 =3D 0;

    struct stat statbuf;
    int stat_success =3D 0;

    struct stat target_statbuf;
    int target_stat_success =3D 0;

    wchar_t* w32link =3D NULL;
    wchar_t* w32target =3D NULL;
    DWORD flags;

    int ret =3D 0;

    if(lstat(link, &lstatbuf) =3D=3D 0) {
        lstat_success =3D 1;

        if(stat(link, &statbuf) =3D=3D 0) {
            stat_success =3D 1;
        }

        if(force) {
            if(unlink(link)) {
                fprintf(stderr,
                        PRGNAME ": cannot remove `%s': %s\n",
                        link, strerror(errno));
                ret =3D 5;
                goto out;
            }
        } else {
            fprintf(stderr,
                    PRGNAME ": could not create link `%s': file exists\n",
                    link);
            ret =3D 1;
            goto out;
        }
    }

    if(stat(target, &target_statbuf) =3D=3D 0) {
        target_stat_success =3D 1;
    }

    w32link =3D conv_path_to_win32(link);
    if(w32link =3D=3D NULL) {
        fprintf(stderr, PRGNAME ": could not convert `%s' to win32 path\n",
                link);
        ret =3D 2;
        goto out;
    }

    w32target =3D conv_path_to_win32(target);
    if(w32target =3D=3D NULL) {
        fprintf(stderr, PRGNAME ": could not convert `%s' to win32 path\n",
                target);
        ret =3D 2;
        goto out;
    }


    switch(mode)
    {
        case MODE_FORCE_DIR:
            flags =3D SYMBOLIC_LINK_FLAG_DIRECTORY;
            break;
        case MODE_FORCE_FILE:
            flags =3D 0;
            break;
        default:
            flags =3D 0;
            if(target_stat_success && S_ISDIR(target_statbuf.st_mode)) {
                flags |=3D 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 =3D 2;
            goto out;
        }
    } else {
        if(CreateHardLinkW(w32link, w32target, 0)) {
            if(verbose) {
                printf("`%s' =3D> `%s'\n", link, target);
            }
        } else {
            fprintf(stderr, PRGNAME ": failed to create hard link `%s': %s\=
n",
                    link, errmsg(GetLastError()));
            ret =3D 2;
            goto out;
        }
    }

    out:
    free(w32link);
    free(w32target);
    return ret;
}

static int
is_dir(const char* path)
{
    struct stat statbuf;
    return stat(path, &statbuf) =3D=3D 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 =3D NULL;
    success =3D 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 =3D 1;
    tp.Privileges[0].Luid =3D luid;
    if (bEnablePrivilege) {
        tp.Privileges[0].Attributes =3D SE_PRIVILEGE_ENABLED;
    } else {
        tp.Privileges[0].Attributes =3D 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 () =3D=3D ERROR_NOT_ALL_ASSIGNED) {
        goto out;
    }

    success =3D TRUE;

  out:

    if (hToken) {
        CloseHandle (hToken);
    }

    return success;
}

int
main(int argc, char* argv[])
{
    int c;
    char* tgt_dir =3D NULL;
    int ret =3D 0;

    setlocale(LC_ALL, "");

    to_mbs(L"");
    to_wcs("");

    while ((c =3D getopt_long(argc, argv, "VvdfFsATt:", longopts, 0)) !=3D =
-1) {
        switch(c) {
            case 'v':
                verbose =3D 1;
                break;
            case 'd':
                mode =3D MODE_FORCE_DIR;
                break;
            case 'f':
                force =3D 1;
                break;
            case 'F':
                mode =3D MODE_FORCE_FILE;
                break;
            case 's':
                symbolic =3D 1;
                break;
            case 'A':
                mode =3D MODE_AUTO;
                break;
            case 'T':
                no_tgt_dir =3D 1;
                break;
            case 't':
                tgt_dir =3D strdup(optarg);
                break;
            case 'h':
                usage();
                ret =3D 0;
                goto out;
            case 'V':
                versinfo ();
                ret =3D 0;
                goto out;
            default:
                fprintf(stderr, PRGNAME ": use --help for usage\n");
                ret =3D 4;
                goto out;
        }
    }

    if(symbolic) {
        HMODULE hKernel32 =3D LoadLibraryW(L"kernel32");
        if(hKernel32 =3D=3D NULL) {
            fprintf(stderr, PRGNAME ": could not load kernel32: %s\n",
                    errmsg(GetLastError()));
            ret =3D 1;
            goto out;
        }

        XCreateSymbolicLinkW =3D
            (void*)GetProcAddress(hKernel32, "CreateSymbolicLinkW");

        if(XCreateSymbolicLinkW =3D=3D NULL) {
            fprintf(stderr, PRGNAME ": symbolic links not supported on this=
 OS\n");
            ret =3D 2;
            goto out;
        }

        if(!set_privilege_status(L"SeCreateSymbolicLinkPrivilege", TRUE)) {
            fprintf(stderr,
                    PRGNAME ": you don't permission to create symbolic link=
s. Run,"
                    " as administrator,\n"
                    PRGNAME ":   editrights -a SeCreateSymbolicLinkPrivileg=
e -a $YOUR_USER\n"
                );

            ret =3D 3;
            goto out;
        }
    }

    argc -=3D optind;
    argv +=3D optind;

    if(argc =3D=3D 0) {
        fprintf(stderr, PRGNAME ": no arguments. Use --help for usage\n");
        ret =3D 1;
        goto out;
    }

    if(no_tgt_dir) {
        if(argc !=3D 2) {
            fprintf(stderr, PRGNAME ": must have exactly two args with -T\n=
");
            ret =3D 1;
            goto out;
        }

        ret =3D do_link(argv[0], argv[1]);
        goto out;
    }

    if(tgt_dir =3D=3D NULL && argc =3D=3D 1) {
        tgt_dir =3D ".";
    }

    if(tgt_dir =3D=3D NULL) {
        int last_is_dir =3D is_dir(argv[argc - 1]);
        if(argc =3D=3D 2 && !last_is_dir) {
            ret =3D do_link(argv[0], argv[1]);
            goto out;
        }

        if(!last_is_dir) {
            fprintf(stderr, PRGNAME ": `%s': not a directory\n",
                    argv[argc - 1]);
            ret =3D 1;
            goto out;
        }

        tgt_dir =3D argv[--argc];
        argv[argc] =3D NULL;
    }

    for(; *argv; ++argv) {
        char* tgt;
        int r;

        if(asprintf(&tgt, "%s/%s", tgt_dir, basename(*argv)) =3D=3D -1) {
            fprintf(stderr, PRGNAME ": asprintf: %s\n",
                    strerror(errno));
            ret =3D 1;
            goto out;
        }

        r =3D do_link(*argv, tgt);
        if(r && ret =3D=3D 0) {
            ret =3D r;
        }

        free(tgt);
    }

    out:
    return ret;
}

--------------020704010003050809020108
Content-Type: text/plain; charset=windows-1252;
 name="Makefile"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
 filename="Makefile"

winln: winln.c
	$(CC) -Wall -Os -g -o $@ $^

clean:
	rm -f winln.exe

--------------020704010003050809020108--

------enig2RBKIBRFBFOLOGAMKHTEQ
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.13 (Cygwin)
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iEYEARECAAYFAlFb1xgACgkQ17c2LVA10Vuj0ACfW6LhyccNLoyPc9if/wWqjSu7
j2UAn3aOqZz2OH8bHrT9yxwr/KVq5AkU
=/2C4
-----END PGP SIGNATURE-----

------enig2RBKIBRFBFOLOGAMKHTEQ--

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019