1 # archive_conf.py -- functionality common to archive-conf and dispatch-conf
2 # Copyright 2003-2011 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 # Library by Wayne Davison <gentoo@blorf.net>, derived from code
7 # written by Jeremy Wohl (http://igmus.org)
9 from __future__ import print_function
11 import os, sys, shutil
14 from portage.env.loaders import KeyValuePairFileLoader
15 from portage.localization import _
18 RCS_LOCK = 'rcs -ko -M -l'
19 RCS_PUT = 'ci -t-"Archived config file." -m"dispatch-conf update."'
21 RCS_MERGE = "rcsmerge -p -r" + RCS_BRANCH + " '%s' > '%s'"
23 DIFF3_MERGE = "diff3 -mE '%s' '%s' '%s' > '%s'"
25 def diffstatusoutput_len(cmd):
27 Execute the string cmd in a shell with getstatusoutput() and return a
28 2-tuple (status, output_length). If getstatusoutput() raises
29 UnicodeDecodeError (known to happen with python3.1), return a
30 2-tuple (1, 1). This provides a simple way to check for non-zero
31 output length of diff commands, while providing simple handling of
32 UnicodeDecodeError when necessary.
35 status, output = portage.subprocess_getstatusoutput(cmd)
36 return (status, len(output))
37 except UnicodeDecodeError:
40 def read_config(mandatory_opts):
41 loader = KeyValuePairFileLoader(
42 '/etc/dispatch-conf.conf', None)
43 opts, errors = loader.load()
45 print(_('dispatch-conf: Error reading /etc/dispatch-conf.conf; fatal'), file=sys.stderr)
48 # Handle quote removal here, since KeyValuePairFileLoader doesn't do that.
50 for k, v in opts.items():
51 if v[:1] in quotes and v[:1] == v[-1:]:
54 for key in mandatory_opts:
57 opts["merge"] = "sdiff --suppress-common-lines --output='%s' '%s' '%s'"
59 print(_('dispatch-conf: Missing option "%s" in /etc/dispatch-conf.conf; fatal') % (key,), file=sys.stderr)
61 if not os.path.exists(opts['archive-dir']):
62 os.mkdir(opts['archive-dir'])
63 # Use restrictive permissions by default, in order to protect
64 # against vulnerabilities (like bug #315603 involving rcs).
65 os.chmod(opts['archive-dir'], 0o700)
66 elif not os.path.isdir(opts['archive-dir']):
67 print(_('dispatch-conf: Config archive dir [%s] must exist; fatal') % (opts['archive-dir'],), file=sys.stderr)
73 def rcs_archive(archive, curconf, newconf, mrgconf):
74 """Archive existing config in rcs (on trunk). Then, if mrgconf is
75 specified and an old branch version exists, merge the user's changes
76 and the distributed changes and put the result into mrgconf. Lastly,
77 if newconf was specified, leave it in the archive dir with a .dist.new
78 suffix along with the last 1.1.1 branch version with a .dist suffix."""
81 os.makedirs(os.path.dirname(archive))
85 if os.path.isfile(curconf):
87 shutil.copy2(curconf, archive)
88 except(IOError, os.error) as why:
89 print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \
90 {"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
92 if os.path.exists(archive + ',v'):
93 os.system(RCS_LOCK + ' ' + archive)
94 os.system(RCS_PUT + ' ' + archive)
98 os.system(RCS_GET + ' -r' + RCS_BRANCH + ' ' + archive)
99 has_branch = os.path.exists(archive)
101 os.rename(archive, archive + '.dist')
104 shutil.copy2(newconf, archive)
105 except(IOError, os.error) as why:
106 print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \
107 {"newconf": newconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
111 # This puts the results of the merge into mrgconf.
112 ret = os.system(RCS_MERGE % (archive, mrgconf))
113 mystat = os.lstat(newconf)
114 os.chmod(mrgconf, mystat.st_mode)
115 os.chown(mrgconf, mystat.st_uid, mystat.st_gid)
116 os.rename(archive, archive + '.dist.new')
120 def file_archive(archive, curconf, newconf, mrgconf):
121 """Archive existing config to the archive-dir, bumping old versions
122 out of the way into .# versions (log-rotate style). Then, if mrgconf
123 was specified and there is a .dist version, merge the user's changes
124 and the distributed changes and put the result into mrgconf. Lastly,
125 if newconf was specified, archive it as a .dist.new version (which
126 gets moved to the .dist version at the end of the processing)."""
129 os.makedirs(os.path.dirname(archive))
133 # Archive the current config file if it isn't already saved
134 if os.path.exists(archive) \
135 and diffstatusoutput_len("diff -aq '%s' '%s'" % (curconf,archive))[1] != 0:
137 while suf < 9 and os.path.exists(archive + '.' + str(suf)):
141 os.rename(archive + '.' + str(suf-1), archive + '.' + str(suf))
144 os.rename(archive, archive + '.1')
146 if os.path.isfile(curconf):
148 shutil.copy2(curconf, archive)
149 except(IOError, os.error) as why:
150 print(_('dispatch-conf: Error copying %(curconf)s to %(archive)s: %(reason)s; fatal') % \
151 {"curconf": curconf, "archive": archive, "reason": str(why)}, file=sys.stderr)
154 # Save off new config file in the archive dir with .dist.new suffix
156 shutil.copy2(newconf, archive + '.dist.new')
157 except(IOError, os.error) as why:
158 print(_('dispatch-conf: Error copying %(newconf)s to %(archive)s: %(reason)s; fatal') % \
159 {"newconf": newconf, "archive": archive + '.dist.new', "reason": str(why)}, file=sys.stderr)
162 if mrgconf != '' and os.path.exists(archive + '.dist'):
163 # This puts the results of the merge into mrgconf.
164 ret = os.system(DIFF3_MERGE % (curconf, archive + '.dist', newconf, mrgconf))
165 mystat = os.lstat(newconf)
166 os.chmod(mrgconf, mystat.st_mode)
167 os.chown(mrgconf, mystat.st_uid, mystat.st_gid)
172 def rcs_archive_post_process(archive):
173 """Check in the archive file with the .dist.new suffix on the branch
174 and remove the one with the .dist suffix."""
175 os.rename(archive + '.dist.new', archive)
176 if os.path.exists(archive + '.dist'):
177 # Commit the last-distributed version onto the branch.
178 os.system(RCS_LOCK + RCS_BRANCH + ' ' + archive)
179 os.system(RCS_PUT + ' -r' + RCS_BRANCH + ' ' + archive)
180 os.unlink(archive + '.dist')
182 # Forcefully commit the last-distributed version onto the branch.
183 os.system(RCS_PUT + ' -f -r' + RCS_BRANCH + ' ' + archive)
186 def file_archive_post_process(archive):
187 """Rename the archive file with the .dist.new suffix to a .dist suffix"""
188 os.rename(archive + '.dist.new', archive + '.dist')