* syscalls.cc (enum bin_status): Add dir_not_empty.
(try_to_bin): Call NtQueryInformationFile(FileInternalInformation) with exact buffer size. Explain why. Ditto for NtSetInformationFile(FileRenameInformation). Handle race-condition which might lead to renaming a non-empty directory. (unlink_nt): Rearrange and partially rephrase comments related to the STATUS_SHARING_VIOLATION case. Fix condition under which a dir is tested for being non-empty. Handle dir_not_empty return code from try_to_bin. Gracefully handle disappearing directory in rm -r workaround. Fix typo in comment.
This commit is contained in:
parent
67d71dbf10
commit
a654829ade
|
@ -1,3 +1,17 @@
|
|||
2012-07-25 Corinna Vinschen <corinna@vinschen.de>
|
||||
|
||||
* syscalls.cc (enum bin_status): Add dir_not_empty.
|
||||
(try_to_bin): Call NtQueryInformationFile(FileInternalInformation)
|
||||
with exact buffer size. Explain why.
|
||||
Ditto for NtSetInformationFile(FileRenameInformation).
|
||||
Handle race-condition which might lead to renaming a non-empty
|
||||
directory.
|
||||
(unlink_nt): Rearrange and partially rephrase comments related to the
|
||||
STATUS_SHARING_VIOLATION case. Fix condition under which a dir is
|
||||
tested for being non-empty. Handle dir_not_empty return code from
|
||||
try_to_bin. Gracefully handle disappearing directory in rm -r
|
||||
workaround. Fix typo in comment.
|
||||
|
||||
2012-07-24 Corinna Vinschen <corinna@vinschen.de>
|
||||
|
||||
* wincap.cc (wincapc::init): Drop memset call since it can result in
|
||||
|
|
|
@ -228,7 +228,8 @@ enum bin_status
|
|||
{
|
||||
dont_move,
|
||||
move_to_bin,
|
||||
has_been_moved
|
||||
has_been_moved,
|
||||
dir_not_empty
|
||||
};
|
||||
|
||||
static bin_status
|
||||
|
@ -245,6 +246,7 @@ try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
|
|||
PFILE_NAME_INFORMATION pfni;
|
||||
PFILE_INTERNAL_INFORMATION pfii;
|
||||
PFILE_RENAME_INFORMATION pfri;
|
||||
ULONG frisiz;
|
||||
FILE_DISPOSITION_INFORMATION disp = { TRUE };
|
||||
bool fs_has_per_user_recycler = pc.fs_is_ntfs () || pc.fs_is_refs ();
|
||||
|
||||
|
@ -339,7 +341,10 @@ try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
|
|||
pc.fs_flags () & FILE_UNICODE_ON_DISK
|
||||
? L".\xdc63\xdc79\xdc67" : L".cyg");
|
||||
pfii = (PFILE_INTERNAL_INFORMATION) infobuf;
|
||||
status = NtQueryInformationFile (fh, &io, pfii, 65536,
|
||||
/* Note: Modern Samba versions apparently don't like buffer sizes of more
|
||||
than 65535 in some NtQueryInformationFile/NtSetInformationFile calls.
|
||||
Therefore we better use exact buffer sizes from now on. */
|
||||
status = NtQueryInformationFile (fh, &io, pfii, sizeof *pfii,
|
||||
FileInternalInformation);
|
||||
if (!NT_SUCCESS (status))
|
||||
{
|
||||
|
@ -356,7 +361,8 @@ try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
|
|||
pfri->RootDirectory = pc.isremote () ? NULL : rootdir;
|
||||
pfri->FileNameLength = recycler.Length;
|
||||
memcpy (pfri->FileName, recycler.Buffer, recycler.Length);
|
||||
status = NtSetInformationFile (fh, &io, pfri, 65536, FileRenameInformation);
|
||||
frisiz = sizeof *pfri + pfri->FileNameLength - sizeof (WCHAR);
|
||||
status = NtSetInformationFile (fh, &io, pfri, frisiz, FileRenameInformation);
|
||||
if (status == STATUS_OBJECT_PATH_NOT_FOUND && !pc.isremote ())
|
||||
{
|
||||
/* Ok, so the recycler and/or the recycler/SID directory don't exist.
|
||||
|
@ -477,7 +483,7 @@ try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
|
|||
}
|
||||
NtClose (recyclerdir);
|
||||
/* Shoot again. */
|
||||
status = NtSetInformationFile (fh, &io, pfri, 65536,
|
||||
status = NtSetInformationFile (fh, &io, pfri, frisiz,
|
||||
FileRenameInformation);
|
||||
}
|
||||
if (!NT_SUCCESS (status))
|
||||
|
@ -493,6 +499,26 @@ try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
|
|||
Otherwise the below code closes the handle to allow replacing the file. */
|
||||
status = NtSetInformationFile (fh, &io, &disp, sizeof disp,
|
||||
FileDispositionInformation);
|
||||
if (status == STATUS_DIRECTORY_NOT_EMPTY)
|
||||
{
|
||||
/* Uh oh! This was supposed to be avoided by the check_dir_not_empty
|
||||
test in unlink_nt, but given that the test isn't atomic, this *can*
|
||||
happen. Try to move the dir back ASAP. */
|
||||
pfri->RootDirectory = NULL;
|
||||
pfri->FileNameLength = pc.get_nt_native_path ()->Length;
|
||||
memcpy (pfri->FileName, pc.get_nt_native_path ()->Buffer,
|
||||
pc.get_nt_native_path ()->Length);
|
||||
frisiz = sizeof *pfri + pfri->FileNameLength - sizeof (WCHAR);
|
||||
if (NT_SUCCESS (NtSetInformationFile (fh, &io, pfri, frisiz,
|
||||
FileRenameInformation)))
|
||||
{
|
||||
/* Give notice to unlink_nt and leave immediately. This avoids
|
||||
closing the handle, which might still be used if called from
|
||||
the rm -r workaround code. */
|
||||
bin_stat = dir_not_empty;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/* In case of success, restore R/O attribute to accommodate hardlinks.
|
||||
That leaves potentially hardlinks around with the R/O bit suddenly
|
||||
off if setting the delete disposition failed, but please, keep in
|
||||
|
@ -524,7 +550,7 @@ try_to_bin (path_conv &pc, HANDLE &fh, ACCESS_MASK access)
|
|||
status);
|
||||
goto out;
|
||||
}
|
||||
status = NtSetInformationFile (tmp_fh, &io, pfri, 65536,
|
||||
status = NtSetInformationFile (tmp_fh, &io, pfri, frisiz,
|
||||
FileRenameInformation);
|
||||
NtClose (tmp_fh);
|
||||
if (!NT_SUCCESS (status))
|
||||
|
@ -691,46 +717,45 @@ unlink_nt (path_conv &pc)
|
|||
if a file is already open elsewhere for other purposes than
|
||||
reading and writing data. */
|
||||
status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_DELETE, flags);
|
||||
/* STATUS_SHARING_VIOLATION is what we expect. STATUS_LOCK_NOT_GRANTED can
|
||||
be generated under not quite clear circumstances when trying to open a
|
||||
file on NFS with FILE_SHARE_DELETE only. This has been observed with
|
||||
SFU 3.5 if the NFS share has been mounted under a drive letter. It's
|
||||
not generated for all files, but only for some. If it's generated once
|
||||
for a file, it will be generated all the time. It looks as if wrong file
|
||||
state information is stored within the NFS client which never times out.
|
||||
Opening the file with FILE_SHARE_VALID_FLAGS will work, though, and it
|
||||
is then possible to delete the file quite normally. */
|
||||
if (status == STATUS_SHARING_VIOLATION || status == STATUS_LOCK_NOT_GRANTED)
|
||||
{
|
||||
/* STATUS_LOCK_NOT_GRANTED can be generated under not quite clear
|
||||
circumstances when trying to open a file on NFS with FILE_SHARE_DELETE
|
||||
only. This has been observed with SFU 3.5 if the NFS share has been
|
||||
mounted under a drive letter. It's not generated for all files, but
|
||||
only for some. If it's generated once for a file, it will be
|
||||
generated all the time. It looks like wrong file state information
|
||||
is stored within the NFS client, for no apparent reason, which never
|
||||
times out. Opening the file with FILE_SHARE_VALID_FLAGS will work,
|
||||
though, and it is then possible to delete the file quite normally.
|
||||
|
||||
NFS implements its own mechanism to remove in-use files which
|
||||
looks quite similar to what we do in try_to_bin for remote files.
|
||||
That's why we don't call try_to_bin on NFS.
|
||||
debug_printf ("Sharing violation when opening %S",
|
||||
pc.get_nt_native_path ());
|
||||
/* We never call try_to_bin on NFS and NetApp for the follwing reasons:
|
||||
|
||||
NFS implements its own mechanism to remove in-use files, which looks
|
||||
quite similar to what we do in try_to_bin for remote files.
|
||||
|
||||
Netapp filesystems don't understand the "move and delete" method
|
||||
at all and have all kinds of weird effects. Just setting the delete
|
||||
dispositon usually works fine, though. */
|
||||
debug_printf ("Sharing violation when opening %S",
|
||||
pc.get_nt_native_path ());
|
||||
if (!pc.fs_is_nfs () && !pc.fs_is_netapp ())
|
||||
bin_stat = move_to_bin;
|
||||
if (!pc.isdir () || pc.isremote ())
|
||||
/* If the file is not a directory, of if we didn't set the move_to_bin
|
||||
flag, just proceed with the FILE_SHARE_VALID_FLAGS set. */
|
||||
if (!pc.isdir () || bin_stat == dont_move)
|
||||
status = NtOpenFile (&fh, access, &attr, &io,
|
||||
FILE_SHARE_VALID_FLAGS, flags);
|
||||
else
|
||||
{
|
||||
/* It's getting tricky. The directory is opened in some process,
|
||||
so we're supposed to move it to the recycler and mark it for
|
||||
deletion. But what if the directory is not empty? The move
|
||||
/* Otherwise it's getting tricky. The directory is opened in some
|
||||
process, so we're supposed to move it to the recycler and mark it
|
||||
for deletion. But what if the directory is not empty? The move
|
||||
will work, but the subsequent delete will fail. So we would
|
||||
have to move it back. That's bad, because the directory would
|
||||
be moved around which results in a temporary inconsistent state.
|
||||
So, what we do here is to test if the directory is empty. If
|
||||
not, we bail out with STATUS_DIRECTORY_NOT_EMPTY. The below code
|
||||
tests for at least three entries in the directory, ".", "..",
|
||||
and another one. Three entries means, not empty. This doesn't
|
||||
work for the root directory of a drive, but the root dir can
|
||||
neither be deleted, nor moved anyway. */
|
||||
have to move it back. While we do that in try_to_bin, it's bad,
|
||||
because the move results in a temporary inconsistent state.
|
||||
So, we test first if the directory is empty. If not, we bail
|
||||
out with STATUS_DIRECTORY_NOT_EMPTY. This avoids most of the
|
||||
problems. */
|
||||
status = NtOpenFile (&fh, access | FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
||||
&attr, &io, FILE_SHARE_VALID_FLAGS,
|
||||
flags | FILE_SYNCHRONOUS_IO_NONALERT);
|
||||
|
@ -764,9 +789,15 @@ unlink_nt (path_conv &pc)
|
|||
/* Try to move to bin if a sharing violation occured. If that worked,
|
||||
we're done. */
|
||||
if (bin_stat == move_to_bin
|
||||
&& (bin_stat = try_to_bin (pc, fh, access)) == has_been_moved)
|
||||
&& (bin_stat = try_to_bin (pc, fh, access)) >= has_been_moved)
|
||||
{
|
||||
status = STATUS_SUCCESS;
|
||||
if (bin_stat == has_been_moved)
|
||||
status = STATUS_SUCCESS;
|
||||
else
|
||||
{
|
||||
status = STATUS_DIRECTORY_NOT_EMPTY;
|
||||
NtClose (fh);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@ -831,6 +862,7 @@ try_again:
|
|||
bin_stat = try_to_bin (pc, fh, access);
|
||||
}
|
||||
}
|
||||
/* Do NOT handle bin_stat == dir_not_empty here! */
|
||||
if (bin_stat == has_been_moved)
|
||||
status = STATUS_SUCCESS;
|
||||
else
|
||||
|
@ -842,12 +874,15 @@ try_again:
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (status2 != STATUS_OBJECT_PATH_NOT_FOUND
|
||||
&& status2 != STATUS_OBJECT_NAME_NOT_FOUND)
|
||||
{
|
||||
fh = NULL;
|
||||
debug_printf ("Opening dir %S for check_dir_not_empty failed, "
|
||||
"status = %p", pc.get_nt_native_path (), status2);
|
||||
}
|
||||
else /* Directory disappeared between NtClose and NtOpenFile. */
|
||||
status = STATUS_SUCCESS;
|
||||
}
|
||||
/* Trying to delete a hardlink to a file in use by the system in some
|
||||
way (for instance, font files) by setting the delete disposition fails
|
||||
|
@ -885,8 +920,10 @@ try_again:
|
|||
unlinking didn't work. */
|
||||
if (bin_stat == dont_move)
|
||||
bin_stat = try_to_bin (pc, fh, access);
|
||||
if (bin_stat == has_been_moved)
|
||||
status = STATUS_SUCCESS;
|
||||
if (bin_stat >= has_been_moved)
|
||||
status = bin_stat == has_been_moved
|
||||
? STATUS_SUCCESS
|
||||
: STATUS_DIRECTORY_NOT_EMPTY;
|
||||
}
|
||||
else
|
||||
NtClose (fh2);
|
||||
|
@ -896,7 +933,7 @@ try_again:
|
|||
{
|
||||
if (access & FILE_WRITE_ATTRIBUTES)
|
||||
{
|
||||
/* Restore R/O attribute if setting the delete dispostion failed. */
|
||||
/* Restore R/O attribute if setting the delete disposition failed. */
|
||||
if (!NT_SUCCESS (status))
|
||||
NtSetAttributesFile (fh, pc.file_attributes ());
|
||||
/* If we succeeded, restore R/O attribute to accommodate hardlinks.
|
||||
|
|
Loading…
Reference in New Issue