File manipulation in PHP: lock, read, clear, unlock - php

I want some PHP to do the following in this order:
Gain exclusive lock to a file (waiting if already locked)
Read the contents of the file
Empty the file of all contents
Remove the lock
But any code I'm coming up with one way or another always relinquishes the lock between the reading and writing.
$fp = fopen('status.txt', 'r+');
flock($fp, LOCK_EX);
$str = fread($fp,1000); // [another hack. I just want it to read everything]
unlink('status.txt');
touch('status.txt');
Any ideas? I don't trust anything I do with files.

I think ftruncate can do what you want, since it works on a file that you already have open.
http://www.php.net/manual/en/function.ftruncate.php
Here's their example:
<?php
$filename = 'lorem_ipsum.txt';
$handle = fopen($filename, 'r+');
ftruncate($handle, rand(1, filesize($filename)));
rewind($handle);
echo fread($handle, filesize($filename));
fclose($handle);
?>
So I think what you want then is something like:
$fp = fopen('status.txt', 'r+');
flock($fp, LOCK_EX);
$str = fread($fp, filesize('status.txt'));
ftruncate($fp, 0);
flock($fp, LOCK_UN);
fclose($fp);

Related

Write and read from the same file- PHP

I am trying to write to a file and then read the data from the same file. But sometimes I am facing this issue that the file reading process is getting started even before the file writing gets finished. How can I solve this issue ? How can i make file writing process finish before moving ahead?
// writing to file
$string= <12 kb of specific data which i need>;
$filename.="/ttc/";
$filename.="datasave.html";
if($fp = fopen($filename, 'w'))
{
fwrite($fp, $string);
fclose($fp);
}
// writing to the file
$handle = fopen($filename, "r") ;
$datatnc = fread($handle, filesize($filename));
$datatnc = addslashes($datatnc);
fclose($handle);
The reason it does not work is because when you are done writing a string to the file the file pointer points to the end of the file so later when you try to read the same file with the same file pointer there is nothing more to read. All you have to do is rewind the pointer to the beginning of the file. Here is an example:
<?php
$fileName = 'test_file';
$savePath = "tmp/tests/" . $fileName;
//create file pointer handle
$fp = fopen($savePath, 'r+');
fwrite($fp, "Writing and Reading with same fopen handle!");
//Now rewind file pointer to start reading
rewind($fp);
//this will output "Writing and Reading with same fopen handle!"
echo fread($fp, filesize($savePath));
fclose($fp);
?>
Here is more info on the rewind() method http://php.net/manual/en/function.rewind.php
I have mentioned the URL through which i got the solution. I implemented the same. If you want me to copy the text from that link then here it is :
$file = fopen("test.txt","w+");
// exclusive lock
if (flock($file,LOCK_EX))
{
fwrite($file,"Write something");
// release lock
flock($file,LOCK_UN);
}
else
{
echo "Error locking file!";
}
fclose($file);
Use fclose after writing to close the file pointer and then fopen again to open it.

fwrite writes NUL

