// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.PlatformAbstractions; using Microsoft.DotNet.Tools; using Microsoft.DotNet.Tools.Common; using Microsoft.Extensions.EnvironmentAbstractions; using Newtonsoft.Json; namespace Microsoft.DotNet.ShellShim { internal class ShellShimRepository : IShellShimRepository { private const string ApphostNameWithoutExtension = "apphost"; private readonly DirectoryPath _shimsDirectory; private readonly IFileSystem _fileSystem; private readonly IAppHostShellShimMaker _appHostShellShimMaker; private readonly IFilePermissionSetter _filePermissionSetter; public ShellShimRepository( DirectoryPath shimsDirectory, string appHostSourceDirectory = null, IFileSystem fileSystem = null, IAppHostShellShimMaker appHostShellShimMaker = null, IFilePermissionSetter filePermissionSetter = null) { _shimsDirectory = shimsDirectory; _fileSystem = fileSystem ?? new FileSystemWrapper(); _appHostShellShimMaker = appHostShellShimMaker ?? new AppHostShellShimMaker(appHostSourceDirectory: appHostSourceDirectory); _filePermissionSetter = filePermissionSetter ?? new FilePermissionSetter(); } public void CreateShim(FilePath targetExecutablePath, string commandName, IReadOnlyList packagedShims = null) { if (string.IsNullOrEmpty(targetExecutablePath.Value)) { throw new ShellShimException(CommonLocalizableStrings.CannotCreateShimForEmptyExecutablePath); } if (string.IsNullOrEmpty(commandName)) { throw new ShellShimException(CommonLocalizableStrings.CannotCreateShimForEmptyCommand); } if (ShimExists(commandName)) { throw new ShellShimException( string.Format( CommonLocalizableStrings.ShellShimConflict, commandName)); } TransactionalAction.Run( action: () => { try { if (!_fileSystem.Directory.Exists(_shimsDirectory.Value)) { _fileSystem.Directory.CreateDirectory(_shimsDirectory.Value); } if (TryGetPackagedShim(packagedShims, commandName, out FilePath? packagedShim)) { _fileSystem.File.Copy(packagedShim.Value.Value, GetShimPath(commandName).Value); _filePermissionSetter.SetUserExecutionPermission(GetShimPath(commandName).Value); } else { _appHostShellShimMaker.CreateApphostShellShim( targetExecutablePath, GetShimPath(commandName)); } } catch (FilePermissionSettingException ex) { throw new ShellShimException( string.Format(CommonLocalizableStrings.FailedSettingShimPermissions, ex.Message)); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { throw new ShellShimException( string.Format( CommonLocalizableStrings.FailedToCreateShellShim, commandName, ex.Message ), ex); } }, rollback: () => { foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value))) { File.Delete(file.Value); } }); } public void RemoveShim(string commandName) { var files = new Dictionary(); TransactionalAction.Run( action: () => { try { foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value))) { var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); _fileSystem.File.Move(file.Value, tempPath); files[file.Value] = tempPath; } } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { throw new ShellShimException( string.Format( CommonLocalizableStrings.FailedToRemoveShellShim, commandName, ex.Message ), ex); } }, commit: () => { foreach (var value in files.Values) { _fileSystem.File.Delete(value); } }, rollback: () => { foreach (var kvp in files) { _fileSystem.File.Move(kvp.Value, kvp.Key); } }); } private class StartupOptions { public string appRoot { get; set; } } private class RootObject { public StartupOptions startupOptions { get; set; } } private bool ShimExists(string commandName) { return GetShimFiles(commandName).Any(p => _fileSystem.File.Exists(p.Value)); } private IEnumerable GetShimFiles(string commandName) { if (string.IsNullOrEmpty(commandName)) { yield break; } yield return GetShimPath(commandName); } private FilePath GetShimPath(string commandName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return new FilePath(_shimsDirectory.WithFile(commandName).Value +".exe"); } else { return _shimsDirectory.WithFile(commandName); } } private bool TryGetPackagedShim( IReadOnlyList packagedShims, string commandName, out FilePath? packagedShim) { packagedShim = null; if (packagedShims != null && packagedShims.Count > 0) { FilePath[] candidatepackagedShim = packagedShims .Where(s => string.Equals( Path.GetFileName(s.Value), Path.GetFileName(GetShimPath(commandName).Value))).ToArray(); if (candidatepackagedShim.Length > 1) { throw new ShellShimException( string.Format( CommonLocalizableStrings.MoreThanOnePackagedShimAvailable, string.Join(';', candidatepackagedShim))); } if (candidatepackagedShim.Length == 1) { packagedShim = candidatepackagedShim.Single(); return true; } } return false; } } }