[PATCH] cygcheck: follow symbolic links

Igor Peshansky pechtcha@cs.nyu.edu
Wed Feb 22 18:55:00 GMT 2006


On Fri, 17 Feb 2006, Igor Peshansky wrote:

> On Fri, 17 Feb 2006, Corinna Vinschen wrote:
>
> > On Feb 16 12:26, Igor Peshansky wrote:
> > > On Thu, 16 Feb 2006, Corinna Vinschen wrote:
> > > > - Most of your patch should go into path.cc so it can be reused,
> > > >   for instance in strace.
> > >
> > > Agreed -- that's why I put that TODO in there. :-)  Should I move it
> > > in the next iteration of the patch?
> >
> > Please move it now.  I don't think it's non-trivial enough to justify
> > multiple iterations.
>
> Whoops.  Misspoke.  I meant "incarnation".  Never mind, I'll just do it.
> :-)  Expect a new patch today.

I guess "today" is a stretchable concept. :-)  In any case, here's a new
patch.  Moving things into path.cc turned out to be indeed non-trivial,
since the new functionality was using static functions in cygcheck.cc
which now needed to be moved out into a separate file.  I don't expect
this to be applied right away (hence no ChangeLog), but is this along the
lines of what you were expecting?

> > > > - Couldn't you just reuse the readlink implementation in
> > > >   ../cygwin/path.cc as is, to avoid having to different
> > > >   implementations?
> > >
> > > Umm, most of that code is very general purpose, and has too much
> > > extra stuff in it.  I basically used part of it
> > > (symlink_info::check_shortcut) for my implementation.  I wanted
> > > something lightweight and easy to understand (also, the code in
> > > path.cc doesn't check for PE headers, so I had to write that part
> > > anyway).
> >
> > Well, what I meant isn't readlink but symlink_info::check_shortcut and
> > cmp_shortcut_header.  It would be helpful if the rules to identify a
> > symlink are identical, wouldn't it?  As for the PE headers, that's
> > fine.
>
> It would certainly help, but then we would need to extract the bit of
> code that deals with symlinks and put it in a Cygwin-independent static
> library.  See my reply to Dave.

I did copy cmp_shortcut_header, but most of the rest of the code was
judicious cut-and-paste (with some rewriting on the side).  Again, it
wasn't as trivial as you made it sound.
	Igor
-- 
				http://cs.nyu.edu/~pechtcha/
      |\      _,,,---,,_	    pechtcha@cs.nyu.edu | igor@watson.ibm.com
ZZZzz /,`.-'`'    -.  ;-;;,_		Igor Peshansky, Ph.D. (name changed!)
     |,4-  ) )-,_. ,\ (  `'-'		old name: Igor Pechtchanski
    '---''(_/--'  `-'\_) fL	a.k.a JaguaR-R-R-r-r-r-.-.-.  Meow!

"Las! je suis sot... -Mais non, tu ne l'es pas, puisque tu t'en rends compte."
"But no -- you are no fool; you call yourself a fool, there's proof enough in
that!" -- Rostand, "Cyrano de Bergerac"
-------------- next part --------------
Index: Makefile.in
===================================================================
RCS file: /cvs/src/src/winsup/utils/Makefile.in,v
retrieving revision 1.62
diff -u -p -r1.62 Makefile.in
--- Makefile.in	18 Jan 2006 15:57:55 -0000	1.62
+++ Makefile.in	22 Feb 2006 18:49:42 -0000
@@ -91,23 +91,23 @@ endif
 
 all: Makefile $(PROGS)
 
-strace.exe: strace.o path.o $(MINGW_DEP_LDLIBS)
+strace.exe: strace.o path.o fileutil.o $(MINGW_DEP_LDLIBS)
 ifdef VERBOSE
-	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,2,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS)
+	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,3,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS)
 else
-	@echo $(CXX) -o $@ ${wordlist 1,2,$^} ${filter-out -B%, $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS)};\
-	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,2,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS)
+	@echo $(CXX) -o $@ ${wordlist 1,3,$^} ${filter-out -B%, $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS)};\
+	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,3,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS)
 endif
 
