chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using System;
using System.Collections.Generic;
using System.IO;
 
namespace OpenTap.Package;
 
/// <summary> Moves files to a temporary location during uninstall. </summary>
internal class UninstallContext
{
    readonly struct Move(string originalFile, string deletedFile)
    {
        public string OriginalFile { get; } = originalFile;
        public string DeletedFile { get; } = deletedFile;
    }
 
    public static UninstallContext Create(Installation installation)
    {
        // Try to delete files from previous uninstall operations.
        // These files could still be in use, so we just delete whatever we can.
        DeletePreviouslyUninstalledFiles(installation);
        return new UninstallContext(installation);
    }
    
    private static readonly TraceSource log = Log.CreateSource("Uninstall");
    private readonly Installation install;
    private readonly string Target;
    private readonly List<Move> Moves = [];
 
    private UninstallContext(Installation installation)
    {
        install = installation;
 
        var uninstallDirectory = GetUninstallDir(install);
        Target = Path.Combine(uninstallDirectory, Guid.NewGuid().ToString());
        Directory.CreateDirectory(Target);
        // Ensure plugins are not loaded from the uninstall directory.
        var ignoreFile = Path.Combine(uninstallDirectory, ".OpenTapIgnore");
        // a buffersize of 0 is invalid in .NET Framework
        int bufferSize = 1;
        if (!File.Exists(ignoreFile))
            File.Create(ignoreFile, bufferSize).Close();
    }
 
    // Previous iterations attempted to uninstall by moving files to the temp directory, but 
    // File.Move has different semantics when moving files between storage volumes (e.g. C:\ to D:\ drive)
    // In such cases, it will copy the file and then attempt to delete it normally. This doesn't work
    // on Windows if the file is in use. To work around this limitation, we uninstall files to a subdirectory of
    // the OpenTAP installation; this should ensure the file can always be moved.
    private static string GetUninstallDir(Installation install) => Path.Combine(install.Directory, ".uninstall");
 
    public static void DeletePreviouslyUninstalledFiles(Installation install)
    {
        var dir = GetUninstallDir(install);
        if (!Directory.Exists(dir)) return;
        var subdirs = Directory.GetDirectories(dir);
        foreach (var subdir in subdirs)
        {
            try
            {
                FileSystemHelper.DeleteDirectory(subdir);
            }
            catch 
            {
                // ignore
            }
        }
    }
 
    public bool Delete(PackageFile file)
    {
        return Delete(file.RelativeDestinationPath);
    }
 
    public bool Delete(string path)
    {
        var fullname = Path.Combine(install.Directory, path);
        if (File.Exists(fullname))
        {
            var destination = Path.Combine(Target, path);
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(destination));
                File.Move(fullname, destination);
                Moves.Add(new Move(fullname, destination));
                log.Debug("File {0} moved to {1}.", fullname, destination);
            }
            catch(Exception e)
            {
                log.Error("Unable to move file {0} to {1}: {2}", fullname, destination, e.Message);
                log.Debug(e);
                return false;
            }
        }
 
        return true;
    }
 
    public void UndoAllDeletions()
    {
        foreach (var move in Moves)
        {
            File.Move(move.DeletedFile, move.OriginalFile);
            log.Debug("File {0} reverted to {1}.", move.DeletedFile, move.OriginalFile);
        }
        Moves.Clear();
    }
}