OLD | NEW |
| (Empty) |
1 # Copyright © 2008-2012 Raphaël Hertzog <hertzog@debian.org> | |
2 # | |
3 # This program is free software; you can redistribute it and/or modify | |
4 # it under the terms of the GNU General Public License as published by | |
5 # the Free Software Foundation; either version 2 of the License, or | |
6 # (at your option) any later version. | |
7 # | |
8 # This program is distributed in the hope that it will be useful, | |
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 # GNU General Public License for more details. | |
12 # | |
13 # You should have received a copy of the GNU General Public License | |
14 # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | |
16 package Dpkg::Source::Quilt; | |
17 | |
18 use strict; | |
19 use warnings; | |
20 | |
21 our $VERSION = '0.01'; | |
22 | |
23 use Dpkg::Gettext; | |
24 use Dpkg::ErrorHandling; | |
25 use Dpkg::Source::Patch; | |
26 use Dpkg::Source::Functions qw(erasedir fs_time); | |
27 use Dpkg::Vendor qw(get_current_vendor); | |
28 | |
29 use File::Spec; | |
30 use File::Copy; | |
31 use File::Find; | |
32 use File::Path qw(make_path); | |
33 use File::Basename; | |
34 | |
35 sub new { | |
36 my ($this, $dir, %opts) = @_; | |
37 my $class = ref($this) || $this; | |
38 | |
39 my $self = { | |
40 dir => $dir, | |
41 }; | |
42 bless $self, $class; | |
43 | |
44 $self->load_series(); | |
45 $self->load_db(); | |
46 | |
47 return $self; | |
48 } | |
49 | |
50 sub setup_db { | |
51 my ($self) = @_; | |
52 my $db_dir = $self->get_db_file(); | |
53 if (not -d $db_dir) { | |
54 mkdir $db_dir or syserr(_g('cannot mkdir %s'), $db_dir); | |
55 } | |
56 my $file = $self->get_db_file('.version'); | |
57 if (not -e $file) { | |
58 open(my $version_fh, '>', $file) or syserr(_g('cannot write %s'), $file)
; | |
59 print { $version_fh } "2\n"; | |
60 close($version_fh); | |
61 } | |
62 # The files below are used by quilt to know where patches are stored | |
63 # and what file contains the patch list (supported by quilt >= 0.48-5 | |
64 # in Debian). | |
65 $file = $self->get_db_file('.quilt_patches'); | |
66 if (not -e $file) { | |
67 open(my $qpatch_fh, '>', $file) or syserr(_g('cannot write %s'), $file); | |
68 print { $qpatch_fh } "debian/patches\n"; | |
69 close($qpatch_fh); | |
70 } | |
71 $file = $self->get_db_file('.quilt_series'); | |
72 if (not -e $file) { | |
73 open(my $qseries_fh, '>', $file) or syserr(_g('cannot write %s'), $file)
; | |
74 my $series = $self->get_series_file(); | |
75 $series = (File::Spec->splitpath($series))[2]; | |
76 print { $qseries_fh } "$series\n"; | |
77 close($qseries_fh); | |
78 } | |
79 } | |
80 | |
81 sub load_db { | |
82 my ($self) = @_; | |
83 | |
84 my $pc_applied = $self->get_db_file('applied-patches'); | |
85 $self->{applied_patches} = [ $self->read_patch_list($pc_applied) ]; | |
86 } | |
87 | |
88 sub write_db { | |
89 my ($self) = @_; | |
90 | |
91 $self->setup_db(); | |
92 my $pc_applied = $self->get_db_file('applied-patches'); | |
93 open(my $applied_fh, '>', $pc_applied) or | |
94 syserr(_g('cannot write %s'), $pc_applied); | |
95 foreach my $patch (@{$self->{applied_patches}}) { | |
96 print { $applied_fh } "$patch\n"; | |
97 } | |
98 close($applied_fh); | |
99 } | |
100 | |
101 sub load_series { | |
102 my ($self, %opts) = @_; | |
103 | |
104 my $series = $self->get_series_file(); | |
105 $self->{series} = [ $self->read_patch_list($series, %opts) ]; | |
106 } | |
107 | |
108 sub series { | |
109 my ($self) = @_; | |
110 return @{$self->{series}}; | |
111 } | |
112 | |
113 sub applied { | |
114 my ($self) = @_; | |
115 return @{$self->{applied_patches}}; | |
116 } | |
117 | |
118 sub top { | |
119 my ($self) = @_; | |
120 my $count = scalar @{$self->{applied_patches}}; | |
121 return $self->{applied_patches}[$count - 1] if $count; | |
122 return; | |
123 } | |
124 | |
125 sub next { | |
126 my ($self) = @_; | |
127 my $count_applied = scalar @{$self->{applied_patches}}; | |
128 my $count_series = scalar @{$self->{series}}; | |
129 return $self->{series}[$count_applied] if ($count_series > $count_applied); | |
130 return; | |
131 } | |
132 | |
133 sub push { | |
134 my ($self, %opts) = @_; | |
135 $opts{verbose} //= 0; | |
136 $opts{timestamp} //= fs_time($self->{dir}); | |
137 | |
138 my $patch = $self->next(); | |
139 return unless defined $patch; | |
140 | |
141 my $path = $self->get_patch_file($patch); | |
142 my $obj = Dpkg::Source::Patch->new(filename => $path); | |
143 | |
144 info(_g('applying %s'), $patch) if $opts{verbose}; | |
145 eval { | |
146 $obj->apply($self->{dir}, timestamp => $opts{timestamp}, | |
147 verbose => $opts{verbose}, | |
148 force_timestamp => 1, create_dirs => 1, remove_backup => 0, | |
149 options => [ '-t', '-F', '0', '-N', '-p1', '-u', | |
150 '-V', 'never', '-g0', '-E', '-b', | |
151 '-B', ".pc/$patch/", '--reject-file=-' ]); | |
152 }; | |
153 if ($@) { | |
154 info(_g('fuzz is not allowed when applying patches')); | |
155 info(_g("if patch '%s' is correctly applied by quilt, use '%s' to update
it"), | |
156 $patch, 'quilt refresh'); | |
157 $self->restore_quilt_backup_files($patch, %opts); | |
158 erasedir($self->get_db_file($patch)); | |
159 die $@; | |
160 } | |
161 CORE::push @{$self->{applied_patches}}, $patch; | |
162 $self->write_db(); | |
163 } | |
164 | |
165 sub pop { | |
166 my ($self, %opts) = @_; | |
167 $opts{verbose} //= 0; | |
168 $opts{timestamp} //= fs_time($self->{dir}); | |
169 $opts{reverse_apply} //= 0; | |
170 | |
171 my $patch = $self->top(); | |
172 return unless defined $patch; | |
173 | |
174 info(_g('unapplying %s'), $patch) if $opts{verbose}; | |
175 my $backup_dir = $self->get_db_file($patch); | |
176 if (-d $backup_dir and not $opts{reverse_apply}) { | |
177 # Use the backup copies to restore | |
178 $self->restore_quilt_backup_files($patch); | |
179 } else { | |
180 # Otherwise reverse-apply the patch | |
181 my $path = $self->get_patch_file($patch); | |
182 my $obj = Dpkg::Source::Patch->new(filename => $path); | |
183 | |
184 $obj->apply($self->{dir}, timestamp => $opts{timestamp}, | |
185 verbose => 0, force_timestamp => 1, remove_backup => 0, | |
186 options => [ '-R', '-t', '-N', '-p1', | |
187 '-u', '-V', 'never', '-g0', '-E', | |
188 '--no-backup-if-mismatch' ]); | |
189 } | |
190 | |
191 erasedir($backup_dir); | |
192 pop @{$self->{applied_patches}}; | |
193 $self->write_db(); | |
194 } | |
195 | |
196 sub get_db_version { | |
197 my ($self) = @_; | |
198 my $pc_ver = $self->get_db_file('.version'); | |
199 if (-f $pc_ver) { | |
200 open(my $ver_fh, '<', $pc_ver) or syserr(_g('cannot read %s'), $pc_ver); | |
201 my $version = <$ver_fh>; | |
202 chomp $version; | |
203 close($ver_fh); | |
204 return $version; | |
205 } | |
206 return; | |
207 } | |
208 | |
209 sub find_problems { | |
210 my ($self) = @_; | |
211 my $patch_dir = $self->get_patch_file(); | |
212 if (-e $patch_dir and not -d _) { | |
213 return sprintf(_g('%s should be a directory or non-existing'), $patch_di
r); | |
214 } | |
215 my $series = $self->get_series_file(); | |
216 if (-e $series and not -f _) { | |
217 return sprintf(_g('%s should be a file or non-existing'), $series); | |
218 } | |
219 return; | |
220 } | |
221 | |
222 sub get_series_file { | |
223 my ($self) = @_; | |
224 my $vendor = lc(get_current_vendor() || 'debian'); | |
225 # Series files are stored alongside patches | |
226 my $default_series = $self->get_patch_file('series'); | |
227 my $vendor_series = $self->get_patch_file("$vendor.series"); | |
228 return $vendor_series if -e $vendor_series; | |
229 return $default_series; | |
230 } | |
231 | |
232 sub get_db_file { | |
233 my $self = shift; | |
234 return File::Spec->catfile($self->{dir}, '.pc', @_); | |
235 } | |
236 | |
237 sub get_db_dir { | |
238 my ($self) = @_; | |
239 return $self->get_db_file(); | |
240 } | |
241 | |
242 sub get_patch_file { | |
243 my $self = shift; | |
244 return File::Spec->catfile($self->{dir}, 'debian', 'patches', @_); | |
245 } | |
246 | |
247 sub get_patch_dir { | |
248 my ($self) = @_; | |
249 return $self->get_patch_file(); | |
250 } | |
251 | |
252 ## METHODS BELOW ARE INTERNAL ## | |
253 | |
254 sub read_patch_list { | |
255 my ($self, $file, %opts) = @_; | |
256 return () if not defined $file or not -f $file; | |
257 $opts{warn_options} //= 0; | |
258 my @patches; | |
259 open(my $series_fh, '<' , $file) or syserr(_g('cannot read %s'), $file); | |
260 while (defined($_ = <$series_fh>)) { | |
261 chomp; s/^\s+//; s/\s+$//; # Strip leading/trailing spaces | |
262 s/(^|\s+)#.*$//; # Strip comment | |
263 next unless $_; | |
264 if (/^(\S+)\s+(.*)$/) { | |
265 $_ = $1; | |
266 if ($2 ne '-p1') { | |
267 warning(_g('the series file (%s) contains unsupported ' . | |
268 "options ('%s', line %s); dpkg-source might " . | |
269 'fail when applying patches'), | |
270 $file, $2, $.) if $opts{warn_options}; | |
271 } | |
272 } | |
273 error(_g('%s contains an insecure path: %s'), $file, $_) if m{(^|/)\.\./
}; | |
274 CORE::push @patches, $_; | |
275 } | |
276 close($series_fh); | |
277 return @patches; | |
278 } | |
279 | |
280 sub restore_quilt_backup_files { | |
281 my ($self, $patch, %opts) = @_; | |
282 my $patch_dir = $self->get_db_file($patch); | |
283 return unless -d $patch_dir; | |
284 info(_g('restoring quilt backup files for %s'), $patch) if $opts{verbose}; | |
285 find({ | |
286 no_chdir => 1, | |
287 wanted => sub { | |
288 return if -d $_; | |
289 my $relpath_in_srcpkg = File::Spec->abs2rel($_, $patch_dir); | |
290 my $target = File::Spec->catfile($self->{dir}, $relpath_in_srcpkg); | |
291 if (-s $_) { | |
292 unlink($target); | |
293 make_path(dirname($target)); | |
294 unless (link($_, $target)) { | |
295 copy($_, $target) | |
296 or syserr(_g('failed to copy %s to %s'), $_, $target); | |
297 chmod((stat(_))[2], $target) | |
298 or syserr(_g("unable to change permission of `%s'"), $ta
rget); | |
299 } | |
300 } else { | |
301 # empty files are "backups" for new files that patch created | |
302 unlink($target); | |
303 } | |
304 } | |
305 }, $patch_dir); | |
306 } | |
307 | |
308 1; | |
OLD | NEW |