Example #1
0
            // Remove mac code signature if applied before bundling
            static void RemoveCodesignIfNecessary(string bundlePath)
            {
                Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
                Debug.Assert(HostModelUtils.IsCodesignAvailable());

                // `codesign -v` returns 0 if app is signed
                if (HostModelUtils.RunCodesign("-v", bundlePath).ExitCode == 0)
                {
                    var(exitCode, stdErr) = HostModelUtils.RunCodesign("--remove-signature", bundlePath);
                    if (exitCode != 0)
                    {
                        throw new InvalidOperationException($"Removing codesign from '{bundlePath}' failed: {stdErr}");
                    }
                }
            }
Example #2
0
        /// <summary>
        /// Generate a bundle, given the specification of embedded files
        /// </summary>
        /// <param name="fileSpecs">
        /// An enumeration FileSpecs for the files to be embedded.
        ///
        /// Files in fileSpecs that are not bundled within the single file bundle,
        /// and should be published as separate files are marked as "IsExcluded" by this method.
        /// This doesn't include unbundled files that should be dropped, and not published as output.
        /// </param>
        /// <returns>
        /// The full path the generated bundle file
        /// </returns>
        /// <exceptions>
        /// ArgumentException if input is invalid
        /// IOExceptions and ArgumentExceptions from callees flow to the caller.
        /// </exceptions>
        public string GenerateBundle(IReadOnlyList <FileSpec> fileSpecs)
        {
            _tracer.Log($"Bundler Version: {BundlerMajorVersion}.{BundlerMinorVersion}");
            _tracer.Log($"Bundle  Version: {BundleManifest.BundleVersion}");
            _tracer.Log($"Target Runtime: {_target}");
            _tracer.Log($"Bundler Options: {_options}");

            if (fileSpecs.Any(x => !x.IsValid()))
            {
                throw new ArgumentException("Invalid input specification: Found entry with empty source-path or bundle-relative-path.");
            }

            string hostSource;

            try
            {
                hostSource = fileSpecs.Where(x => x.BundleRelativePath.Equals(_hostName)).Single().SourcePath;
            }
            catch (InvalidOperationException)
            {
                throw new ArgumentException("Invalid input specification: Must specify the host binary");
            }

            string bundlePath = Path.Combine(_outputDir, _hostName);

            if (File.Exists(bundlePath))
            {
                _tracer.Log($"Ovewriting existing File {bundlePath}");
            }

            BinaryUtils.CopyFile(hostSource, bundlePath);

            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
            {
                RemoveCodesignIfNecessary(bundlePath);
            }

            // Note: We're comparing file paths both on the OS we're running on as well as on the target OS for the app
            // We can't really make assumptions about the file systems (even on Linux there can be case insensitive file systems
            // and vice versa for Windows). So it's safer to do case sensitive comparison everywhere.
            var relativePathToSpec = new Dictionary <string, FileSpec>(StringComparer.Ordinal);

            long headerOffset = 0;

            using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(bundlePath)))
            {
                Stream bundle = writer.BaseStream;
                bundle.Position = bundle.Length;

                foreach (var fileSpec in fileSpecs)
                {
                    string relativePath = fileSpec.BundleRelativePath;

                    if (IsHost(relativePath))
                    {
                        continue;
                    }

                    if (ShouldIgnore(relativePath))
                    {
                        _tracer.Log($"Ignore: {relativePath}");
                        continue;
                    }

                    FileType type = InferType(fileSpec);

                    if (ShouldExclude(type, relativePath))
                    {
                        _tracer.Log($"Exclude [{type}]: {relativePath}");
                        fileSpec.Excluded = true;
                        continue;
                    }

                    if (relativePathToSpec.TryGetValue(fileSpec.BundleRelativePath, out var existingFileSpec))
                    {
                        if (!string.Equals(fileSpec.SourcePath, existingFileSpec.SourcePath, StringComparison.Ordinal))
                        {
                            throw new ArgumentException($"Invalid input specification: Found entries '{fileSpec.SourcePath}' and '{existingFileSpec.SourcePath}' with the same BundleRelativePath '{fileSpec.BundleRelativePath}'");
                        }

                        // Exact duplicate - intentionally skip and don't include a second copy in the bundle
                        continue;
                    }
                    else
                    {
                        relativePathToSpec.Add(fileSpec.BundleRelativePath, fileSpec);
                    }

                    using (FileStream file = File.OpenRead(fileSpec.SourcePath))
                    {
                        FileType targetType = _target.TargetSpecificFileType(type);
                        (long startOffset, long compressedSize) = AddToBundle(bundle, file, targetType);
                        FileEntry entry = BundleManifest.AddEntry(targetType, file, relativePath, startOffset, compressedSize, _target.BundleMajorVersion);
                        _tracer.Log($"Embed: {entry}");
                    }
                }

                // Write the bundle manifest
                headerOffset = BundleManifest.Write(writer);
                _tracer.Log($"Header Offset={headerOffset}");
                _tracer.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}");
                _tracer.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
            }

            HostWriter.SetAsBundle(bundlePath, headerOffset);

            // Sign the bundle if requested
            if (_macosCodesign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
            {
                var(exitCode, stdErr) = HostModelUtils.RunCodesign("-s -", bundlePath);
                if (exitCode != 0)
                {
                    throw new InvalidOperationException($"Failed to codesign '{bundlePath}': {stdErr}");
                }
            }

            return(bundlePath);
Example #3
0
        /// <summary>
        /// Create an AppHost with embedded configuration of app binary location
        /// </summary>
        /// <param name="appHostSourceFilePath">The path of Apphost template, which has the place holder</param>
        /// <param name="appHostDestinationFilePath">The destination path for desired location to place, including the file name</param>
        /// <param name="appBinaryFilePath">Full path to app binary or relative path to the result apphost file</param>
        /// <param name="windowsGraphicalUserInterface">Specify whether to set the subsystem to GUI. Only valid for PE apphosts.</param>
        /// <param name="assemblyToCopyResourcesFrom">Path to the intermediate assembly, used for copying resources to PE apphosts.</param>
        /// <param name="enableMacOSCodeSign">Sign the app binary using codesign with an anonymous certificate.</param>
        public static void CreateAppHost(
            string appHostSourceFilePath,
            string appHostDestinationFilePath,
            string appBinaryFilePath,
            bool windowsGraphicalUserInterface = false,
            string assemblyToCopyResourcesFrom = null,
            bool enableMacOSCodeSign           = false)
        {
            var bytesToWrite = Encoding.UTF8.GetBytes(appBinaryFilePath);

            if (bytesToWrite.Length > 1024)
            {
                throw new AppNameTooLongException(appBinaryFilePath);
            }

            bool appHostIsPEImage = false;

            void RewriteAppHost(MemoryMappedViewAccessor accessor)
            {
                // Re-write the destination apphost with the proper contents.
                BinaryUtils.SearchAndReplace(accessor, AppBinaryPathPlaceholderSearchValue, bytesToWrite);

                appHostIsPEImage = PEUtils.IsPEImage(accessor);

                if (windowsGraphicalUserInterface)
                {
                    if (!appHostIsPEImage)
                    {
                        throw new AppHostNotPEFileException();
                    }

                    PEUtils.SetWindowsGraphicalUserInterfaceBit(accessor);
                }
            }

            void UpdateResources()
            {
                if (assemblyToCopyResourcesFrom != null && appHostIsPEImage)
                {
                    if (ResourceUpdater.IsSupportedOS())
                    {
                        // Copy resources from managed dll to the apphost
                        new ResourceUpdater(appHostDestinationFilePath)
                        .AddResourcesFromPEImage(assemblyToCopyResourcesFrom)
                        .Update();
                    }
                    else
                    {
                        throw new AppHostCustomizationUnsupportedOSException();
                    }
                }
            }

            try
            {
                RetryUtil.RetryOnIOError(() =>
                {
                    FileStream appHostSourceStream    = null;
                    MemoryMappedFile memoryMappedFile = null;
                    MemoryMappedViewAccessor memoryMappedViewAccessor = null;
                    try
                    {
                        // Open the source host file.
                        appHostSourceStream      = new FileStream(appHostSourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1);
                        memoryMappedFile         = MemoryMappedFile.CreateFromFile(appHostSourceStream, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
                        memoryMappedViewAccessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.CopyOnWrite);

                        // Get the size of the source app host to ensure that we don't write extra data to the destination.
                        // On Windows, the size of the view accessor is rounded up to the next page boundary.
                        long sourceAppHostLength = appHostSourceStream.Length;

                        // Transform the host file in-memory.
                        RewriteAppHost(memoryMappedViewAccessor);

                        // Save the transformed host.
                        using (FileStream fileStream = new FileStream(appHostDestinationFilePath, FileMode.Create))
                        {
                            BinaryUtils.WriteToStream(memoryMappedViewAccessor, fileStream, sourceAppHostLength);

                            // Remove the signature from MachO hosts.
                            if (!appHostIsPEImage)
                            {
                                MachOUtils.RemoveSignature(fileStream);
                            }
                        }
                    }
                    finally
                    {
                        memoryMappedViewAccessor?.Dispose();
                        memoryMappedFile?.Dispose();
                        appHostSourceStream?.Dispose();
                    }
                });

                RetryUtil.RetryOnWin32Error(UpdateResources);

                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    var       filePermissionOctal = Convert.ToInt32("755", 8); // -rwxr-xr-x
                    const int EINTR           = 4;
                    int       chmodReturnCode = 0;

                    do
                    {
                        chmodReturnCode = chmod(appHostDestinationFilePath, filePermissionOctal);
                    }while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);

                    if (chmodReturnCode == -1)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {appHostDestinationFilePath}.");
                    }

                    if (enableMacOSCodeSign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
                    {
                        (int exitCode, string stdErr) = HostModelUtils.RunCodesign("-s -", appHostDestinationFilePath);
                        if (exitCode != 0)
                        {
                            throw new AppHostSigningException(exitCode, stdErr);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                // Delete the destination file so we don't leave an unmodified apphost
                try
                {
                    File.Delete(appHostDestinationFilePath);
                }
                catch (Exception failedToDeleteEx)
                {
                    throw new AggregateException(ex, failedToDeleteEx);
                }

                throw;
            }
        }