microsoft / VFSForGit

Virtual File System for Git: Enable Git at Enterprise Scale
MIT License
5.97k stars 453 forks source link

AutoMount fails when ACLs on directory are note set correctly #1811

Open GabeDeBacker opened 5 months ago

GabeDeBacker commented 5 months ago

The GVFS.Service service is running as local system.

If a directory is cloned into a directory that only has ACLs (full control) for a specific user... say: D:\os.2>ICACLS . . REDMOND\gabrield:(OI)(CI)(F)

Then auto-mount fails because the service cannot open the directory when it tries to fix the path casing.

This can be corrected in two ways..

First, use .net to fix the path casing instead of PInvoking into native APIs. (I have confirmed this works with ACL changse). Second, GVFS clone should ensure that the ACLs on the clone directory are properly set for the access rights it needs.

For the first issue, the fix is using the following code in WindowsFileSystem.Shared.cs

using GVFS.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;

namespace GVFS.Platform.Windows
{
    public partial class WindowsFileSystem
    {
        /// <summary>
        /// Getting the logical drives isn't exactly super fast (it isn't super slow either).
        /// It is super unlikely that the drive set will change while the server is running.
        /// </summary>
        private static readonly Lazy<IReadOnlyDictionary<string, string>> LogicalDrives = new Lazy<IReadOnlyDictionary<string, string>>(() =>
        {
            var logicalDrives = Directory.GetLogicalDrives();
            var dictionary = new Dictionary<string, string>(logicalDrives.Length, StringComparer.OrdinalIgnoreCase);
            foreach (var logicalDrive in logicalDrives)
            {
                dictionary.Add(logicalDrive, logicalDrive);
            }

            return dictionary;
        });

        public static bool TryGetNormalizedPathImplementation(string path, out string normalizedPath, out string errorMessage)
        {
            normalizedPath = null;
            errorMessage = null;
            try
            {
                if (!(File.Exists(path) || Directory.Exists(path)))
                {
                    errorMessage = "Path does not exist: " + path;
                    return false;
                }

                // We don't want ".." etc in our file paths.
                path = Path.GetFullPath(path);

                // Build a list of DirectoryInfo objects so we can use them to retrieve
                // the "file system info".
                var directoryInfoList = new List<DirectoryInfo>();
                for (var di = new DirectoryInfo(path); di != null; di = di.Parent)
                {
                    // Always insert at the head to make sure the path parts are in the correct order.
                    directoryInfoList.Insert(0, di);
                }

                // Now, create a IEnumerable<string> from them.
                normalizedPath = Path.Combine(directoryInfoList.Select((foundEntry) =>
                {
                    // If we have a parent "directory info" (which is true for every path part except the root - i.e. drive letter)
                    // then ask the parent to get the "File System Info" for ourselves, and THAT gives us the correct casing.
                    if (foundEntry.Parent != null)
                    {
                        return foundEntry.Parent.GetFileSystemInfos(foundEntry.Name).First().Name;
                    }

                    // Now, for the root, well, we will ask the file system to give us the logical
                    // drives and then when we find a match, return what it gave us.
                    if (LogicalDrives.Value.TryGetValue(foundEntry.Name, out var fixedDriveCasing))
                    {
                        return fixedDriveCasing;
                    }

                    // Finally, if we couldn't find that, just return to upper on the entry, which makes this work for UNC shares.
                    return foundEntry.Name.ToUpperInvariant();
                }).ToArray());
            }
            catch (Win32Exception e)
            {
                errorMessage = "Could not get path root. Failed to determine volume: " + e.Message;
                return false;
            }

            return true;
        }
    }
}

I have not yet had time to investigate the clone code to add "full control" to the clone directory.