X-Recipient: archive-cygwin AT delorie DOT com X-SWARE-Spam-Status: No, hits=-2.2 required=5.0 tests=AWL,BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,RCVD_IN_DNSWL_LOW,TW_FS,TW_NL,TW_RG,T_TO_NO_BRKTS_FREEMAIL X-Spam-Check-By: sourceware.org Message-ID: <4D9A2A5B.7060502@gmail.com> Date: Mon, 04 Apr 2011 13:30:19 -0700 From: Daniel Colascione User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.15) Gecko/20110303 Thunderbird/3.1.9 MIME-Version: 1.0 To: cygwin AT cygwin DOT com Subject: Utility: winln, a drop-in replacement for ln(1) Content-Type: multipart/mixed; boundary="------------080804000500010802050806" X-IsSubscribed: yes 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 --------------080804000500010802050806 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit 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). --------------080804000500010802050806 Content-Type: text/plain; name="winln.c" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="winln.c" #define _WIN32_WINNT 0x0500 /*Win2k*/ #define STRICT #include #include #include #include #include #include #include #include #include #define PRGNAME "winln" #define PRGVER "1.0" #define PRGAUTHOR "Daniel Colascione " #define PRGCOPY "Copyright (C) 2011 " PRGAUTHOR #define PRGLICENSE "GPLv2 or later " /** * 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; } --------------080804000500010802050806 Content-Type: text/plain; charset=us-ascii -- 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 --------------080804000500010802050806--