File Encryption and Decryption issue - c#

I've been playing around with encrypting and decrypting files in VC# Express 2010.
All the tutorials and documentation I've seen require two FileStreams in order to encrypt the file - one for reading the unencrypted version, and the other for encrypting. When I actually wrote the code it kept throwing an error telling me it could not open the file because it was opened by another process at the output filestream.
I'm assuming that's because the file is opened by the input filestream. So that means I have to specify a different filename? So even after the operation is successful I'll now have the original unencrypted file in the directory and a separate encrypted version? Doesn't that defeat the point? Or am I doing something wrong here? My code is similar to this...
public string filename = "test.xml";
using (FileStream input = new FileStream(filename, FileMode.Open, FileAccess.Read))
using (FileStream output = new FileStram(filename, FileMode.Open, FileAccess.Write))
using (....all the crypto stream and transform stuff...)
{
...do the encryption....
}

You're right but it's not defeating the point. The (streaming) crypto APIs are intended to encrypt from Src to Dst. Think encrypting output while sending/receiving over a network etc. This keeps them simple, as they should be.
You complicate the issue by using the same file for Src and Dst. That is not totally impossible but like Copying a File over itself it needs some extra care.
Consider that in general, encrypting will increase the File size. So it is not safe to Encrypt a file in place. Decrypting might be, but I wouldn't risk it.
What you need is a Temp file and a rename action after completion.

In your example, you can't create a separate filestream for both input and output on the same file, but you can create a handle that will read and write. The FileAccess enum has the flags attribute, so you'd just say var handle = new FileStream(filename, FileAccess.Read | FileAccess.Write); The obvious downside to this is you are going to have data lost if your encryption doesn't complete successfully.
I recommend having a separate file for the output though, atleast that way you won't lose data if your program breaks unexpectedly. If the encryption completes successfully, then delete the original and rename the encrypted file with the original file name.

Use File.ReadAllBytes. Then those bytes post to your encryptor, must work.

There is another parameter where you can specify whether or not to allow another process to read or write to the file.
openFile is a string that represents the file name.
using (FileStream fileIn = new FileStream(openFile, FileMode.Open, FileAccess.Read, FileShare.Write))
using (FileStream fileOut = new FileStream(openFile, FileMode.Open, FileAccess.Write, FileShare.Open))
This way, you can read and write to the same file.
while (myfileStream.Position < fileLength)
{
fileIn.Read(buffer, 0, 51200);
buffer = encrypt(buffer);
fileOut.Write(buffer, 0, 51200);
}
While this is easy and you don't have to write to a temporary file or have to move/rename etc, this can be really dangerous because if the encryption breaks suddenly for some reason, you will lose data!
Also, the encrypt function is something I implemented. AesCryptoServiceProvider along with CryptoStream can be used :)

Related

How do I encrypt/decrypt the same xml file in C#?

So I need the ability to encrypt/decrypt the same settings XML file in my application. I can only find examples that use a seperate output file. I don't need an output file. Nothing super secure. I just basically need to make the settings file not readable to protect a couple connections strings. I am able to encrypt the file just fine using this code:
public static void EncryptAndSerialize(Object obj)
{
UnicodeEncoding aUE = new UnicodeEncoding();
byte[] key = aUE.GetBytes("password");
RijndaelManaged RMCrypto = new RijndaelManaged();
using (FileStream fs = File.Open(#"D:\Sample.xml", FileMode.Create))
{
using (CryptoStream cs = new CryptoStream(fs, RMCrypto.CreateEncryptor(key, key), CryptoStreamMode.Write))
{
XmlSerializer xmlser = new XmlSerializer(obj.GetType());
xmlser.Serialize(cs, obj);
}
fs.Close();
}
I just need to know how to use this same sort of process to decrypt that same file. If anyone could point me in the right direction it would be much appreciated. Keep in mind ultra security is not an issue. Forgive my ignorance this task is new to me.
What he said plus...
MemoryStream...
https://msdn.microsoft.com/en-us/library/system.io.memorystream(v=vs.110).aspx
using (var sink = new MemoryStream())
{
// Write your encyphered data to the sink
// (from your FileStream, via the encryption provider)
// and then later, read (or copy) from sink back into
// the FileStream. Don't forget to re-position your
// FileStream before doing so.
}
If it were me I'd use two independent FileStreams. One for read only and, later, another one for writing back to the .xml file (that you just read from). I'd only bother constructing and writing the later if and only if I knew I had a fully formed/filled sink.
You might also investigate the use of SecureString (but I'm digressing):
https://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110).aspx
All that said, you NEVER want to directly overwrite a file (i.e., in place edit). If you do, and the write fails (part way through) your original file will be, well, corrupt. Leaving you with zip/nadda/crap. Always write to a new, temp file and only after that completes 100% do you promote the temp file to the source path (typically via FileInfo.MoveTo).
https://msdn.microsoft.com/en-us/library/system.io.fileinfo.moveto(v=vs.110).aspx
If bad stuff happens along the way (and it will!!!), all you do is delete the temp file (off of a catch block, etc.) and you're back where you started from.
The humans won't know that you actually worked two files (and if you have some monitor automation that is getting tripped up by tandem files then rewrite it cause it's well, crap).
Something like this. Notice that this calls CreateDecryptor with the CryptoStreamMode.Read flag telling it to read from the stream.
var decryptor = new RijndaelManaged ();
var transformer = decryptor.CreateDecryptor(_decryptKey, _decryptSeed);
var cryptoStream = new CryptoStream(encryptedStream, transformer, CryptoStreamMode.Read);
cryptoStream.CopyTo(resultStream);
resultStream.Close();
Some editorial comments:
1) AES would be better and here's why:
https://blogs.msdn.microsoft.com/shawnfa/2006/10/09/the-differences-between-rijndael-and-aes/
2) Don't underestimate .net encryption - Microsoft doesn't fool around
3) Bad encryption can be worse than no encryption because it provides a false sense of security

