ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/File.pm
Revision: 1.2
Committed: Wed Oct 21 17:28:16 2026 UTC (2 weeks, 2 days ago) by root
Branch: MAIN
CVS Tags: rel-0_6
Changes since 1.1: +20 -0 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::File - private state file locking and replacement
4    
5 root 1.2 =head1 DESCRIPTION
6    
7     Internal utility for small private state files shared by MCP processes,
8     such as the workdir ID registry. C<lock> obtains a lock on the current
9     pathname; C<replace> writes a complete new version atomically. The caller
10     owns serialization, validation and the lock's lifetime.
11    
12     For example, after building a new complete file body:
13    
14     use AnyEvent::MCP::File ();
15    
16     my $lock = AnyEvent::MCP::File::lock $path;
17     # read and validate the current state under this lock
18     AnyEvent::MCP::File::replace $path, $new_bytes;
19     close $lock;
20    
21     The lock protects cooperating writers, not other programs that write the
22     file without this protocol. Use L<AnyEvent::MCP::Workdirs> for the workdir
23     registry rather than implementing its update rules here.
24    
25 root 1.1 =head1 FUNCTIONS
26    
27     =over
28    
29     =item $lock = AnyEvent::MCP::File::lock $file
30    
31     Create the parent directory and file if necessary, and lock the current file
32     exclusively. The file must be private and owned by this user. Retries if an
33     atomic replacement changed the pathname while waiting for the lock.
34     Keep the returned handle alive through reading, modifying and replacing
35     the file. All writers must use this locking protocol.
36    
37     =item AnyEvent::MCP::File::replace $file, $bytes
38    
39     Write bytes to a private temporary file beside C<$file>, then atomically
40     rename it over C<$file>. The caller holds the lock. Removes the temporary
41     file on failure. This provides atomic visibility, not crash durability.
42    
43     =back
44    
45     =cut
46    
47     package AnyEvent::MCP::File;
48    
49     use common::sense;
50     use File::Path ();
51     use File::Basename ();
52     use Fcntl qw(O_RDWR O_WRONLY O_CREAT O_EXCL LOCK_EX);
53     use Errno qw(ENOENT EEXIST);
54    
55     sub lock {
56     my ($file) = @_;
57    
58     File::Path::mkpath (File::Basename::dirname ($file), 0, 0700);
59    
60     while (1) {
61     my $fh;
62     sysopen $fh, $file, O_RDWR | O_CREAT | O_EXCL, 0600 or do {
63     die "$file: $!\n" unless $! == EEXIST;
64     sysopen $fh, $file, O_RDWR or die "$file: $!\n";
65     };
66    
67     my @stat = stat $fh;
68     die "$file: file must be private and owned by this user\n" if $stat[4] != $< || $stat[2] & 077;
69    
70     flock $fh, LOCK_EX or die "$file: $!\n";
71     my @current = stat $file;
72    
73     return $fh if @current && $stat[0] == $current[0] && $stat[1] == $current[1];
74    
75     die "$file: $!\n" unless @current || $! == ENOENT;
76    
77     close $fh;
78     }
79     }
80    
81     sub replace {
82     my ($file, $bytes) = @_;
83    
84     my $temporary = "$file.$$~";
85     sysopen my $fh, $temporary, O_WRONLY | O_CREAT | O_EXCL, 0600 or die "$temporary: $!\n";
86    
87     my $ok = eval {
88     print {$fh} $bytes or die "$temporary: $!\n";
89     close $fh or die "$temporary: $!\n";
90     rename $temporary, $file or die "$file: $!\n";
91     1
92     };
93    
94     my $error = $@;
95     unlink $temporary unless $ok;
96     die $error unless $ok;
97     }
98    
99     1;