Fix HardLinkDmbProvider not mirroring GameStaticFiles and other symlinks

Also update readme about conditional requirements for GameStaticFiles to function

Fixes #1686
This commit is contained in:
Jordan Dominion
2023-10-22 14:09:42 -04:00
parent 720c42c522
commit 9d674618a9
3 changed files with 78 additions and 10 deletions
+20 -2
View File
@@ -570,7 +570,26 @@ This folder can contain anything. But, when certain events occur in the instance
#### GameStaticFiles
Any files and folders contained in this root level of this folder will be symbolically linked to all deployments at the time they are created. This allows persistent game data (BYOND `.sav`s or code configuration files for example) to persist across all deployments. This folder contains a .tgsignore file which can be used to prevent symlinks from being generated by entering the names of files and folders (1 per line)
Any files and folders contained in this root level of this folder will be symbolically linked to all deployments at the time they are created. This allows persistent game data (BYOND `.sav`s or code configuration files for example) to persist across all deployments. This folder contains a .tgsignore file which can be used to prevent symlinks from being generated by entering the names of files and folders (1 per line).
This functionality has the following prerequisites:
- You are using Windows.
**OR**
- Your world uses the TGS DreamMaker API.
- Your world runs with the `Trusted` security level.
**OR**
- You are NOT using the basic watchdog.
- The contents of the `GameStaticFiles` directory are on the same filesystem as the instance's `Game` directory.
**OR**
- You are using the basic watchdog.
- Your world runs with the `Trusted` security level.
### Clients
@@ -614,4 +633,3 @@ Feel free to ask for help [on the discussions page](https://github.com/tgstation
* The remainder of the project is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html)
See the files in the `/src/DMAPI` tree for the MIT license
@@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
@@ -18,6 +20,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <summary>
/// A <see cref="IDmbProvider"/> that uses hard links.
/// </summary>
[UnsupportedOSPlatform("windows")]
sealed class HardLinkDmbProvider : SwappableDmbProvider
{
/// <summary>
@@ -96,6 +99,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <inheritdoc />
protected override async Task DoSwap(CancellationToken cancellationToken)
{
logger.LogTrace("Begin DoSwap, mirroring task complete: {complete}...", mirroringTask.IsCompleted);
var mirroredDir = await mirroringTask.WaitAsync(cancellationToken);
var goAheadTcs = new TaskCompletionSource();
@@ -103,12 +107,15 @@ namespace Tgstation.Server.Host.Components.Deployment
async void DisposeOfOldDirectory()
{
var directoryMoved = false;
var disposePath = Guid.NewGuid().ToString();
var disposeGuid = Guid.NewGuid();
var disposePath = disposeGuid.ToString();
logger.LogTrace("Moving Live directory to {path} for deletion...", disposeGuid);
try
{
await IOManager.MoveDirectory(LiveGameDirectory, disposePath, cancellationToken);
directoryMoved = true;
goAheadTcs.SetResult();
logger.LogTrace("Deleting old Live directory {path}...", disposePath);
await IOManager.DeleteDirectory(disposePath, CancellationToken.None); // DCT: We're detached at this point
}
catch (DirectoryNotFoundException ex)
@@ -127,7 +134,9 @@ namespace Tgstation.Server.Host.Components.Deployment
DisposeOfOldDirectory();
await goAheadTcs.Task;
logger.LogTrace("Moving mirror directory {path} to Live...", mirroredDir);
await IOManager.MoveDirectory(mirroredDir, LiveGameDirectory, cancellationToken);
logger.LogTrace("Swap complete!");
}
/// <summary>
@@ -173,17 +182,40 @@ namespace Tgstation.Server.Host.Components.Deployment
{
var dir = new DirectoryInfo(src);
Task subdirCreationTask = null;
var dreamDaemonWillAcceptOutOfDirectorySymlinks = CompileJob.MinimumSecurityLevel == DreamDaemonSecurity.Trusted;
foreach (var subDirectory in dir.EnumerateDirectories())
{
var mirroredName = Path.Combine(dest, subDirectory.Name);
// check if we are a symbolic link
if (!subDirectory.Attributes.HasFlag(FileAttributes.Directory) || subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
logger.LogTrace("Skipping symlink to {subdir}", subDirectory.Name);
continue;
}
if (subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
{
var target = subDirectory.ResolveLinkTarget(false);
logger.LogDebug("Recreating directory {name} as symlink to {target}", subDirectory.Name, target);
if (subdirCreationTask == null)
{
subdirCreationTask = IOManager.CreateDirectory(dest, cancellationToken);
yield return subdirCreationTask;
}
async Task CopyLink()
{
await subdirCreationTask.WaitAsync(cancellationToken);
using var lockContext = semaphore != null
? await SemaphoreSlimContext.Lock(semaphore, cancellationToken)
: null;
await LinkFactory.CreateSymbolicLink(target.FullName, mirroredName, cancellationToken);
}
yield return CopyLink();
continue;
}
else
logger.LogDebug("Recreating symlinked directory {name} as hard links...", subDirectory.Name);
var checkingSubdirCreationTask = true;
foreach (var copyTask in MirrorDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), semaphore, cancellationToken))
foreach (var copyTask in MirrorDirectoryImpl(subDirectory.FullName, mirroredName, semaphore, cancellationToken))
{
if (subdirCreationTask == null)
{
@@ -214,7 +246,24 @@ namespace Tgstation.Server.Host.Components.Deployment
using var lockContext = semaphore != null
? await SemaphoreSlimContext.Lock(semaphore, cancellationToken)
: null;
await LinkFactory.CreateHardLink(sourceFile, destFile, cancellationToken);
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
// AHHHHHHHHHHHHH
var target = fileInfo.ResolveLinkTarget(!dreamDaemonWillAcceptOutOfDirectorySymlinks);
if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
{
logger.LogDebug("Recreating symlinked file {name} as symlink to {target}", fileInfo.Name, target.FullName);
await LinkFactory.CreateSymbolicLink(target.FullName, destFile, cancellationToken);
}
else
{
logger.LogDebug("Recreating symlinked file {name} as hard link to {target}", fileInfo.Name, target.FullName);
await LinkFactory.CreateHardLink(target.FullName, destFile, cancellationToken);
}
}
else
await LinkFactory.CreateHardLink(sourceFile, destFile, cancellationToken);
}
yield return LinkThisFile();
@@ -1,4 +1,5 @@
using System;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;