Make .txt file unreadable / uneditable

I have a program which saves a little .txt file with a highscore in it:
// Create a file to write to.
string createHighscore = _higscore + Environment.NewLine;
File.WriteAllText(path, createText);
// Open the file to read from.
string createHighscore = File.ReadAllText(path);
The problem is that the user can edit the file as simple as possible – with a texteditor. So I want to make the file unreadable / uneditable or encrypt it.
My thinking was that I could save the data in a resource file, but can I write in a resource file?
Or save it as .dll, encrypt/decrypt it or look for a MD5-sum/hash.
You can't prevent the user from modifying the file. It's their computer, so they can do whatever they want (that's why the whole DRM issue is… difficult).
Since you said you're using the file to save an high-score, you have a couple of alternatives. Do note that as previously said no method will stop a really determined attacker from tampering with the value: since your application is running on the user computer he can simply decompile it, look at how you're protecting the value (gaining access to any secret used in the process) and act accordingly. But if you're willing to decompile an application, find out the protection scheme used and come up with a script/patch to get around it only to change a number only you can see, well, go for it?
Obfuscate the content
This will prevent the user from editing the file directly, but it won't stop them as soon as the obfuscation algorithm is known.
var plaintext = Encoding.UTF8.GetBytes("Hello, world.");
var encodedtext = Convert.ToBase64String(plaintext);
Save the ciphertext to the file, and reverse the process when reading the file.
Sign the content
This will not prevent the user from editing the file or seeing its content (but you don't care, an high-score is not secret) but you'll be able to detect if the user tampered with it.
var key = Encoding.UTF8.GetBytes("My secret key");
using (var algorithm = new HMACSHA512(key))
{
var payload = Encoding.UTF8.GetBytes("Hello, world.");
var binaryHash = algorithm.ComputeHash(payload);
var stringHash = Convert.ToBase64String(binaryHash);
}
Save both the payload and the hash in the file, then when reading the file check if the saved hash matches a newly computed one. Your key must be kept secret.
Encrypt the content
Leverage .NET's cryptographic libraries to encrypt the content before saving it and decrypt it when reading the file.
Please take the following example with a grain of salt and spend due time to understand what everything does before implementing it (yes, you'll be using it for a trivial reason, but future you β€” or someone else β€” may not). Pay special attention on how you generate the IV and the key.
// The initialization vector MUST be changed every time a plaintext is encrypted.
// The initialization vector MUST NOT be reused a second time.
// The initialization vector CAN be saved along the ciphertext.
// See https://en.wikipedia.org/wiki/Initialization_vector for more information.
var iv = Convert.FromBase64String("9iAwvNddQvAAfLSJb+JG1A==");
// The encryption key CAN be the same for every encryption.
// The encryption key MUST NOT be saved along the ciphertext.
var key = Convert.FromBase64String("UN8/gxM+6fGD7CdAGLhgnrF0S35qQ88p+Sr9k1tzKpM=");
using (var algorithm = new AesManaged())
{
algorithm.IV = iv;
algorithm.Key = key;
byte[] ciphertext;
using (var memoryStream = new MemoryStream())
{
using (var encryptor = algorithm.CreateEncryptor())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
using (var streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write("MySuperSecretHighScore");
}
}
}
ciphertext = memoryStream.ToArray();
}
// Now you can serialize the ciphertext however you like.
// Do remember to tag along the initialization vector,
// otherwise you'll never be able to decrypt it.
// In a real world implementation you should set algorithm.IV,
// algorithm.Key and ciphertext, since this is an example we're
// re-using the existing variables.
using (var memoryStream = new MemoryStream(ciphertext))
{
using (var decryptor = algorithm.CreateDecryptor())
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
using (var streamReader = new StreamReader(cryptoStream))
{
// You have your "MySuperSecretHighScore" back.
var plaintext = streamReader.ReadToEnd();
}
}
}
}
}
As you seem to look for relatively low security, I'd actually recommend going for a checksum. Some pseudo-code:
string toWrite = score + "|" + md5(score+"myKey") + Environment.NewLine
If the score would be 100, this would become
100|a6b6b0a8e56e42d8dac51a4812def434
To make sure the user didn't temper with the file, you can then use:
string[] split = readString().split("|");
if (split[1] != md5(split[0]+"myKey")){
alert("No messing with the scores!");
}else{
alert("Your score is "+split[0]);
}
Now of course as soon as someone gets to know your key they can mess with this whatever they want, but I'd consider that beyond the scope of this question. The same risk applies to any encryption/decryption mechanism.
One of the problems, as mentioned in the comments down below, is that once someone figures out your key (through brute-forcing), they could share it and everybody will be able to very easily change their files. A way to resolve this would be to add something computer-specific to the key. For instance, the name of the user who logged in, ran through md5.
string toWrite = score + "|" + md5(score+"myKey"+md5(System.username /**or so**/)) + Environment.NewLine
This will prevent the key from being "simply shared".
Probably your best bet is securing the whole file using standard NT security and programmatically change the access control list to protect the whole file from being edited by unwanted users (excepting the one impersonating your own application, of course).
Cryptography can't help here because the file could be still editable using a regular text editor (for example, notepad) and the end user can corrupt the file just adding an extra character (or dropping one too).
There's an alternate approach which doesn't involve programming effort...
Tell your users that once they've manually edited the whole text file they've lost your support. At the end of the day, if you're storing this data is because it's required by your application. Corrupting it or doing the risky task of manually editing it can make your application produce errors.
Another alternate approach which involves programming effort...
Whenever you change the file from your application, you can compute a MD5 or SHA hash and store in a separate file, and once you want to read or write it again, you're going to check that the whole file produces the same hash before writing on it again.
This way, the user can still edit your file manually, but you'll know when this unexpected behavior was done by the user (unless the user also manually computes the hash whenever the file is changed...).
Something I have not yet seen mentioned is storing the high score on an online leader board. Obviously this solution requires a lot more development, but since you are talking about a game, you could probably make use of a third party provider like Steam, Origin, Uplay, ... This has the added advantage of leader boards not just being for your machine.
You cannot save data in a dll, and both Resource file and txt file are editable. It sounds like encryption is the only way for you. You can encrypt the string before saving it to a txt file. Take a look at this thread:
Encrypt and decrypt a string
You can serialize it and deserialize with encryption with CryptoStream :
Serialize file :
Create and open FileStream in write mode
Create Cryptostream and pass your filestream
Write contents to Cryptostream (encrypt)
Deserialize file :
Create and open FileStream in read mode
Create Cryptostream and pass your filestream
Read from Cryptostream (decrypt)
You can find examples and more information here :
msdn.microsoft.com/en-us/library/system.security.cryptography.cryptostream.aspx
http://www.codeproject.com/Articles/6465/Using-CryptoStream-in-C
Example :
byte[] key = { 1, 2, 3, 4, 5, 6, 7, 8 }; // Where to store these keys is the tricky part,
byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 };
string path = #"C:\path\to.file";
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
// Encryption and serialization
using (var fStream = new FileStream(path, FileMode.Create, FileAccess.Write))
using (var cryptoStream = new CryptoStream(fStream , des.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
BinaryFormatter serializer = new BinaryFormatter();
// This is where you serialize your data
serializer.Serialize(cryptoStream, yourData);
}
// Decryption
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (var cryptoStream = new CryptoStream(fs, des.CreateDecryptor(key, iv), CryptoStreamMode.Read))
{
BinaryFormatter serializer = new BinaryFormatter();
// Deserialize your data from file
yourDataType yourData = (yourDataType)serializer.Deserialize(cryptoStream);
}
Simple solution:
To mitigate the hackish user ability to change the score, you can write it as a binary I guess.
Another solution:
Write the data in a SQLite DB?
You can name your file as something that doesn't suggest it has a score table in it (e.g. YourApp.dat) and encrypt the contents.
The accepted answer here contains the code for encryption and decryption of text.
Update
I also suggest using some Guid as a password for the encryption.
You can't write in Resources, more information exists in this answer
The reason that you can't change a resource string at runtime, is
because the resource is compiled into your executable. If you reverse
engineer the compiled *.exe or *.dll file, you can actually see your
string in the code. Editing an already compiled executable file is
never a good idea (unless you're trying to hack it), but when you try
to do it from the executables code, it just plain impossible, as the
file is locked during execution.
You can add Read Only or Hidden attributes to your files using
File.SetAttributes, But still user can remove the attributes
from windows and edit the file.
An example:
File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
Another way I could suggest is to save the data in a file with some
weird extensions so that the user can't think of it as an editable or
important file. somthing like ghf.ytr (Can't think of somthing
more weird right now!)
I'd also suggest making a text file with .dll extension and saving it in one of windows folders like system32. This way user will have a really hard time trying to find out where does the score information go!
Here is a code to make an text file not editable. in the same way you use this technique to make it not readable etc.
string pathfile = #"C:\Users\Public\Documents\Filepath.txt";
if (File.Exists(pathfile))
{
File.Delete(pathfile);
}
if (!File.Exists(pathfile))
{
using (FileStream fs = File.Create(pathfile))
{
Byte[] info = new UTF8Encoding(true).GetBytes("your text to be written to the file place here");
FileSecurity fsec = File.GetAccessControl(pathfile);
fsec.AddAccessRule(new FileSystemAccessRule("Everyone",
FileSystemRights.WriteData, AccessControlType.Deny));
File.SetAccessControl(pathfile, fsec);
}
}

Delete last 4 bytes of a file without opening the file?

This is a question which was asked to me in an interview and still could not find a way to do it-
Suppose I have a .txt file and I want to delete the last 4 characters from the content of that file without opening the file. The first question is- Is it really doable? If yes, what is the way to do it?
I guess you can't read the content of the file. So if you can "open" it with write only access you could do:
using (var fileStream = File.Open("initDoc.txt", FileMode.Open, FileAccess.Write))
{
fileStream.SetLength(fileStream.Length - 4);
}
Of course you would need additional checks to make sure you are subtracting the correct number of bytes depending on the encoding, not subtracting more than the length etc.
If you can't use FileMode.Open, you can use an overload of the FileStream constructor that uses a SafeFileHandle. To acquire a SafeFileHandle to a file, you need to use C# Interop. In this example below i have wrapped the interop code to get a file handle in a class called "UnmanagedFileLoader":
var unmanagedFileLoader = new UnmanagedFileLoader("initDoc.txt");
using (var fileStream = new FileStream(unmanagedFileLoader.Handle, FileAccess.Write))
{
fileStream.SetLength(fileStream.Length - 4);
}
The UnmanagedFileLoader internally uses the unmanaged CreateFile function to open an existing file with write permissions:
handleValue = CreateFile(Path, GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
For more info how to acquire a SafeFileHandle you can check out this link:
http://msdn.microsoft.com/en-us/library/microsoft.win32.safehandles.safefilehandle%28v=vs.110%29.aspx
If you want to skip the FileStream ways, the third way to do it would be to use StreamReader and StreamWriter, and then read a file with StreamReader without the last 4 bytes, and then write it using a StreamWriter. But i would still recommend using the FileStream examples above.
EDIT: I assume "opening the file" means "getting a handle to the file".
Sure it's possible :
Open a handle to the drive that contains the file
Get the file system type
Scan the content of the structure that contains information about all files: the MFT (for NTFS), the FAT records..etc.
Find the entry that corresponds to your file
Updates the entry (write) by subtracting 4 to the value that stores the "file size" information :)
If your concern is about reading all the data for a long file: that isn't necessary. If we assume you really do mean bytes, simply:
using (var file = File.Open(path, FileMode.Open, FileAccess.Write)) {
file.SetLength(file.Length - 4);
}
this does not read the contents of the file.
If you mean characters, then you need to think very carefully about the encoding - 4 characters is not necessarily 4 bytes.

Using MemoryMappedFile and FileSystemWatcher to detect new entries to logfile

I have a logfile that is written by a 3rd party application and I'd like my application to "read" that log file in real/near-time, parse the new log entries and act upon certain events.
My thought was that I could achieve this with a combination of FileSystemWatcher (to signal file changes) and MemoryMappedFile (to continue reading from a certain offset).
However, since this is the first time I'm using MemoryMappedFiles I do run into some issues which probably arise from not understanding the concept correctly (e.g. I'm unable to open the existing File as it's in use by the other process).
I was wondering if someone has an example of how to use MemoryMappedFiles to read a file that is locked by another process?
Thanks,
Tom
EDIT:
From the comments, it looks like Memory Mapped Files won't help me accessing files that have an exclusive lock. However, "tail" tools like, e.g. Baretail (http://www.baremetalsoft.com/baretail/index.php) are able to do just that. It has no problem reading the file that has an exclusive lock from another application in 1s intervals). So, there has to be some way to do this?
EDITEDIT:
To answer my own question, the trick in opening a locked file is, creating the FileStream with the following access flags:
fileStream = new System.IO.FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite);
To answer my own question, the trick in reading a locked file is creating the FileStream with the following access flags:
FileStream fileStream = new System.IO.FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite);
Now it's just a matter of either doing interval based polling or looking for FileSystemWatcher change events to detect file changes
I'm not sure if MemoryMappedFiles are going to help you. Take a look at FileStream:
var stream = new FileStream(path, FileMode.Open, FileShare.Read);
stream.Seek(offset, SeekOrigin.Begin);
Though if the 3rd-party application has the file locked exclusively, there's not much that you can do about it...
[Begin 2nd EDIT]
One more idea...
If the third party app happens to use a logging framework like NLog, log4net, or System.Diagnostics, you could still write your own Target/Appender/TraceListener and route the messages somewhere that you could look at them (such as a file that is not opened exclusively, to another process, etc).
If your third party app is using a logging framework, we probably would have heard about it by now ;-)
[End 2nd EDIT]
[Begin EDIT]
I think I misread the question. It sounded at first like you were using a third party library that had logging implemented and you wanted to do this parsing from within the program that was generating the logging. Having reread your question, it sounds like you want to "listen" to the log file from outside of the application. If that is the case, my answer probably won't help you. Sorry.
[End EDIT]
I don't have anything to offer about MemoryMappedFiles, but I wonder if you could achieve what you are after by writing a custom listener/target/appender for the 3rd party logging system?
For example, if you are using NLog, you could write a custom Target and direct all of your logging messages there (while also directing them to the "real" Target(s)). This way you get crack at each log message as it is logged (so it is actually real time, not near real time). You could do the same thing with log4net and System.Diagnostics.
Note that NLog even has a "MethodCall" target. To use that one you only have to write a static method with the correct signature. I don't know if log4net has a similar concept to this.
This seems like it would be easier to get working reliably than trying to read and parse the log file as it is being written by the third party software.
If the file is "in use", there isn't anything that can be done about that. It truly is "in use". MemoryMappedFiles are for either reading large amounts of data off the drive or sharing data with other programs. It will not help getting around the "in use" limitation.
Memorymapped files are under the same restrictions as the FileStream you initialize it with, be sure that you initialize your Memory-Mapped-File like this
var readerStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var mmf = MemoryMappedFile.CreateFromFile(readerStream, null, 0, MemoryMappedFileAccess.Read, null, HandleInheritability.None, false);
If some other process have completely locked it even from writing you're in bad luck, not sure if there's a way around that. Perhaps use some timer do detect when the process has stopped writing to it.
I've done something similar just for monitoring log files on a console (as opposed to processing), but the principles are the same. Like you, I use a FileSystemWatcher, and the important logic is in my OnChanged event handler:
case WatcherChangeTypes.Changed:
{
System.IO.FileInfo fi = new FileInfo(e.FullPath);
long prevLength;
if (lastPositions.TryGetValue(e.FullPath, out prevLength))
{
using (System.IO.FileStream fs = new FileStream(
e.FullPath, FileMode.Open, FileAccess.Read))
{
fs.Seek(prevLength, SeekOrigin.Begin);
DumpNewData(fs, (int)(fi.Length - prevLength));
lastPositions[e.FullPath] = fs.Position;
}
}
else
lastPositions.Add(e.FullPath, fi.Length);
break;
}
where lastPositions is
Dictionary<string, Int64> lastPositions = new Dictionary<string, long>();
and DumpNewData is simply
private static void DumpNewData(FileStream fs, int bytesToRead)
{
byte[] bytesRead = new byte[bytesToRead];
fs.Read(bytesRead, 0, bytesToRead);
string s = System.Text.ASCIIEncoding.ASCII.GetString(bytesRead);
Console.Write(s);
}

