]>
Commit | Line | Data |
---|---|---|
964aa579 | 1 | #!/usr/bin/python3 |
b792d887 MT |
2 | ############################################################################### |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2011 Pakfire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
47a4cb89 | 21 | |
0a9a2371 | 22 | import fcntl |
102c5ee7 | 23 | import hashlib |
c07a3ca7 | 24 | import math |
47a4cb89 MT |
25 | import os |
26 | import random | |
47a4cb89 | 27 | import shutil |
aa7f15fe | 28 | import signal |
47a4cb89 | 29 | import string |
0a9a2371 | 30 | import struct |
47a4cb89 | 31 | import sys |
0a9a2371 | 32 | import termios |
47a4cb89 MT |
33 | import time |
34 | ||
8b6bc023 MT |
35 | import logging |
36 | log = logging.getLogger("pakfire") | |
37 | ||
964aa579 MT |
38 | from .constants import * |
39 | from .i18n import _ | |
47a4cb89 | 40 | |
3c0e282f | 41 | # Import binary version of version_compare and capability functions |
964aa579 | 42 | from ._pakfire import version_compare, get_capabilities, set_capabilities, personality |
102c5ee7 | 43 | |
e9c20259 MT |
44 | def cli_is_interactive(): |
45 | """ | |
46 | Say weather a shell is interactive or not. | |
47 | """ | |
48 | if sys.stdin.isatty() and sys.stdout.isatty() and sys.stderr.isatty(): | |
49 | return True | |
50 | ||
51 | return False | |
52 | ||
c0fd807c MT |
53 | def ask_user(question): |
54 | """ | |
55 | Ask the user the question, he or she can answer with yes or no. | |
56 | ||
57 | This function returns True for "yes" and False for "no". | |
58 | ||
59 | If the software is running in a non-inteactive shell, no question | |
60 | is asked at all and the answer is always "yes". | |
61 | """ | |
62 | if not cli_is_interactive(): | |
63 | return True | |
64 | ||
964aa579 MT |
65 | print(_("%s [y/N]") % question, end=' ') |
66 | ret = input() | |
67 | print() # Just an empty line. | |
c0fd807c MT |
68 | |
69 | return ret in ("y", "Y", "z", "Z", "j", "J") | |
70 | ||
7c8f2953 MT |
71 | def random_string(length=20): |
72 | s = "" | |
73 | ||
74 | for i in range(length): | |
964aa579 | 75 | s += random.choice(string.ascii_letters) |
7c8f2953 MT |
76 | |
77 | return s | |
78 | ||
38ba21f9 | 79 | def make_progress(message, maxval, eta=True, speed=False): |
964aa579 MT |
80 | # XXX delay importing the progressbar module |
81 | # (because of a circular dependency) | |
82 | from . import progressbar | |
83 | ||
4496b160 MT |
84 | # Return nothing if stdout is not a terminal. |
85 | if not sys.stdout.isatty(): | |
86 | return | |
87 | ||
dccd65ea MT |
88 | if not maxval: |
89 | maxval = 1 | |
4496b160 | 90 | |
dccd65ea MT |
91 | pb = progressbar.ProgressBar(maxval) |
92 | pb.add("%-50s" % message) | |
38ba21f9 | 93 | |
dccd65ea MT |
94 | bar = progressbar.WidgetBar() |
95 | pb.add(bar) | |
1e80d5d7 | 96 | |
dccd65ea MT |
97 | if speed: |
98 | percentage = progressbar.WidgetPercentage() | |
99 | pb.add(percentage) | |
4496b160 | 100 | |
dccd65ea MT |
101 | filetransfer = progressbar.WidgetFileTransferSpeed() |
102 | pb.add(filetransfer) | |
4496b160 | 103 | |
dccd65ea MT |
104 | if eta: |
105 | eta = progressbar.WidgetETA() | |
106 | pb.add(eta) | |
107 | ||
108 | return pb.start() | |
4496b160 | 109 | |
47a4cb89 MT |
110 | def rm(path, *args, **kargs): |
111 | """ | |
112 | version of shutil.rmtree that ignores no-such-file-or-directory errors, | |
113 | and tries harder if it finds immutable files | |
114 | """ | |
115 | tryAgain = 1 | |
116 | failedFilename = None | |
117 | while tryAgain: | |
118 | tryAgain = 0 | |
119 | try: | |
120 | shutil.rmtree(path, *args, **kargs) | |
964aa579 | 121 | except OSError as e: |
47a4cb89 MT |
122 | if e.errno == 2: # no such file or directory |
123 | pass | |
124 | elif e.errno==1 or e.errno==13: | |
125 | tryAgain = 1 | |
126 | if failedFilename == e.filename: | |
127 | raise | |
128 | failedFilename = e.filename | |
129 | os.system("chattr -R -i %s" % path) | |
130 | else: | |
131 | raise | |
0a9a2371 MT |
132 | |
133 | def ioctl_GWINSZ(fd): | |
134 | try: | |
dccd65ea | 135 | return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")) |
0a9a2371 | 136 | except: |
dccd65ea | 137 | pass |
0a9a2371 MT |
138 | |
139 | def terminal_size(): | |
140 | cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) | |
141 | ||
142 | if not cr: | |
143 | try: | |
144 | fd = os.open(os.ctermid(), os.O_RDONLY) | |
145 | cr = ioctl_GWINSZ(fd) | |
146 | os.close(fd) | |
147 | except: | |
148 | pass | |
149 | ||
150 | if not cr: | |
151 | try: | |
152 | cr = (os.environ['LINES'], os.environ['COLUMNS']) | |
153 | except: | |
154 | cr = (25, 80) | |
155 | ||
156 | return int(cr[1]), int(cr[0]) | |
102c5ee7 MT |
157 | |
158 | def format_size(s): | |
159 | sign = 1 | |
160 | ||
161 | # If s is negative, we save the sign and run the calculation with the | |
162 | # absolute value of s. | |
163 | if s < 0: | |
164 | sign = -1 | |
165 | s = -1 * s | |
166 | ||
167 | units = (" ", "k", "M", "G", "T") | |
168 | unit = 0 | |
169 | ||
170 | while s >= 1024 and unit < len(units): | |
171 | s /= 1024 | |
172 | unit += 1 | |
173 | ||
c62d93f1 | 174 | return "%d%s" % (round(s) * sign, units[unit]) |
102c5ee7 MT |
175 | |
176 | def format_time(s): | |
177 | return "%02d:%02d" % (s // 60, s % 60) | |
178 | ||
179 | def format_speed(s): | |
180 | return "%sB/s" % format_size(s) | |
181 | ||
182 | def calc_hash1(filename=None, data=None): | |
e3bcfd23 | 183 | h = hashlib.new("sha1") |
102c5ee7 MT |
184 | |
185 | if filename: | |
186 | f = open(filename) | |
187 | buf = f.read(BUFFER_SIZE) | |
188 | while buf: | |
189 | h.update(buf) | |
190 | buf = f.read(BUFFER_SIZE) | |
191 | ||
192 | f.close() | |
193 | ||
194 | elif data: | |
195 | h.update(data) | |
196 | ||
197 | return h.hexdigest() | |
198 | ||
199 | def text_wrap(s, length=65): | |
c07a3ca7 MT |
200 | if not s: |
201 | return "" | |
202 | ||
203 | lines = [] | |
204 | ||
205 | words = [] | |
206 | for line in s.splitlines(): | |
207 | if not line: | |
208 | words.append("") | |
209 | else: | |
210 | words += line.split() | |
211 | ||
212 | line = [] | |
213 | while words: | |
214 | word = words.pop(0) | |
215 | ||
216 | # An empty words means a line break. | |
217 | if not word: | |
218 | if line: | |
219 | lines.append(" ".join(line)) | |
220 | lines.append("") | |
221 | line = [] | |
222 | ||
223 | else: | |
224 | if len(" ".join(line)) + len(word) >= length: | |
225 | lines.append(" ".join(line)) | |
226 | line = [] | |
227 | words.insert(0, word) | |
228 | else: | |
229 | line.append(word) | |
102c5ee7 | 230 | |
c07a3ca7 MT |
231 | if line: |
232 | lines.append(" ".join(line)) | |
102c5ee7 | 233 | |
c07a3ca7 | 234 | assert not words |
102c5ee7 | 235 | |
c07a3ca7 MT |
236 | #return "\n".join(lines) |
237 | return lines | |
aa7f15fe MT |
238 | |
239 | def orphans_kill(root, killsig=signal.SIGTERM): | |
240 | """ | |
241 | kill off anything that is still chrooted. | |
242 | """ | |
8b6bc023 | 243 | log.debug(_("Killing orphans...")) |
aa7f15fe | 244 | |
536438d9 | 245 | killed = False |
aa7f15fe MT |
246 | for fn in [d for d in os.listdir("/proc") if d.isdigit()]: |
247 | try: | |
248 | r = os.readlink("/proc/%s/root" % fn) | |
249 | if os.path.realpath(root) == os.path.realpath(r): | |
8b6bc023 | 250 | log.warning(_("Process ID %s is still running in chroot. Killing...") % fn) |
536438d9 | 251 | killed = True |
aa7f15fe MT |
252 | |
253 | pid = int(fn, 10) | |
254 | os.kill(pid, killsig) | |
255 | os.waitpid(pid, 0) | |
964aa579 | 256 | except OSError as e: |
aa7f15fe | 257 | pass |
c07a3ca7 | 258 | |
536438d9 MT |
259 | # If something was killed, wait a couple of seconds to make sure all file descriptors |
260 | # are closed and we can proceed with umounting the filesystems. | |
261 | if killed: | |
8b6bc023 | 262 | log.warning(_("Waiting for processes to terminate...")) |
536438d9 MT |
263 | time.sleep(3) |
264 | ||
7814c06d MT |
265 | # Calling ourself again to make sure all processes were killed. |
266 | orphans_kill(root, killsig=killsig) | |
267 | ||
c07a3ca7 MT |
268 | def scriptlet_interpreter(scriptlet): |
269 | """ | |
270 | This function returns the interpreter of a scriptlet. | |
271 | """ | |
272 | # XXX handle ELF? | |
273 | interpreter = None | |
274 | ||
275 | for line in scriptlet.splitlines(): | |
276 | if line.startswith("#!/"): | |
277 | interpreter = line[2:] | |
278 | interpreter = interpreter.split()[0] | |
279 | break | |
280 | ||
281 | return interpreter |