From ca3967b60982776413de85ff72f0c365bdccfaa2 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Mon, 11 Jun 2007 14:26:02 +0000 Subject: [PATCH] Merged from trunk 6765:6802 +------------------------------------------------------------------------+ | 6766 | For bug #181104, make econf give an appropriate error | | zmedico | message when configure is not executable. | |---------+--------------------------------------------------------------| | 6768 | For bug #139383, in xtermTitleReset() use os.system() | | zmedico | instead of commands.getoutput() since the latter puts too | | | much restriction on what $PROMPT_COMMAND may contain. | |---------+--------------------------------------------------------------| | 6770 | For bug #152828, run ${CHOST}-ldconfig when ${CBUILD} != | | zmedico | ${CHOST}. | |---------+--------------------------------------------------------------| | 6772 | Only execute $PROMPT_COMMAND when appropriate. | | zmedico | | |---------+--------------------------------------------------------------| | 6775 | Skip bogus '$ECLASS inherited illegally' messages when | | zmedico | installing from a binary package. | |---------+--------------------------------------------------------------| | 6778 | For bug #181387, fix an UnboundLocalError for the mymtime | | zmedico | variable that is triggered by confmem. | |---------+--------------------------------------------------------------| | 6780 | For bug #148702, automatically enable flags from IUSE if | | zmedico | they correspond to unset USE_EXPAND variables such as | | | LINGUAS. The variable only has to be set to some value or an | | | empty string in order to disable these flags. | |---------+--------------------------------------------------------------| | 6782 | When an update is discarded due to confmem, record it's | | zmedico | mtime in the contents (it's md5 is already recorded there | | | too). | |---------+--------------------------------------------------------------| | 6784 | For bug #148702, use the * token to trigger wildcard | | zmedico | expansion of IUSE. This adds flexibility and prevents flags | | | from being enabled unexpectedly. | |---------+--------------------------------------------------------------| | 6786 | Preserve order of flags from IUSE in wildcard expansion. | | zmedico | | |---------+--------------------------------------------------------------| | 6788 | Make repoman work with both + and - flags in IUSE. | | zmedico | | |---------+--------------------------------------------------------------| | 6790 | For bug #181551, support slot deps in package.keywords. | | zmedico | | |---------+--------------------------------------------------------------| | 6792 | For bug #81097, detect and report suspicious hardlinks to | | zmedico | suid/sgid files. False positives are prevented by doing | | | reference counts for each inode having suid/sgid bits and | | | multiple hardlinks. The security check is done prior to each | | | merge or unmerge phase and it will cause the phase to abort | | | if a problem is found (so that the user can investigate | | | before any files are removed). | |---------+--------------------------------------------------------------| | 6793 | Don't bother to warn about hardlinks to suid/sgid files | | zmedico | before unlink since it can give false positives and the | | | existing suid/sgid security check should spot problems | | | before this anyway. | |---------+--------------------------------------------------------------| | 6794 | Fix the suid/sgid check so it only looks at regular files | | zmedico | since it can generate false positives otherwise. | |---------+--------------------------------------------------------------| | 6796 | For bug #181355, detect parenthesis mismatch in | | zmedico | paren_reduce(), raise an InvalidDependString exception, and | | | make sure that all callers handle the exception properly. | |---------+--------------------------------------------------------------| | 6797 | Remove the "missing left parenthesis" test for now since it | | zmedico | gives false positives. | |---------+--------------------------------------------------------------| | 6799 | For consistency with other xterm titles code, redirect | | zmedico | $PROMPT_COMMAND output to stderr. | |---------+--------------------------------------------------------------| | 6802 | Bump 2.1.3 to 2.2 for some features and add news about | | zmedico | USE_EXPAND wildcards. | +------------------------------------------------------------------------+ svn path=/main/branches/prefix/; revision=6804 --- NEWS | 9 +++- RELEASE-NOTES | 2 +- bin/ebuild.sh | 10 ++++- bin/repoman | 15 ++++--- pym/portage/__init__.py | 76 ++++++++++++++++++++++++++------- pym/portage/dbapi/bintree.py | 17 ++++++-- pym/portage/dbapi/porttree.py | 7 ++-- pym/portage/dbapi/vartree.py | 79 ++++++++++++++++++++++++++++++----- pym/portage/dep.py | 11 ++++- pym/portage/output.py | 14 ++++++- 10 files changed, 196 insertions(+), 44 deletions(-) diff --git a/NEWS b/NEWS index e0f8d66c0..3dce95537 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ News (mainly features/major bug fixes) -portage-2.1.3 +portage-2.2 ------------- * Allow per-module setting of PORTAGE_ELOG_CLASSES @@ -17,6 +17,13 @@ portage-2.1.3 * Make elog functionality available to python code * Add support for news items (GLEP 42) +portage-2.1.3 +------------- + +* USE_EXPAND variables such as LINGUAS support a * wildcard that will enable + all of the corresponding flags that are listed in IUSE for a given package. + USE_EXPAND wildcards such as linguas_* are supported in USE and package.use. + portage-2.1.2 ------------- diff --git a/RELEASE-NOTES b/RELEASE-NOTES index eba5316e9..01507f0c2 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,7 +1,7 @@ Release Notes; upgrade information mainly. Features/major bugfixes are listed in NEWS -portage-2.1.3 +portage-2.2 ================================== * The python namespace for portage has been sanitized, all portage related code diff --git a/bin/ebuild.sh b/bin/ebuild.sh index af075880f..66d86818c 100755 --- a/bin/ebuild.sh +++ b/bin/ebuild.sh @@ -515,6 +515,8 @@ econf() { fi die "econf failed" fi + elif [ -f "${ECONF_SOURCE:-.}/configure" ]; then + die "configure is not executable" else die "no configure script found" fi @@ -1159,7 +1161,8 @@ inherit() { export ECLASS="$1" if [ "${EBUILD_PHASE}" != "depend" ] && \ - [[ ${EBUILD_PHASE} != *rm ]]; then + [[ ${EBUILD_PHASE} != *rm ]] && \ + [[ ${EMERGE_FROM} != "binary" ]] ; then # This is disabled in the *rm phases because they frequently give # false alarms due to INHERITED in /var/db/pkg being outdated # in comparison the the eclasses from the portage tree. @@ -1525,6 +1528,11 @@ if [ "${EBUILD_PHASE}" != "depend" ]; then done export IUSE=${iuse_temp} unset iuse_temp + # unset USE_EXPAND variables that contain only the special "*" token + for x in ${USE_EXPAND} ; do + [ "${!x}" == "*" ] && unset ${x} + done + unset x # Lock the dbkey variables after the global phase declare -r DEPEND RDEPEND SLOT SRC_URI RESTRICT HOMEPAGE LICENSE DESCRIPTION declare -r KEYWORDS INHERITED IUSE PDEPEND PROVIDE diff --git a/bin/repoman b/bin/repoman index 2433adcb7..6b53577a0 100755 --- a/bin/repoman +++ b/bin/repoman @@ -1259,6 +1259,10 @@ for x in scanlist: except ValueError: badsyntax.append("parenthesis mismatch") mydeplist = [] + except portage.exception.InvalidDependString, e: + badsyntax.append(str(e)) + del e + mydeplist = [] try: portage.dep.use_reduce(mydeplist, excludeall=myiuse) @@ -1412,13 +1416,12 @@ for x in scanlist: myuse = [] default_use = [] for myflag in myaux["IUSE"].split(): - if myflag.startswith("+"): + flag_name = myflag.lstrip("+-") + if myflag != flag_name: default_use.append(myflag) - myflag = myflag[1:] - myuse.append(myflag) - for mypos in range(len(myuse)-1,-1,-1): - if myuse[mypos] and (myuse[mypos] in uselist): - del myuse[mypos] + if flag_name not in uselist: + myuse.append(flag_name) + # uselist checks - local mykey = portage.dep_getkey(catpkg) if luselist.has_key(mykey): diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 48ef66eba..ec84a80b4 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -461,13 +461,16 @@ class digraph: #parse /etc/env.d and generate /etc/profile.env -def env_update(makelinks=1, target_root=None, prev_mtimes=None, contents=None): +def env_update(makelinks=1, target_root=None, prev_mtimes=None, contents=None, + env=None): if target_root is None: global root target_root = root if prev_mtimes is None: global mtimedb prev_mtimes = mtimedb["ldpath"] + if env is None: + env = os.environ envd_dir = os.path.join(target_root + EPREFIX, "etc", "env.d") portage.util.ensure_dirs(envd_dir, mode=0755) fns = listdir(envd_dir, EmptyOnError=1) @@ -652,8 +655,14 @@ def env_update(makelinks=1, target_root=None, prev_mtimes=None, contents=None): if not libdir_contents_changed: makelinks = False + ldconfig = EPREFIX+"/sbin/ldconfig" + if "CHOST" in env and "CBUILD" in env and \ + env["CHOST"] != env["CBUILD"]: + from portage.process import find_binary + ldconfig = find_binary("%s-ldconfig" % env["CHOST"]) + # Only run ldconfig as needed - if (ld_cache_update or makelinks): + if (ld_cache_update or makelinks) and ldconfig: # ldconfig has very different behaviour between FreeBSD and Linux if ostype=="Linux" or ostype.lower().endswith("gnu"): # We can't update links if we haven't cleaned other versions first, as @@ -662,14 +671,15 @@ def env_update(makelinks=1, target_root=None, prev_mtimes=None, contents=None): # we can safely create links. writemsg(">>> Regenerating %s/etc/ld.so.cache...\n" % (target_root+EPREFIX)) if makelinks: - commands.getstatusoutput("cd / ; "+EPREFIX+"/sbin/ldconfig -r '%s'" % target_root) + os.system("cd / ; %s -r '%s'" % (ldconfig, target_root)) else: - commands.getstatusoutput("cd / ; "+EPREFIX+"/sbin/ldconfig -X -r '%s'" % target_root) + os.system("cd / ; %s -X -r '%s'" % (ldconfig, target_root)) elif ostype in ("FreeBSD","DragonFly"): - writemsg(">>> Regenerating %svar/run/ld-elf.so.hints...\n" % target_root+EPREFIX+os.sep) - commands.getstatusoutput( - "cd / ; "+EPREFIX+"/sbin/ldconfig -elf -i -f '%s/var/run/ld-elf.so.hints' '%s/etc/ld.so.conf'" % \ - (target_root+EPREFIX, target_root+EPREFIX)) + writemsg(">>> Regenerating %svar/run/ld-elf.so.hints...\n" % \ + target_root+EPREFIX) + os.system(("cd / ; %s -elf -i " + \ + "-f '%svar/run/ld-elf.so.hints' '%setc/ld.so.conf'") % \ + (ldconfig, target_root+EPREFIX, target_root+EPREFIX)) del specials["LDPATH"] @@ -1705,6 +1715,7 @@ class config: cp = dep_getkey(mycpv) cpv_slot = self.mycpv pkginternaluse = "" + iuse = "" if mydb: slot, iuse = mydb.aux_get(self.mycpv, ["SLOT", "IUSE"]) cpv_slot = "%s:%s" % (self.mycpv, slot) @@ -1765,6 +1776,9 @@ class config: has_changed = True self.configdict["pkg"]["PKGUSE"] = self.puse[:] # For saving to PUSE file self.configdict["pkg"]["USE"] = self.puse[:] # this gets appended to USE + if iuse != self.configdict["pkg"].get("IUSE",""): + self.configdict["pkg"]["IUSE"] = iuse + has_changed = True # CATEGORY is essential for doebuild calls self.configdict["pkg"]["CATEGORY"] = mycpv.split("/")[0] if has_changed: @@ -2028,10 +2042,10 @@ class config: usesplit = [ x for x in myflags if \ x not in self.usemask] - usesplit.sort() - # Use the calculated USE flags to regenerate the USE_EXPAND flags so # that they are consistent. + iuse = self.configdict["pkg"].get("IUSE","").split() + iuse = [ x.lstrip("+-") for x in iuse ] for var in use_expand: prefix = var.lower() + "_" prefix_len = len(prefix) @@ -2042,11 +2056,34 @@ class config: # like LINGUAS. var_split = [ x for x in var_split if x in expand_flags ] var_split.extend(expand_flags.difference(var_split)) - if var_split or var in self: + if (var_split or var in self) and \ + "*" not in var_split: # Don't export empty USE_EXPAND vars unless the user config # exports them as empty. This is required for vars such as # LINGUAS, where unset and empty have different meanings. self[var] = " ".join(var_split) + elif "*" in var_split: + # * means to enable everything in IUSE that's not masked + filtered_split = [] + for x in var_split: + if x == "*": + continue + if (prefix + x) in iuse: + filtered_split.append(x) + var_split = filtered_split + for x in iuse: + if x.startswith(prefix) and x not in self.usemask: + suffix = x[prefix_len:] + if suffix in var_split: + continue + var_split.append(suffix) + usesplit.append(x) + if var_split: + self[var] = " ".join(var_split) + elif var in self: + # ebuild.sh will see this and unset the variable so + # that things like LINGUAS work properly + self[var] = "*" # Pre-Pend ARCH variable to USE settings so '-*' in env doesn't kill arch. if self.configdict["defaults"].has_key("ARCH"): @@ -2054,6 +2091,7 @@ class config: if self.configdict["defaults"]["ARCH"] not in usesplit: usesplit.insert(0,self.configdict["defaults"]["ARCH"]) + usesplit.sort() self.configlist[-1]["USE"]= " ".join(usesplit) self.already_in_regenerate = 0 @@ -3201,9 +3239,14 @@ def doebuild_environment(myebuild, mydo, myroot, mysettings, debug, use_cache, m if not eapi_is_supported(eapi): # can't do anything with this. raise portage.exception.UnsupportedAPIException(mycpv, eapi) - mysettings["PORTAGE_RESTRICT"] = " ".join(flatten( - portage.dep.use_reduce(portage.dep.paren_reduce( - mysettings["RESTRICT"]), uselist=mysettings["USE"].split()))) + try: + mysettings["PORTAGE_RESTRICT"] = " ".join(flatten( + portage.dep.use_reduce(portage.dep.paren_reduce( + mysettings.get("RESTRICT","")), + uselist=mysettings.get("USE","").split()))) + except portage.exception.InvalidDependString: + # RESTRICT is validated again inside doebuild, so let this go + mysettings["PORTAGE_RESTRICT"] = "" if mysplit[2] == "r0": mysettings["PVR"]=mysplit[1] @@ -4489,7 +4532,10 @@ def dep_check(depstring, mydbapi, mysettings, use="yes", mode=None, myuse=None, myusesplit=[] #convert parenthesis to sublists - mysplit = portage.dep.paren_reduce(depstring) + try: + mysplit = portage.dep.paren_reduce(depstring) + except portage.exception.InvalidDependString, e: + return [0, str(e)] mymasks = set() useforce = set() diff --git a/pym/portage/dbapi/bintree.py b/pym/portage/dbapi/bintree.py index 31086a5c5..7fb58748f 100644 --- a/pym/portage/dbapi/bintree.py +++ b/pym/portage/dbapi/bintree.py @@ -718,10 +718,19 @@ class binarytree(object): from portage.dep import paren_reduce, use_reduce, \ paren_normalize, paren_enclose for k in "LICENSE", "RDEPEND", "DEPEND", "PDEPEND", "PROVIDE": - deps = paren_reduce(d[k]) - deps = use_reduce(deps, uselist=use) - deps = paren_normalize(deps) - deps = paren_enclose(deps) + try: + deps = paren_reduce(d[k]) + deps = use_reduce(deps, uselist=use) + deps = paren_normalize(deps) + deps = paren_enclose(deps) + except portage.exception.InvalidDependString, e: + writemsg("%s: %s\n" % (k, str(e)), + noiselevel=-1) + del e + writemsg("!!! Invalid binary package: '%s'\n" % \ + self.getname(cpv), noiselevel=-1) + self.dbapi.cpv_remove(cpv) + return if deps: d[k] = deps else: diff --git a/pym/portage/dbapi/porttree.py b/pym/portage/dbapi/porttree.py index 9a26e038e..0f23ce70e 100644 --- a/pym/portage/dbapi/porttree.py +++ b/pym/portage/dbapi/porttree.py @@ -648,10 +648,10 @@ class portdbapi(dbapi): accept_keywords = self.mysettings["ACCEPT_KEYWORDS"].split() pkgdict = self.mysettings.pkeywordsdict - aux_keys = ["KEYWORDS", "LICENSE", "EAPI"] + aux_keys = ["KEYWORDS", "LICENSE", "EAPI", "SLOT"] for mycpv in mylist: try: - keys, licenses, eapi = self.aux_get(mycpv, aux_keys) + keys, licenses, eapi, slot = self.aux_get(mycpv, aux_keys) except KeyError: continue except PortageException, e: @@ -666,7 +666,8 @@ class portdbapi(dbapi): match=0 cp = dep_getkey(mycpv) if pkgdict.has_key(cp): - matches = match_to_list(mycpv, pkgdict[cp].keys()) + cpv_slot = "%s:%s" % (mycpv, slot) + matches = match_to_list(cpv_slot, pkgdict[cp].keys()) for atom in matches: pgroups.extend(pkgdict[cp][atom]) if matches: diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 445d0d229..850b5c43f 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -996,6 +996,17 @@ class dblink(object): before and after this method. """ + # When new_contents is supplied, the security check has already been + # done for this slot, so it shouldn't be repeated until the next + # replacement or unmerge operation. + if new_contents is None: + slot = self.vartree.dbapi.aux_get(self.mycpv, ["SLOT"])[0] + slot_matches = self.vartree.dbapi.match( + "%s:%s" % (dep_getkey(self.mycpv), slot)) + retval = self._security_check(slot_matches) + if retval: + return retval + contents = self.getcontents() # Now, don't assume that the name of the ebuild is the same as the # name of the dir; the package may have been moved. @@ -1091,7 +1102,7 @@ class dblink(object): del e unlockdir(catdir_lock) env_update(target_root=self.myroot, prev_mtimes=ldpath_mtimes, - contents=contents) + contents=contents, env=self.settings.environ()) return os.EX_OK def _unmerge_pkgfiles(self, pkgfiles, new_contents=None): @@ -1215,12 +1226,9 @@ class dblink(object): writemsg_stdout("--- !md5 %s %s\n" % ("obj", obj)) continue try: - if statobj.st_mode & (stat.S_ISUID | stat.S_ISGID): - # Always blind chmod 0 before unlinking to avoid race conditions. - os.chmod(obj, 0000) - if statobj.st_nlink > 1: - writemsg("setXid: "+str(statobj.st_nlink-1)+ \ - " hardlinks to '%s'\n" % obj) + # Remove permissions to ensure that any hardlinks to + # suid/sgid files are rendered harmless. + os.chmod(obj, 0) os.unlink(obj) except (OSError, IOError), e: pass @@ -1458,6 +1466,48 @@ class dblink(object): except OSError: pass + def _security_check(self, slot_matches): + if not slot_matches: + return 0 + file_paths = set() + for cpv in slot_matches: + file_paths.update(dblink(self.cat, catsplit(cpv)[1], + self.vartree.root, self.settings, + vartree=self.vartree).getcontents()) + inode_map = {} + for path in file_paths: + try: + s = os.lstat(path) + except OSError, e: + if e.errno != errno.ENOENT: + raise + del e + continue + if stat.S_ISREG(s.st_mode) and \ + s.st_nlink > 1 and \ + s.st_mode & (stat.S_ISUID | stat.S_ISGID): + k = (s.st_dev, s.st_ino) + inode_map.setdefault(k, []).append((path, s)) + suspicious_hardlinks = [] + for path_list in inode_map.itervalues(): + path, s = path_list[0] + if len(path_list) == s.st_nlink: + # All hardlinks seem to be owned by this package. + continue + suspicious_hardlinks.append(path_list) + if not suspicious_hardlinks: + return 0 + from portage.output import colorize + prefix = colorize("SECURITY_WARN", "*") + " WARNING: " + writemsg(prefix + "suid/sgid file(s) " + \ + "with suspicious hardlink(s):\n", noiselevel=-1) + for path_list in suspicious_hardlinks: + for path, s in path_list: + writemsg(prefix + " '%s'\n" % path, noiselevel=-1) + writemsg(prefix + "See the Gentoo Security Handbook " + \ + "guide for advice on how to proceed.\n", noiselevel=-1) + return 1 + def treewalk(self, srcroot, destroot, inforoot, myebuild, cleanup=0, mydbapi=None, prev_mtimes=None): """ @@ -1507,6 +1557,10 @@ class dblink(object): slot_matches = self.vartree.dbapi.match( "%s:%s" % (self.mysplit[0], self.settings["SLOT"])) + retval = self._security_check(slot_matches) + if retval: + return retval + if slot_matches: # Used by self.isprotected(). max_cpv = None @@ -1692,7 +1746,7 @@ class dblink(object): #update environment settings, library paths. DO NOT change symlinks. env_update(makelinks=(not downgrade), target_root=self.settings["ROOT"], prev_mtimes=prev_mtimes, - contents=contents) + contents=contents, env=self.settings.environ()) #dircache may break autoclean because it remembers the -MERGING-pkg file global dircache if dircache.has_key(self.dbcatdir): @@ -1777,12 +1831,14 @@ class dblink(object): # handy variables; mydest is the target object on the live filesystems; # mysrc is the source object in the temporary install dir try: - mydmode = os.lstat(mydest).st_mode + mydstat = os.lstat(mydest) + mydmode = mydstat.st_mode except OSError, e: if e.errno != errno.ENOENT: raise del e #dest file doesn't exist + mydstat = None mydmode = None if stat.S_ISLNK(mymode): @@ -1902,6 +1958,7 @@ class dblink(object): mydestdir = os.path.dirname(mydest) moveme = 1 zing = "!!!" + mymtime = None if mydmode != None: # destination file exists if stat.S_ISDIR(mydmode): @@ -1925,9 +1982,11 @@ class dblink(object): """ An identical update has previously been merged. Skip it unless the user has chosen --noconfmem.""" - zing = "-o-" moveme = cfgfiledict["IGNORE"] cfgprot = cfgfiledict["IGNORE"] + if not moveme: + zing = "-o-" + mymtime = long(mystat.st_mtime) else: moveme = 1 cfgprot = 1 diff --git a/pym/portage/dep.py b/pym/portage/dep.py index 34291c006..235829ba2 100644 --- a/pym/portage/dep.py +++ b/pym/portage/dep.py @@ -89,13 +89,20 @@ def paren_reduce(mystr,tokenize=1): """ mylist = [] while mystr: - if ("(" not in mystr) and (")" not in mystr): + left_paren = mystr.find("(") + has_left_paren = left_paren != -1 + right_paren = mystr.find(")") + has_right_paren = right_paren != -1 + if not has_left_paren and not has_right_paren: freesec = mystr subsec = None tail = "" elif mystr[0] == ")": return [mylist,mystr[1:]] - elif ("(" in mystr) and (mystr.index("(") < mystr.index(")")): + elif has_left_paren and not has_right_paren: + raise portage.exception.InvalidDependString( + "missing right parenthesis: '%s'" % mystr) + elif has_left_paren and left_paren < right_paren: freesec,subsec = mystr.split("(",1) subsec,tail = paren_reduce(subsec,tokenize) else: diff --git a/pym/portage/output.py b/pym/portage/output.py index 46fc77381..301832ccc 100644 --- a/pym/portage/output.py +++ b/pym/portage/output.py @@ -127,6 +127,7 @@ codes["BRACKET"] = codes["blue"] # Portage functions codes["INFORM"] = codes["darkgreen"] codes["UNMERGE_WARN"] = codes["red"] +codes["SECURITY_WARN"] = codes["red"] codes["MERGE_LIST_PROGRESS"] = codes["yellow"] def parse_color_map(): @@ -194,7 +195,18 @@ def xtermTitleReset(): if prompt_command == "": default_xterm_title = "" elif prompt_command is not None: - default_xterm_title = commands.getoutput(prompt_command) + if dotitles and "TERM" in os.environ and sys.stderr.isatty(): + from portage.process import find_binary, spawn + shell = os.environ.get("SHELL") + if not shell or not os.access(shell, os.EX_OK): + shell = find_binary("sh") + if shell: + spawn([shell, "-c", prompt_command], env=os.environ, + fdpipes={0:sys.stdin.fileno(),1:sys.stderr.fileno(), + 2:sys.stderr.fileno()}) + else: + os.system(prompt_command) + return else: pwd = os.getenv('PWD','') home = os.getenv('HOME', '') -- 2.26.2