7

I've got a form that writes its input to a textfile. Would it be possible to lock a text file for editing, and perhaps give a friendly message "the file is edited by another user, please try again later."

I'd like to avoid conflicts if the file has multiple editors at the same time.

Here's how the entry is currently added.

$content = file_get_contents("./file.csv");
$fh = fopen("./file.csv", "w");
fwrite($fh, $date_yy . '-' . $date_mm . '-' . $date_dd . '|' . $address . '|' . $person . '|' . $time_hh . ':' . $time_mm);
fwrite($fh, "\n" . $content);
fclose($fh);

Any thoughts?

juco
  • 6,331
  • 3
  • 25
  • 42
Janne
  • 67
  • 1
  • 4
  • I don't know of a method on doing that, however you can `flock` it, if you're not aware of that function. Consult the PHP.net manual > http://php.net/manual/en/function.flock.php – Funk Forty Niner Mar 15 '13 at 15:05

5 Answers5

4

You can use flock() function to lock the file. For more see this

Something like:

   <?php

      $content = file_get_contents("./file.csv");
      $fp = fopen("./file.csv", "w"); // open it for WRITING ("w")
      if (flock($fp, LOCK_EX)) 
      {
      // do your file writes here
      fwrite($fh, $date_yy . '-' . $date_mm . '-' . $date_dd . '|' . $address . '|' .  $person . '|' . $time_hh . ':' . $time_mm);
      fwrite($fh, "\n" . $content);
      fclose($fh);
      flock($fh, LOCK_UN); // unlock the file
      } 
   ?> 
Emmanuel N
  • 7,350
  • 2
  • 26
  • 36
  • 2
    Between *fopen* and *flock* the file will be zeroed (due to `w` access), hopefully this is what the OP wants... Also the unlock should be before the file is closed... – Déjà vu Mar 15 '13 at 15:29
  • The target is to add new lines to a file, not reset or replace the content. – Janne Mar 15 '13 at 15:50
  • 1
    @Janne so you better use `a` access, not `w`. – Déjà vu Mar 15 '13 at 15:57
  • @ring0, "the unlock should be before the file is closed", right, but the unlock can also be omitted entirely, since when the file is closed the lock is released. – Qtax Jul 28 '15 at 15:15
2

In order of desirability:

  1. Use a database.
  2. Use more than one text file.
  3. Use locks:

eg:

$lockwait = 2;       // seconds to wait for lock
$waittime = 250000;  // microseconds to wait between lock attempts
// 2s / 250000us = 8 attempts.
$myfile = '/path/to/file.txt';

if( $fh = fopen($myfile, 'a') ) {
  $waitsum = 0;
  // attempt to get exclusive, non-blocking lock
  $locked = flock($fh, LOCK_EX | LOCK_NB); 
  while( !$locked && ($waitsum <= $lockwait) ) {
    $waitsum += $waittime/1000000; // microseconds to seconds
    usleep($waittime);
    $locked = flock($fh, LOCK_EX | LOCK_NB);
  }
  if( !$locked ) {
    echo "Could not lock $myfile for write within $lockwait seconds.";
  } else {
    // write out your data here
    flock($fh, LOCK_UN);  // ALWAYS unlock
  }
  fclose($fh);            // ALWAYS close your file handle
} else {
  echo "Could not open $myfile";
  exit 1;
}
Sammitch
  • 30,782
  • 7
  • 50
  • 77
1

You can use PHP's flock function to lock a file for writing, but that lock won't persist across web requests and doesn't work on NFS mounts (at least in my experience).

Your best be may be to create a token file in the same directory, check for its existence and report an error if it exists.

As with any locking scheme, you're going to have race conditions and locks that remain after the operation has completed, so you'll need a way to mitigate those.

I would recommend creating a hash of the file before editing and storing that value in the lock file. Also send that hash to the client as part of the edit form (so it comes back as data on the commit request). Before writing, compare the passed hash value to the value in the file. If they are the same, commit the data and remove the lock.

If they are different, show an error.

J.D. Pace
  • 586
  • 1
  • 3
  • 17
  • Why wouldn't the lock persist across web requests? Locks are on files, not descriptors (at least on Linux) – Déjà vu Mar 15 '13 at 16:00
  • I read that fclose removes locks, so your lock would be for the duration the file handle was open. I haven't had a lot of luck with flocking, so I tend to avoid it. I think using the semaphore file in this case is a better solution, since the lock should be obtained when the edit is started and released when it's done, which could be several submit/validate/resubmit cycles later. I'm not trying to avoid your question, I just don't have a good answer. – J.D. Pace Mar 15 '13 at 19:20
  • No, the flock c function (which php's flock implements) operates on descriptors. http://linux.die.net/man/2/flock – symcbean Sep 01 '16 at 22:35
0

You could try flock — Portable advisory file locking ?

http://php.net/manual/en/function.flock.php

rinchik
  • 2,642
  • 8
  • 29
  • 46
-1

I would just use a simple integer or something like that.

  $content = file_get_contents("./file.csv");
  $fh = fopen("./file.csv", "w");
  $status = 1;

...      

if($status == 1){
   fwrite($fh, $date_yy . '-' . $date_mm . '-' . $date_dd . '|' . $address . '|' . $person . '|' . $time_hh . ':' . $time_mm);
   fwrite($fh, "\n" . $content);
   fclose($fh);
   $status = 0;
  }
else
     echo "the file is edited by another user, please try again later.";

Is that what you mean?

shanehoban
  • 870
  • 1
  • 9
  • 30
  • `$status = 1; if ($status == 1) {` I wish I was able to understand that algorithm – Déjà vu Mar 15 '13 at 15:41
  • @ring0 lol I understand that it's not viable to do it exactly as I've put there. Maybe earlier or have a checkStatus() function. Somewhere that simply keeps track of if a user has clicked edit, and resets back to 0 when submitted. I realized the above is technically incorrect. Apologies – shanehoban Mar 15 '13 at 15:46