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; | ||||
| } | ||||
|  | ||||
| /* Check reparse point for type.  IO_REPARSE_TAG_MOUNT_POINT types are | ||||
|    either volume mount points, which are treated as directories, or they | ||||
|    are directory mount points, which are treated as symlinks. | ||||
|    IO_REPARSE_TAG_SYMLINK types are always symlinks.  We don't know | ||||
|    anything about other reparse points, so they are treated as unknown.  */ | ||||
| static inline uint8_t | ||||
| readdir_check_reparse_point (POBJECT_ATTRIBUTES attr) | ||||
| /* Check reparse point to determine if it should be treated as a posix symlink | ||||
|    or as a normal file/directory. Mount points are treated as normal directories | ||||
|    to match behavior of other systems. Unknown reparse tags are used for | ||||
|    things other than links (HSM, compression, dedup), and generally should be | ||||
|    treated as a normal file/directory. Native symlinks and mount points are | ||||
|    treated as posix symlinks, depending on the prefix of the target name. | ||||
|    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; | ||||
|   HANDLE reph; | ||||
|   UNICODE_STRING subst; | ||||
| @@ -185,20 +189,29 @@ readdir_check_reparse_point (POBJECT_ATTRIBUTES attr) | ||||
| 		      &io, FSCTL_GET_REPARSE_POINT, NULL, 0, | ||||
| 		      (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, | ||||
| 		  (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer | ||||
| 			    + rp->MountPointReparseBuffer.SubstituteNameOffset), | ||||
| 		  rp->MountPointReparseBuffer.SubstituteNameLength); | ||||
| 	      /* Only volume mountpoints are treated as directories. */ | ||||
| 	      if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE)) | ||||
| 		ret = DT_DIR; | ||||
| 	      else | ||||
| 		ret = DT_LNK; | ||||
| 	      if (check_reparse_point_target (&subst)) | ||||
| 	        ret = true; | ||||
| 	    } | ||||
| 	  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); | ||||
| 	} | ||||
|     } | ||||
| @@ -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 | ||||
|      symlinks, d_type will be reset below.  Reparse points can be NTFS | ||||
|      symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */ | ||||
|   if (attr && | ||||
|       !(attr & (~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_REPARSE_POINT))) | ||||
|   if (attr && !(attr & ~FILE_ATTRIBUTE_VALID_FLAGS)) | ||||
|     { | ||||
|       if (attr & FILE_ATTRIBUTE_DIRECTORY) | ||||
| 	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; | ||||
|     } | ||||
|  | ||||
|   /* Check for directory reparse point. These may be treated as a posix | ||||
|      symlink, or as mount point, so need to figure out whether to return | ||||
|      a directory or link type. In all cases, returning the INO of the | ||||
|      reparse point (not of the target) matches behavior of posix systems. | ||||
|   /* Check for reparse points that can be treated as posix symlinks. | ||||
|      Mountpoints and unknown or unhandled reparse points will be treated | ||||
|      as normal file/directory/unknown. In all cases, returning the INO of | ||||
|      the reparse point (not of the target) matches behavior of posix systems. | ||||
|      */ | ||||
|   if ((attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) | ||||
|       == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) | ||||
|   if (attr & FILE_ATTRIBUTE_REPARSE_POINT) | ||||
|     { | ||||
|       OBJECT_ATTRIBUTES oattr; | ||||
|  | ||||
|       InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (), | ||||
| 				  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 | ||||
|   | ||||
| @@ -2261,6 +2261,31 @@ symlink_info::check_sysfile (HANDLE h) | ||||
|   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 | ||||
| symlink_info::check_reparse_point (HANDLE h, bool remote) | ||||
| { | ||||
| @@ -2299,6 +2324,7 @@ symlink_info::check_reparse_point (HANDLE h, bool remote) | ||||
|       return 0; | ||||
|     } | ||||
|   if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) | ||||
|     { | ||||
|       /* 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 | ||||
|          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 | ||||
| 			  + rp->SymbolicLinkReparseBuffer.SubstituteNameOffset), | ||||
| 		    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) | ||||
|     { | ||||
|       /* 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 | ||||
| 			  + rp->MountPointReparseBuffer.SubstituteNameOffset), | ||||
| 		  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 | ||||
| 	     value of -1 is a hint for the caller to treat this as a | ||||
| 	     volume mount point. */ | ||||
| 	  /* Volume mount point, or unsupported native 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; | ||||
| 	} | ||||
|     } | ||||
|   | ||||
| @@ -88,6 +88,7 @@ enum path_types | ||||
| }; | ||||
|  | ||||
| NTSTATUS file_get_fai (HANDLE, PFILE_ALL_INFORMATION); | ||||
| bool check_reparse_point_target (PUNICODE_STRING); | ||||
|  | ||||
| class symlink_info; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user