line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package IO::File::AtomicChange; |
2
|
|
|
|
|
|
|
|
3
|
7
|
|
|
7
|
|
642435
|
use strict; |
|
7
|
|
|
|
|
20
|
|
|
7
|
|
|
|
|
288
|
|
4
|
7
|
|
|
7
|
|
38
|
use warnings; |
|
7
|
|
|
|
|
13
|
|
|
7
|
|
|
|
|
531
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
7
|
|
|
|
|
|
|
|
8
|
7
|
|
|
7
|
|
42
|
use base qw(IO::File); |
|
7
|
|
|
|
|
17
|
|
|
7
|
|
|
|
|
1719
|
|
9
|
7
|
|
|
7
|
|
13023
|
use Carp; |
|
7
|
|
|
|
|
16
|
|
|
7
|
|
|
|
|
560
|
|
10
|
7
|
|
|
7
|
|
3856
|
use File::Temp qw(:mktemp); |
|
7
|
|
|
|
|
23218
|
|
|
7
|
|
|
|
|
1343
|
|
11
|
7
|
|
|
7
|
|
1070
|
use File::Copy; |
|
7
|
|
|
|
|
2934
|
|
|
7
|
|
|
|
|
516
|
|
12
|
7
|
|
|
7
|
|
6859
|
use File::Sync; |
|
7
|
|
|
|
|
32332
|
|
|
7
|
|
|
|
|
7504
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
sub new { |
15
|
20
|
|
|
20
|
1
|
31346
|
my $class = shift; |
16
|
20
|
|
|
|
|
239
|
my $self = $class->SUPER::new(); |
17
|
20
|
|
|
|
|
916
|
$self->_temp_file(""); |
18
|
20
|
|
|
|
|
80
|
$self->_target_file(""); |
19
|
20
|
|
|
|
|
75
|
$self->_backup_dir(""); |
20
|
20
|
50
|
|
|
|
128
|
$self->open(@_) if @_; |
21
|
20
|
|
|
|
|
109762
|
$self; |
22
|
|
|
|
|
|
|
} |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub _accessor { |
25
|
174
|
|
|
174
|
|
371
|
my($self, $tag, $val) = @_; |
26
|
174
|
100
|
|
|
|
440
|
${*$self}{$tag} = $val if $val; |
|
44
|
|
|
|
|
170
|
|
27
|
174
|
|
|
|
|
313
|
return ${*$self}{$tag}; |
|
174
|
|
|
|
|
5891
|
|
28
|
|
|
|
|
|
|
} |
29
|
59
|
|
|
59
|
|
221
|
sub _temp_file { return shift->_accessor("io_file_atomicchange_temp", @_) } |
30
|
69
|
|
|
69
|
|
240
|
sub _target_file { return shift->_accessor("io_file_atomicchange_path", @_) } |
31
|
46
|
|
|
46
|
|
195
|
sub _backup_dir { return shift->_accessor("io_file_atomicchange_back", @_) } |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub DESTROY { |
34
|
20
|
100
|
|
20
|
|
3166
|
carp "[CAUTION] disposed object without closing file handle." unless $_[0]->_closed; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
sub open { |
38
|
20
|
|
|
20
|
1
|
53
|
my ($self, $path, $mode, $opt) = @_; |
39
|
20
|
50
|
|
|
|
95
|
ref($self) or $self = $self->new; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
|
43
|
20
|
|
|
|
|
154
|
my $temp = mktemp("${path}.XXXXXX"); |
44
|
20
|
|
|
|
|
5891
|
$self->_temp_file($temp); |
45
|
20
|
|
|
|
|
61
|
$self->_target_file($path); |
46
|
|
|
|
|
|
|
|
47
|
20
|
100
|
|
|
|
538
|
copy_preserving_attr($path, $temp) if -f $path; |
48
|
20
|
100
|
|
|
|
89
|
if (exists $opt->{backup_dir}) { |
49
|
4
|
50
|
|
|
|
99
|
unless (-d $opt->{backup_dir}) { |
50
|
0
|
|
|
|
|
0
|
croak "no such directory: $opt->{backup_dir}"; |
51
|
|
|
|
|
|
|
} |
52
|
4
|
|
|
|
|
22
|
$self->_backup_dir($opt->{backup_dir}); |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
20
|
50
|
|
|
|
167
|
$self->SUPER::open($temp, $mode) ? $self : undef; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub _closed { |
59
|
39
|
|
|
39
|
|
140
|
my $self = shift; |
60
|
39
|
|
|
|
|
91
|
my $tag = "io_file_atomicchange_closed"; |
61
|
|
|
|
|
|
|
|
62
|
39
|
|
|
|
|
75
|
my $oldval = ${*$self}{$tag}; |
|
39
|
|
|
|
|
250
|
|
63
|
39
|
100
|
|
|
|
160
|
${*$self}{$tag} = shift if @_; |
|
19
|
|
|
|
|
93
|
|
64
|
39
|
|
|
|
|
838
|
return $oldval; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
sub close { |
68
|
19
|
|
|
19
|
0
|
3360
|
my ($self, $die) = @_; |
69
|
19
|
50
|
|
|
|
104
|
File::Sync::fsync($self) or croak "fsync: $!"; |
70
|
19
|
50
|
|
|
|
1676550
|
unless ($self->_closed(1)) { |
71
|
19
|
50
|
|
|
|
304
|
if ($self->SUPER::close()) { |
72
|
|
|
|
|
|
|
|
73
|
19
|
100
|
100
|
|
|
1985
|
$self->backup if ($self->_backup_dir && -f $self->_target_file); |
74
|
|
|
|
|
|
|
|
75
|
19
|
0
|
|
|
|
238
|
rename($self->_temp_file, $self->_target_file) |
|
|
50
|
|
|
|
|
|
76
|
|
|
|
|
|
|
or ($die ? croak "close (rename) atomic file: $!\n" : return); |
77
|
|
|
|
|
|
|
} else { |
78
|
0
|
0
|
|
|
|
0
|
$die ? croak "close atomic file: $!\n" : return; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
19
|
|
|
|
|
120
|
1; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub copy_modown_to_temp { |
85
|
0
|
|
|
0
|
0
|
0
|
my($self) = @_; |
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
0
|
my($mode, $uid, $gid) = (stat($self->_target_file))[2,4,5]; |
88
|
0
|
|
|
|
|
0
|
chown $uid, $gid, $self->_temp_file; |
89
|
0
|
|
|
|
|
0
|
chmod $mode, $self->_temp_file; |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub backup { |
93
|
3
|
|
|
3
|
0
|
9
|
my($self) = @_; |
94
|
|
|
|
|
|
|
|
95
|
3
|
|
|
|
|
47
|
require Path::Class; |
96
|
3
|
|
|
|
|
3259
|
require POSIX; |
97
|
3
|
|
|
|
|
23880
|
require Time::HiRes; |
98
|
|
|
|
|
|
|
|
99
|
3
|
|
|
|
|
6636
|
my $basename = Path::Class::file($self->_target_file)->basename; |
100
|
|
|
|
|
|
|
|
101
|
3
|
|
|
|
|
531
|
my $backup_file; |
102
|
3
|
|
|
|
|
9
|
my $n = 0; |
103
|
3
|
|
|
|
|
16
|
while ($n < 7) { |
104
|
3
|
50
|
|
|
|
14
|
$backup_file = sprintf("%s/%s_%s.%d_%d%s", |
105
|
|
|
|
|
|
|
$self->_backup_dir, |
106
|
|
|
|
|
|
|
$basename, |
107
|
|
|
|
|
|
|
POSIX::strftime("%Y-%m-%d_%H%M%S",localtime()), |
108
|
|
|
|
|
|
|
(Time::HiRes::gettimeofday())[1], |
109
|
|
|
|
|
|
|
$$, |
110
|
|
|
|
|
|
|
($n == 0 ? "" : ".$n"), |
111
|
|
|
|
|
|
|
); |
112
|
3
|
50
|
|
|
|
141
|
last unless -f $backup_file; |
113
|
0
|
|
|
|
|
0
|
$n++; |
114
|
|
|
|
|
|
|
} |
115
|
3
|
50
|
|
|
|
57
|
croak "already exists backup file: $backup_file" if -f $backup_file; |
116
|
|
|
|
|
|
|
|
117
|
3
|
|
|
|
|
15
|
copy_preserving_attr($self->_target_file, $backup_file); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub delete { |
122
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
123
|
0
|
0
|
|
|
|
0
|
unless ($self->_closed(1)) { |
124
|
0
|
|
|
|
|
0
|
$self->SUPER::close(); |
125
|
0
|
|
|
|
|
0
|
return unlink($self->_temp_file); |
126
|
|
|
|
|
|
|
} |
127
|
0
|
|
|
|
|
0
|
1; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub detach { |
131
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
132
|
0
|
0
|
|
|
|
0
|
$self->SUPER::close() unless ($self->_closed(1)); |
133
|
0
|
|
|
|
|
0
|
1; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub copy_preserving_attr { |
137
|
15
|
|
|
15
|
0
|
45
|
my($from, $to) = @_; |
138
|
|
|
|
|
|
|
|
139
|
15
|
50
|
|
|
|
97
|
File::Copy::copy($from, $to) or croak $!; |
140
|
|
|
|
|
|
|
|
141
|
15
|
|
|
|
|
7362
|
my($mode, $uid, $gid, $atime, $mtime) = (stat($from))[2,4,5,8,9]; |
142
|
15
|
|
|
|
|
561
|
chown $uid, $gid, $to; |
143
|
15
|
|
|
|
|
450
|
chmod $mode, $to; |
144
|
15
|
|
|
|
|
415
|
utime $atime, $mtime, $to; |
145
|
15
|
|
|
|
|
44
|
1; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
1; |
150
|
|
|
|
|
|
|
__END__ |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=encoding utf-8 |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=begin html |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
<a href="https://travis-ci.org/hirose31/IO-File-AtomicChange"><img src="https://travis-ci.org/hirose31/IO-File-AtomicChange.png?branch=master" alt="Build Status" /></a> |
157
|
|
|
|
|
|
|
<a href="https://coveralls.io/r/hirose31/IO-File-AtomicChange?branch=master"><img src="https://coveralls.io/repos/hirose31/IO-File-AtomicChange/badge.png?branch=master" alt="Coverage Status" /></a> |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=end html |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head1 NAME |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
IO::File::AtomicChange - change content of a file atomically |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=head1 SYNOPSIS |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
truncate and write to temporary file. When you call $fh->close, replace |
168
|
|
|
|
|
|
|
target file with temporary file preserved permission and owner (if |
169
|
|
|
|
|
|
|
possible). |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
use IO::File::AtomicChange; |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
my $fh = IO::File::AtomicChange->new("foo.conf", "w"); |
174
|
|
|
|
|
|
|
$fh->print("# create new file\n"); |
175
|
|
|
|
|
|
|
$fh->print("foo\n"); |
176
|
|
|
|
|
|
|
$fh->print("bar\n"); |
177
|
|
|
|
|
|
|
$fh->close; # MUST CALL close EXPLICITLY |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
If you specify "backup_dir", save original file into backup directory (like |
180
|
|
|
|
|
|
|
"/var/backup/foo.conf_YYYY-MM-DD_HHMMSS_PID") before replace. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
my $fh = IO::File::AtomicChange->new("foo.conf", "a", |
183
|
|
|
|
|
|
|
{ backup_dir => "/var/backup/" }); |
184
|
|
|
|
|
|
|
$fh->print("# append\n"); |
185
|
|
|
|
|
|
|
$fh->print("baz\n"); |
186
|
|
|
|
|
|
|
$fh->print("qux\n"); |
187
|
|
|
|
|
|
|
$fh->close; # MUST CALL close EXPLICITLY |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head1 DESCRIPTION |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
IO::File::AtomicChange is intended for people who need to update files |
192
|
|
|
|
|
|
|
reliably and atomically. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
For example, in the case of generating a configuration file, you should be |
195
|
|
|
|
|
|
|
careful about aborting generator program or be loaded by other program |
196
|
|
|
|
|
|
|
in halfway writing. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
IO::File::AtomicChange free you from such a painful situation and boring code. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=head1 INTERNAL |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
* open |
203
|
|
|
|
|
|
|
1. fix filename of temporary file by mktemp. |
204
|
|
|
|
|
|
|
2. if target file already exists, copy target file to temporary file preserving permission and owner. |
205
|
|
|
|
|
|
|
3. open temporary file and return its file handle. |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
* write |
208
|
|
|
|
|
|
|
1. write date into temporary file. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
* close |
211
|
|
|
|
|
|
|
1. close temporary file. |
212
|
|
|
|
|
|
|
2. if target file exists and specified "backup_dir" option, copy target file into backup directory preserving permission and owner, mtime. |
213
|
|
|
|
|
|
|
3. rename temporary file to target file. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=head1 CAVEATS |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
You must call "$fh->close" explicitly when commit changes. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
Currently, "close $fh" or "undef $fh" don't affect target file. So if you |
220
|
|
|
|
|
|
|
exit without calling "$fh->close", CHANGES ARE DISCARDED. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head1 AUTHOR |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
HIROSE Masaaki E<lt>hirose31 _at_ gmail.comE<gt> |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=head1 THANKS TO |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
kazuho gave me many shrewd advice. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=head1 REPOSITORY |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
L<https://github.com/hirose31/IO-File-AtomicChange> |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
git clone git://github.com/hirose31/IO-File-AtomicChange.git |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
patches and collaborators are welcome. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head1 SEE ALSO |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
L<IO::File>, L<IO::AtomicFile>, L<File::AtomicWrite> |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
Copyright HIROSE Masaaki 2009- |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
247
|
|
|
|
|
|
|
it under the same terms as Perl itself. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=begin comment |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head0 SPECIAL THANKS TO |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
typester recommended brand new style "SEE ALSO" section. |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head0 IMHO |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
* IO::AtomicFile |
260
|
|
|
|
|
|
|
* same name of temporary file. |
261
|
|
|
|
|
|
|
several processes update a one file, temporary file is mangled. |
262
|
|
|
|
|
|
|
* close in DESTROY block. |
263
|
|
|
|
|
|
|
leave halfway writing when die in writing process. |
264
|
|
|
|
|
|
|
$fh->print("begin write\n"); |
265
|
|
|
|
|
|
|
$fh->print(generate_contents()); # call die in generate_contents() |
266
|
|
|
|
|
|
|
$fh->print("EOF"); # this is not written... |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
* File::AtomicWrite |
269
|
|
|
|
|
|
|
* shared $tmp_fh globally? |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=end comment |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# for Emacsen |
274
|
|
|
|
|
|
|
# Local Variables: |
275
|
|
|
|
|
|
|
# mode: cperl |
276
|
|
|
|
|
|
|
# cperl-indent-level: 4 |
277
|
|
|
|
|
|
|
# cperl-close-paren-offset: -4 |
278
|
|
|
|
|
|
|
# cperl-indent-parens-as-block: t |
279
|
|
|
|
|
|
|
# indent-tabs-mode: nil |
280
|
|
|
|
|
|
|
# coding: utf-8 |
281
|
|
|
|
|
|
|
# End: |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# vi: set ts=4 sw=4 sts=0 et ft=perl fenc=utf-8 ff=unix : |
284
|
|
|
|
|
|
|
|