Visible symlinks under Windows

Corinna Vinschen corinna at vinschen.de
Mon Jun 23 14:20:56 GMT 2008


Hi,

On Feb  6 12:50, Volker Lendecke wrote:
> On Wed, Feb 06, 2008 at 12:27:07PM +0100, Corinna Vinschen wrote:
> > as Cygwin developer, it's sort of frustrating that there isn't a way to
> > identify symlinks on Samba shares from a Windows user space application
> > for what they really are.
> 
> Well, why stop at symlinks? If you do those, why not the
> whole set of posix inode types like devices, sockets and
> other stuff also?
> 
> This was the original purpose of the Unix extensions, but
> nobody at that point thought about using that stuff from
> Windows user space, so we carved our own number space in the
> protocol. As hardly anybody uses SMB EAs these days, I would
> say this could be a clever trick to pass information from
> Windows user space into smbd and vice versa.

I'd like to pick up this old thread from here.  In the meantime I added
support for Micosoft's NFS client to Cygwin (will be in the next major
release).  To do that I had to learn how the NFS client transfers the
POSIXy information to native Windows clients.  Unfortunately, the
interface is not documented yet, so I tracked it down myself by debugging.

The Microsoft NFS client uses the Extended Attribute API, too, as I
proposed for symlinks in my posting which started this thread.  As I
said, the client side is already implemented in Cygwin and now I was
wondering if there's interest to add the exact same interface (just the
server side) to Samba as well.  The advantage would be that there's only
one common interface for Win32 clients to access the POSIXy file
information, regardless of using NFS or SMB.

Here's a description of the Microsoft NFS client interface from the
Windows application perspective:

When you open a file on the NFS share, the result depends on whether
it's an ordinary file or directory, or, if it's a symlink, whether the
NFS client can resolve the symlink target or not.  If the target of the
symlink points outside of the NFS share or is a dangling symlink, then
opening the file results in getting a handle to the symlink itself.
Win32 applications are usually lost here anyway.  OTOH, if the target is
a file on the share itself, the NFS client can resolve it and you get a
handle to the target, which also dumb Win32 apps can use.

If you want information about the symlink itself, rather than about the
target of the symlink, you have to tell the NtCreateFile call that you
want a handle to the symlink instead of to the target.  You do this by
giving a special EA called "NfsActOnLink" to the NtCreateFile call.
Since you're writing an EA, you also have to specify the FILE_WRITE_EA
access bit:

  ULONG ealen = sizeof (FILE_FULL_EA_INFORMATION)
                + sizeof ("NfsActOnLink");
  PFILE_FULL_EA_INFORMATION eaptr = (PFILE_FULL_EA_INFORMATION)
                                    alloca (ealen);
  eaptr->NextEntryOffset = eaptr->Flags eaptr->EaValueLength = 0;
  eaptr->EaNameLength = sizeof ("NfsActOnLink") - 1;
  strcpy (eaptr->EaName, "NfsActOnLink");
  status = NtCreateFile (&file_handle, GENERIC_READ, &object_attributes,
                         &io_status_block, NULL, FILE_ATTRIBUTE_NORMAL,
                         FILE_SHARE_VALID_FLAGS, FILE_OPEN, 0, eaptr, ealen);

If the file is a symlink, you now have a handle to the symlink itself.
Otherwise you just have a handle to the file as usual.  It's save to use
this EA also for normal files.

To find out what that file you just opened is, you can now retrieve a
struct stat like block by fetching another EA called "NfsV3Attributes":

  ULONG getlen = sizeof (FILE_GET_EA_INFORMATION)
                 + sizeof ("NfsV3Attributes");
  PFILE_GET_EA_INFORMATION getptr = (PFILE_GET_EA_INFORMATION)
                                    alloca (getlen);
  ULONG ealen = sizeof (FILE_FULL_EA_INFORMATION)
                + sizeof ("NfsV3Attributes")
                + sizeof (struct fattr3);     // See below
  PFILE_FULL_EA_INFORMATION eaptr = (PFILE_FULL_EA_INFORMATION)
                                    alloca (ealen);
  getptr->NextEntryOffset = 0;
  getptr->EaNameLength = sizeof ("NfsV3Attributes") - 1;
  strcpy (getptr->EaName, "NfsV3Attributes");
  status = NtQueryEaFile (file_handle, &io_status_block,
                          eaptr, ealen, TRUE, getptr, getlen, TRUE);

