source: trunk/base/src/port1.0/portdestroot.tcl @ 52704

Last change on this file since 52704 was 52704, checked in by jmr@…, 8 years ago

avoid deleting symlinks to directories in destroot_finish

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 15.4 KB
Line 
1# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4
2# portdestroot.tcl
3# $Id: portdestroot.tcl 52704 2009-06-21 20:59:48Z jmr@macports.org $
4#
5# Copyright (c) 2002 - 2003 Apple Computer, Inc.
6# Copyright (c) 2004 - 2005 Robert Shaw <rshaw@opendarwin.org>
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12# 1. Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14# 2. Redistributions in binary form must reproduce the above copyright
15#    notice, this list of conditions and the following disclaimer in the
16#    documentation and/or other materials provided with the distribution.
17# 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
18#    may be used to endorse or promote products derived from this software
19#    without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31# POSSIBILITY OF SUCH DAMAGE.
32#
33
34package provide portdestroot 1.0
35package require portutil 1.0
36
37set org.macports.destroot [target_new org.macports.destroot portdestroot::destroot_main]
38target_provides ${org.macports.destroot} destroot
39target_requires ${org.macports.destroot} main fetch extract checksum patch configure build
40target_prerun ${org.macports.destroot} portdestroot::destroot_start
41target_postrun ${org.macports.destroot} portdestroot::destroot_finish
42
43namespace eval portdestroot {
44    # Save old umask
45    variable oldmask
46}
47
48# define options
49options destroot.target destroot.destdir destroot.clean destroot.keepdirs destroot.umask
50options destroot.violate_mtree destroot.asroot
51options startupitem.create startupitem.requires startupitem.init
52options startupitem.name startupitem.start startupitem.stop startupitem.restart
53options startupitem.type startupitem.executable
54options startupitem.pidfile startupitem.logfile startupitem.logevents startupitem.netchange
55options startupitem.uniquename startupitem.plist startupitem.location
56commands destroot
57
58# Set defaults
59default destroot.asroot no
60default destroot.dir {${build.dir}}
61default destroot.cmd {${build.cmd}}
62default destroot.pre_args {${destroot.target}}
63default destroot.target install
64default destroot.post_args {${destroot.destdir}}
65default destroot.destdir {DESTDIR=${destroot}}
66default destroot.umask {$system_options(destroot_umask)}
67default destroot.clean no
68default destroot.keepdirs ""
69default destroot.violate_mtree no
70
71default startupitem.name        {${name}}
72default startupitem.uniquename  {org.macports.${startupitem.name}}
73default startupitem.plist       {${startupitem.uniquename}.plist}
74default startupitem.location    LaunchDaemons
75default startupitem.init        ""
76default startupitem.start       ""
77default startupitem.stop        ""
78default startupitem.restart     ""
79default startupitem.requires    ""
80default startupitem.executable  ""
81default startupitem.type        {$system_options(startupitem_type)}
82default startupitem.pidfile     ""
83default startupitem.logfile     ""
84default startupitem.logevents   no
85default startupitem.netchange   no
86
87set_ui_prefix
88
89proc portdestroot::destroot_start {args} {
90    global UI_PREFIX prefix name porturl destroot os.platform destroot.clean portsharepath
91    global destroot.umask destroot.asroot macportsuser euid egid usealtworkpath altprefix
92    global applications_dir frameworks_dir
93    variable oldmask
94
95    ui_msg "$UI_PREFIX [format [msgcat::mc "Staging %s into destroot"] ${name}]"
96
97    # start gsoc08-privileges
98    if { [getuid] == 0 && [geteuid] == [name_to_uid "$macportsuser"] } {
99    # if started with sudo but have dropped the privileges
100        ui_debug "Can't run destroot under sudo without elevated privileges (due to mtree)."
101        ui_debug "Run destroot without sudo to avoid root privileges."
102        ui_debug "Going to escalate privileges back to root."
103        setegid $egid
104        seteuid $euid
105        ui_debug "euid changed to: [geteuid]. egid changed to: [getegid]."
106    }
107
108    if { [tbool destroot.asroot] && [getuid] != 0 } {
109        return -code error "You cannot run this port without root privileges. You need to re-run with 'sudo port'.";
110    }
111
112    if {[info exists usealtworkpath] && $usealtworkpath == "yes"} {
113        # rewrite destroot.args
114        set argprefix "=[option prefix]"
115        set newargprefix "=${altprefix}[option prefix]"
116        set newdestrootargs [string map [list $argprefix $newargprefix] [option destroot.args]]
117        option destroot.args $newdestrootargs
118    }
119
120    # end gsoc08-privileges
121
122    set oldmask [umask ${destroot.umask}]
123    set mtree [findBinary mtree ${portutil::autoconf::mtree_path}]
124
125    if { ${destroot.clean} == "yes" } {
126        delete "${destroot}"
127    }
128
129    file mkdir "${destroot}"
130    if { ${os.platform} == "darwin" } {
131        system "cd \"${destroot}\" && ${mtree} -e -U -f [file join ${portsharepath} install macosx.mtree]"
132        file mkdir "${destroot}/${applications_dir}"
133        file mkdir "${destroot}/${frameworks_dir}"
134    }
135    file mkdir "${destroot}/${prefix}"
136    system "cd \"${destroot}/${prefix}\" && ${mtree} -e -U -f [file join ${portsharepath} install prefix.mtree]"
137}
138
139proc portdestroot::destroot_main {args} {
140    command_exec destroot
141    return 0
142}
143
144proc portdestroot::destroot_finish {args} {
145    global UI_PREFIX destroot prefix name startupitem.create destroot.violate_mtree
146    global applications_dir frameworks_dir developer_dir destroot.keepdirs
147    global os.platform os.version
148    variable oldmask
149
150    # Create startup-scripts/items
151    if {[tbool startupitem.create]} {
152        package require portstartupitem 1.0
153        portstartupitem::startupitem_create
154    }
155
156    foreach fileToDelete {share/info/dir lib/charset.alias} {
157        if [file exists "${destroot}${prefix}/${fileToDelete}"] {
158            ui_debug "Deleting stray ${fileToDelete} file."
159            file delete "${destroot}${prefix}/${fileToDelete}"
160        }
161    }
162
163    # Prune empty directories in ${destroot}
164    foreach path ${destroot.keepdirs} {
165        if {![file isdirectory ${path}]} {
166            xinstall -m 0755 -d ${path}
167        }
168        if {![file exists ${path}/.turd_${name}]} {
169            xinstall -c -m 0644 /dev/null ${path}/.turd_${name}
170        }
171    }
172    fs-traverse -depth dir ${destroot} {
173        if {[file type $dir] == "directory"} {
174            catch {file delete $dir}
175        }
176    }
177
178    # Compress all manpages with gzip (instead)
179    set manpath "${destroot}${prefix}/share/man"
180    set gzip [findBinary gzip ${portutil::autoconf::gzip_path}]
181    set gunzip "$gzip -d"
182    set bunzip2 "[findBinary bzip2 ${portutil::autoconf::bzip2_path}] -d"
183    if {[file isdirectory ${manpath}] && [file type ${manpath}] == "directory"} {
184        ui_info "$UI_PREFIX [format [msgcat::mc "Compressing man pages for %s"] ${name}]"
185        set found 0
186        set manlinks [list]
187        foreach mandir [readdir "${manpath}"] {
188            if {![regexp {^(cat|man)(.)$} ${mandir} match ignore manindex]} { continue }
189            set mandirpath [file join ${manpath} ${mandir}]
190            if {[file isdirectory ${mandirpath}] && [file type ${mandirpath}] == "directory"} {
191                ui_debug "Scanning ${mandir}"
192                foreach manfile [readdir ${mandirpath}] {
193                    set manfilepath [file join ${mandirpath} ${manfile}]
194                    if {[file isfile ${manfilepath}] && [file type ${manfilepath}] == "file"} {
195                        if {[regexp "^(.*\[.\]${manindex}\[a-z\]*)\[.\]gz\$" ${manfile} gzfile manfile]} {
196                            set found 1
197                            system "cd ${manpath} && \
198                            $gunzip -f [file join ${mandir} ${gzfile}] && \
199                            $gzip -9vf [file join ${mandir} ${manfile}]"
200                        } elseif {[regexp "^(.*\[.\]${manindex}\[a-z\]*)\[.\]bz2\$" ${manfile} bz2file manfile]} {
201                            set found 1
202                            system "cd ${manpath} && \
203                            $bunzip2 -f [file join ${mandir} ${bz2file}] && \
204                            $gzip -9vf [file join ${mandir} ${manfile}]"
205                        } elseif {[regexp "\[.\]${manindex}\[a-z\]*\$" ${manfile}]} {
206                            set found 1
207                            system "cd ${manpath} && \
208                            $gzip -9vf [file join ${mandir} ${manfile}]"
209                        }
210                        set gzmanfile ${manfile}.gz
211                        set gzmanfilepath [file join ${mandirpath} ${gzmanfile}]
212                        if {[file exists ${gzmanfilepath}]} {
213                            set desired 00444
214                            set current [file attributes ${gzmanfilepath} -permissions]
215                            if {$current != $desired} {
216                                ui_info "[file join ${mandir} ${gzmanfile}]: changing permissions from $current to $desired"
217                                file attributes ${gzmanfilepath} -permissions $desired
218                            }
219                        }
220                    } elseif {[file type ${manfilepath}] == "link"} {
221                        lappend manlinks [file join ${mandir} ${manfile}]
222                    }
223                }
224            }
225        }
226        if {$found == 1} {
227            # check man page links and rename/repoint them if necessary
228            foreach manlink $manlinks {
229                set manlinkpath [file join $manpath $manlink]
230                # if link destination is not gzipped, check it
231                set manlinksrc [file readlink $manlinkpath]
232                # if link destination is an absolute path, convert it to a
233                # relative path
234                if {[file pathtype $manlinksrc] eq "absolute"} {
235                    set manlinksrc [file tail $manlinksrc]
236                }
237                if {![regexp "\[.\]gz\$" ${manlinksrc}]} {
238                    set mandir [file dirname $manlink]
239                    set mandirpath [file join $manpath $mandir]
240                    set pwd [pwd]
241                    if {[catch {_cd $mandirpath} err]} {
242                        puts $err
243                        return
244                    }
245                    # if gzipped destination exists, fix link
246                    if {[file isfile ${manlinksrc}.gz]} {
247                        # if actual link name does not end with gz, rename it
248                        if {![regexp "\[.\]gz\$" ${manlink}]} {
249                            ui_debug "renaming link: $manlink to ${manlink}.gz"
250                            file rename $manlinkpath ${manlinkpath}.gz
251                            set manlink ${manlink}.gz
252                            set manlinkpath [file join $manpath $manlink]
253                        }
254                        # repoint the link
255                        ui_debug "repointing link: $manlink from $manlinksrc to ${manlinksrc}.gz"
256                        file delete $manlinkpath
257                        ln -s "${manlinksrc}.gz" "${manlinkpath}"
258                    }
259                    _cd $pwd
260                }
261            }
262        } else {
263            ui_debug "No man pages found to compress."
264        }
265    }
266
267    # test for violations of mtree
268    if { ${destroot.violate_mtree} != "yes" } {
269        ui_debug "checking for mtree violations"
270        set mtree_violation "no"
271
272        set prefixPaths [list bin etc include lib libexec sbin share src var www Applications Developer Library]
273
274        set pathsToCheck [list /]
275        while {[llength $pathsToCheck] > 0} {
276            set pathToCheck [lshift pathsToCheck]
277            foreach file [glob -nocomplain -directory $destroot$pathToCheck .* *] {
278                if {[file tail $file] eq "." || [file tail $file] eq ".."} {
279                    continue
280                }
281                if {[string equal -length [string length $destroot] $destroot $file]} {
282                    # just double-checking that $destroot is a prefix, as is appropriate
283                    set dfile [file join / [string range $file [string length $destroot] end]]
284                } else {
285                    throw MACPORTS "Unexpected filepath `${file}' while checking for mtree violations"
286                }
287                if {$dfile eq $prefix} {
288                    # we've found our prefix
289                    foreach pfile [glob -nocomplain -tails -directory $file .* *] {
290                        if {$pfile eq "." || $pfile eq ".."} {
291                            continue
292                        }
293                        if {[lsearch -exact $prefixPaths $pfile] == -1} {
294                            ui_warn "violation by [file join $dfile $pfile]"
295                            set mtree_violation "yes"
296                        }
297                    }
298                } elseif {[string equal -length [expr [string length $dfile] + 1] $dfile/ $prefix]} {
299                    # we've found a subpath of our prefix
300                    lpush pathsToCheck $dfile
301                } else {
302                    set dir_allowed no
303                    # these files are (at least potentially) outside of the prefix
304                    foreach dir "$applications_dir $frameworks_dir /Library/LaunchAgents /Library/LaunchDaemons /Library/StartupItems" {
305                        if {[string equal -length [expr [string length $dfile] + 1] $dfile/ $dir]} {
306                            # it's a prefix of one of the allowed paths
307                            set dir_allowed yes
308                            break
309                        }
310                    }
311                    if {$dir_allowed} {
312                        lpush pathsToCheck $dfile
313                    } else {
314                        # not a prefix of an allowed path, so it's either the path itself or a violation
315                        switch $dfile \
316                            $applications_dir - \
317                            $frameworks_dir - \
318                            /Library/LaunchAgents - \
319                            /Library/LaunchDaemons - \
320                            /Library/StartupItems { ui_debug "port installs files in $dfile" } \
321                            default {
322                                ui_warn "violation by $dfile"
323                                set mtree_violation "yes"
324                            }
325                    }
326                }
327            }
328        }
329
330        # abort here only so all violations can be observed
331        if { ${mtree_violation} != "no" } {
332            ui_warn "[format [msgcat::mc "%s violates the layout of the ports-filesystems!"] [option name]]"
333            ui_warn "Please fix or indicate this misbehavior (if it is intended), it will be an error in future releases!"
334            # error "mtree violation!"
335        }
336    } else {
337        ui_msg "[format [msgcat::mc "Note: %s installs files outside the common directory structure."] [option name]]"
338    }
339
340    # Restore umask
341    umask $oldmask
342
343    return 0
344}
Note: See TracBrowser for help on using the repository browser.