1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System;
9 using System.IO;
10 using System.Linq;
11 using Antmicro.Renode.Logging;
12 using Antmicro.Renode.Exceptions;
13 
14 namespace Antmicro.Renode.Utilities
15 {
16     public class FilePath
17     {
FilePath(string path, FileAccess fileAccess, bool validate = true)18         public FilePath(string path, FileAccess fileAccess, bool validate = true)
19         {
20             this.path = path;
21             this.fileAccess = fileAccess;
22 
23             if(validate)
24             {
25                 Validate();
26             }
27         }
28 
Validate()29         public virtual void Validate()
30         {
31             if(!File.Exists(path))
32             {
33                 throw new RecoverableException($"File does not exist: {path}");
34             }
35 
36             try
37             {
38                 using(var fs = File.Open(path, FileMode.Open, fileAccess, FileShare.ReadWrite))
39                 {
40                     if(!fs.CanRead && fileAccess == FileAccess.Read)
41                     {
42                         throw new RecoverableException($"File is not readable: {path}");
43                     }
44                     if(!fs.CanWrite && fileAccess == FileAccess.Write)
45                     {
46                         throw new RecoverableException($"File is not writable: {path}");
47                     }
48                 }
49             }
50             catch(UnauthorizedAccessException e)
51             {
52                 throw new RecoverableException($"Error while accessing {path}: {e.Message}");
53             }
54         }
55 
ToString()56         public override string ToString()
57         {
58             return path;
59         }
60 
operator string(FilePath fp)61         public static implicit operator string(FilePath fp)
62         {
63             return fp.path;
64         }
65 
CanBeCreated()66         protected bool CanBeCreated()
67         {
68             if(File.Exists(path))
69             {
70                 return false;
71             }
72 
73             try
74             {
75                 using(File.Create(path))
76                 {
77                 }
78                 File.Delete(path);
79             }
80             catch
81             {
82                 return false;
83             }
84 
85             return true;
86         }
87 
88         protected readonly string path;
89         protected readonly FileAccess fileAccess;
90     }
91 
92     public class ReadFilePath : FilePath
93     {
ReadFilePath(string path)94         public ReadFilePath(string path) : base(path, FileAccess.Read) {}
95 
operator ReadFilePath(string path)96         public static implicit operator ReadFilePath(string path)
97         {
98             return new ReadFilePath(path);
99         }
100     }
101 
102     public class OptionalReadFilePath : FilePath
103     {
OptionalReadFilePath(string path)104         public OptionalReadFilePath(string path) : base(path, FileAccess.Read, false)
105         {
106             if(path != null)
107             {
108                 Validate();
109             }
110         }
111 
operator string(OptionalReadFilePath fp)112         public static implicit operator string(OptionalReadFilePath fp)
113         {
114             return fp?.path;
115         }
116 
operator OptionalReadFilePath(string path)117         public static implicit operator OptionalReadFilePath(string path)
118         {
119             return new OptionalReadFilePath(path);
120         }
121 
operator OptionalReadFilePath(ReadFilePath fp)122         public static implicit operator OptionalReadFilePath(ReadFilePath fp)
123         {
124             return new OptionalReadFilePath(fp);
125         }
126     }
127 
128     public class AppendFilePath : FilePath
129     {
AppendFilePath(string path)130         public AppendFilePath(string path) : base(path, FileAccess.Write) {}
131 
operator AppendFilePath(string path)132         public static implicit operator AppendFilePath(string path)
133         {
134             return new AppendFilePath(path);
135         }
136     }
137 
138     public class WriteFilePath : FilePath
139     {
WriteFilePath(string path)140         public WriteFilePath(string path) : base(path, FileAccess.Write) {}
141 
Validate()142         public override void Validate()
143         {
144             if(!File.Exists(path))
145             {
146                 if(!CanBeCreated())
147                 {
148                     throw new RecoverableException($"File {path} could not be created");
149                 }
150                 return;
151             }
152 
153             base.Validate();
154         }
155 
operator WriteFilePath(string path)156         public static implicit operator WriteFilePath(string path)
157         {
158             return new WriteFilePath(path);
159         }
160     }
161 
162     public class SequencedFilePath : WriteFilePath
163     {
SequencedFilePath(string path)164         public SequencedFilePath(string path) : base(path) {}
165 
Validate()166         public override void Validate()
167         {
168             if(!File.Exists(path))
169             {
170                 if(!CanBeCreated())
171                 {
172                     throw new RecoverableException($"File {path} could not be created");
173                 }
174                 return;
175             }
176 
177             var lastSplit = path.LastIndexOf(Path.DirectorySeparatorChar);
178             if(lastSplit == -1)
179             {
180                 throw new RecoverableException($"{path} is an invalid path");
181             }
182 
183             var dirPath = path.Substring(0, lastSplit);
184             var fileName = path.Substring(lastSplit + 1);
185             var pathGlob = string.Concat(fileName, ".*");
186 
187             var lastIndex = Directory.EnumerateFiles(dirPath, pathGlob)
188                 .Select(path => path.Substring(path.LastIndexOf('.') + 1))
189                 .Where(suffix => suffix.All(char.IsDigit))
190                 .Select(suffix => int.Parse(suffix))
191                 .Concat(new [] { 0 })
192                 .Max();
193 
194             var newPath = string.Format("{0}.{1}", path, lastIndex + 1);
195 
196             try
197             {
198                 File.Move(path, newPath);
199             }
200             catch(Exception e)
201             {
202                 throw new RecoverableException($"Error occured while moving old file to {newPath}: {e.Message}");
203             }
204 
205             Logger.Log(LogLevel.Info, "Old file {0} moved to {1}", path, newPath);
206 
207             if(!CanBeCreated())
208             {
209                 throw new RecoverableException($"File {newPath} could not be created");
210             }
211         }
212 
operator SequencedFilePath(string path)213         public static implicit operator SequencedFilePath(string path)
214         {
215             return new SequencedFilePath(path);
216         }
217     }
218 }
219