If this has gone well, the EA value in eaptr contains a struct
fattr3 now, as defined in RFC 1813 (http://tools.ietf.org/html/rfc1813),
with just a minor change.  The structure returned by the above call has
four extra bytes, apparently for alignment reasons.  It boils down to
this structure (compare with chapter 2.5/2.6 from RFC 1813):

  struct fattr3 {
    uint32_t type;            // file type (NFS ftype3, see above RFC)
    uint32_t mode;            // Mode bits rwxrwxrwx + suid/sgid/vtx
    uint32_t nlink;
    uint32_t uid;             // owner id, original value from server
    uint32_t gid;             // group id, original value from server
    uint32_t __dummy;
    uint64_t size;            // file size
    uint64_t used;            // allocation size
    struct
      {
        uint32_t specdata1;
        uint32_t specdata2;
      } rdev;                 // major/minor numbers for BLK/CHR devices
    uint64_t fsid;            // file system device id
    uint64_t fileid;          // inode number
    struct timespec atime;    // last access, seconds since epoch
    struct timespec mtime;    // last modify, seconds since epoch
    struct timespec ctime;    // last change, seconds since epoch
  };

If you want to know if the file is a symlink and if so, where it points to,
fetch the EA called "NfsSymlinkTargetName":

  struct fattr3 *nfs_stat_p = (struct fattr3 *)
                              (eaptr->EaName + eaptr->EaNameLength + 1);
  if (nfs_stat_p->type == NF3LNK) // NF3LNK == 5, see RFC 1813
    {
      ULONG getlen = sizeof (FILE_GET_EA_INFORMATION)
                     + sizeof ("NfsSymlinkTargetName");
      PFILE_GET_EA_INFORMATION getptr = (PFILE_GET_EA_INFORMATION)
                                        alloca (getlen);
      ULONG ealen = 65536; // Max. size of a EA
      PFILE_FULL_EA_INFORMATION eaptr = (PFILE_FULL_EA_INFORMATION)
                                        alloca (ealen);
      getptr->NextEntryOffset = 0;
      getptr->EaNameLength = sizeof ("NfsSymlinkTargetName") - 1;
      strcpy (getptr->EaName, "NfsSymlinkTargetName");
      status = NtQueryEaFileA (file_handle, &io_status_block,
                               eaptr, ealen, TRUE, getptr, getlen, TRUE);
      if (NT_SUCCESS (status))
        PWCHAR syml_target = (PWCHAR)
                             (eaptr->EaName + eaptr->EaNameLength + 1);
    }

Along the same lines you can create symlinks using a NfsSymlinkTargetName
EA in a NtCreateFile, set correct mode bits using a NfsV3Attributes EA
in a NtSetEaFile call or, when creating a file, in a NtCreateFile call.

There's also an interesting difference when calling NtQueryDirectoryFile
on the NFS shares in relation to symlinks.  When you use the
FileNamesInformation info class, then the NFS client lists all files,
including dangling symlinks.  Every other info class does not list
dangling symlinks.

Since *all* Win32 functions are based on using other info classes, you
will typically not see dangling symlinks in Win32 applications,
including Windows Explorer.  Only applications which know to use the
FileNamesInformation info class get *all* the information. 

For sake of completeness, the file names returned by
NtQueryDirectoryFile(FileNamesInformation) are not mangled for Win32
applications.  They are returned and listed as is.


Anybody still here?  If so, does that sound as something worth to
consider for Samba as well?


Thanks for listening,
Corinna


More information about the samba-technical mailing list