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 push @files, `git ls-files $1`; 139 } 140 elsif(/^Copyright: (.*)/i) { 141 $copy = $1; 142 } 143 elsif(/^License: (.*)/i) { 144 my $license = $1; 145 for my $f (@files) { 146 chomp $f; 147 if($f =~ /\.gitignore\z/) { 148 # ignore .gitignore 149 } 150 else { 151 if($skiplist{$f}) { 152 print STDERR "$f already skipped at $skiplist{$f}\n"; 153 } 154 $skiplist{$f} = "dep5:$line"; 155 } 156 } 157 undef @files; 158 } 159 } 160 close(F); 161} 162 163dep5(".reuse/dep5"); 164 165my $checkall = 0; 166my @all; 167my $verbose; 168if($ARGV[0] eq "-v") { 169 $verbose = 1; 170 shift @ARGV; 171} 172if($ARGV[0]) { 173 push @all, @ARGV; 174} 175else { 176 @all = `git ls-files`; 177 $checkall = 1; 178} 179 180for my $f (@all) { 181 chomp $f; 182 my $skipped = 0; 183 my $miss; 184 my $wro; 185 my $pattern; 186 if($skiplist{$f}) { 187 $pattern = $skip; 188 $skiplisted++; 189 $skipped = 1; 190 $skip{$f}++; 191 } 192 193 my $r = checkfile($f, $skipped, $pattern); 194 $mis=1 if($r == 2); 195 $wro=1 if(!$r); 196 197 if(!$skipped) { 198 $missing += $mis; 199 $wrong += $wro; 200 } 201} 202 203if($verbose) { 204 print STDERR "$missing files have no copyright\n" if($missing); 205 print STDERR "$wrong files have wrong copyright year\n" if ($wrong); 206 print STDERR "$skiplisted files are skipped\n" if ($skiplisted); 207 208 for my $s (@skiplist) { 209 if(!$skips{$s}) { 210 printf ("Never skipped pattern: %s\n", $s); 211 } 212 if($superf{$s}) { 213 printf ("%s was skipped superfluously %u times and legitimately %u times\n", 214 $s, $superf{$s}, $skips{$s}); 215 } 216 } 217} 218 219if($checkall) { 220 for(keys %skiplist) { 221 if(!$skip{$_}) { 222 printf STDERR "$_ is marked for SKIP but is missing!\n"; 223 } 224 } 225} 226 227exit 1 if($missing || $wrong); 228