@@ -216,7 +216,7 @@ def _create_metadata(self, source, follow_symlinks=True):
216216 pass
217217
218218 def _create (self , source , follow_symlinks , dirs_exist_ok , preserve_metadata ):
219- self . _ensure_distinct_path (source )
219+ ensure_distinct_paths (source , self . _path )
220220 if not follow_symlinks and source .is_symlink ():
221221 self ._create_symlink (source , preserve_metadata )
222222 elif source .is_dir ():
@@ -243,7 +243,7 @@ def _create_dir(self, source, follow_symlinks, dirs_exist_ok, preserve_metadata)
243243
244244 def _create_file (self , source , preserve_metadata ):
245245 """Copy the given file to our path."""
246- self . _ensure_different_file (source )
246+ ensure_different_files (source , self . _path )
247247 with magic_open (source , 'rb' ) as source_f :
248248 try :
249249 with magic_open (self ._path , 'wb' ) as target_f :
@@ -263,30 +263,25 @@ def _create_symlink(self, source, preserve_metadata):
263263 if preserve_metadata :
264264 self ._create_metadata (source , follow_symlinks = False )
265265
266- def _ensure_different_file (self , source ):
267- """
268- Raise OSError(EINVAL) if both paths refer to the same file.
269- """
270- pass
271266
272- def _ensure_distinct_path ( self , source ):
273- """
274- Raise OSError(EINVAL) if the other path is within this path.
275- """
276- # Note: there is no straightforward, foolproof algorithm to determine
277- # if one directory is within another (a particularly perverse example
278- # would be a single network share mounted in one location via NFS, and
279- # in another location via CIFS), so we simply checks whether the
280- # other path is lexically equal to, or within, this path.
281- if source == self . _path :
282- err = OSError (EINVAL , "Source and target are the same path" )
283- elif source in self . _path .parents :
284- err = OSError (EINVAL , "Source path is a parent of target path" )
285- else :
286- return
287- err .filename = str (source )
288- err .filename2 = str (self . _path )
289- raise err
267+ def ensure_distinct_paths ( source , target ):
268+ """
269+ Raise OSError(EINVAL) if the other path is within this path.
270+ """
271+ # Note: there is no straightforward, foolproof algorithm to determine
272+ # if one directory is within another (a particularly perverse example
273+ # would be a single network share mounted in one location via NFS, and
274+ # in another location via CIFS), so we simply checks whether the
275+ # other path is lexically equal to, or within, this path.
276+ if source == target :
277+ err = OSError (EINVAL , "Source and target are the same path" )
278+ elif source in target .parents :
279+ err = OSError (EINVAL , "Source path is a parent of target path" )
280+ else :
281+ return
282+ err .filename = str (source )
283+ err .filename2 = str (target )
284+ raise err
290285
291286
292287class LocalCopyWriter (CopyWriter ):
@@ -376,19 +371,31 @@ def _create_symlink(self, source, preserve_metadata):
376371 if preserve_metadata :
377372 self ._create_metadata (source , follow_symlinks = False )
378373
379- def _ensure_different_file (self , source ):
380- """
381- Raise OSError(EINVAL) if both paths refer to the same file.
382- """
374+
375+ def ensure_different_files (source , target ):
376+ """
377+ Raise OSError(EINVAL) if both paths refer to the same file.
378+ """
379+ try :
380+ source_file_id = source .info ._file_id
381+ target_file_id = target .info ._file_id
382+ source_device_id = source .info ._device_id
383+ target_device_id = target .info ._device_id
384+ except AttributeError :
385+ if source != target :
386+ return
387+ else :
383388 try :
384- if not self ._path .samefile (source ):
389+ if source_file_id () != target_file_id ():
390+ return
391+ if source_device_id () != target_device_id ():
385392 return
386393 except (OSError , ValueError ):
387394 return
388- err = OSError (EINVAL , "Source and target are the same file" )
389- err .filename = str (source )
390- err .filename2 = str (self . _path )
391- raise err
395+ err = OSError (EINVAL , "Source and target are the same file" )
396+ err .filename = str (source )
397+ err .filename2 = str (target )
398+ raise err
392399
393400
394401class _PathInfoBase :
@@ -439,6 +446,14 @@ def _posix_permissions(self, *, follow_symlinks=True):
439446 """Return the POSIX file permissions."""
440447 return S_IMODE (self ._stat (follow_symlinks = follow_symlinks ).st_mode )
441448
449+ def _file_id (self , * , follow_symlinks = True ):
450+ """Returns the identifier of the file (unique for a device ID)."""
451+ return self ._stat (follow_symlinks = follow_symlinks ).st_ino
452+
453+ def _device_id (self , * , follow_symlinks = True ):
454+ """Returns the identifier of the device on which the file resides."""
455+ return self ._stat (follow_symlinks = follow_symlinks ).st_dev
456+
442457 def _access_time_ns (self , * , follow_symlinks = True ):
443458 """Return the access time in nanoseconds."""
444459 return self ._stat (follow_symlinks = follow_symlinks ).st_atime_ns
0 commit comments