Summary:
I dropped a long way down the rabbit-hole on this one. I finally found the
problem, and what initially seemed like a boneheaded Cygwin bug turns out
to be an annoying fact of life. It has to do with file permissions and
broken Windows software, specifically Visual Source Safe (VSS). But it also
applies to most Windows software that attempts to modify file permissions.
I found the FAQs suggestive but not helpful enough, since they didn't lead
me to understanding the problem well enough to work around it with
confidence. I probably did not go far enough back in the archives, so if
I'm ignoring a brilliant treatise on this subject, I apologize in advance.
Background:
I use tar to package a set of files on Unix boxes (multiple flavors), and
'download' them to my NTFS WinXP Pro box under Cygwin, where I maintain
them using Windows-style tools, including Microsoft Visual Source Safe.
Then, I 'upload' them back to the Linux box, where they get compiled and
executed.
When I 'download' files from Unix, the permissions somehow get messed up,
and VSS balks with an error 'Access to file 'whatever' denied." The MS Help
claims this is permissions or a file-locking problem. It turns out, it's a
permissions problem, but it's deep in the bowels of the software.
Solution:
When Cygwin is done touching the files (e.g. tar), you MUST override the
original Unix permissions and make the files writeable under Cygwin. This
will cause VSS to complain mightily about writeable files in some
instances, but you can either override the complaint manually in VSS, or
write a simple Windows program to make all the affected files Read-Only,
which you can use AFTER you've used Cygwin chmod to make the file writeable.
This solution also applies to any older Windows software that attempts to
make a Read-Only file writeable.
Details:
I eventually reverse-engineered the mechanism that Cygwin uses to "store"
its permissions on Windows NTFS. Given the number of queries on the FAQ
about file permissions, I was surprised to find few authoritative or
detailed responses.
Cygwin creates and uses three NTFS security ACLs on each file it handles,
for three different MS security SID values: one is the well-known group SID
'Everyone' (used for the Unix 'all' permissions), one is a group named
'None' (used for the Unix group permissions), and one is a user equal to
the current user (used for the Unix user permissions). Each ACL sets
separate NTFS permissions on the file analogous to the Unix permissions
bits, which not only store the permissions for display, but actually
enforce them under NTFS.
The NTFS ACLs provide a lot of permissions for controlling access. For
files and directories, the relevant ones are:
00000001 Read Data
00000002 Write Data
00000004 Append Data
00000008 Read Extended Attributes
00000010 Write Extended Attributes
00000020 Execute
00000040 Delete Subdirectory
00000080 Read Attributes
00000010 Write Attributes
00010000 Delete
00020000 Read DAC
00040000 Write DAC
00080000 Write Owner
00100000 Synchronize
00020088 is the minimal pattern for files, present when there are no Unix
permissions. Without these (Read EA, Read Attr, Read DAC) you would not be
able to determine whether you had permission to discover your permissions.
The owner always has a slightly higher minimal set of 001F0110, which
allows the owner to change ownership, delete the file, and change all
permissions.
The Unix execute bit maps to 00000020 (Execute), read to 00000001 (Read
Data), and write to 00000006 (Write and Append Data). So far, so good.
Because of the way ACLs interact with each other, there can be a rather
complex mess of 'grant' and 'deny' ACLs, but the use of the bits is pretty
straightforward.
Now, here's the rub. There is also the "normal" attributes mask, which is
what almost all of the generic Windows software, such as VSS, uses
exclusively to determine whether the file is writeable. There is a single
bit in the normal attributes called Read-Only. This is the bit reflected in
the Properties dialog box for the file, as well as the file attributes
acquired through Windows calls to GetFileAttributes() and
GetFileAttributesEx(), and also the Windows POSIX library _stat() and
_chmod() calls. None of these calls will tell you anything at all about the
ACLs on the file, and therefore they will tell you nothing about the Cygwin
file permissions. Getting into the NTFS ACL calls is the usual slog through
the Microsoft maze of horrors, and is not for the faint of heart: see
closing notes.
In particular, software like VSS will attempt to modify the file
permissions so that it can manage the file. When a file is "checked out"
under VSS, it is made writeable, meaning the Read-Only bit is cleared. When
it is "checked in", the Read-Only bit is set. However, VSS does not make
changes to the NTFS ACL permissions. I doubt that it even knows about them
- it's very old, pre-NT software. All it knows is that it "takes control"
of the write permissions, makes the file writeable, and then - when it
tries to actually write - gets blocked by the NTFS ACL permissions that
have been set up by Cygwin and fails.
This seems to be just a fact of life: VSS isn't going to fix this, and
there is nothing Cygwin could or should do about it. The solution is to
make the file always writeable under Cygwin, and then let VSS have its way
with the Read-Only bit in the standard attributes. This is a little awkward
when VSS expects to find the file in a Read-Only state after you've handled
it with Cygwin, because when Cygwin makes the file writeable (with the
ACLs) it also clears the Read-Only bit. However, you can then go through
with a simple Windows program that calls the POSIX library _chmod() to make
the file Read-Only: it won't affect the ACLs, but it will set the Read-Only
bit and keep VSS happy.
Incidentally, setting the Read-Only bit this way will make all of the write
permissions in Cygwin appear to go away, which is appropriate, since an
attempt to write the Read-Only file will fail. However, if you clear the
Read-Only bit, all of the write permissions will come back just exactly the
way you had them before, because nothing was changed in the ACLs.
Closing Notes:
If you are as foolish as me and try to get into the Windows side of this,
here are a few useful hints.
First, if you are running WinXP Professional, you can gain access to the
ACLs through the Windows interface, but it's tricky. They ship WinXP Pro
with 'Simple Sharing' turned ON by default, which prevents you from even
looking at the ACLs. To turn this OFF, you go to:
Start->MyComputer->Tools|FolderOptions->View(tab)
Down near the bottom of 'Advanced Options' is a box called 'Use Simple File
Sharing (recommended)'. Turn this OFF. Now, if you look at the Properties
for any file or folder, there will be an extra Security tab available, and
you can view and edit the ACLs through this tab.
The notes I saw said that WinXP Home does not allow simple sharing to be
turned off. I can only assume this is true.
If you try to do programmatic access to the ACLs, you are in for some pain.
Here are some hints: you can't get to the ACLs at all unless you elevate
your process token privileges to include SE_SECURITY_NAME - this is
somewhat analogous to doing setuid() on Unix, except that under Windows,
even root doesn't have root privileges. As is typical, the documentation
leads you through this backwards and upside-down, using calls that don't
work quite as advertised. Here's a sequence that works:
// You need the process token (not the thread token) to adjust its privileges
if (! OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
<error from GetLastError()>
// You need a magic number corresponding to the SE_SECURITY_NAME text
if (! LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tkp.Privileges[0].Luid))
<error from GetLastError()>
// Elevate privileges
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(token, FALSE, &tkp, 0, NULL, NULL);
if ((error = GetLastError()) != 0)
<error>
// NOW you can get the UID, GID, and DACLs for the file
// I never saw any SACLs on my system, they don't seem relevant to this
// You have to LocalFree(pudesc) when you are done, but not the other pointers
if ((error = GetNamedSecurityInfo(fnam, SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION|
GROUP_SECURITY_INFORMATION|
DACL_SECURITY_INFORMATION,
&pusid, &pgsid, &pdacl, NULL, &pudesc)) != 0)
<error>
// Go back to normal privileges
tkp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(token, FALSE, &tkp, 0, NULL, NULL);
// Finally, extract the array of ACLs
// You have to LocalFree(pEntries) when you are done
if ((error = GetExplicitEntriesFromAcl(pdacl,&count, &pentries)) != 0)
<error>
// pusid points to the file owner
// pgsid points to the file group
// pentries[] is an array of 'count' ACL entries for the file
In particular, notice the hodgepodge of different error reporting schemes,
and the fact that sometimes 0 means success, sometimes it means failure,
and in one case, means pretty much nothing at all. This caught me a couple
of times while I was trying to muddle through it.
-- Joe
--
Unsubscribe info: http://cygwin.com/ml/#unsubscribe-simple
Problem reports: http://cygwin.com/problems.html
Documentation: http://cygwin.com/docs.html
FAQ: http://cygwin.com/faq/