I'm trying to write to a file with PHP and this is the code I'm using (taken from this answer to my previous question):
$fp = fopen("counter.txt", "r+");
while(!flock($fp, LOCK_EX)) { // acquire an exclusive lock
// waiting to lock the file
}
$counter = intval(fread($fp, filesize("counter.txt")));
$counter++;
ftruncate($fp, 0); // truncate file
fwrite($fp, $counter); // set your data
fflush($fp); // flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
fclose($fp);
The read part works fine, if the file gets read, it's content is read well, i.e. if the file contains 2289, then 2289 is read.
The problem is that when it increments and rewrites the value to that file, [NUL][NUL][NUL][NUL][NUL][NUL][NUL][NUL]1 gets written.
What am I missing? Why do null characters get written?
The thing you are missing is rewind(). Without it, after you truncate to 0 bytes, the pointer is still not at the beginning (reference). So when you write your new value, it pads it with NULL in your file.
This script will read a file (or create if it doesn't exist) for a current count, increments, and writes it back to the same file every time the page loads.
$filename = date('Y-m-d').".txt";
$fp = fopen($filename, "c+");
if (flock($fp, LOCK_EX)) {
$number = intval(fread($fp, filesize($filename)));
$number++;
ftruncate($fp, 0); // Clear the file
rewind($fp); // Move pointer to the beginning
fwrite($fp, $number); // Write incremented number
fflush($fp); // Write any buffered output
flock($fp, LOCK_UN); // Unlock the file
}
fclose($fp);
EDIT #2:
Try this with flock (tested)
If file is not locked, it will throw an Exception (see added line) if(...
I borrowed the Exception snippet from this accepted answer.
<?php
$filename = "numbers.txt";
$filename = fopen($filename, 'a') or die("can't open file");
if (!flock($filename, LOCK_EX)) {
throw new Exception(sprintf('Unable to obtain lock on file: %s', $filename));
}
file_put_contents('numbers.txt', ((int)file_get_contents('numbers.txt'))+1);
// To show the contents of the file, you
// include("numbers.txt");
fflush($filename); // flush output before releasing the lock
flock($filename, LOCK_UN); // release the lock
fclose($filename);
echo file_get_contents('numbers.txt');
?>
You can use this code, a simplified version, but am not sure if it's the best:
<?php
$fr = fopen("count.txt", "r");
$text = fread($fr, filesize("count.txt"));
$fw = fopen("count.txt", "w");
$text++;
fwrite($fw, $text);
?>

Shared locks, exclusive locks and data corruption

My question is: can not using shared locks during reading cause write errors, even if the write operation is using exclusive locks?
Let's say I want to create a file-based counter, like this:
//increment counter by 1
$fp = fopen($path, 'r+b');
if (flock($fp, LOCK_EX)) {
//read
fseek($fp, 0, SEEK_END);
$size = ftell($fp);
fseek($fp, 0, SEEK_SET);
if ($size == 0) {
$counter = 0;
} else {
$data = fread($fp, $size);
$counter = intval($data);
}
//do something with data we just read
$counter ++;
//write
fseek($fp, 0, SEEK_SET);
ftruncate($fp, 0);
fwrite($fp, $counter);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
} else {
fclose($fp);
throw new Exception("Lock failed");
}
Now I want to present it somewhere else:
echo intval(file_get_contents($path));
Note that file_get_contents does not use shared locks.
This code has proven to corrupt the data under heavy page load, i.e. counter was reset few times back to 0.
I changed code to use fopen and LOCK_SH and it seems okay for now, but I have no means to confirm that this indeed was source of the problem as I have no control over load. Local execution of above code using multiple CLI PHP instances suggested that code worked even with file_get_contents...
The comments in the manual for ftell discuss unpredictable behavior for files opened with append mode. Maybe you could try to open the file in read only for the read, or just read the file anyway, I don't see why you have a line in the code that can explicity set the counter to 0.
echo intval(false); //0
And fread at EOF returns false, so:
$fp = fopen($path, 'r+b');
if (flock($fp, LOCK_EX)) {
//read
$data = fread($fp, $size);
$counter = intval($data);
//do something with data we just read
$counter ++;
//write
fseek($fp, 0, SEEK_SET);
ftruncate($fp, 0);
fwrite($fp, $counter);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
} else {
fclose($fp);
throw new Exception("Lock failed");
}
...finally, found the culprit. Our production server has flock function turned off and unfortunately we are in no position to change that. I guess I'll have to implement custom locking mechanism.
This is due to production server having NFS file system and locking files on NFS not working at all in most cases.

Lock the file while reading and writing

I have a file which stores some value. Users can add stuff to that file and the counter in that file is updated. But if two users open the file, they'll get the same counter ($arr['counter']). What should I do? Maybe can I lock the file for one user and release the lock after he updates the counter and add some stuff back to the file? Or PHP already locks the file once is opened and I don't need to worry? Here's my current code:
$handle = fopen($file, 'r');
$contents = fread($handle, filesize($file));
fclose($handle);
$arr = json_decode($contents);
//Add stuff here to $arr and update counter $arr['counter']++
$handle = fopen($file, 'w');
fwrite($handle, json_encode($arr));
fclose($handle);
PHP has the flock function which will lock the file before writing to it, example,
$handle = fopen($file, 'r');
$contents = fread($handle, filesize($file));
fclose($handle);
$arr = json_decode($contents);
//Add stuff here to $arr and update counter $arr['counter']++
$handle = fopen($file, 'w');
if(flock($handle, LOCK_EX))
{
fwrite($handle, json_encode($arr));
flock($handle, LOCK_UN);
}
else
{
// couldn't lock the file
}
fclose($handle);

Does file() lock the file when reading?

I'm using file() to read through a file like an array with tabs. I want to lock the file but I cannot seem to get flock() working on the file. Is it possible to do this? If so, how? If not, does the file() lock the file from the start and alleviate any potential sharing problems?
According to the documentation (specifically the comments), it won't read a file that was locked via flock.
You have 2 alternatives.
Read the file with fgets (without checks for errors):
$f = fopen($file, 'r');
flock($f, LOCK_SH);
$data = array();
while ($row = fgets($f)) {
$data[] = $row;
}
flock($f, LOCK_UN);
fclose($f);
Read the file with file() and using a separate "lockfile":
$f = fopen($file . '.lock', 'w');
flock($f, LOCK_SH);
$data = file($file);
flock($f, LOCK_UN);
fclose($f);
unlink($file . '.lock');

Categories