5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
24 # Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
27 # Some userland consolidation specific lint checks
29 import pkg
.lint
.base
as base
30 from pkg
.lint
.engine
import lint_fmri_successor
37 import pkg
.client
.api_errors
38 import pkg
.client
.progress
40 class UserlandActionChecker(base
.ActionChecker
):
41 """An opensolaris.org-specific class to check actions."""
43 name
= "userland.action"
45 def __init__(self
, config
):
47 "checks Userland packages for common content errors")
48 path
= os
.getenv('PROTO_PATH')
50 self
.proto_path
= path
.split()
52 self
.proto_path
= None
54 # These lists are used to check if a 32/64-bit binary
55 # is in a proper 32/64-bit directory.
61 "i86pc-solaris-64int", # perl path
62 "sun4-solaris-64int" # perl path
68 "i86pc-solaris-64", # perl path
69 "sun4-solaris-64" # perl path
72 re
.compile('^/lib(/.*)?$'),
74 re
.compile('^\$ORIGIN/')
76 self
.runpath_64_re
= [
77 re
.compile('^.*/64(/.*)?$'),
78 re
.compile('^.*/amd64(/.*)?$'),
79 re
.compile('^.*/sparcv9(/.*)?$'),
80 re
.compile('^.*/i86pc-solaris-64(/.*)?$'), # perl path
81 re
.compile('^.*/sun4-solaris-64(/.*)?$') # perl path
83 self
.initscript_re
= re
.compile("^etc/(rc.|init)\.d")
88 super(UserlandActionChecker
, self
).__init
__(config
)
90 def startup(self
, engine
):
91 """Initialize the checker with a dictionary of paths, so that we
92 can do link resolution.
94 This is copied from the core pkglint code, but should eventually
98 def seed_dict(mf
, attr
, dic
, atype
=None, verbose
=False):
99 """Updates a dictionary of { attr: [(fmri, action), ..]}
100 where attr is the value of that attribute from
101 actions of a given type atype, in the given
104 pkg_vars
= mf
.get_all_variants()
107 mfg
= (a
for a
in mf
.gen_actions_by_type(atype
))
109 mfg
= (a
for a
in mf
.gen_actions())
112 if atype
and action
.name
!= atype
:
114 if attr
not in action
.attrs
:
117 variants
= action
.get_variant_template()
118 variants
.merge_unknown(pkg_vars
)
119 action
.attrs
.update(variants
)
121 p
= action
.attrs
[attr
]
122 dic
.setdefault(p
, []).append((mf
.fmri
, action
))
124 # construct a set of FMRIs being presented for linting, and
125 # avoid seeding the reference dictionary with any for which
126 # we're delivering new packages.
128 for m
in engine
.gen_manifests(engine
.lint_api_inst
,
129 release
=engine
.release
, pattern
=engine
.pattern
):
130 lint_fmris
.setdefault(m
.fmri
.get_name(), []).append(m
.fmri
)
131 for m
in engine
.lint_manifests
:
132 lint_fmris
.setdefault(m
.fmri
.get_name(), []).append(m
.fmri
)
135 _("Seeding reference action path dictionaries."))
137 for manifest
in engine
.gen_manifests(engine
.ref_api_inst
,
138 release
=engine
.release
):
139 # Only put this manifest into the reference dictionary
140 # if it's not an older version of the same package.
142 lint_fmri_successor(fmri
, manifest
.fmri
)
144 in lint_fmris
.get(manifest
.fmri
.get_name(), [])
146 seed_dict(manifest
, "path", self
.ref_paths
)
149 _("Seeding lint action path dictionaries."))
151 # we provide a search pattern, to allow users to lint a
152 # subset of the packages in the lint_repository
153 for manifest
in engine
.gen_manifests(engine
.lint_api_inst
,
154 release
=engine
.release
, pattern
=engine
.pattern
):
155 seed_dict(manifest
, "path", self
.lint_paths
)
158 _("Seeding local action path dictionaries."))
160 for manifest
in engine
.lint_manifests
:
161 seed_dict(manifest
, "path", self
.lint_paths
)
163 self
.__merge
_dict
(self
.lint_paths
, self
.ref_paths
,
164 ignore_pubs
=engine
.ignore_pubs
)
166 def __merge_dict(self
, src
, target
, ignore_pubs
=True):
167 """Merges the given src dictionary into the target
168 dictionary, giving us the target content as it would appear,
169 were the packages in src to get published to the
170 repositories that made up target.
172 We need to only merge packages at the same or successive
173 version from the src dictionary into the target dictionary.
174 If the src dictionary contains a package with no version
175 information, it is assumed to be more recent than the same
176 package with no version in the target."""
184 """Builds a dictionary of fmri:action entries"""
186 for (pfmri
, action
) in arr
:
188 dic
[pfmri
].append(action
)
190 dic
[pfmri
] = [action
]
193 src_dic
= build_dic(src
[p
])
194 targ_dic
= build_dic(target
[p
])
196 for src_pfmri
in src_dic
:
197 # we want to remove entries deemed older than
198 # src_pfmri from targ_dic.
199 for targ_pfmri
in targ_dic
.copy():
200 sname
= src_pfmri
.get_name()
201 tname
= targ_pfmri
.get_name()
202 if lint_fmri_successor(src_pfmri
,
204 ignore_pubs
=ignore_pubs
):
205 targ_dic
.pop(targ_pfmri
)
206 targ_dic
.update(src_dic
)
208 for pfmri
in targ_dic
:
209 for action
in targ_dic
[pfmri
]:
210 l
.append((pfmri
, action
))
213 def __realpath(self
, path
, target
):
214 """Combine path and target to get the real path."""
216 result
= os
.path
.dirname(path
)
218 for frag
in target
.split(os
.sep
):
220 result
= os
.path
.dirname(result
)
224 result
= os
.path
.join(result
, frag
)
228 def __elf_aslr_check(self
, path
, engine
):
231 ei
= elf
.get_info(path
)
232 type = ei
.get("type");
236 # get the ASLR tag string for this binary
237 aslr_tag_process
= subprocess
.Popen(
238 "/usr/bin/elfedit -r -e 'dyn:sunw_aslr' "
240 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
242 # aslr_tag_string will get stdout; err will get stderr
243 aslr_tag_string
, err
= aslr_tag_process
.communicate()
245 # No ASLR tag was found; everthing must be tagged
246 if aslr_tag_process
.returncode
!= 0:
248 _("'%s' is not tagged for aslr") % (path
),
249 msgid
="%s%s.5" % (self
.name
, "001"))
252 # look for "ENABLE" anywhere in the string;
253 # warn about binaries which are not ASLR enabled
254 if re
.search("ENABLE", aslr_tag_string
) is not None:
257 _("'%s' does not have aslr enabled") % (path
),
258 msgid
="%s%s.6" % (self
.name
, "001"))
261 def __elf_runpath_check(self
, path
, engine
):
265 ed
= elf
.get_dynamic(path
)
266 ei
= elf
.get_info(path
)
267 bits
= ei
.get("bits")
268 for dir in ed
.get("runpath", "").split(":"):
269 if dir == None or dir == '':
273 for expr
in self
.runpath_re
:
282 for expr
in self
.runpath_64_re
:
285 _("64-bit runpath in 32-bit binary, '%s' includes '%s'") % (path
, dir),
286 msgid
="%s%s.3" % (self
.name
, "001"))
289 for expr
in self
.runpath_64_re
:
295 _("32-bit runpath in 64-bit binary, '%s' includes '%s'") % (path
, dir),
296 msgid
="%s%s.3" % (self
.name
, "001"))
298 result
= _("bad RUNPATH, '%%s' includes '%s'" %
303 def __elf_wrong_location_check(self
, path
):
306 ei
= elf
.get_info(path
)
307 bits
= ei
.get("bits")
308 type = ei
.get("type");
309 elems
= os
.path
.dirname(path
).split("/")
312 for p
in self
.pathlist64
:
317 for p
in self
.pathlist32
:
321 # ignore 64-bit executables in normal (non-32-bit-specific)
322 # locations, that's ok now.
323 if (type == "exe" and bits
== 64 and path32
== False and path64
== False):
326 if bits
== 32 and path64
:
327 result
= _("32-bit object '%s' in 64-bit path")
328 elif bits
== 64 and not path64
:
329 result
= _("64-bit object '%s' in 32-bit path")
332 def file_action(self
, action
, manifest
, engine
, pkglint_id
="001"):
333 """Checks for existence in the proto area."""
335 if action
.name
not in ["file"]:
339 if path
== None or path
== 'NOHASH':
340 path
= action
.attrs
["path"]
342 # check for writable files without a preserve attribute
343 if "mode" in action
.attrs
:
344 mode
= action
.attrs
["mode"]
346 if (int(mode
, 8) & 0222) != 0 and "preserve" not in action
.attrs
:
348 _("%(path)s is writable (%(mode)s), but missing a preserve"
349 " attribute") % {"path": path
, "mode": mode
},
350 msgid
="%s%s.0" % (self
.name
, pkglint_id
))
351 elif "preserve" in action
.attrs
:
352 if "mode" in action
.attrs
:
353 mode
= action
.attrs
["mode"]
354 if (int(mode
, 8) & 0222) == 0:
356 _("%(path)s has a preserve action, but is not writable (%(mode)s)") % {"path": path
, "mode": mode
},
357 msgid
="%s%s.4" % (self
.name
, pkglint_id
))
360 _("%(path)s has a preserve action, but no mode") % {"path": path
, "mode": mode
},
361 msgid
="%s%s.3" % (self
.name
, pkglint_id
))
363 # checks that require a physical file to look at
364 if self
.proto_path
is not None:
365 for directory
in self
.proto_path
:
366 fullpath
= directory
+ "/" + path
368 if os
.path
.exists(fullpath
):
371 if not os
.path
.exists(fullpath
):
373 _("%s missing from proto area, skipping"
374 " content checks") % path
,
375 msgid
="%s%s.1" % (self
.name
, pkglint_id
))
376 elif elf
.is_elf_object(fullpath
):
377 # 32/64 bit in wrong place
378 result
= self
.__elf
_wrong
_location
_check
(fullpath
)
380 engine
.error(result
% path
,
381 msgid
="%s%s.2" % (self
.name
, pkglint_id
))
382 result
= self
.__elf
_runpath
_check
(fullpath
, engine
)
384 engine
.error(result
% path
,
385 msgid
="%s%s.3" % (self
.name
, pkglint_id
))
386 # illumos does not support ASLR
387 #result = self.__elf_aslr_check(fullpath, engine)
389 file_action
.pkglint_desc
= _("Paths should exist in the proto area.")
391 def link_resolves(self
, action
, manifest
, engine
, pkglint_id
="002"):
392 """Checks for link resolution."""
394 if action
.name
not in ["link", "hardlink"]:
397 path
= action
.attrs
["path"]
398 target
= action
.attrs
["target"]
399 realtarget
= self
.__realpath
(path
, target
)
401 # Check against the target image (ref_paths), since links might
402 # resolve outside the packages delivering a particular
405 # links to files should directly match a patch in the reference
407 if self
.ref_paths
.get(realtarget
, None):
410 # If it didn't match a path in the reference repo, it may still
411 # be a link to a directory that has no action because it uses
412 # the default attributes. Look for a path that starts with
413 # this value plus a trailing slash to be sure this it will be
414 # resolvable on a fully installed system.
416 for key
in self
.ref_paths
:
417 if key
.startswith(realtarget
):
420 engine
.error(_("%s %s has unresolvable target '%s'") %
421 (action
.name
, path
, target
),
422 msgid
="%s%s.0" % (self
.name
, pkglint_id
))
424 link_resolves
.pkglint_desc
= _("links should resolve.")
426 def init_script(self
, action
, manifest
, engine
, pkglint_id
="003"):
427 """Checks for SVR4 startup scripts."""
429 if action
.name
not in ["file", "dir", "link", "hardlink"]:
432 path
= action
.attrs
["path"]
433 if self
.initscript_re
.match(path
):
435 _("SVR4 startup '%s', deliver SMF"
436 " service instead") % path
,
437 msgid
="%s%s.0" % (self
.name
, pkglint_id
))
439 init_script
.pkglint_desc
= _(
440 "SVR4 startup scripts should not be delivered.")
442 class UserlandManifestChecker(base
.ManifestChecker
):
443 """An opensolaris.org-specific class to check manifests."""
445 name
= "userland.manifest"
447 def __init__(self
, config
):
448 super(UserlandManifestChecker
, self
).__init
__(config
)
450 def forbidden_publisher(self
, manifest
, engine
, pkglint_id
="1001"):
451 if not os
.environ
.get("ENCUMBERED"):
452 for action
in manifest
.gen_actions_by_type("depend"):
453 for f
in action
.attrlist("fmri"):
454 pkg_name
=pkg
.fmri
.PkgFmri(f
).pkg_name
455 info_needed
= pkg
.client
.api
.PackageInfo
.ALL_OPTIONS
- \
456 (pkg
.client
.api
.PackageInfo
.ACTION_OPTIONS |
457 frozenset([pkg
.client
.api
.PackageInfo
.LICENSES
]))
458 progtracker
= pkg
.client
.progress
.NullProgressTracker()
459 interface
=pkg
.client
.api
.ImageInterface("/", pkg
.client
.api
.CURRENT_API_VERSION
, progtracker
, lambda x
: False, None,None)
460 ret
= interface
.info([pkg_name
],True,info_needed
)
461 if ret
[pkg
.client
.api
.ImageInterface
.INFO_FOUND
]:
462 allowed_pubs
= engine
.get_param("%s.allowed_pubs" % self
.name
).split(" ") + ["unleashed","userland","userland-extra"]
463 for i
in ret
[pkg
.client
.api
.ImageInterface
.INFO_FOUND
]:
464 if i
.publisher
not in allowed_pubs
:
465 engine
.error(_("package %(pkg)s depends on %(name)s, which comes from forbidden publisher %(publisher)s") %
466 {"pkg":manifest
.fmri
,"name":pkg_name
,"publisher":i
.publisher
}, msgid
="%s%s.1" % (self
.name
, pkglint_id
))
468 forbidden_publisher
.pkglint_desc
= _(
469 "Dependencies should come from standard publishers" )
471 def component_check(self
, manifest
, engine
, pkglint_id
="001"):
476 for action
in manifest
.gen_actions_by_type("file"):
483 for action
in manifest
.gen_actions_by_type("license"):
484 if not action
.attrs
['license']:
485 engine
.error( _("missing vaue for action license attribute 'license' like 'CDDL','MIT','GPL'..."),
486 msgid
="%s%s.0" % (self
.name
, pkglint_id
))
492 engine
.error( _("missing license action"),
493 msgid
="%s%s.0" % (self
.name
, pkglint_id
))
495 # if 'org.opensolaris.arc-caseid' not in manifest:
496 # engine.error( _("missing ARC data (org.opensolaris.arc-caseid)"),
497 # msgid="%s%s.0" % (self.name, pkglint_id))
499 component_check
.pkglint_desc
= _(
500 "license actions and ARC information are required if you deliver files.")
502 def publisher_in_fmri(self
, manifest
, engine
, pkglint_id
="002"):
503 allowed_pubs
= engine
.get_param(
504 "%s.allowed_pubs" % self
.name
).split(" ")
507 if fmri
.publisher
and fmri
.publisher
not in allowed_pubs
:
508 engine
.error(_("package %s has a publisher set!") %
510 msgid
="%s%s.2" % (self
.name
, pkglint_id
))
512 publisher_in_fmri
.pkglint_desc
= _(
513 "extra publisher set" )