1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 using System.Os;
  9 
 10 namespace System.IO
 11 {
 12     public enum FileType : int
 13     {
 14         regular = 1directory = 2fifo = 3
 15     }
 16 
 17     public enum Access : int
 18     {
 19         none = 0read = 1 << 2write = 1 << 1execute = 1 << 0
 20     }
 21 
 22     public int MakeMode(Access ownerAccessAccess groupAccessAccess otherAccess)
 23     {
 24         return (int(ownerAccess & 7) << 6) | ((int(groupAccess) & 7) << 3) | (int(otherAccess) & 7);
 25     }
 26     
 27     public const int rootFileSystemNumber = 0;
 28 
 29     public class FileStatus
 30     {
 31         public int fileSystemNumber;
 32         public int inodeNumber;
 33         public FileType fileType;
 34         public Access ownerAccess;
 35         public Access groupAccess;
 36         public Access otherAccess;
 37         public bool setUID;
 38         public bool setGID;
 39         public int nlinks;
 40         public int uid;
 41         public int gid;
 42         public long fileSize;
 43         public DateTime ctime;
 44         public DateTime mtime;
 45         public DateTime atime;
 46     }
 47     
 48     public void Stat(const char* pathFileStatus& status)
 49     {
 50         byte[statBufSize] statBuf;
 51         int result = stat(path&statBuf[0]statBufSize);
 52         if (result == -1)
 53         {
 54             ThrowSystemError();
 55         }
 56         GetFileStatus(&statBuf[0]status);
 57     }
 58     
 59     public void GetFileStatus(byte* statBufFileStatus& status)
 60     {
 61         MemoryReader reader(statBufstatBufSize);
 62         status.fileSystemNumber = reader.ReadInt();
 63         status.inodeNumber = reader.ReadInt();
 64         status.fileType = cast<FileType>(reader.ReadInt());
 65         status.ownerAccess = cast<Access>(reader.ReadInt());
 66         status.groupAccess = cast<Access>(reader.ReadInt());
 67         status.otherAccess = cast<Access>(reader.ReadInt());
 68         status.setUID = reader.ReadByte() == 1;
 69         status.setGID = reader.ReadByte() == 1;
 70         status.nlinks = reader.ReadInt();
 71         status.uid = reader.ReadInt();
 72         status.gid = reader.ReadInt();
 73         status.fileSize = reader.ReadLong();
 74         status.ctime = reader.ReadDateTime();
 75         status.mtime = reader.ReadDateTime();
 76         status.atime = reader.ReadDateTime();
 77     }
 78     
 79     public void UTime(const char* pathconst DateTime& atimeconst DateTime& mtime)
 80     {
 81         byte[16] timeBuf;
 82         MemoryWriter writer(&timeBuf[0]16);
 83         writer.Write(atime);
 84         writer.Write(mtime);
 85         UTime(path&timeBuf[0]16);
 86     }
 87     
 88     public enum FileCopyOptions : int
 89     {
 90         none = 0update = 1 << 0verbose = 1 << 1dontPreserveTimestamps = 1 << 2dontPreserveMode = 1 << 3removeCrs = 1 << 4addCrs = 1 << 5noBufferedCopy = 1 << 6
 91     }
 92 
 93     public static class File
 94     {
 95         public static bool Exists(const string& filePath)
 96         {
 97             return FileExists(filePath);
 98         }
 99         public static long Size(const string& filePath)
100         {
101             return GetFileSize(filePath);
102         }
103         public static DateTime AccessTime(const string& filePath)
104         {
105             return GetFileAccessTime(filePath);
106         }
107         public static DateTime ModificationTime(const string& filePath)
108         {
109             return GetFileModificationTime(filePath);
110         }
111         public static void GetTimes(const string& pathDateTime& accessTimeDateTime& modificationTime)
112         {
113             GetFileTimes(pathaccessTimemodificationTime);
114         }
115         public static void SetTimes(const string& pathconst DateTime& accessTimeconst DateTime& modificationTime)
116         {
117             SetFileTimes(pathaccessTimemodificationTime);
118         }
119         public static void Copy(const string& sourceFilePathconst string& destFilePath)
120         {
121             Copy(sourceFilePathdestFilePathFileCopyOptions.none);
122         }
123         public static void Copy(const string& sourceFilePathconst string& destFilePathFileCopyOptions options)
124         {
125             CopyFile(sourceFilePathdestFilePathoptions);
126         }
127         public static void Remove(const string& filePath)
128         {
129             RemoveFile(filePath);
130         }
131         public static StreamWriter CreateText(const string& filePath)
132         {
133             return StreamWriter(SharedPtr<Stream>(new BufferedStream(SharedPtr<Stream>(
134                 new FileStream(filePathcast<OpenFlags>(OpenFlags.write | OpenFlags.create | OpenFlags.truncate | OpenFlags.text))))));
135         }
136         public static BinaryWriter CreateBinary(const string& filePath)
137         {
138             return CreateBinary(filePathfalse);
139         }
140         public static BinaryWriter CreateBinary(const string& filePathbool randomAccess)
141         {
142             OpenFlags flags = cast<OpenFlags>(OpenFlags.write | OpenFlags.create | OpenFlags.truncate);
143             if (randomAccess)
144             {
145                 flags = cast<OpenFlags>(flags | OpenFlags.random_access);
146             }
147             return BinaryWriter(SharedPtr<Stream>(new BufferedStream(SharedPtr<Stream>(new FileStream(filePathflags)))));
148         }
149         public static StreamWriter AppendText(const string& filePath)
150         {
151             return StreamWriter(SharedPtr<Stream>(new BufferedStream(SharedPtr<Stream>(
152                 new FileStream(filePathcast<OpenFlags>(OpenFlags.append | OpenFlags.text))))));
153         }
154         public static StreamReader OpenRead(const string& filePath)
155         {
156             return StreamReader(SharedPtr<Stream>(new BufferedStream(SharedPtr<Stream>(
157                 new FileStream(filePathcast<OpenFlags>(OpenFlags.read | OpenFlags.text))))));
158         }
159         public static BinaryReader OpenBinary(const string& filePath)
160         {
161             return OpenBinary(filePathfalse);
162         }
163         public static BinaryReader OpenBinary(const string& filePathbool randomAccess)
164         {
165             OpenFlags flags = OpenFlags.read;
166             if (randomAccess)
167             {
168                 flags = cast<OpenFlags>(flags | OpenFlags.random_access);
169             }
170             return BinaryReader(SharedPtr<Stream>(new BufferedStream(SharedPtr<Stream>(new FileStream(filePathflags)))));
171         }
172         public static string ReadAllText(const string& filePath)
173         {
174             StreamReader reader = OpenRead(filePath);
175             string content = reader.ReadToEnd();
176             if (content.Length() >= 3 && cast<byte>(content[0]) == 0xEFu && cast<byte>(content[1]) == 0xBBu && cast<byte>(content[2]) == 0xBFu)
177             {
178                 return content.Substring(3);
179             }
180             else
181             {
182                 return content;
183             }
184         }
185         public static List<string> ReadAllLines(const string& filePath)
186         {
187             List<string> lines;
188             bool start = true;
189             StreamReader reader = OpenRead(filePath);
190             string line = reader.ReadLine();
191             while (!reader.EndOfStream())
192             {
193                 if (start)
194                 {
195                     if (line.Length() >= 3 && cast<byte>(line[0]) == 0xEFu && cast<byte>(line[1]) == 0xBBu && cast<byte>(line[2]) == 0xBFu)
196                     {
197                         line = line.Substring(3);
198                     }
199                     start = false;
200                 }
201                 lines.Add(line);
202                 line = reader.ReadLine();
203             }
204             if (!line.IsEmpty())
205             {
206                 lines.Add(line);
207             }
208             return lines;
209         }
210     }
211     
212     public bool FileExists(const string& filePath)
213     {
214         byte[statBufSize] statBuf;
215         int result = stat(filePath.Chars()&statBuf[0]statBufSize);
216         if (result == -1)
217         {
218             SystemError systemError = GetSystemError();
219             if (systemError.errorCode == ENOTFOUND)
220             {
221                 return false;
222             }
223             else
224             {
225                 ThrowSystemError();
226             }
227         }
228         else
229         {
230             FileStatus status;
231             GetFileStatus(&statBuf[0]status);
232             if (status.fileType == FileType.regular)
233             {
234                 return true;
235             }
236             else
237             {
238                 throw FileSystemException("path '" + filePath + "' is not a regular file path");
239             }
240         }
241     }
242     
243     public long GetFileSize(const string& filePath)
244     {
245         if (filePath.IsEmpty())
246         {
247             throw InvalidPathException("file path is empty");
248         }
249         FileStatus status;
250         Stat(filePath.Chars()status);
251         if (status.fileType == FileType.regular)
252         {
253             return status.fileSize;
254         }
255         else
256         {
257             throw InvalidPathException("path '" + filePath + "' is not a regular file path");
258         }
259     }
260     
261     public DateTime GetFileAccessTime(const string& filePath)
262     {
263         if (filePath.IsEmpty())
264         {
265             throw InvalidPathException("file path is empty");
266         }
267         FileStatus status;
268         Stat(filePath.Chars()status);
269         return status.atime;
270     }
271     
272     public DateTime GetFileModificationTime(const string& filePath)
273     {
274         if (filePath.IsEmpty())
275         {
276             throw InvalidPathException("file path is empty");
277         }
278         FileStatus status;
279         Stat(filePath.Chars()status);
280         return status.mtime;
281     }
282     
283     public void GetFileTimes(const string& pathDateTime& accessTimeDateTime& modificationTime)
284     {
285         if (path.IsEmpty())
286         {
287             throw InvalidPathException("file path is empty");
288         }
289         FileStatus status;
290         Stat(path.Chars()status);
291         accessTime = status.atime;
292         modificationTime = status.mtime;
293     }
294     
295     public void SetFileTimes(const string& pathconst DateTime& accessTimeconst DateTime& modificationTime)
296     {
297         if (path.IsEmpty())
298         {
299             throw InvalidPathException("file path is empty");
300         }
301         UTime(path.Chars()accessTimemodificationTime);
302     }
303     
304     public void CopyFile(const string& sourceFilePathconst string& destFilePathFileCopyOptions options)
305     {
306         DateTime sourceAccessTime;
307         DateTime sourceModificationTime;
308         GetFileTimes(sourceFilePathsourceAccessTimesourceModificationTime);
309         bool destinationExists = File.Exists(destFilePath);
310         bool copy = false;
311         if ((options & FileCopyOptions.update) != FileCopyOptions.none)
312         {
313             if (!destinationExists)
314             {
315                 copy = true;
316             }
317             else
318             {
319                 DateTime destModificationTime = GetFileModificationTime(destFilePath);
320                 if (sourceModificationTime > destModificationTime)
321                 {
322                     copy = true;
323                 }
324             }
325         }
326         else
327         {
328             copy = true;
329         }
330         if (copy)
331         {
332             bool addCrs = (options & FileCopyOptions.addCrs) != FileCopyOptions.none;
333             bool removeCrs = (options & FileCopyOptions.removeCrs) != FileCopyOptions.none;
334             if (addCrs && removeCrs)
335             {
336                 throw SystemError(EPARAM"cannot have both 'FileCopyOptions.addCrs' and 'FileCopyOptions.removeCrs' option");
337             }
338             long size = File.Size(sourceFilePath);
339             bool bufferedCopy = (options & FileCopyOptions.noBufferedCopy) == FileCopyOptions.none && !addCrs && !removeCrs;
340             if (bufferedCopy)
341             {
342                 BufferedCopy(sourceFilePathdestFilePathsize);
343             }
344             else
345             {
346                 BinaryReader reader = File.OpenBinary(sourceFilePath);
347                 BinaryWriter writer = File.CreateBinary(destFilePath);
348                 for (long i = 0; i < size; ++i;)
349                 {
350                     byte x = reader.ReadByte();
351                     if (removeCrs && x == cast<byte>('\r'))
352                     {
353                         continue;
354                     }
355                     if (addCrs && x == cast<byte>('\n'))
356                     {
357                         writer.Write(cast<byte>('\r'));
358                     }
359                     writer.Write(x);
360                 }
361             }
362         }
363         if (copy)
364         {
365             if ((options & FileCopyOptions.dontPreserveTimestamps) == FileCopyOptions.none)
366             {
367                 File.SetTimes(destFilePathsourceAccessTimesourceModificationTime);
368             }
369             if ((options & FileCopyOptions.dontPreserveMode) == FileCopyOptions.none)
370             {
371                 FileStatus status;
372                 Stat(sourceFilePath.Chars()status);
373                 ChMod(destFilePath.Chars()MakeMode(status.ownerAccessstatus.groupAccessstatus.otherAccess));
374             }
375             if ((options & FileCopyOptions.verbose) != FileCopyOptions.none)
376             {
377                 // DEBUG PID: Console.WriteLine(ToString(GetPid()) + " : " + GetFullPath(sourceFilePath) + " -> " + GetFullPath(destFilePath));
378                 Console.WriteLine(GetFullPath(sourceFilePath) + " -> " + GetFullPath(destFilePath));
379             }
380         }
381     }
382     
383     public void BufferedCopy(const string& sourceFilePathconst string& destFilePathlong size)
384     {
385         FileStream sourceFileStream(sourceFilePathOpenFlags.read);
386         FileStream destFileStream(destFilePathcast<OpenFlags>(OpenFlags.write | OpenFlags.create | OpenFlags.truncate));
387         byte[4096] buffer;
388         while (size > 0)
389         {
390             long bytesRead = sourceFileStream.Read(&buffer[0]4096);
391             destFileStream.Write(&buffer[0]bytesRead);
392             size = size - bytesRead;
393         }
394     }
395     
396     public void RemoveFile(const string& filePath)
397     {
398         Unlink(filePath.Chars());
399     }
400 }