1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 10# 11# This software is licensed as described in the file COPYING, which 12# you should have received as part of this distribution. The terms 13# are also available at https://curl.se/docs/copyright.html. 14# 15# You may opt to use, copy, modify, merge, publish, distribute and/or sell 16# copies of the Software, and permit persons to whom the Software is 17# furnished to do so, under the terms of the COPYING file. 18# 19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20# KIND, either express or implied. 21# 22# SPDX-License-Identifier: curl 23# 24########################################################################### 25# 26# Invoke script in the root of the git checkout. Scans all files in git unless 27# given a specific single file. 28# 29# Usage: copyright.pl [file] 30# 31 32my %skips; 33 34# file names 35my %skiplist = ( 36 # REUSE-specific file 37 ".reuse/dep5" => "<built-in>", 38 39 # License texts 40 "LICENSES/BSD-3-Clause.txt" => "<built-in>", 41 "LICENSES/BSD-4-Clause-UC.txt" => "<built-in>", 42 "LICENSES/ISC.txt" => "<built-in>", 43 "LICENSES/curl.txt" => "<built-in>", 44 "COPYING" => "<built-in>", 45 46 ); 47 48sub scanfile { 49 my ($f) = @_; 50 my $line=1; 51 my $found = 0; 52 open(F, "<$f") || return -1; 53 while (<F>) { 54 chomp; 55 my $l = $_; 56 # check for a copyright statement and save the years 57 if($l =~ /.* ?copyright .* (\d\d\d\d|)/i) { 58 my $count = 0; 59 while($l =~ /([\d]{4})/g) { 60 push @copyright, { 61 year => $1, 62 line => $line, 63 col => index($l, $1), 64 code => $l 65 }; 66 $count++; 67 } 68 if(!$count) { 69 # year-less 70 push @copyright, { 71 year => -1, 72 line => $line, 73 col => index($l, $1), 74 code => $l 75 }; 76 $count++; 77 } 78 $found = $count; 79 } 80 if($l =~ /SPDX-License-Identifier:/) { 81 $spdx = 1; 82 } 83 # allow within the first 100 lines 84 if(++$line > 100) { 85 last; 86 } 87 } 88 close(F); 89 return $found; 90} 91 92sub checkfile { 93 my ($file, $skipped, $pattern) = @_; 94 $spdx = 0; 95 my $found = scanfile($file); 96 97 if($found < 1) { 98 if($skipped) { 99 # just move on 100 $skips{$pattern}++; 101 return 0; 102 } 103 if(!$found) { 104 print "$file:1: missing copyright range\n"; 105 return 2; 106 } 107 # this means the file couldn't open - it might not exist, consider 108 # that fine 109 return 1; 110 } 111 if(!$spdx) { 112 if($skipped) { 113 # move on 114 $skips{$pattern}++; 115 return 0; 116 } 117 print "$file:1: missing SPDX-License-Identifier\n"; 118 return 2; 119 } 120 121 if($skipped) { 122 print "$file:1: ignored superfluously by $pattern\n" if($verbose); 123 $superf{$pattern}++; 124 } 125 126 return 1; 127} 128 129sub dep5 { 130 my ($file) = @_; 131 my @files; 132 my $copy; 133 open(F, "<$file") || die "can't open $file"; 134 my $line = 0; 135 while(<F>) { 136 $line++; 137 if(/^Files: (.*)/i) { 138 my @all = `git ls-files $1`; 139 if(!$all[0]) { 140 print STDERR "$1 matches no files\n"; 141 } 142 else { 143 push @files, @all; 144 } 145 } 146 elsif(/^Copyright: (.*)/i) { 147 $copy = $1; 148 } 149 elsif(/^License: (.*)/i) { 150 my $license = $1; 151 for my $f (@files) { 152 chomp $f; 153 if($f =~ /\.gitignore\z/) { 154 # ignore .gitignore 155 } 156 else { 157 if($skiplist{$f}) { 158 print STDERR "$f already skipped at $skiplist{$f}\n"; 159 } 160 $skiplist{$f} = "dep5:$line"; 161 } 162 } 163 undef @files; 164 } 165 } 166 close(F); 167} 168 169dep5(".reuse/dep5"); 170 171my $checkall = 0; 172my @all; 173my $verbose; 174if($ARGV[0] eq "-v") { 175 $verbose = 1; 176 shift @ARGV; 177} 178if($ARGV[0]) { 179 push @all, @ARGV; 180} 181else { 182 @all = `git ls-files`; 183 $checkall = 1; 184} 185 186for my $f (@all) { 187 chomp $f; 188 my $skipped = 0; 189 my $miss; 190 my $wro; 191 my $pattern; 192 if($skiplist{$f}) { 193 $pattern = $skip; 194 $skiplisted++; 195 $skipped = 1; 196 $skip{$f}++; 197 } 198 199 my $r = checkfile($f, $skipped, $pattern); 200 $mis=1 if($r == 2); 201 $wro=1 if(!$r); 202 203 if(!$skipped) { 204 $missing += $mis; 205 $wrong += $wro; 206 } 207} 208 209if($verbose) { 210 print STDERR "$missing files have no copyright\n" if($missing); 211 print STDERR "$wrong files have wrong copyright year\n" if ($wrong); 212 print STDERR "$skiplisted files are skipped\n" if ($skiplisted); 213 214 for my $s (@skiplist) { 215 if(!$skips{$s}) { 216 printf ("Never skipped pattern: %s\n", $s); 217 } 218 if($superf{$s}) { 219 printf ("%s was skipped superfluously %u times and legitimately %u times\n", 220 $s, $superf{$s}, $skips{$s}); 221 } 222 } 223} 224 225if($checkall) { 226 for(keys %skiplist) { 227 if(!$skip{$_}) { 228 printf STDERR "$_ is marked for SKIP but is missing!\n"; 229 } 230 } 231} 232 233exit 1 if($missing || $wrong); 234