source: trunk/base/src/port1.0/portchecksum.tcl @ 141176

Last change on this file since 141176 was 141176, checked in by jeremyhu@…, 4 years ago

Fix checksum line suggestion for ports with multiple distfiles

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.6 KB
Line 
1# et:ts=4
2# portchecksum.tcl
3# $Id: portchecksum.tcl 141176 2015-10-11 18:51:13Z jeremyhu@macports.org $
4#
5# Copyright (c) 2002 - 2004 Apple Inc.
6# Copyright (c) 2004 - 2005 Paul Guyot <pguyot@kallisys.net>
7# Copyright (c) 2006-2011 The MacPorts Project
8# All rights reserved.
9#
10# Redistribution and use in source and binary forms, with or without
11# modification, are permitted provided that the following conditions
12# are met:
13# 1. Redistributions of source code must retain the above copyright
14#    notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16#    notice, this list of conditions and the following disclaimer in the
17#    documentation and/or other materials provided with the distribution.
18# 3. Neither the name of Apple Inc. nor the names of its contributors
19#    may be used to endorse or promote products derived from this software
20#    without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32# POSSIBILITY OF SUCH DAMAGE.
33#
34
35package provide portchecksum 1.0
36package require portutil 1.0
37
38set org.macports.checksum [target_new org.macports.checksum portchecksum::checksum_main]
39target_provides ${org.macports.checksum} checksum
40target_requires ${org.macports.checksum} main fetch
41target_prerun ${org.macports.checksum} portchecksum::checksum_start
42
43namespace eval portchecksum {
44}
45
46# Options
47options checksums checksum.skip
48
49# Defaults
50default checksums ""
51default checksum.skip false
52
53set_ui_prefix
54
55# The list of the types of checksums we know.
56set checksum_types [list md5 sha1 rmd160 sha256 size]
57
58# The number of types we know.
59set checksum_types_count [llength $checksum_types]
60
61# types to recommend if none are specified in the portfile
62set default_checksum_types [list rmd160 sha256]
63
64# Using global all_dist_files, parse the checksums and store them into the
65# global array checksums_array.
66#
67# There are two formats:
68# type value [type value [type value]]                      for a single file
69# file1 type value [type value [type value]] [file2 ...]    for multiple files.
70#
71# Portfile is in format #1 if:
72# (1) There is only one distfile.
73# (2) There are an even number of words in checksums (i.e. "md5 cksum sha1 cksum" = 4 words).
74# (3) There are no more than $checksum_types_count checksums specified.
75# (4) first word is one of the checksums types.
76#
77# return yes if the syntax was correct, no if there was a problem.
78proc portchecksum::parse_checksums {checksums_str} {
79    global checksums_array all_dist_files checksum_types checksum_types_count
80
81    # Parse the string of checksums.
82    set nb_checksum [llength $checksums_str]
83
84    if {[llength $all_dist_files] == 1
85        && [expr {$nb_checksum % 2}] == 0
86        && [expr {$nb_checksum / 2}] <= $checksum_types_count
87        && [lindex $checksums_str 0] in $checksum_types} {
88        # Convert to format #2
89        set checksums_str [linsert $checksums_str 0 [lindex $all_dist_files 0]]
90        # We increased the size.
91        incr nb_checksum
92    }
93
94    # Create the array with the checksums.
95    array set checksums_array {}
96
97    set result yes
98
99    # Catch out of bounds errors (they're syntax errors).
100    if {[catch {
101        # Parse the string as if it was in format #2.
102        for {set ix_checksum 0} {$ix_checksum < $nb_checksum} {incr ix_checksum} {
103            # first word is the file.
104            set checksum_filename [lindex $checksums_str $ix_checksum]
105
106            # retrieve the list of values we already know for this file.
107            set checksum_values {}
108            if {[info exists checksums_array($checksum_filename)]} {
109                set checksum_values $checksums_array($checksum_filename)
110            }
111
112            # append the new value
113            incr ix_checksum
114            while {1} {
115                set checksum_type [lindex $checksums_str $ix_checksum]
116                if {$checksum_type in $checksum_types} {
117                    # append the type and the value.
118                    incr ix_checksum
119                    set checksum_value [lindex $checksums_str $ix_checksum]
120                    incr ix_checksum
121
122                    lappend checksum_values $checksum_type
123                    lappend checksum_values $checksum_value
124                } else {
125                    # this wasn't a type but the next dist file.
126                    incr ix_checksum -1
127                    break
128                }
129
130                # stop if we exhausted all the items in the list.
131                if {$ix_checksum == $nb_checksum} {
132                    break
133                }
134            }
135
136            # set the values in the array.
137            set checksums_array($checksum_filename) $checksum_values
138        }
139    } error]} {
140        # An error occurred.
141        global errorInfo
142        ui_debug "$errorInfo"
143        ui_error "Couldn't parse checksum line ($checksums_str) [$error]"
144
145        # Something wrong happened.
146        set result no
147    }
148
149    return $result
150}
151
152# calc_md5
153#
154# Calculate the md5 checksum for the given file.
155# Return the checksum.
156#
157proc portchecksum::calc_md5 {file} {
158    return [md5 file $file]
159}
160
161# calc_sha1
162#
163# Calculate the sha1 checksum for the given file.
164# Return the checksum.
165#
166proc portchecksum::calc_sha1 {file} {
167    return [sha1 file $file]
168}
169
170# calc_rmd160
171#
172# Calculate the rmd160 checksum for the given file.
173# Return the checksum.
174#
175proc portchecksum::calc_rmd160 {file} {
176    return [rmd160 file $file]
177}
178
179# calc_sha256
180#
181# Calculate the sha256 checksum for the given file.
182# Return the checksum.
183#
184proc portchecksum::calc_sha256 {file} {
185    return [sha256 file $file]
186}
187
188# calc_size
189#
190# Get the size of the given file.
191# Return the size.
192#
193proc portchecksum::calc_size {file} {
194    return [file size $file]
195}
196
197# checksum_start
198#
199# Target prerun procedure; simply prints a message about what we're doing.
200#
201proc portchecksum::checksum_start {args} {
202    global UI_PREFIX
203
204    ui_notice "$UI_PREFIX [format [msgcat::mc "Verifying checksums for %s"] [option subport]]"
205}
206
207# checksum_main
208#
209# Target main procedure. Verifies the checksums of all distfiles.
210#
211proc portchecksum::checksum_main {args} {
212    global UI_PREFIX all_dist_files checksum_types checksums_array portverbose checksum.skip \
213           usealtworkpath altprefix default_checksum_types
214
215    # If no files have been downloaded, there is nothing to checksum.
216    if {![info exists all_dist_files]} {
217        return 0
218    }
219
220    # Completely bypass checksumming if checksum.skip=yes
221    # This should be considered an extreme measure
222    if {[tbool checksum.skip]} {
223        ui_info "$UI_PREFIX Skipping checksum phase"
224        return 0
225    }
226
227    # so far, everything went fine.
228    set fail no
229
230    # Set the list of checksums as the option checksums.
231    set checksums_str [option checksums]
232
233    # store the calculated checksums to avoid repeated calculations
234    set sums ""
235
236    # if everything is fine with the syntax, keep on and check the checksum of
237    # the distfiles.
238    if {[parse_checksums $checksums_str] eq "yes"} {
239        set distpath [option distpath]
240
241        foreach distfile $all_dist_files {
242            ui_info "$UI_PREFIX [format [msgcat::mc "Checksumming %s"] $distfile]"
243
244            # get the full path of the distfile.
245            set fullpath [file join $distpath $distfile]
246            if {![file isfile $fullpath]} {
247                if {!$usealtworkpath && [file isfile "${altprefix}${fullpath}"]} {
248                    set fullpath "${altprefix}${fullpath}"
249                } else {
250                    return -code error "$distfile does not exist in $distpath"
251                }
252            }
253
254            if {[llength $all_dist_files] > 1} {
255                lappend sums $distfile
256            }
257
258            # check that there is at least one checksum for the distfile.
259            if {![info exists checksums_array($distfile)] || [llength $checksums_array($distfile)] < 1} {
260                ui_error "[format [msgcat::mc "No checksum set for %s"] $distfile]"
261                set fail yes
262
263                # no checksums specified; output the default set
264                foreach type $default_checksum_types {
265                    lappend sums [format "%-8s%s" $type [calc_$type $fullpath]]
266                }
267
268            } else {
269                # retrieve the list of types/values from the array.
270                set portfile_checksums $checksums_array($distfile)
271
272                # iterate on this list to check the actual values.
273                foreach {type sum} $portfile_checksums {
274                    set calculated_sum [calc_$type $fullpath]
275                    lappend sums [format "%-8s%s" $type $calculated_sum]
276
277                    # Used for regression testing
278                    ui_debug "[format [msgcat::mc "Calculated (%s) is %s"] $type $calculated_sum]"
279
280                    if {$sum eq $calculated_sum} {
281                        ui_debug "[format [msgcat::mc "Correct (%s) checksum for %s"] $type $distfile]"
282                    } else {
283                        ui_error "[format [msgcat::mc "Checksum (%s) mismatch for %s"] $type $distfile]"
284                        ui_info "[format [msgcat::mc "Portfile checksum: %s %s %s"] $distfile $type $sum]"
285                        ui_info "[format [msgcat::mc "Distfile checksum: %s %s %s"] $distfile $type $calculated_sum]"
286
287                        # Raise the failure flag
288                        set fail yes
289                    }
290                }
291                if {[tbool fail] && ![regexp {\.html?$} ${distfile}] &&
292                    ![catch {strsed [exec [findBinary file $portutil::autoconf::file_path] $fullpath --brief --mime] {s/;.*$//}} mimetype]
293                    && "text/html" == $mimetype} {
294                    # file --mime-type would be preferable to file --mime and strsed, but is only available as of Snow Leopard
295                    set wrong_mimetype yes
296                    set htmlfile_path ${fullpath}.html
297                    file rename -force $fullpath $htmlfile_path
298                }
299            }
300
301        }
302    } else {
303        # Something went wrong with the syntax.
304        set fail yes
305    }
306
307    if {[tbool fail]} {
308
309        if {[tbool wrong_mimetype]} {
310            # We got an HTML file, though the distfile name does not suggest that one was
311            # expected. Probably a helpful DNS server sent us to its search results page
312            # instead of admitting that the server we asked for doesn't exist, or a mirror that
313            # no longer has the file served its error page with a 200 response.
314            ui_notice "***"
315            ui_notice "The non-matching file appears to be HTML. See this page for possible reasons"
316            ui_notice "for the checksum mismatch:"
317            ui_notice "<https://trac.macports.org/wiki/MisbehavingServers>"
318            ui_notice "***"
319            ui_notice "The file has been moved to: $htmlfile_path"
320        } else {
321            # Show the desired checksum line for easy cut-paste
322            ui_info "The correct checksum line may be:"
323            #ui_info [format "%-20s%s" "checksums" [join $sums [format " \\\n%-20s" ""]]]
324
325            set default_sums {}
326            foreach distfile $all_dist_files {
327                if {[llength $all_dist_files] > 1} {
328                    lappend default_sums $distfile
329                }
330
331                # get the full path of the distfile.
332                set fullpath [file join $distpath $distfile]
333                if {![file isfile $fullpath]} {
334                    if {!$usealtworkpath && [file isfile "${altprefix}${fullpath}"]} {
335                        set fullpath "${altprefix}${fullpath}"
336                    } else {
337                        return -code error "$distfile does not exist in $distpath"
338                    }
339                }
340
341                foreach type $default_checksum_types {
342                    lappend default_sums [format "%-8s%s" $type [calc_$type $fullpath]]
343                }
344            }
345
346            ui_info [format "%-20s%s" "checksums" [join $default_sums [format " \\\n%-20s" ""]]]
347        }
348
349        return -code error "[msgcat::mc "Unable to verify file checksums"]"
350    }
351
352    return 0
353}
Note: See TracBrowser for help on using the repository browser.