OK to have multiple processes reading from the same text file? - c#

I've inherited development responsibilities for a set of .NET projects which include Windows services, an ASP.NET website and standard executables which are launched via task manager.
All of these components connect to a MySQL database. Currently, the encrypted connection string is located in the individual app.config/web.config files. I think it's kind of a pain to have to update the connection string in multiple places, so I had the idea to put the connection string in a text file which would be accessed by all the individual components.
Before I move full-steam ahead on that, I wanted to ask if I might be introducing a bug since there could potentially be multiple processes trying to access this text file at the same time. I've written a universal method for reading the text file, and I was careful to ensure that the file is being opened in "Read" mode:
public string AESDecryptFromFile(string path, string password, string IV)
{
if (!File.Exists(path))
throw new Exception("File not found: " + path);
string cryptogram;
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
cryptogram = sr.ReadToEnd().Trim(new char[] { '\r', '\n', ' ' });
}
}
return AESDecrypt(cryptogram, password, IV);
}
I don't have any reason to believe that the processes would lock the file if I implement it this way, but I thought I'd ask.
Thanks in advance!

By default with the (string path, FileMode mode, FileAccess access) constructor of FileStream will get the following configuration as well:
FileShare = FileShare.Read
bufferSize = 4096
useAsync = false
Because you don't want the process to lock the file, this will be perfect for you as you'll require Read only (See https://msdn.microsoft.com/en-us/library/system.io.fileshare(v=vs.110).aspx)
However if you want to be explicit you can use the following constructor:
FileStream s = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);

Until you do not change the file it's OK. It makes problems when you want to write or write and read at multiple threads, because you don't know which one thread will write/read end/start first so you can't predict any behaviour

I think you might need to tell your FileStream constructor how you want to share the file? "fileshare"

Related

File already being accessed error

