Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to Delete Broken Symlink #308

Open
gtripoli opened this issue Jan 23, 2025 · 6 comments
Open

Unable to Delete Broken Symlink #308

gtripoli opened this issue Jan 23, 2025 · 6 comments

Comments

@gtripoli
Copy link

Hello,

I have encountered a problem when trying to delete a broken symbolic link.
The symbolic link points to a nonexistent file, and when you try to delete it, the operation fails, saying that the file does not exist.

Steps to Reproduce:

  • Create a symlink pointing to a non-existent file.
  • Try to delete the symlink using the appropriate method, smbclient.unlink()
  • The operation fails.

Obviously, if the symlink is not broken then the unlink method works correctly

@adiroiban
Copy link
Contributor

adiroiban commented Jan 23, 2025

Thanks for the report.

I just did a quick investigation based on the source code.

Are you using the latest version?

Which SMB server do you use ?

I am looking at the code and I see that unlink() uses remove() which used _delete()

The code has explicit support for this

    # Ensures we delete the symlink (if present) and don't follow it down.
    co = CreateOptions.FILE_OPEN_REPARSE_POINT

def _delete(raw_type, path, **kwargs):
# Ensures we delete the symlink (if present) and don't follow it down.
co = CreateOptions.FILE_OPEN_REPARSE_POINT
co |= {
"dir": CreateOptions.FILE_DIRECTORY_FILE,
"file": CreateOptions.FILE_NON_DIRECTORY_FILE,
}.get(raw_type.FILE_TYPE, 0)


If the latest version still doesn't work, then this need further investigation

Documentation for FILE_OPEN_REPARSE_POINT is here

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/25c118f2-4c5d-4afa-b06c-64b71368076c

@gtripoli
Copy link
Author

Thanks for the quick reply.

Yes, I am using the latest version of the library. I have verified that the unlink() function calls remove(), which in turn uses _delete() with the CreateOptions.FILE_OPEN_REPARSE_POINT option.

However, when I try to delete a broken symlink (pointing to a non-existent file), the operation fails with the error "file not found."

I haven't tried modifying the "create_options"
used in the call yet, but I noticed that in other parts of the code, the value 0 is used to handle the follow_symlinks=False behavior.

Would it be worth trying a similar approach here?

Thanks for your support

@jborean93
Copy link
Owner

Are you able to share the full error and traceback? @adiroiban is right in that the delete operation is done with FILE_OPEN_REPARSE_POINT to ensure the symlink itself is the one being deleted and Windows doesn't attempt to follow it. You could also try and see if smbclient.lstat works on the same path or whether it also errors as well.

@gtripoli
Copy link
Author

gtripoli commented Jan 24, 2025

  • Environment:
(server)
~]# samba -V
Version 4.15.6

~]# cat /etc/samba/smb-extend.conf | grep link
follow symlinks         = yes
wide links              = yes

~]# cat /etc/samba/smb-extend.conf | grep unix
unix extensions         = no

---------------------------------------------------------------

(client)
$ python -V
Python 3.12.7

$ pip freeze | grep smbprotocol
smbprotocol==1.15.0
$
  • directory tree example
~]# tree
.
├── symlink_broken -> ../this_file_not_exist
├── symlink_ok -> this_is_a_real_file
└── this_is_a_real_file

0 directories, 3 files
try :
    print(smbclient.lstat(f"{smb_path}/this_is_a_real_file", **smb_params))
except Exception as ex :
    print(ex)

try :
    print(smbclient.lstat(f"{smb_path}/symlink_ok", **smb_params))
except Exception as ex :
    print(ex)

try :
    print(smbclient.lstat(f"{smb_path}/symlink_broken", **smb_params))
except Exception as ex :
    print(ex)

---------------------------------------------------------------------------------------------------------

> SMBStatResult(st_mode=33206, st_ino=119785, st_dev=3783238930, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1737713822.157852, st_mtime=1737713822.157852, st_ctime=1737713822.157852, st_chgtime=1737713822.157852, st_atime_ns=1737713822157852000, st_mtime_ns=1737713822157852000, st_ctime_ns=1737713822157852000, st_chgtime_ns=1737713822157852000, st_file_attributes=128, st_reparse_tag=0)

> SMBStatResult(st_mode=33206, st_ino=119785, st_dev=3783238930, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1737713822.157852, st_mtime=1737713822.157852, st_ctime=1737713822.157852, st_chgtime=1737713822.157852, st_atime_ns=1737713822157852000, st_mtime_ns=1737713822157852000, st_ctime_ns=1737713822157852000, st_chgtime_ns=1737713822157852000, st_file_attributes=128, st_reparse_tag=0)

