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: List-Subscribe: List-Archive: List-Post: List-Help: , 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 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 Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="----enig2RBKIBRFBFOLOGAMKHTEQ" 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 #include #include #include #include #include #include #include #include #include #include #include #define PRGNAME "winln" #define PRGVER "1.2" #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 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 .. 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--