-cygcheck.exe: cygcheck.o path.o dump_setup.o $(MINGW_DEP_LDLIBS)
+cygcheck.exe: cygcheck.o path.o fileutil.o dump_setup.o $(MINGW_DEP_LDLIBS)
 ifeq "$(libz)" ""
 	@echo '*** Building cygcheck without package content checking due to missing mingw libz.a.'
 endif
 ifdef VERBOSE
-	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,3,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS) $(libz)
+	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,4,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS) $(libz)
 else
-	@echo $(CXX) -o $@ ${wordlist 1,3,$^} ${filter-out -B%, $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS)} $(libz);\
-	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,3,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS) $(libz)
+	@echo $(CXX) -o $@ ${wordlist 1,4,$^} ${filter-out -B%, $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS)} $(libz);\
+	$(CXX) $(MINGW_CXXFLAGS) -o $@ ${wordlist 1,4,$^} -B$(mingw_build)/ $(MINGW_LDFLAGS) $(libz)
 endif
 
 dumper.o: dumper.cc dumper.h
@@ -150,6 +150,14 @@ else
 	$(MINGW_CXX) $(zconf_h) $(zlib_h) $c -o $(@D)/$(basename $@)$o $(MINGW_CXXFLAGS) $<
 endif
 
+fileutil.o: fileutil.cc
+ifdef VERBOSE
+	$(MINGW_CXX) $c -o $(@D)/$(basename $@)$o $(MINGW_CXXFLAGS) $<
+else
+	@echo $(MINGW_CXX) $c -o $(@D)/$(basename $@)$o $(MINGW_CXXFLAGS) ... $^;\
+	${MINGW_CXX} $c -o $(@D)/$(basename $@)$o $(MINGW_CXXFLAGS) $<
+endif
+
 cygcheck.o: cygcheck.cc
 ifdef VERBOSE
 	${MINGW_CXX} $c -o $(@D)/$(basename $@)$o $(MINGW_CXXFLAGS) -I$(updir) $<
Index: cygcheck.cc
===================================================================
RCS file: /cvs/src/src/winsup/utils/cygcheck.cc,v
retrieving revision 1.90
diff -u -p -r1.90 cygcheck.cc
--- cygcheck.cc	8 Feb 2006 14:19:40 -0000	1.90
+++ cygcheck.cc	22 Feb 2006 18:49:42 -0000
@@ -52,6 +52,10 @@ void dump_setup (int, char **, bool);
 void package_find (int, char **);
 void package_list (int, char **);
 
+int get_word (HANDLE, int);
+int get_dword (HANDLE, int);
+int display_error (const char *, bool show_error = true, bool print_failed = true);
+
 static const char version[] = "$Revision: 1.90 $";
 
 static const char *known_env_vars[] = {
@@ -126,21 +130,6 @@ eprintf (const char *format, ...)
   va_end (ap);
 }
 
-/*
- * display_error() is used to report failure modes
- */
-static int
-display_error (const char *name, bool show_error = true, bool print_failed = true)
-{
-  if (show_error)
-    fprintf (stderr, "cygcheck: %s%s: %lu\n", name,
-	print_failed ? " failed" : "", GetLastError ());
-  else
-    fprintf (stderr, "cygcheck: %s%s\n", name,
-	print_failed ? " failed" : "");
-  return 1;
-}
-
 /* Display a WinInet error message, and close a variable number of handles.
    (Passed a list of handles terminated by NULL.)  */
 static int
@@ -249,9 +238,41 @@ init_paths ()
     }
 }
 