I have a few files in \AppData\Roaming that my app is writing to. I create the files when the application starts like this:
private void Form1_Load(object sender, EventArgs e)
{
DirectoryInfo _File = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), #"MyApp\myfile.txt"));
}
Later, when I write to the file with a button click, I get an error saying that the file is already in use and cannot be accessed. How would I fix this?
The code to write to the file is correct because when I remove the code above and make the files myself, the application writes to them without any issues. Therefore, I dont think the problem is with the code I use to write to the files. But, here it is for reference:
var myfile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), #"MyApp\myfile.txt"));
StreamWriter sw = new StreamWriter(myfile);
sw.WriteLine(textBox1.text);
sw.Close();
Thanks in advance for any help!
There are a few concepts at play here and I am not sure that we have enough information to definitively address the root problem, but I will give you a few pointers.
You need to be aware of the FileMode, FileAccess and FileShare enumerations.
The first, FileMode, specifies what you intend to do with regard to the file's existence. There are various options, documented in the link above. mI don't think that you have a problem here, but it bears mentioning.
The second, FileAccess, concerns your intended interaction with the file (read, write, or both). If you ask for access to read, then anyone else who opens the file or had it open already (including that web browser control) must have allowed sharing with other readers.
The final one, FileShare, defines who you are willing to share access to the file with. You can specify that others can read it, others can write to it, both, or neither.
The code that you are using is accessing the file using the very simplest defaults, which may be incompatible with the WebBrowser's access mode. Here's what I'd suggest instead:
var myfile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), #"MyApp\myfile.txt"));
using (var fs = new FileStream(myfile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)) {
using (var sw = new StreamWriter(fs)) {
// You probably want to move to the end of the file before writing...
fs.Seek(0, SeekOrigin.End);
sw.WriteLine(textBox1.text);
sw.Close();
}
}
This very clearly expresses your intent, as well as the fact that you are willing to share with others who might read or write (we know the webbrowser will not write to the file, but for some reason maybe it is trying to open it with write intent anyway).
For file operations (as well as anytime your are accessing unmanaged resources) your best bet is to only grab a handle to the file long enough to perform the operation you want and then release it.
In your case, you are opening the resource, then trying to open it again later. Change this. Don't create the files until you are actually going to do something with it. Also, look into the USING clause. You want to release it as soon as you are done reading or writing from it.
Try to Access the FileInfo object to create/append/write files as follows, the file is already been taken by FileInfo class,
use as following,
FileInfo fi1 = new FileInfo(path);
//Create a file to write to.
using (StreamWriter sw = fi1.CreateText())
{
sw.WriteLine("Hello");
sw.WriteLine("And");
sw.WriteLine("Welcome");
}
//Open the file to read from.
using (StreamReader sr = fi1.OpenText())
{
string s = "";
while ((s = sr.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
Try using using.
using (StreamWriter sw = new StreamWriter(myfile))
{
sw.Write(textBox1.text);
}

File.Open for read access denied on executing file in Windows

I have a problem with file permissions on executing files in Windows that seems to be solved after following a forum tip[1], but I cannot understand why. Maybe you guys can help.
I'm checking the banner of a file by executing it (reading the console output) and then opening the same file for reading afterwards using FileStream:
public void fileMD5(string filename) {
if (!File.Exists(filename)) return NT.Fail("File does not exist: " + filename);
BinaryReader stream = new BinaryReader(File.Open(filename,
FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int readBytes;
while ((readBytes = stream.Read(buffer, 0, bufferSize)) > 0) {
md5Hasher.TransformBlock(buffer, 0, readBytes, buffer, 0);
}
stream.Close();
}
fileMD5('sample.exe');
and every once in a while I would get "file is being used by another process". From Wikipedia I know that Windows will set a lock on executing files denying write access[2], but I'm only reading. Also the process should have stopped already when I try to open it.
From the forum post it would seem that adding a FileShare.ReadWrite would help and it seems that It does:
FileStream stream = File.Open('sample.exe',
FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
But I don't understand why. Is there a race condition here that I don't see?
Also the File.Open call seems to be much quicker with FileShare.ReadWrite instead of the default (which I guess is FileShare.Read).
[1] http://www.xtremevbtalk.com/archive/index.php/t-192118.html
[2] http://en.wikipedia.org/wiki/File_locking#In_Microsoft_Windows
When you do not specify a FileShare parameter the default for this option is FileShare.None, in fact the code within the File class simply executes this:
public static FileStream Open(string path, FileMode mode, FileAccess access)
{
return File.Open(path, mode, access, FileShare.None);
}
With regards to the performance I can only imagine that specifying FileShare.ReadWrite means that Windows does not need to aquire a lock on the file.
As far as the "file is being used by another process" error you are getting does this issue go away if you wrap the usage of the stream variable within a using block so that the Stream gets disposed of as soon as you are done?
using (var stream = File.Open('sample.exe', FileMode.Open, FileAccess.Read))
{
//do something with the stream here
}
You should close your FileStream and then afterwards open a new FileStream.
FileShare is needed when applications want to share a file not only one application or an application has multiple readers or writers at the same time.
Why? It's get messy when everyone can read and write at the same time. In this case you should better explicitly set it so its clear that its get messy. :)
It has to do with the underlying windows API CreateFile flags.
See http://msdn.microsoft.com/en-us/library/aa363858%28v=vs.85%29.aspx
for an overview,
http://blogs.msdn.com/b/larryosterman/archive/2004/05/13/131263.aspx
for en explanation on how NT (and following) load the execs with FILE_SHARE_DELETE
And especially this
http://blogs.msdn.com/b/oldnewthing/archive/2004/05/11/129759.aspx
For a great explanation on how the share permission and desired access compose together!
It seems like specifying the wrong FileShare can prohibit you from accessing a file. If you specify FileShare.Read but some other application has currently write access to that file, you cannot access the file, since your FileShare.Read is currently not satisfiable. FileShare.ReadWrite is less restrictive, because its easier satisfiable. Source: http://blogs.msdn.com/b/oldnewthing/archive/2004/05/11/129759.aspx

Open file ReadOnly

Currently, this is how I'm opening a file to read it:
using (TextReader reader = new StreamReader(Path.Combine(client._WorkLogFileLoc, "dump.txt")))
{
//do stuff
}
How can I open the file in ReadOnly mode, so that if another process has the file open at the same time, my program can still read it.
The typical problem is that the other process has the file open for writing. All of the standard File methods and StreamReader constructors open the file with FileShare.Read. That cannot work, that denies write sharing. You cannot deny writing, the other process was first and got write access. So you'll be denied access instead.
You have to use FileShare.ReadWrite, like this:
var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using (var sr = new StreamReader(fs))
{
// etc...
}
Beware that you'll still have a tricky problem, you are reading a half-written file. The other process flushes data to the file at random points in time, you may well read only half a line of text. YMMV.
If you want to open the file read-only, try this:
using (TextReader reader
= new StreamReader(File.OpenRead(Path.Combine(client._WorkLogFileLoc, "dump.txt"))))
{
//do stuff
}
Notice the call to File.OpenRead().
You can set the file attribute by calling File.SetAttributes
string path = Path.Combine(client._WorkLogFileLoc, "dump.txt");
FileAttributes curAttributes = File.GetAttributes(path);
File.SetAttributes(path, curAttributes | FileAttributes.ReadOnly);
Per https://learn.microsoft.com/en-us/dotnet/api/system.io.file.openread?redirectedfrom=MSDN&view=netcore-3.1#System_IO_File_OpenRead_System_String_
File.OpenRead enables read shared access not read/write. This prevents the "other process" from being able to close/reopen/write more data as xbonez wants to permit. hans-passant addresses what was requested.
Per the referenced documentation: This method is equivalent to the FileStream(String, FileMode, FileAccess, FileShare) constructor overload with a FileMode value of Open, a FileAccess value of Read and a FileShare value of Read.

How can I read a file even when getting an "in use by another process" exception?

In VB.NET or C#, I'm trying to read the contents of a text file that is in use by another program (that's the point, actually, I can't stop the program or it stops writing to the text file, and I want to periodically read out what is currently in the text file in another program).
This is the code I'm using (VB.NET)
Dim strContents As String
Dim objReader As StreamReader
objReader = New StreamReader(FullPath)
strContents = objReader.ReadToEnd()
objReader.Close()
Or in C#:
var objReader = new StreamReader(FullPath);
var strContents = objReader.ReadToEnd();
objReader.Close();
The above, however, throws the IO exception "The process cannot access the file 'file.txt' because it is being used by another process." Are there any workarounds in this scenario?
FileStream logFileStream = new FileStream("c:\test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
StreamReader logFileReader = new StreamReader(logFileStream);
while (!logFileReader.EndOfStream)
{
string line = logFileReader.ReadLine();
// Your code here
}
// Clean up
logFileReader.Close();
logFileStream.Close();
Original source for code
I'll do the fish. The FileShare mode is critical, you must allow for write sharing. That cannot be denied since the process that is writing the file already obtained write access. The StreamReader() constructor uses FileShare.Read and doesn't have an option to use a different value. Using the StreamReader(Stream) constructor is instead is indeed the workaround.
Beware however that this sharing mode also has implications for your code. You cannot predict when the other process flushes the file. The last line you read may contain only part of a line of text. When it flushes is file buffer again, later, you'll get the rest of the line. Clearly this can mess up your logic.
It depends on the FileShare mode with which the file was opened by the other application that is appending to the file. When the other application was opening the file, it specified a FileShare mode for other applications to access the file. This FileShare mode could have been read, write, both, delete, all of these, or none.
You have to specify the very same FileShare mode that the other application specified. If the other application allowed only reading, use FileShare.Read; if it allowed both reading and writing, use FileShare.ReadWrite.
StreamReader uses only FileShare.Read mode, so you can already assume that that's not the right one. So, try ReadWrite, like so:
FileStream fs = new FileStream(FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
StreamReader reader = new StreamReader(fs);
Not sure how this will behave with an already open file, but this will prevent your application from locking it:
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
StreamReader sr = new StreamReader(fs);
Hope it helps!

File Encryption and Decryption issue

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 :)

Categories