Commit | Line | Data |
3fea05b9 |
1 | package IO::AtomicFile; |
2 | |
3 | ### DOCUMENTATION AT BOTTOM OF FILE |
4 | |
5 | # Be strict: |
6 | use strict; |
7 | |
8 | # External modules: |
9 | use IO::File; |
10 | |
11 | |
12 | #------------------------------ |
13 | # |
14 | # GLOBALS... |
15 | # |
16 | #------------------------------ |
17 | use vars qw($VERSION @ISA); |
18 | |
19 | # The package version, both in 1.23 style *and* usable by MakeMaker: |
20 | $VERSION = "2.110"; |
21 | |
22 | # Inheritance: |
23 | @ISA = qw(IO::File); |
24 | |
25 | |
26 | #------------------------------ |
27 | # new ARGS... |
28 | #------------------------------ |
29 | # Class method, constructor. |
30 | # Any arguments are sent to open(). |
31 | # |
32 | sub new { |
33 | my $class = shift; |
34 | my $self = $class->SUPER::new(); |
35 | ${*$self}{'io_atomicfile_suffix'} = ''; |
36 | $self->open(@_) if @_; |
37 | $self; |
38 | } |
39 | |
40 | #------------------------------ |
41 | # DESTROY |
42 | #------------------------------ |
43 | # Destructor. |
44 | # |
45 | sub DESTROY { |
46 | shift->close(1); ### like close, but raises fatal exception on failure |
47 | } |
48 | |
49 | #------------------------------ |
50 | # open PATH, MODE |
51 | #------------------------------ |
52 | # Class/instance method. |
53 | # |
54 | sub open { |
55 | my ($self, $path, $mode) = @_; |
56 | ref($self) or $self = $self->new; ### now we have an instance! |
57 | |
58 | ### Create tmp path, and remember this info: |
59 | my $temp = "${path}..TMP" . ${*$self}{'io_atomicfile_suffix'}; |
60 | ${*$self}{'io_atomicfile_temp'} = $temp; |
61 | ${*$self}{'io_atomicfile_path'} = $path; |
62 | |
63 | ### Open the file! Returns filehandle on success, for use as a constructor: |
64 | $self->SUPER::open($temp, $mode) ? $self : undef; |
65 | } |
66 | |
67 | #------------------------------ |
68 | # _closed [YESNO] |
69 | #------------------------------ |
70 | # Instance method, private. |
71 | # Are we already closed? Argument sets new value, returns previous one. |
72 | # |
73 | sub _closed { |
74 | my $self = shift; |
75 | my $oldval = ${*$self}{'io_atomicfile_closed'}; |
76 | ${*$self}{'io_atomicfile_closed'} = shift if @_; |
77 | $oldval; |
78 | } |
79 | |
80 | #------------------------------ |
81 | # close |
82 | #------------------------------ |
83 | # Instance method. |
84 | # Close the handle, and rename the temp file to its final name. |
85 | # |
86 | sub close { |
87 | my ($self, $die) = @_; |
88 | unless ($self->_closed(1)) { ### sentinel... |
89 | $self->SUPER::close(); |
90 | rename(${*$self}{'io_atomicfile_temp'}, |
91 | ${*$self}{'io_atomicfile_path'}) |
92 | or ($die ? die "close atomic file: $!\n" : return undef); |
93 | } |
94 | 1; |
95 | } |
96 | |
97 | #------------------------------ |
98 | # delete |
99 | #------------------------------ |
100 | # Instance method. |
101 | # Close the handle, and delete the temp file. |
102 | # |
103 | sub delete { |
104 | my $self = shift; |
105 | unless ($self->_closed(1)) { ### sentinel... |
106 | $self->SUPER::close(); |
107 | return unlink(${*$self}{'io_atomicfile_temp'}); |
108 | } |
109 | 1; |
110 | } |
111 | |
112 | #------------------------------ |
113 | # detach |
114 | #------------------------------ |
115 | # Instance method. |
116 | # Close the handle, but DO NOT delete the temp file. |
117 | # |
118 | sub detach { |
119 | my $self = shift; |
120 | $self->SUPER::close() unless ($self->_closed(1)); |
121 | 1; |
122 | } |
123 | |
124 | #------------------------------ |
125 | 1; |
126 | __END__ |
127 | |
128 | |
129 | =head1 NAME |
130 | |
131 | IO::AtomicFile - write a file which is updated atomically |
132 | |
133 | |
134 | =head1 SYNOPSIS |
135 | |
136 | use IO::AtomicFile; |
137 | |
138 | ### Write a temp file, and have it install itself when closed: |
139 | my $FH = IO::AtomicFile->open("bar.dat", "w"); |
140 | print $FH "Hello!\n"; |
141 | $FH->close || die "couldn't install atomic file: $!"; |
142 | |
143 | ### Write a temp file, but delete it before it gets installed: |
144 | my $FH = IO::AtomicFile->open("bar.dat", "w"); |
145 | print $FH "Hello!\n"; |
146 | $FH->delete; |
147 | |
148 | ### Write a temp file, but neither install it nor delete it: |
149 | my $FH = IO::AtomicFile->open("bar.dat", "w"); |
150 | print $FH "Hello!\n"; |
151 | $FH->detach; |
152 | |
153 | |
154 | =head1 DESCRIPTION |
155 | |
156 | This module is intended for people who need to update files |
157 | reliably in the face of unexpected program termination. |
158 | |
159 | For example, you generally don't want to be halfway in the middle of |
160 | writing I</etc/passwd> and have your program terminate! Even |
161 | the act of writing a single scalar to a filehandle is I<not> atomic. |
162 | |
163 | But this module gives you true atomic updates, via rename(). |
164 | When you open a file I</foo/bar.dat> via this module, you are I<actually> |
165 | opening a temporary file I</foo/bar.dat..TMP>, and writing your |
166 | output there. The act of closing this file (either explicitly |
167 | via close(), or implicitly via the destruction of the object) |
168 | will cause rename() to be called... therefore, from the point |
169 | of view of the outside world, the file's contents are updated |
170 | in a single time quantum. |
171 | |
172 | To ensure that problems do not go undetected, the "close" method |
173 | done by the destructor will raise a fatal exception if the rename() |
174 | fails. The explicit close() just returns undef. |
175 | |
176 | You can also decide at any point to trash the file you've been |
177 | building. |
178 | |
179 | |
180 | =head1 AUTHOR |
181 | |
182 | =head2 Primary Maintainer |
183 | |
184 | David F. Skoll (F<dfs@roaringpenguin.com>). |
185 | |
186 | =head2 Original Author |
187 | |
188 | Eryq (F<eryq@zeegee.com>). |
189 | President, ZeeGee Software Inc (F<http://www.zeegee.com>). |
190 | |
191 | |
192 | =head1 REVISION |
193 | |
194 | $Revision: 1.2 $ |
195 | |
196 | =cut |