+#define LINK_EXTENSION ".lnk"
+
+static bool
+check_existence (char *file, int showall, int foundone, char *first)
+{
+  if (GetFileAttributes (file) != (DWORD) - 1)
+    {
+      char *lastdot = strrchr (file, '.');
+      bool is_link = lastdot && !strcmp (lastdot, LINK_EXTENSION);
+      // If file is a link, fix up the extension before printing
+      if (is_link)
+	*lastdot = '\0';
+      if (showall)
+	printf ("Found: %s\n", file);
+      if (foundone)
+	{
+	  char *flastdot = strrchr (first, '.');
+	  bool f_is_link = flastdot && !strcmp (flastdot, LINK_EXTENSION);
+	  // if first is a link, fix up the extension before printing
+	  if (f_is_link)
+	    *flastdot = '\0';
+	  printf ("Warning: %s hides %s\n", first, file);
+	  if (f_is_link)
+	    *flastdot = '.';
+	}
+      if (is_link)
+	*lastdot = '.';
+      return true;
+    }
+  return false;
+}
+
 static char *
 find_on_path (char *file, char *default_extension,
-	      int showall = 0, int search_sysdirs = 0)
+	      int showall = 0, int search_sysdirs = 0, int checklinks = 0)
 {
   static char rv[4000];
   char tmp[4000], *ptr = rv;
@@ -270,11 +291,21 @@ find_on_path (char *file, char *default_
 
   if (strchr (file, ':') || strchr (file, '\\') || strchr (file, '/'))
     {
+      // FIXME: this will find "foo" before "foo.exe" -- contrary to Windows
       char *fn = cygpath (file, NULL);
       if (access (fn, F_OK) == 0)
 	return fn;
       strcpy (rv, fn);
       strcat (rv, default_extension);
+      if (access (rv, F_OK) == 0)
+	return rv;
+      if (!checklinks)
+	return fn;
+      strcat (rv, LINK_EXTENSION);
+      if (access (rv, F_OK) == 0)
+	return rv;
+      strcpy (rv, fn);
+      strcat (rv, LINK_EXTENSION);
       return access (rv, F_OK) == 0 ? strdup (rv) : fn;
     }
 
@@ -286,14 +317,25 @@ find_on_path (char *file, char *default_
       if (i == 0 || !search_sysdirs || strcasecmp (paths[i], paths[0]))
 	{
 	  sprintf (ptr, "%s\\%s%s", paths[i], file, default_extension);
-	  if (GetFileAttributes (ptr) != (DWORD) - 1)
-	    {
-	      if (showall)
-		printf ("Found: %s\n", ptr);
-	      if (ptr == tmp && verbose)
-		printf ("Warning: %s hides %s\n", rv, ptr);
-	      ptr = tmp;
-	    }
+	  if (check_existence (ptr, showall, ptr == tmp && verbose, rv))
+	    ptr = tmp;
+
+	  if (!checklinks)
+	    continue;
+
+	  sprintf (ptr, "%s\\%s%s%s", paths[i], file, default_extension, LINK_EXTENSION);
+	  if (check_existence (ptr, showall, ptr == tmp && verbose, rv))
+	    ptr = tmp;
+
+	  if (!*default_extension)
+	    continue;
+
+	  sprintf (ptr, "%s\\%s", paths[i], file);
+	  if (check_existence (ptr, showall, ptr == tmp && verbose, rv))
+	    ptr = tmp;
+	  sprintf (ptr, "%s\\%s%s", paths[i], file, LINK_EXTENSION);
+	  if (check_existence (ptr, showall, ptr == tmp && verbose, rv))
+	    ptr = tmp;
 	}
     }
 
@@ -330,38 +372,6 @@ already_did (char *file)
   return d;
 }
 
-static int
-get_word (HANDLE fh, int offset)
-{
-  short rv;
-  unsigned r;
-
-  if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
-      && GetLastError () != NO_ERROR)
-    display_error ("get_word: SetFilePointer()");
-
-  if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
-    display_error ("get_word: Readfile()");
-
-  return rv;
-}
-
-static int
-get_dword (HANDLE fh, int offset)
-{
-  int rv;
-  unsigned r;
-
-  if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
-      && GetLastError () != NO_ERROR)
-    display_error ("get_dword: SetFilePointer()");
-
-  if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
-    display_error ("get_dword: Readfile()");
-
-  return rv;
-}
-
 struct Section
 {
   char name[8];
@@ -610,6 +620,10 @@ dll_info (const char *path, HANDLE fh, i
     cygwin_info (fh);
 }
 
+extern int is_exe (HANDLE);
+extern int is_symlink (HANDLE);
+extern int readlink (HANDLE, char *, int);
+
 // Return true on success, false if error printed
 static bool
 track_down (char *file, char *suffix, int lvl)
@@ -682,7 +696,17 @@ track_down (char *file, char *suffix, in
 
   d->state = DID_ACTIVE;
 
-  dll_info (path, fh, lvl, 1);
+  if (is_exe (fh))
+    dll_info (path, fh, lvl, 1);
+  else if (is_symlink (fh))
+    display_error ("track_down: Huh?  Got a symlink!");
+  else
+    {
+      int magic = get_word (fh, 0x0);
+      magic &= 0x00FFFFFF;
+      printf (" - Not a DLL: magic number %x (%d) '%s'\n", magic, magic, (char *)&magic);
+    }
+
   d->state = DID_INACTIVE;
   if (!CloseHandle (fh))
     display_error ("track_down: CloseHandle()");
@@ -711,11 +735,55 @@ ls (char *f)
     display_error ("ls: CloseHandle()");
 }
 
+// Find a real application on the path (possibly following symlinks)
+static char *
+find_app_on_path (char *app, int showall = 0)
+{
+  char *papp = find_on_path (app, (char *) ".exe", showall, 0, 1);
+
+  HANDLE fh =
+    CreateFile (papp, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+  if (fh == INVALID_HANDLE_VALUE)
+    {
+      printf (" - Cannot open\n");
+      return NULL;
+    }
+
+  if (is_symlink (fh))
+    {
+      static char tmp[4000] = "";
+      char *ptr;
+      readlink(fh, tmp, 3999);
+      ptr = cygpath(tmp, NULL);
+      for (char *p = ptr; (p = strchr (p, '/')); p++)
+	*p = '\\';
+      printf (" -> %s\n", ptr);
+      if (!strchr (ptr, '\\'))
+	{
+	  char *lastsep;
+	  strncpy (tmp, cygpath (papp, NULL), 3999);
+	  for (char *p = tmp; (p = strchr (p, '/')); p++)
+	    *p = '\\';
+	  lastsep = strrchr (tmp, '\\');
+	  strncpy(lastsep+1, ptr, 3999-(lastsep-tmp));
+	  ptr = tmp;
+	}
+      if (!CloseHandle (fh))
+	display_error ("find_app_on_path: CloseHandle()");
+      return find_app_on_path (ptr, showall);
+    }
+
+  if (!CloseHandle (fh))
+    display_error ("find_app_on_path: CloseHandle()");
+  return papp;
+}
+
 // Return true on success, false if error printed
 static bool
 cygcheck (char *app)
 {
-  char *papp = find_on_path (app, (char *) ".exe", 1, 0);
+  char *papp = find_app_on_path (app, 1);
   if (!papp)
     {
       printf ("Error: could not find %s\n", app);
@@ -1441,7 +1509,7 @@ dump_sysinfo ()
     printf
       ("Looking to see where common programs can be found, if at all...\n");
   for (i = 0; common_apps[i].name; i++)
-    if (!find_on_path ((char *) common_apps[i].name, (char *) ".exe", 1, 0))
+    if (!find_app_on_path ((char *) common_apps[i].name, 1))
       {
 	if (common_apps[i].missing_is_good)
 	  printf ("Not Found: %s (good!)\n", common_apps[i].name);
Index: fileutil.cc
===================================================================
RCS file: fileutil.cc
diff -N fileutil.cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fileutil.cc	22 Feb 2006 18:49:42 -0000
@@ -0,0 +1,62 @@
+/* fileutil.cc
+
+   Copyright 2006 Red Hat, Inc.
+
+   This file is part of Cygwin.
+
+   This software is a copyrighted work licensed under the terms of the
+   Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
+   details. */
+
+#define cygwin_internal cygwin_internal_dontuse
+#include <stdio.h>
+#include <windows.h>
+#undef cygwin_internal
+
+/*
+ * display_error() is used to report failure modes
+ */
+int
+display_error (const char *name, bool show_error = true, bool print_failed = true)
+{
+  if (show_error)
+    fprintf (stderr, "cygcheck: %s%s: %lu\n", name,
+	print_failed ? " failed" : "", GetLastError ());
+  else
+    fprintf (stderr, "cygcheck: %s%s\n", name,
+	print_failed ? " failed" : "");
+  return 1;
+}
+
+int
+get_word (HANDLE fh, int offset)
+{
+  short rv;
+  unsigned r;
+
+  if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
+      && GetLastError () != NO_ERROR)
+    display_error ("get_word: SetFilePointer()");
+
+  if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
+    display_error ("get_word: Readfile()");
+
+  return rv;
+}
+
+int
+get_dword (HANDLE fh, int offset)
+{
+  int rv;
+  unsigned r;
+
+  if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
+      && GetLastError () != NO_ERROR)
+    display_error ("get_dword: SetFilePointer()");
+
+  if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
+    display_error ("get_dword: Readfile()");
+
+  return rv;
+}
+
Index: path.cc
===================================================================
RCS file: /cvs/src/src/winsup/utils/path.cc,v
retrieving revision 1.9
diff -u -p -r1.9 path.cc
--- path.cc	29 Apr 2005 16:39:34 -0000	1.9
+++ path.cc	22 Feb 2006 18:49:43 -0000
@@ -9,8 +9,9 @@ Cygwin license.  Please consult the file
 details. */
 
 /* The purpose of this file is to hide all the details about accessing
-   Cygwin's mount table.  If the format or location of the mount table
-   changes, this is the file to change to match it. */
+   Cygwin's mount table, shortcuts, etc.  If the format or location of
+   the mount table, or the shortcut format changes, this is the file to
+   change to match it. */
 
 #define str(a) #a
 #define scat(a,b) str(a##b)
@@ -21,6 +22,9 @@ details. */
 #include "cygwin/include/sys/mount.h"
 #include "cygwin/include/mntent.h"
 
+int get_word (HANDLE, int);
+int display_error (const char *, bool show_error = true, bool print_failed = true);
+
 /* Used when treating / and \ as equivalent. */
 #define SLASH_P(ch) \
   ({ \
@@ -29,6 +33,178 @@ details. */
    })
 
 
+static const GUID GUID_shortcut
+			= { 0x00021401L, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46 };
+
+enum {
+  WSH_FLAG_IDLIST = 0x01,	/* Contains an ITEMIDLIST. */
+  WSH_FLAG_FILE = 0x02,		/* Contains a file locator element. */
+  WSH_FLAG_DESC = 0x04,		/* Contains a description. */
+  WSH_FLAG_RELPATH = 0x08,	/* Contains a relative path. */
+  WSH_FLAG_WD = 0x10,		/* Contains a working dir. */
+  WSH_FLAG_CMDLINE = 0x20,	/* Contains command line args. */
+  WSH_FLAG_ICON = 0x40		/* Contains a custom icon. */
+};
+
+struct win_shortcut_hdr
+  {
+    DWORD size;		/* Header size in bytes.  Must contain 0x4c. */
+    GUID magic;		/* GUID of shortcut files. */
+    DWORD flags;	/* Content flags.  See above. */
+
+    /* The next fields from attr to icon_no are always set to 0 in Cygwin
+       and U/Win shortcuts. */
+    DWORD attr;	/* Target file attributes. */
+    FILETIME ctime;	/* These filetime items are never touched by the */
+    FILETIME mtime;	/* system, apparently. Values don't matter. */
+    FILETIME atime;
+    DWORD filesize;	/* Target filesize. */
+    DWORD icon_no;	/* Icon number. */
+
+    DWORD run;		/* Values defined in winuser.h. Use SW_NORMAL. */
+    DWORD hotkey;	/* Hotkey value. Set to 0.  */
+    DWORD dummy[2];	/* Future extension probably. Always 0. */
+  };
+
+static bool
+cmp_shortcut_header (win_shortcut_hdr *file_header)
+{
+  /* A Cygwin or U/Win shortcut only contains a description and a relpath.
+     Cygwin shortcuts also might contain an ITEMIDLIST. The run type is
+     always set to SW_NORMAL. */
+  return file_header->size == sizeof (win_shortcut_hdr)
+      && !memcmp (&file_header->magic, &GUID_shortcut, sizeof GUID_shortcut)
+      && (file_header->flags & ~WSH_FLAG_IDLIST)
+	 == (WSH_FLAG_DESC | WSH_FLAG_RELPATH)
+      && file_header->run == SW_NORMAL;
+}
+
+#define EXE_MAGIC ((int)*(unsigned short *)"MZ")
+#define SHORTCUT_MAGIC ((int)*(unsigned short *)"L\0")
+#define SYMLINK_COOKIE "!<symlink>"
+#define SYMLINK_MAGIC ((int)*(unsigned short *)SYMLINK_COOKIE)
+
+bool
+is_exe (HANDLE fh)
+{
+  int magic = get_word (fh, 0x0);
+  return magic == EXE_MAGIC;
+}
+
+bool
+is_symlink (HANDLE fh)
+{
+  int magic = get_word (fh, 0x0);
+  if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
+    return false;
+  DWORD got;
+  BY_HANDLE_FILE_INFORMATION local;
+  if (!GetFileInformationByHandle (fh, &local))
+    return false;
+  if (magic == SHORTCUT_MAGIC)
+    {
+      DWORD size;
+      if (!local.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+	return false; /* Not a Cygwin symlink. */
+      if ((size = GetFileSize (fh, NULL)) > 8192)
+	return false; /* Not a Cygwin symlink. */
+      char buf[size];
+      SetFilePointer (fh, 0, 0, FILE_BEGIN);
+      if (!ReadFile (fh, buf, size, &got, 0))
+	return false;
+      if (got != size || !cmp_shortcut_header ((win_shortcut_hdr *) buf))
+	return false; /* Not a Cygwin symlink. */
+      /* TODO: check for invalid path contents (see ../cygwin/path.cc:3313 */
+    }
+  else /* magic == SYMLINK_MAGIC */
+    {
+      if (!local.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
+	return false; /* Not a Cygwin symlink. */
+      char buf[sizeof (SYMLINK_COOKIE) - 1];
+      SetFilePointer (fh, 0, 0, FILE_BEGIN);
+      if (!ReadFile (fh, buf, sizeof (buf), &got, 0))
+	return false;
+      if (got != sizeof (buf) || memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
+	return false; /* Not a Cygwin symlink. */
+    }
+  return true;
+}
+
+/* Assumes is_symlink(fh) is true */
+bool
+readlink (HANDLE fh, char *path, int maxlen)
+{
+  int got;
+  int magic = get_word (fh, 0x0);
+
+  if (magic == SHORTCUT_MAGIC)
+    {
+      int offset = get_word (fh, 0x4c);
+      int slen = get_word (fh, 0x4c + offset + 2);
+      if (slen >= maxlen)
+	{
+	  display_error ("readlink: Path too long");
+	  return false;
+	}
+      if (SetFilePointer (fh, 0x4c + offset + 4, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
+	  && GetLastError () != NO_ERROR)
+	{
+	  display_error ("readlink: SetFilePointer()");
+	  return false;
+	}
+
+      if (!ReadFile (fh, path, slen, (DWORD *) &got, 0))
+	{
+	  display_error ("readlink: ReadFile()");
+	  return false;
+	}
+      else if (got < slen)
+	{
+	  display_error ("readlink: ReadFile()");
+	  return false;
+	}
+      else
+	path[got] = '\0';
+    }
+  else if (magic == SYMLINK_MAGIC)
+    {
+      char cookie_buf[sizeof (SYMLINK_COOKIE) - 1];
+
+      if (SetFilePointer (fh, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
+	  && GetLastError () != NO_ERROR)
+	{
+	  display_error ("readlink: SetFilePointer()");
+	  return false;
+	}
+
+      if (!ReadFile (fh, cookie_buf, sizeof (cookie_buf), (DWORD *) &got, 0))
+	{
+	  display_error ("readlink: Readfile()");
+	  return false;
+	}
+      else if (got == sizeof (cookie_buf)
+	       && memcmp (cookie_buf, SYMLINK_COOKIE, sizeof (cookie_buf)) == 0)
+	{
+	  if (!ReadFile (fh, path, maxlen, (DWORD *) &got, 0))
+	    {
+	      display_error ("readlink: ReadFile()");
+	      return false;
+	    }
+	  else if (got >= maxlen)
+	    {
+	      display_error ("readlink: Path too long");
+	      path[0] = '\0';
+	      return false;
+	    }
+	  else
+	    path[got] = '\0';
+	}
+    }
+  else
+    return false;
+  return true;
+}
+
 static struct mnt
   {
     const char *native;


More information about the Cygwin-patches mailing list