Compatibility improvements to reparse point handling.
This commit is contained in:
		
				
					committed by
					
						 Corinna Vinschen
						Corinna Vinschen
					
				
			
			
				
	
			
			
			
						parent
						
							ec86124748
						
					
				
				
					commit
					7a4e299a18
				
			| @@ -161,15 +161,19 @@ path_conv::isgood_inode (ino_t ino) const | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Check reparse point for type.  IO_REPARSE_TAG_MOUNT_POINT types are | /* Check reparse point to determine if it should be treated as a posix symlink | ||||||
|    either volume mount points, which are treated as directories, or they |    or as a normal file/directory. Mount points are treated as normal directories | ||||||
|    are directory mount points, which are treated as symlinks. |    to match behavior of other systems. Unknown reparse tags are used for | ||||||
|    IO_REPARSE_TAG_SYMLINK types are always symlinks.  We don't know |    things other than links (HSM, compression, dedup), and generally should be | ||||||
|    anything about other reparse points, so they are treated as unknown.  */ |    treated as a normal file/directory. Native symlinks and mount points are | ||||||
| static inline uint8_t |    treated as posix symlinks, depending on the prefix of the target name. | ||||||
| readdir_check_reparse_point (POBJECT_ATTRIBUTES attr) |    This logic needs to agree with equivalent logic in path.cc | ||||||
|  |    symlink_info::check_reparse_point() . | ||||||
|  |    */ | ||||||
|  | static inline bool | ||||||
|  | readdir_check_reparse_point (POBJECT_ATTRIBUTES attr, bool remote) | ||||||
| { | { | ||||||
|   uint8_t ret = DT_UNKNOWN; |   bool ret = false; | ||||||
|   IO_STATUS_BLOCK io; |   IO_STATUS_BLOCK io; | ||||||
|   HANDLE reph; |   HANDLE reph; | ||||||
|   UNICODE_STRING subst; |   UNICODE_STRING subst; | ||||||
| @@ -185,20 +189,29 @@ readdir_check_reparse_point (POBJECT_ATTRIBUTES attr) | |||||||
| 		      &io, FSCTL_GET_REPARSE_POINT, NULL, 0, | 		      &io, FSCTL_GET_REPARSE_POINT, NULL, 0, | ||||||
| 		      (LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))) | 		      (LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))) | ||||||
| 	{ | 	{ | ||||||
| 	  if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) | 	  if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) | ||||||
| 	    { | 	    { | ||||||
| 	      RtlInitCountedUnicodeString (&subst, | 	      RtlInitCountedUnicodeString (&subst, | ||||||
| 		  (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer | 		  (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer | ||||||
| 			    + rp->MountPointReparseBuffer.SubstituteNameOffset), | 			    + rp->MountPointReparseBuffer.SubstituteNameOffset), | ||||||
| 		  rp->MountPointReparseBuffer.SubstituteNameLength); | 		  rp->MountPointReparseBuffer.SubstituteNameLength); | ||||||
| 	      /* Only volume mountpoints are treated as directories. */ | 	      if (check_reparse_point_target (&subst)) | ||||||
| 	      if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE)) | 	        ret = true; | ||||||
| 		ret = DT_DIR; |  | ||||||
| 	      else |  | ||||||
| 		ret = DT_LNK; |  | ||||||
| 	    } | 	    } | ||||||
| 	  else if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) | 	  else if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) | ||||||
| 	    ret = DT_LNK; | 	    { | ||||||
|  | 	      if (rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) | ||||||
|  | 		ret = true; | ||||||
|  | 	      else | ||||||
|  | 		{ | ||||||
|  | 		  RtlInitCountedUnicodeString (&subst, | ||||||
|  | 		      (WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer | ||||||
|  | 			    + rp->SymbolicLinkReparseBuffer.SubstituteNameOffset), | ||||||
|  | 		      rp->SymbolicLinkReparseBuffer.SubstituteNameLength); | ||||||
|  | 		  if (check_reparse_point_target (&subst)) | ||||||
|  | 		    ret = true; | ||||||
|  | 		} | ||||||
|  | 	    } | ||||||
| 	  NtClose (reph); | 	  NtClose (reph); | ||||||
| 	} | 	} | ||||||
|     } |     } | ||||||
| @@ -1995,8 +2008,7 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err, | |||||||
|   /* Set d_type if type can be determined from file attributes.  For .lnk |   /* Set d_type if type can be determined from file attributes.  For .lnk | ||||||
|      symlinks, d_type will be reset below.  Reparse points can be NTFS |      symlinks, d_type will be reset below.  Reparse points can be NTFS | ||||||
|      symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */ |      symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */ | ||||||
|   if (attr && |   if (attr && !(attr & ~FILE_ATTRIBUTE_VALID_FLAGS)) | ||||||
|       !(attr & (~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_REPARSE_POINT))) |  | ||||||
|     { |     { | ||||||
|       if (attr & FILE_ATTRIBUTE_DIRECTORY) |       if (attr & FILE_ATTRIBUTE_DIRECTORY) | ||||||
| 	de->d_type = DT_DIR; | 	de->d_type = DT_DIR; | ||||||
| @@ -2005,19 +2017,22 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err, | |||||||
| 	de->d_type = DT_REG; | 	de->d_type = DT_REG; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   /* Check for directory reparse point. These may be treated as a posix |   /* Check for reparse points that can be treated as posix symlinks. | ||||||
|      symlink, or as mount point, so need to figure out whether to return |      Mountpoints and unknown or unhandled reparse points will be treated | ||||||
|      a directory or link type. In all cases, returning the INO of the |      as normal file/directory/unknown. In all cases, returning the INO of | ||||||
|      reparse point (not of the target) matches behavior of posix systems. |      the reparse point (not of the target) matches behavior of posix systems. | ||||||
|      */ |      */ | ||||||
|   if ((attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) |   if (attr & FILE_ATTRIBUTE_REPARSE_POINT) | ||||||
|       == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) |  | ||||||
|     { |     { | ||||||
|       OBJECT_ATTRIBUTES oattr; |       OBJECT_ATTRIBUTES oattr; | ||||||
|  |  | ||||||
|       InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (), |       InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (), | ||||||
| 				  get_handle (), NULL); | 				  get_handle (), NULL); | ||||||
|       de->d_type = readdir_check_reparse_point (&oattr); |       /* FUTURE: Ideally would know at this point if reparse point | ||||||
|  |          is stored on a remote volume. Without this, may return DT_LNK | ||||||
|  |          for remote names that end up lstat-ing as a normal directory. */ | ||||||
|  |       if (readdir_check_reparse_point (&oattr, false/*remote*/)) | ||||||
|  |         de->d_type = DT_LNK; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   /* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the |   /* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the | ||||||
|   | |||||||
| @@ -2261,6 +2261,31 @@ symlink_info::check_sysfile (HANDLE h) | |||||||
|   return res; |   return res; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool | ||||||
|  | check_reparse_point_target (PUNICODE_STRING subst) | ||||||
|  | { | ||||||
|  |   /* Native mount points, or native non-relative symbolic links, | ||||||
|  |      can be treated as posix symlinks only if the SubstituteName | ||||||
|  |      can be converted from a native NT object namespace name to | ||||||
|  |      a win32 name. We only know how to convert names with two | ||||||
|  |      prefixes : | ||||||
|  |        "\??\UNC\..." | ||||||
|  |        "\??\X:..." | ||||||
|  |      Other reparse points will be treated as files or | ||||||
|  |      directories, not as posix symlinks. | ||||||
|  |      */ | ||||||
|  |   if (RtlEqualUnicodePathPrefix (subst, &ro_u_natp, FALSE)) | ||||||
|  |     { | ||||||
|  |       if (subst->Length >= 6*sizeof(WCHAR) && subst->Buffer[5] == L':' && | ||||||
|  |           (subst->Length == 6*sizeof(WCHAR) || subst->Buffer[6] == L'\\')) | ||||||
|  |         return true; | ||||||
|  |       else if (subst->Length >= 8*sizeof(WCHAR) && | ||||||
|  |           wcsncmp (subst->Buffer + 4, L"UNC\\", 4) == 0) | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| int | int | ||||||
| symlink_info::check_reparse_point (HANDLE h, bool remote) | symlink_info::check_reparse_point (HANDLE h, bool remote) | ||||||
| { | { | ||||||
| @@ -2299,6 +2324,7 @@ symlink_info::check_reparse_point (HANDLE h, bool remote) | |||||||
|       return 0; |       return 0; | ||||||
|     } |     } | ||||||
|   if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) |   if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) | ||||||
|  |     { | ||||||
|       /* Windows evaluates native symlink literally.  If a remote symlink points |       /* Windows evaluates native symlink literally.  If a remote symlink points | ||||||
|          to, say, C:\foo, it will be handled as if the target is the local file |          to, say, C:\foo, it will be handled as if the target is the local file | ||||||
|          C:\foo.  That comes in handy since that's how symlinks are treated under |          C:\foo.  That comes in handy since that's how symlinks are treated under | ||||||
| @@ -2307,6 +2333,15 @@ symlink_info::check_reparse_point (HANDLE h, bool remote) | |||||||
| 		    (WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer | 		    (WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer | ||||||
| 			  + rp->SymbolicLinkReparseBuffer.SubstituteNameOffset), | 			  + rp->SymbolicLinkReparseBuffer.SubstituteNameOffset), | ||||||
| 		    rp->SymbolicLinkReparseBuffer.SubstituteNameLength); | 		    rp->SymbolicLinkReparseBuffer.SubstituteNameLength); | ||||||
|  |       if (!(rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) && | ||||||
|  |           !check_reparse_point_target (&subst)) | ||||||
|  | 	{ | ||||||
|  | 	  /* Unsupport native symlink target prefix. Not treated as symlink. | ||||||
|  | 	     The return value of -1 indicates name needs to be opened without | ||||||
|  | 	     FILE_OPEN_REPARSE_POINT flag. */ | ||||||
|  | 	  return -1; | ||||||
|  | 	} | ||||||
|  |     } | ||||||
|   else if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) |   else if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) | ||||||
|     { |     { | ||||||
|       /* Don't handle junctions on remote filesystems as symlinks.  This type |       /* Don't handle junctions on remote filesystems as symlinks.  This type | ||||||
| @@ -2318,11 +2353,11 @@ symlink_info::check_reparse_point (HANDLE h, bool remote) | |||||||
| 		  (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer | 		  (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer | ||||||
| 			  + rp->MountPointReparseBuffer.SubstituteNameOffset), | 			  + rp->MountPointReparseBuffer.SubstituteNameOffset), | ||||||
| 		  rp->MountPointReparseBuffer.SubstituteNameLength); | 		  rp->MountPointReparseBuffer.SubstituteNameLength); | ||||||
|       if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE)) |       if (!check_reparse_point_target (&subst)) | ||||||
| 	{ | 	{ | ||||||
| 	  /* Volume mount point.  Not treated as symlink. The return | 	  /* Volume mount point, or unsupported native target prefix. Not | ||||||
| 	     value of -1 is a hint for the caller to treat this as a | 	     treated as symlink. The return value of -1 indicates name needs | ||||||
| 	     volume mount point. */ | 	     to be opened without FILE_OPEN_REPARSE_POINT flag. */ | ||||||
| 	  return -1; | 	  return -1; | ||||||
| 	} | 	} | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -88,6 +88,7 @@ enum path_types | |||||||
| }; | }; | ||||||
|  |  | ||||||
| NTSTATUS file_get_fai (HANDLE, PFILE_ALL_INFORMATION); | NTSTATUS file_get_fai (HANDLE, PFILE_ALL_INFORMATION); | ||||||
|  | bool check_reparse_point_target (PUNICODE_STRING); | ||||||
|  |  | ||||||
| class symlink_info; | class symlink_info; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user