In C#, if 2 processes are reading and writing to the same file, what is the best way to avoid process locking exceptions?

With the following file reading code:
using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
using (TextReader tr = new StreamReader(fileStream))
{
string fileContents = tr.ReadToEnd();
}
}
And the following file write code:
using (TextWriter tw = new StreamWriter(fileName))
{
tw.Write(fileContents);
tw.Close();
}
The following exception details are seen:
The process cannot access the file
'c:\temp\myfile.txt' because it is
being used by another process.
What is the best way of avoiding this? Does the reader need to retry upon receipt of the exception or is there some better way?
Note that the reader process is using a FileSystemWatcher to know when the file has changed.
Also note that, in this instance, I'm not looking for alternatives ways of sharing strings between the 2 processes.
You can open a file for writing and only lock write access, thereby allowing others to still read the file.
For example,
using (FileStream stream = new FileStream(#"C:\Myfile.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
// Do your writing here.
}
Other file access just opens the file for reading and not writing, and allows readwrite sharing.
using (FileStream stream = new FileStream(#"C:\Myfile.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
// Does reading here.
}
If you want to ensure that readers will always read an up-to-date file, you will either need to use a locking file that indicates someone is writing to the file (though you may get a race condition if not carefully implemented) or make sure you block write-sharing when opening to read and handle the exception so you can try again until you get exclusive access.
If you create a named Mutex you can define the mutex in the writing application, and have the reading application wait until the mutex is released.
So in the notification process that is currently working with the FileSystemWatcher, simply check to see if you need to wait for the mutex, if you do, it will wait, then process.
Here is a VB example of a Mutex like this that I found, it should be easy enough to convert to C#.
Get your process to check the status of the file if it is being written to. You can do this by the presence of a lock file (i.e. the presence of this other file, which can be empty, prevents writing to the main file).
Even this is not failsafe however, as the two processes may create the lock file at the same time - but you can check for this before you commit the write.
If your process encounters a lock file then get it to simply sleep/wait and try again at a predefined interval in the future.
Is there any particular reason for opening the file with FileShare.None? That'll prevent the file from being opened by any other process.
FileShare.Write or FileShare.ReadWrite should allow the other process (subject to permissions) to open and write to the file while you are reading it, however you'll have to watch for the file changing underneath you while you read it - simply buffering the contents upon opening may help here.
All of these answers, however, are equally valid - the best solution depends on exactly what you're trying to do with the file: if it's important to read it while guaranteeing it doesn't change, then lock it and handle the subsequent exception in your writing code; if it's important to read and write to it at the same time, then change the FileShare constant.
You can use a Mutex object for this.
The reader and writer both need retry mechanisms. Also FileShare should be set to FileShare.read for the readers and FileShare.none for the writer. This should ensure that the readers don't read the file while writing is in progress.
The reader (excluding retry) becomes
using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (TextReader tr = new StreamReader(fileStream))
{
string fileContents = tr.ReadToEnd();
}
}
The writer (excluding retry) becomes:
FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
using (TextWriter tw = new StreamWriter(fileStream))
{
tw.Write(fileContents);
tw.Close();
}
Write to a temp file, when finished writing rename/move the file to the location and/or name that the reader is looking for.
The best thing to do, is to put an application protocol on top of a file transfer/ownership transfer mechanism. The "lock-file" mechanism is an old UNIX hack that has been around for ages. The best thing to do, is to just "hand" the file over to the reader. There are lots of ways to do this. You can create the file with a random file name, and then "give" that name to the reader. That would allow the writer to asynchronously write another file. Think of how the "web page" works. A web page has a "link" to more information in it, for images, scripts, external content etc. The server hands you that page, because it's a coherent view of the "resource" you want. Your browser then goes and gets the appropriate content, based on what the page description (the HTML file or other returned content), and then transfers what it needs.
This is the most resilient type of "sharing" mechanism to use. Write the file, share the name, move to the next file. The "sharing the name" part, is the atomic hand off that makes sure that both parties (the reader and the writer) agree that the content is "complete."

Categories