| 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 |