#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; }