> [Error 2] [NtStatus 0xc0000034] No such file or directory: 'xxxxx/xxxxx/xxx/symlink_broken'

try :
    smbclient.unlink(f"{smb_path}/symlink_ok", **smb_params)
except Exception as ex :
    print(ex)

try :
    smbclient.unlink(f"{smb_path}/symlink_broken", **smb_params)
except Exception as ex :
    print(ex)


------------------------------------------------------------------------------------

> [Error 2] [NtStatus 0xc0000034] No such file or directory: 'xxxxx/xxxxx/xxx/symlink_broken'


~]# tree
.
├── symlink_broken -> ../this_file_not_exist
└── this_is_a_real_file

0 directories, 2 files
~]# 

@jborean93
Copy link
Owner

This may be a problem with how Samba deals with broken symlinks. On Windows I have the following

PS D:\test> gci

    Directory: D:\test

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
la---          28/01/2025  7:05 AM              0 link-bad -> fake.txt
la---          28/01/2025  7:05 AM              0 link-ok -> test.txt
-a---          28/01/2025  7:05 AM              5 test.txt

Then I run lstat on the two symlinks and both return the symlink stat info while stat on the broken symlink fails because the symlink doesn't have a target

import smbclient

print(smbclient.lstat(r"\\host.domain.test\d$\test\link-ok"))
print(smbclient.lstat(r"\\host.domain.test\d$\test\link-bad"))
print(smbclient.stat(r"\\host.domain.test\d$\test\link-bad"))
SMBStatResult(st_mode=41398, st_ino=4611686018427616645, st_dev=517726233, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1738011933.2488062, st_mtime=1738011933.2488062, st_ctime=1738011933.2488062, st_chgtime=1738011933.2506526, st_atime_ns=1738011933248806200, st_mtime_ns=1738011933248806200, st_ctime_ns=1738011933248806200, st_chgtime_ns=1738011933250652600, st_file_attributes=1056, st_reparse_tag=2684354572)

SMBStatResult(st_mode=41398, st_ino=4611686018427682181, st_dev=517726233, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1738011943.8006275, st_mtime=1738011943.8006275, st_ctime=1738011943.8006275, st_chgtime=1738011943.8006275, st_atime_ns=1738011943800627500, st_mtime_ns=1738011943800627500, st_ctime_ns=1738011943800627500, st_chgtime_ns=1738011943800627500, st_file_attributes=1056, st_reparse_tag=2684354572)

Traceback (most recent call last):
  File "/home/jborean/dev/smbprotocol/link-test.py", line 9, in <module>
    res = smbclient.stat(r"\\host.domain.test\d$\test\link-bad")
  File "/home/jborean/dev/smbprotocol/src/smbclient/_os.py", line 743, in stat
    with SMBFileTransaction(raw) as transaction:
         ~~~~~~~~~~~~~~~~~~^^^^^
  File "/home/jborean/dev/smbprotocol/src/smbclient/_io.py", line 260, in __exit__
    self.commit()
    ~~~~~~~~~~~^^
  File "/home/jborean/dev/smbprotocol/src/smbclient/_io.py", line 349, in commit
    raise failures[0]
smbprotocol.exceptions.SMBOSError: [Error 2] [NtStatus 0xc0000034] No such file or directory: '\\host.domain.test\d$\test\link-bad'

I can also successfully unlink/remove it on the Windows share

import smbclient

smbclient.unlink(r"\\host.domain.test\d$\test\link-bad")

I'll have to try and setup a Samba server using your configuration to see if maybe the problem is on the Samba end or in how we interpret some of the results.

@jborean93
Copy link
Owner

I setup a Samba host with this setup, the share directory on the Samba server side is

root@7885089cf07e:/app# ls -al /srv/samba/share                                                          
total 0
drwxr-xr-x. 2 smbuser smbgroup 49 Jan 28 01:20 .
drwxr-xr-x. 5 root    root     57 Jan 28 01:18 ..
-rw-r--r--. 1 root    root      0 Jan 28 01:18 file
lrwxrwxrwx. 1 root    root      4 Jan 28 01:20 link-bad -> fake
lrwxrwxrwx. 1 root    root      4 Jan 28 01:20 link-ok -> file

It looks like Samba just pretends the link does not exist, I cannot even see it when enumerating the directory

python -c "import smbclient; print(smbclient.listdir(r'\\localhost\share', port=8445, username='smbuser', password='smbpass'))"

['file', 'link-ok']  

This explains why we cannot remove the broken link as Samba is acting like it doesn't exist at all. I recommend trying to run smbclient.listdir on the directory with the link to see if it is exposed. Unfortunately this looks to be a problem on the Samba end and either requires some config option to expose or fix on their end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants