diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 73fd3a24d..13a5cf15f 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,39 @@ +Wed Feb 21 22:41:00 2001 Corinna Vinschen + + * Makefile.in: Add `-lshell32 -luuid' to link pass for new-cygwin1.dll. + * autoload.cc: Add LoadDLLinitfunc for ole32.dll. + Add LoadDLLfuncEx statements for CoInitialize@4, CoUninitialize@0 + and CoCreateInstance@20. + * dir.cc (dir_suffixes): New datastructure. + (readdir): Check for R/O *.lnk files to hide the suffix. + (opendir): Use `dir_suffixes' in path conversion. + (rmdir): Ditto. + * fhandler.cc (fhandler_disk_file::fstat): Add S_IFLNK flag + before calling `get_file_attribute'. Take FILE_ATTRIBUTE_READONLY + into account only if the file is no symlink. + * path.cc (inner_suffixes): New datastructure. + (SYMLINKATTR): Eliminated. + (path_conv::check): Use `inner_suffixes' on inner path components. + (shortcut_header): New global static variable. + (shortcut_initalized): Ditto. + (create_shortcut_header): New function. + (cmp_shortcut_header): Ditto. + (symlink): Create symlinks by creating windows shortcuts. Preserve + the old code. + (symlink_info::check_shortcut): New method. + (symlink_info::check_sysfile): Ditto. + (symlink_info::check): Check for shortcuts. Move code reading + old system attribute symlinks into symlink_info::check_sysfile(). + (chdir): Use `dir_suffixes' in path conversion. + * security.cc (get_file_attribute): Check for S_IFLNK flag. + Force 0777 permissions then. + * spawn.cc (std_suffixes): Add ".lnk" suffix. + * syscalls.cc (_unlink): Use `inner_suffixes' in path conversion. + Check for shortcut symlinks to eliminate R/O attribute before + calling DeleteFile(). + (stat_suffixes): Add ".lnk" suffix. + (stat_worker): Force 0777 permissions if file is a symlink. + 2001-02-21 Egor Duda * sigproc.cc (getsem): Make semaphore always non-inheritable. diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in index 18dbdc7c0..547b94727 100644 --- a/winsup/cygwin/Makefile.in +++ b/winsup/cygwin/Makefile.in @@ -190,7 +190,7 @@ new-$(LIB_NAME): $(LIB_NAME) new-$(DLL_NAME): $(DLL_OFILES) $(DEF_FILE) $(DLL_IMPORTS) $(LIBC) $(LIBM) Makefile winver_stamp $(CXX) $(CXXFLAGS) -nostdlib -Wl,-shared -o $@ -e $(DLL_ENTRY) $(DEF_FILE) $(DLL_OFILES) version.o \ - winver.o $(DLL_IMPORTS) $(MALLOC_OBJ) $(LIBM) $(LIBC) -lgcc -lstdc++ + winver.o $(DLL_IMPORTS) $(MALLOC_OBJ) $(LIBM) $(LIBC) -lgcc -lstdc++ -lshell32 -luuid dll_ofiles: $(DLL_OFILES) diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc index 00ee37b2c..bd035e397 100644 --- a/winsup/cygwin/autoload.cc +++ b/winsup/cygwin/autoload.cc @@ -90,6 +90,13 @@ LoadDLLinitfunc (advapi32) LoadDLLinitfunc (netapi32) { HANDLE h; + static NO_COPY LONG here = -1L; + + while (InterlockedIncrement (&here)) + { + InterlockedDecrement (&here); + Sleep (0); + } if ((h = LoadLibrary ("netapi32.dll")) != NULL) netapi32_handle = h; @@ -197,6 +204,28 @@ LoadDLLinitfunc (iphlpapi) return 0; } +LoadDLLinitfunc (ole32) +{ + HANDLE h; + static NO_COPY LONG here = -1L; + + while (InterlockedIncrement (&here)) + { + InterlockedDecrement (&here); + Sleep (0); + } + + if (ole32_handle) + /* nothing to do */; + else if ((h = LoadLibrary ("ole32.dll")) != NULL) + ole32_handle = h; + else if (!ole32_handle) + api_fatal ("could not load ole32.dll, %E"); + + InterlockedDecrement (&here); + return 0; +} + static void __stdcall dummy_autoload (void) __attribute__ ((unused)); static void __stdcall dummy_autoload (void) @@ -339,5 +368,10 @@ LoadDLLfuncEx (WSASocketA, 24, ws2_32, 1) LoadDLLinit (iphlpapi) LoadDLLfuncEx (GetIfTable, 12, iphlpapi, 1) LoadDLLfuncEx (GetIpAddrTable, 12, iphlpapi, 1) + +LoadDLLinit (ole32) +LoadDLLfunc (CoInitialize, 4, ole32) +LoadDLLfunc (CoUninitialize, 0, ole32) +LoadDLLfunc (CoCreateInstance, 20, ole32) } } diff --git a/winsup/cygwin/dir.cc b/winsup/cygwin/dir.cc index a14b44f8f..b4b93da20 100644 --- a/winsup/cygwin/dir.cc +++ b/winsup/cygwin/dir.cc @@ -59,6 +59,13 @@ writable_directory (const char *file) #endif } +suffix_info dir_suffixes[] = +{ + suffix_info ("", 1), + suffix_info (".lnk", 1), + suffix_info (NULL) +}; + /* opendir: POSIX 5.1.2.1 */ extern "C" DIR * opendir (const char *dirname) @@ -68,7 +75,7 @@ opendir (const char *dirname) DIR *res = 0; struct stat statbuf; - path_conv real_dirname (dirname, PC_SYM_FOLLOW | PC_FULL); + path_conv real_dirname (dirname, PC_SYM_FOLLOW | PC_FULL, dir_suffixes); if (real_dirname.error) { @@ -174,6 +181,14 @@ readdir (DIR * dir) /* We get here if `buf' contains valid data. */ strcpy (dir->__d_dirent->d_name, buf.cFileName); + if (buf.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + { + char *c = dir->__d_dirent->d_name; + int len = strlen (c); + if (!strcasecmp (c + len - 4, ".lnk")) + c[len - 4] = '\0'; + } + /* Compute d_ino by combining filename hash with the directory hash (which was stored in dir->__d_dirhash when opendir was called). */ if (buf.cFileName[0] == '.') @@ -316,7 +331,7 @@ rmdir (const char *dir) { int res = -1; - path_conv real_dir (dir, PC_SYM_NOFOLLOW); + path_conv real_dir (dir, PC_SYM_NOFOLLOW, dir_suffixes); if (real_dir.error) { diff --git a/winsup/cygwin/fhandler.cc b/winsup/cygwin/fhandler.cc index 1c9e600a8..fdd6e91f8 100644 --- a/winsup/cygwin/fhandler.cc +++ b/winsup/cygwin/fhandler.cc @@ -921,6 +921,8 @@ fhandler_disk_file::fstat (struct stat *buf) directory. This is used, to set S_ISVTX, if needed. */ if (local.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) buf->st_mode |= S_IFDIR; + if (get_symlink_p ()) + buf->st_mode |= S_IFLNK; if (!get_file_attribute (has_acls (), get_win32_name (), &buf->st_mode, @@ -928,7 +930,8 @@ fhandler_disk_file::fstat (struct stat *buf) &buf->st_gid)) { /* If read-only attribute is set, modify ntsec return value */ - if (local.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + if ((local.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + && !get_symlink_p ()) buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); buf->st_mode &= ~S_IFMT; diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc index 20553920c..2b7b1eb53 100644 --- a/winsup/cygwin/path.cc +++ b/winsup/cygwin/path.cc @@ -57,6 +57,10 @@ details. */ #include #include #include +#include +#include +#include +#include #include #include #include "cygerrno.h" @@ -70,6 +74,9 @@ details. */ #include "registry.h" #include "security.h" #include +#include +#include +#include static int normalize_win32_path (const char *src, char *dst); static void slashify (const char *src, char *dst, int trailing_slash_p); @@ -87,19 +94,25 @@ struct symlink_info int is_symlink; int error; symlink_info (): known_suffix (NULL), contents (buf + MAX_PATH + 1) {} + int check_shortcut (const char *, DWORD, HANDLE); + int check_sysfile (const char *, DWORD, HANDLE); int check (const char *path, const suffix_info *suffixes); }; +/* These suffixes are the only ones allowed in inner path components. */ +suffix_info inner_suffixes[] = +{ + suffix_info ("", 1), + suffix_info (".lnk", 1), + suffix_info (NULL) +}; + cwdstuff cygcwd; /* The current working directory. */ #define path_prefix_p(p1, p2, l1) \ ((cyg_tolower(*(p1))==cyg_tolower(*(p2))) && \ path_prefix_p_(p1, p2, l1)) -#define SYMLINKATTR(x) \ - (((x) & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY)) == \ - FILE_ATTRIBUTE_SYSTEM) - /* Determine if path prefix matches current cygdrive */ #define iscygdrive(path) \ (path_prefix_p (mount_table->cygdrive, (path), mount_table->cygdrive_len)) @@ -264,7 +277,7 @@ path_conv::check (const char *src, unsigned opt, class if we're working on an inner component of the path */ if (component) { - suff = NULL; + suff = inner_suffixes; sym.pflags = 0; } else @@ -2182,6 +2195,35 @@ endmntent (FILE *) /********************** Symbolic Link Support **************************/ +/* The header written to a shortcut by Cygwin or U/WIN. */ +#define SHORTCUT_HDR_SIZE 76 +static char shortcut_header[SHORTCUT_HDR_SIZE]; +static BOOL shortcut_initalized = FALSE; + +static void +create_shortcut_header (void) +{ + if (!shortcut_initalized) + { + shortcut_header[0] = 'L'; + shortcut_header[4] = '\001'; + shortcut_header[5] = '\024'; + shortcut_header[6] = '\002'; + shortcut_header[12] = '\300'; + shortcut_header[19] = 'F'; + shortcut_header[20] = '\f'; + shortcut_header[60] = '\001'; + shortcut_initalized = TRUE; + } +} + +static BOOL +cmp_shortcut_header (const char *file_header) +{ + create_shortcut_header (); + return memcmp (shortcut_header, file_header, SHORTCUT_HDR_SIZE); +} + /* Create a symlink from FROMPATH to TOPATH. */ extern "C" @@ -2191,7 +2233,17 @@ symlink (const char *topath, const char *frompath) HANDLE h; int res = -1; +#if 0 path_conv win32_path (frompath, PC_SYM_NOFOLLOW); +#else + char from[MAX_PATH]; + unsigned short len = strlen (frompath); + strcpy (from, frompath); + if (len <= 4 || strcasecmp (from + len - 4, ".lnk")) + strcpy (from + len, ".lnk"); + path_conv win32_path (from, PC_SYM_NOFOLLOW); +#endif + if (win32_path.error) { set_errno (win32_path.error); @@ -2224,20 +2276,17 @@ symlink (const char *topath, const char *frompath) __seterrno (); else { + DWORD written; +#if 0 + /* This is the old technique creating a symlink. + Preserved to have a fallback. */ char buf[sizeof (SYMLINK_COOKIE) + MAX_PATH + 10]; __small_sprintf (buf, "%s%s", SYMLINK_COOKIE, topath); DWORD len = strlen (buf) + 1; /* Note that the terminating nul is written. */ - DWORD written; - if (!WriteFile (h, buf, len, &written, NULL) || written != len) - { - __seterrno (); - CloseHandle (h); - DeleteFileA (win32_path.get_win32 ()); - } - else + if (WriteFile (h, buf, len, &written, NULL) || written != len) { CloseHandle (h); set_file_attribute (win32_path.has_acls (), @@ -2246,6 +2295,36 @@ symlink (const char *topath, const char *frompath) SetFileAttributesA (win32_path.get_win32 (), FILE_ATTRIBUTE_SYSTEM); res = 0; } +#else + create_shortcut_header (); + path_conv win32_topath (topath, PC_SYM_NOFOLLOW); + len = strlen (topath); + unsigned short win_len = strlen (win32_topath.get_win32 ()); + if (WriteFile (h, shortcut_header, SHORTCUT_HDR_SIZE, &written, NULL) + && written == SHORTCUT_HDR_SIZE + && WriteFile (h, &len, sizeof len, &written, NULL) + && written == sizeof len + && WriteFile (h, topath, len, &written, NULL) + && written == len + && WriteFile (h, &win_len, sizeof win_len, &written, NULL) + && written == sizeof win_len + && WriteFile (h, win32_topath.get_win32 (), win_len, &written, NULL) + && written == win_len) + { + CloseHandle (h); + set_file_attribute (win32_path.has_acls (), + win32_path.get_win32 (), + S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO); + SetFileAttributesA (win32_path.get_win32 (), FILE_ATTRIBUTE_READONLY); + res = 0; + } +#endif + else + { + __seterrno (); + CloseHandle (h); + DeleteFileA (win32_path.get_win32 ()); + } } done: @@ -2283,6 +2362,177 @@ next_suffix (char *ext_here, const suffix_info *&suffixes) return 0; } +int +symlink_info::check_shortcut (const char *path, DWORD fileattr, HANDLE h) +{ + HRESULT hres; + IShellLink *psl = NULL; + IPersistFile *ppf = NULL; + WCHAR wc_path[MAX_PATH]; + char full_path[MAX_PATH]; + WIN32_FIND_DATA wfd; + DWORD len = 0; + int res = 0; + + /* Initialize COM library. */ + CoInitialize (NULL); + + /* Get a pointer to the IShellLink interface. */ + hres = CoCreateInstance (CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLink, (void **)&psl); + if (FAILED (hres)) + { + debug_printf ("CoCreateInstance failed"); + goto close_it; + } + /* Get a pointer to the IPersistFile interface. */ + hres = psl->QueryInterface (IID_IPersistFile, (void **)&ppf); + if (FAILED (hres)) + { + debug_printf ("QueryInterface failed"); + goto close_it; + } + /* Load the shortcut. */ + MultiByteToWideChar(CP_ACP, 0, path, -1, wc_path, MAX_PATH); + hres = ppf->Load (wc_path, STGM_READ); + if (FAILED (hres)) + { + debug_printf ("Load failed"); + goto close_it; + } + /* Try the description (containing a POSIX path) first. */ + if (fileattr & FILE_ATTRIBUTE_READONLY) + { + /* An additional check is needed to prove if it's a shortcut + really created by Cygwin or U/WIN. */ + char file_header[SHORTCUT_HDR_SIZE]; + DWORD got; + + if (! ReadFile (h, file_header, SHORTCUT_HDR_SIZE, &got, 0)) + { + debug_printf ("ReadFile failed"); + error = EIO; + goto close_it_dont_set_error; + } + if (got == SHORTCUT_HDR_SIZE && !cmp_shortcut_header (file_header)) + { + hres = psl->GetDescription (contents, MAX_PATH); + if (FAILED (hres)) + { + debug_printf ("GetDescription failed"); + goto close_it; + } + len = strlen (contents); + } + } + /* No description or not R/O: Check the "official" path. */ + if (len == 0) + { + /* Convert to full path (easy way) */ + if ((path[0] == '\\' && path[1] == '\\') + || (_toupper (path[0]) >= 'A' && _toupper (path[0]) <= 'Z' + && path[1] == ':')) + len = 0; + else + { + len = GetCurrentDirectory (MAX_PATH, full_path); + if (path[0] == '\\') + len = 2; + else if (full_path[len - 1] != '\\') + strcpy (full_path + len++, "\\"); + } + strcpy (full_path + len, path); + debug_printf ("full_path = <%s>", full_path); + /* Set relative path inside of IShellLink interface. */ + hres = psl->SetRelativePath (full_path, 0); + if (FAILED (hres)) + { + debug_printf ("SetRelativePath failed"); + goto close_it; + } + /* Get the path to the shortcut target. */ + hres = psl->GetPath (contents, MAX_PATH, &wfd, 0); + if (FAILED(hres)) + { + debug_printf ("GetPath failed"); + goto close_it; + } + } + /* It's a symlink. */ + pflags = PATH_SYMLINK; + res = strlen (contents); + +close_it: + if (FAILED (hres)) + error = geterrno_from_win_error (HRESULT_CODE (hres), EACCES); + +close_it_dont_set_error: + /* Release the pointer to IPersistFile. */ + if (ppf) + ppf->Release(); + /* Release the pointer to IShellLink. */ + if (psl) + psl->Release(); + /* Uninitialize COM library. */ + CoUninitialize (); + + syscall_printf ("%d = symlink.check_shortcut (%s, %s) (%p)", + res, path, contents, pflags); + return res; +} + +int +symlink_info::check_sysfile (const char *path, DWORD fileattr, HANDLE h) +{ + char cookie_buf[sizeof (SYMLINK_COOKIE) - 1]; + DWORD got; + int res = 0; + + if (! ReadFile (h, cookie_buf, sizeof (cookie_buf), &got, 0)) + { + debug_printf ("ReadFile1 failed"); + error = EIO; + } + else if (got == sizeof (cookie_buf) + && memcmp (cookie_buf, SYMLINK_COOKIE, sizeof (cookie_buf)) == 0) + { + /* It's a symlink. */ + pflags = PATH_SYMLINK; + + res = ReadFile (h, contents, MAX_PATH + 1, &got, 0); + if (!res) + { + debug_printf ("ReadFile2 failed"); + error = EIO; + } + else + { + /* Versions prior to b16 stored several trailing + NULs with the path (to fill the path out to 1024 + chars). Current versions only store one trailing + NUL. The length returned is the path without + *any* trailing NULs. We also have to handle (or + at least not die from) corrupted paths. */ + if (memchr (contents, 0, got) != NULL) + res = strlen (contents); + else + res = got; + } + } + else if (got == sizeof (cookie_buf) + && memcmp (cookie_buf, SOCKET_COOKIE, sizeof (cookie_buf)) == 0) + pflags |= PATH_SOCKET; + else + { + /* Not a symlink, see if executable. */ + if (!(pflags & PATH_ALL_EXEC) && has_exec_chars (cookie_buf, got)) + pflags |= PATH_EXEC; + } + syscall_printf ("%d = symlink.check_sysfile (%s, %s) (%p)", + res, path, contents, pflags); + return res; +} + /* Check if PATH is a symlink. PATH must be a valid Win32 path name. If PATH is a symlink, put the value of the symlink--the file to @@ -2340,9 +2590,22 @@ symlink_info::check (const char *in_path, const suffix_info *suffixes) continue; } + int sym_check = 0; + + if (fileattr & FILE_ATTRIBUTE_DIRECTORY) + goto file_not_symlink; + + /* Windows shortcuts are treated as symlinks. */ + if (!strcasecmp (path + strlen (path) - 4, ".lnk")) + sym_check = 1; + + /* The old Cygwin method creating symlinks: */ /* A symlink will have the `system' file attribute. */ /* Only files can be symlinks (which can be symlinks to directories). */ - if (!(pflags & PATH_SYMLINK) && !SYMLINKATTR (fileattr)) + if (fileattr & FILE_ATTRIBUTE_SYSTEM) + sym_check = 2; + + if (!sym_check && !(pflags & PATH_SYMLINK)) goto file_not_symlink; /* Open the file. */ @@ -2352,54 +2615,15 @@ symlink_info::check (const char *in_path, const suffix_info *suffixes) res = -1; if (h == INVALID_HANDLE_VALUE) goto file_not_symlink; - else + else if (sym_check == 1 && !(res = check_shortcut (path, fileattr, h))) { - char cookie_buf[sizeof (SYMLINK_COOKIE) - 1]; - DWORD got; - - if (! ReadFile (h, cookie_buf, sizeof (cookie_buf), &got, 0)) - error = EIO; - else if (got == sizeof (cookie_buf) - && memcmp (cookie_buf, SYMLINK_COOKIE, - sizeof (cookie_buf)) == 0) - { - /* It's a symlink. */ - pflags = PATH_SYMLINK; - - res = ReadFile (h, contents, MAX_PATH + 1, &got, 0); - if (!res) - error = EIO; - else - { - /* Versions prior to b16 stored several trailing - NULs with the path (to fill the path out to 1024 - chars). Current versions only store one trailing - NUL. The length returned is the path without - *any* trailing NULs. We also have to handle (or - at least not die from) corrupted paths. */ - if (memchr (contents, 0, got) != NULL) - res = strlen (contents); - else - res = got; - } - } - else if (got == sizeof (cookie_buf) - && memcmp (cookie_buf, SOCKET_COOKIE, - sizeof (cookie_buf)) == 0) - { - pflags |= PATH_SOCKET; - goto close_and_return; - } - else - { - /* Not a symlink, see if executable. */ - if (!(pflags & PATH_ALL_EXEC) && - has_exec_chars (cookie_buf, got)) - pflags |= PATH_EXEC; - close_and_return: - CloseHandle (h); - goto file_not_symlink; - } + CloseHandle (h); + goto file_not_symlink; + } + else if (sym_check == 2 && !(res = check_sysfile (path, fileattr, h))) + { + CloseHandle (h); + goto file_not_symlink; } CloseHandle (h); @@ -2553,8 +2777,9 @@ int chdir (const char *dir) { MALLOC_CHECK; + extern suffix_info dir_suffixes[]; syscall_printf ("dir %s", dir); - path_conv path (dir, PC_FULL | PC_SYM_FOLLOW); + path_conv path (dir, PC_FULL | PC_SYM_FOLLOW, dir_suffixes); if (path.error) { diff --git a/winsup/cygwin/security.cc b/winsup/cygwin/security.cc index 34ffc02fe..0f1a52dae 100644 --- a/winsup/cygwin/security.cc +++ b/winsup/cygwin/security.cc @@ -808,8 +808,15 @@ int get_file_attribute (int use_ntsec, const char *file, int *attribute, uid_t *uidret, gid_t *gidret) { + int res; + if (use_ntsec && allow_ntsec) - return get_nt_attribute (file, attribute, uidret, gidret); + { + res = get_nt_attribute (file, attribute, uidret, gidret); + if (attribute && (*attribute & S_IFLNK) == S_IFLNK) + *attribute |= S_IRWXU | S_IRWXG | S_IRWXO; + return res; + } if (uidret) *uidret = getuid (); @@ -819,8 +826,7 @@ get_file_attribute (int use_ntsec, const char *file, if (!attribute) return 0; - int res = NTReadEA (file, ".UNIXATTR", - (char *) attribute, sizeof (*attribute)); + res = NTReadEA (file, ".UNIXATTR", (char *) attribute, sizeof (*attribute)); /* symlinks are everything for everyone!*/ if ((*attribute & S_IFLNK) == S_IFLNK) diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index cb30d6be7..507dfa563 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -41,6 +41,7 @@ details. */ static suffix_info std_suffixes[] = { suffix_info (".exe", 1), suffix_info ("", 1), + suffix_info (".lnk", 1), suffix_info (".com"), suffix_info (".cmd"), suffix_info (".bat"), suffix_info (".dll"), suffix_info (NULL) diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc index d5bb510ee..efe2c69ea 100644 --- a/winsup/cygwin/syscalls.cc +++ b/winsup/cygwin/syscalls.cc @@ -65,10 +65,11 @@ close_all_files (void) extern "C" int _unlink (const char *ourname) { + extern suffix_info inner_suffixes[]; int res = -1; sigframe thisframe (mainthread); - path_conv win32_name (ourname, PC_SYM_NOFOLLOW | PC_FULL); + path_conv win32_name (ourname, PC_SYM_NOFOLLOW | PC_FULL, inner_suffixes); if (win32_name.error) { @@ -94,6 +95,15 @@ _unlink (const char *ourname) goto done; } + /* Check for shortcut as symlink condition. */ + if (atts != 0xffffffff && atts & FILE_ATTRIBUTE_READONLY) + { + int len = strlen (win32_name.get_win32 ()); + if (len > 4 && !strcasecmp (win32_name.get_win32 () + len - 4, ".lnk")) + SetFileAttributes (win32_name.get_win32 (), + win32_name.file_attributes () & ~FILE_ATTRIBUTE_READONLY); + } + for (int i = 0; i < 2; i++) { if (DeleteFile (win32_name)) @@ -1021,6 +1031,7 @@ suffix_info stat_suffixes[] = { suffix_info ("", 1), suffix_info (".exe", 1), + suffix_info (".lnk", 1), suffix_info (NULL) }; @@ -1135,6 +1146,8 @@ stat_worker (const char *caller, const char *name, struct stat *buf, buf->st_mode |= STD_RBITS | STD_XBITS; if ((atts & FILE_ATTRIBUTE_READONLY) == 0) buf->st_mode |= STD_WBITS; + if (real_path.issymlink ()) + buf->st_mode |= S_IRWXU | S_IRWXG | S_IRWXO; get_file_attribute (FALSE, real_path.get_win32 (), NULL, &buf->st_uid, &buf->st_gid); }