~smoser/ubuntu/quantal/cloud-init/sru

« back to all changes in this revision

Viewing changes to .pc/lp-1100545-allow-config-drive-from-cdrom.patch/cloudinit/util.py

  • Committer: Scott Moser
  • Date: 2013-01-18 15:28:08 UTC
  • Revision ID: smoser@ubuntu.com-20130118152808-jy5uq9pc79t82r85
Tags: 0.7.0-0ubuntu2.3~ppa0
debian/patches/lp-1100545-allow-config-drive-from-cdrom.patch:
in config-drive data to be provided from a CD-ROM (LP: #1100545)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vi: ts=4 expandtab
 
2
#
 
3
#    Copyright (C) 2012 Canonical Ltd.
 
4
#    Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
 
5
#    Copyright (C) 2012 Yahoo! Inc.
 
6
#
 
7
#    Author: Scott Moser <scott.moser@canonical.com>
 
8
#    Author: Juerg Haefliger <juerg.haefliger@hp.com>
 
9
#    Author: Joshua Harlow <harlowja@yahoo-inc.com>
 
10
#
 
11
#    This program is free software: you can redistribute it and/or modify
 
12
#    it under the terms of the GNU General Public License version 3, as
 
13
#    published by the Free Software Foundation.
 
14
#
 
15
#    This program is distributed in the hope that it will be useful,
 
16
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
#    GNU General Public License for more details.
 
19
#
 
20
#    You should have received a copy of the GNU General Public License
 
21
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
22
#
 
23
# pylint: disable=C0302
 
24
 
 
25
from StringIO import StringIO
 
26
 
 
27
import contextlib
 
28
import copy as obj_copy
 
29
import errno
 
30
import glob
 
31
import grp
 
32
import gzip
 
33
import hashlib
 
34
import os
 
35
import platform
 
36
import pwd
 
37
import random
 
38
import shutil
 
39
import socket
 
40
import stat
 
41
import string  # pylint: disable=W0402
 
42
import subprocess
 
43
import sys
 
44
import tempfile
 
45
import time
 
46
import types
 
47
import urlparse
 
48
 
 
49
import yaml
 
50
 
 
51
from cloudinit import importer
 
52
from cloudinit import log as logging
 
53
from cloudinit import safeyaml
 
54
from cloudinit import url_helper as uhelp
 
55
 
 
56
from cloudinit.settings import (CFG_BUILTIN)
 
57
 
 
58
 
 
59
_DNS_REDIRECT_IP = None
 
60
LOG = logging.getLogger(__name__)
 
61
 
 
62
# Helps cleanup filenames to ensure they aren't FS incompatible
 
63
FN_REPLACEMENTS = {
 
64
    os.sep: '_',
 
65
}
 
66
FN_ALLOWED = ('_-.()' + string.digits + string.ascii_letters)
 
67
 
 
68
# Helper utils to see if running in a container
 
69
CONTAINER_TESTS = ['running-in-container', 'lxc-is-container']
 
70
 
 
71
 
 
72
class ProcessExecutionError(IOError):
 
73
 
 
74
    MESSAGE_TMPL = ('%(description)s\n'
 
75
                    'Command: %(cmd)s\n'
 
76
                    'Exit code: %(exit_code)s\n'
 
77
                    'Reason: %(reason)s\n'
 
78
                    'Stdout: %(stdout)r\n'
 
79
                    'Stderr: %(stderr)r')
 
80
 
 
81
    def __init__(self, stdout=None, stderr=None,
 
82
                 exit_code=None, cmd=None,
 
83
                 description=None, reason=None):
 
84
        if not cmd:
 
85
            self.cmd = '-'
 
86
        else:
 
87
            self.cmd = cmd
 
88
 
 
89
        if not description:
 
90
            self.description = 'Unexpected error while running command.'
 
91
        else:
 
92
            self.description = description
 
93
 
 
94
        if not isinstance(exit_code, (long, int)):
 
95
            self.exit_code = '-'
 
96
        else:
 
97
            self.exit_code = exit_code
 
98
 
 
99
        if not stderr:
 
100
            self.stderr = ''
 
101
        else:
 
102
            self.stderr = stderr
 
103
 
 
104
        if not stdout:
 
105
            self.stdout = ''
 
106
        else:
 
107
            self.stdout = stdout
 
108
 
 
109
        if reason:
 
110
            self.reason = reason
 
111
        else:
 
112
            self.reason = '-'
 
113
 
 
114
        message = self.MESSAGE_TMPL % {
 
115
            'description': self.description,
 
116
            'cmd': self.cmd,
 
117
            'exit_code': self.exit_code,
 
118
            'stdout': self.stdout,
 
119
            'stderr': self.stderr,
 
120
            'reason': self.reason,
 
121
        }
 
122
        IOError.__init__(self, message)
 
123
 
 
124
 
 
125
class SeLinuxGuard(object):
 
126
    def __init__(self, path, recursive=False):
 
127
        # Late import since it might not always
 
128
        # be possible to use this
 
129
        try:
 
130
            self.selinux = importer.import_module('selinux')
 
131
        except ImportError:
 
132
            self.selinux = None
 
133
        self.path = path
 
134
        self.recursive = recursive
 
135
 
 
136
    def __enter__(self):
 
137
        if self.selinux:
 
138
            return True
 
139
        else:
 
140
            return False
 
141
 
 
142
    def __exit__(self, excp_type, excp_value, excp_traceback):
 
143
        if self.selinux:
 
144
            path = os.path.realpath(os.path.expanduser(self.path))
 
145
            do_restore = False
 
146
            try:
 
147
                # See if even worth restoring??
 
148
                stats = os.lstat(path)
 
149
                if stat.ST_MODE in stats:
 
150
                    self.selinux.matchpathcon(path, stats[stat.ST_MODE])
 
151
                    do_restore = True
 
152
            except OSError:
 
153
                pass
 
154
            if do_restore:
 
155
                LOG.debug("Restoring selinux mode for %s (recursive=%s)",
 
156
                          path, self.recursive)
 
157
                self.selinux.restorecon(path, recursive=self.recursive)
 
158
 
 
159
 
 
160
class MountFailedError(Exception):
 
161
    pass
 
162
 
 
163
 
 
164
class DecompressionError(Exception):
 
165
    pass
 
166
 
 
167
 
 
168
def ExtendedTemporaryFile(**kwargs):
 
169
    fh = tempfile.NamedTemporaryFile(**kwargs)
 
170
    # Replace its unlink with a quiet version
 
171
    # that does not raise errors when the
 
172
    # file to unlink has been unlinked elsewhere..
 
173
    LOG.debug("Created temporary file %s", fh.name)
 
174
    fh.unlink = del_file
 
175
 
 
176
    # Add a new method that will unlink
 
177
    # right 'now' but still lets the exit
 
178
    # method attempt to remove it (which will
 
179
    # not throw due to our del file being quiet
 
180
    # about files that are not there)
 
181
    def unlink_now():
 
182
        fh.unlink(fh.name)
 
183
 
 
184
    setattr(fh, 'unlink_now', unlink_now)
 
185
    return fh
 
186
 
 
187
 
 
188
def fork_cb(child_cb, *args):
 
189
    fid = os.fork()
 
190
    if fid == 0:
 
191
        try:
 
192
            child_cb(*args)
 
193
            os._exit(0)  # pylint: disable=W0212
 
194
        except:
 
195
            logexc(LOG, ("Failed forking and"
 
196
                         " calling callback %s"), obj_name(child_cb))
 
197
            os._exit(1)  # pylint: disable=W0212
 
198
    else:
 
199
        LOG.debug("Forked child %s who will run callback %s",
 
200
                  fid, obj_name(child_cb))
 
201
 
 
202
 
 
203
def is_true(val, addons=None):
 
204
    if isinstance(val, (bool)):
 
205
        return val is True
 
206
    check_set = ['true', '1', 'on', 'yes']
 
207
    if addons:
 
208
        check_set = check_set + addons
 
209
    if str(val).lower().strip() in check_set:
 
210
        return True
 
211
    return False
 
212
 
 
213
 
 
214
def is_false(val, addons=None):
 
215
    if isinstance(val, (bool)):
 
216
        return val is False
 
217
    check_set = ['off', '0', 'no', 'false']
 
218
    if addons:
 
219
        check_set = check_set + addons
 
220
    if str(val).lower().strip() in check_set:
 
221
        return True
 
222
    return False
 
223
 
 
224
 
 
225
def translate_bool(val, addons=None):
 
226
    if not val:
 
227
        # This handles empty lists and false and
 
228
        # other things that python believes are false
 
229
        return False
 
230
    # If its already a boolean skip
 
231
    if isinstance(val, (bool)):
 
232
        return val
 
233
    return is_true(val, addons)
 
234
 
 
235
 
 
236
def rand_str(strlen=32, select_from=None):
 
237
    if not select_from:
 
238
        select_from = string.letters + string.digits
 
239
    return "".join([random.choice(select_from) for _x in range(0, strlen)])
 
240
 
 
241
 
 
242
def read_conf(fname):
 
243
    try:
 
244
        return load_yaml(load_file(fname), default={})
 
245
    except IOError as e:
 
246
        if e.errno == errno.ENOENT:
 
247
            return {}
 
248
        else:
 
249
            raise
 
250
 
 
251
 
 
252
# Merges X lists, and then keeps the
 
253
# unique ones, but orders by sort order
 
254
# instead of by the original order
 
255
def uniq_merge_sorted(*lists):
 
256
    return sorted(uniq_merge(*lists))
 
257
 
 
258
 
 
259
# Merges X lists and then iterates over those
 
260
# and only keeps the unique items (order preserving)
 
261
# and returns that merged and uniqued list as the
 
262
# final result.
 
263
#
 
264
# Note: if any entry is a string it will be
 
265
# split on commas and empty entries will be
 
266
# evicted and merged in accordingly.
 
267
def uniq_merge(*lists):
 
268
    combined_list = []
 
269
    for a_list in lists:
 
270
        if isinstance(a_list, (str, basestring)):
 
271
            a_list = a_list.strip().split(",")
 
272
            # Kickout the empty ones
 
273
            a_list = [a for a in a_list if len(a)]
 
274
        combined_list.extend(a_list)
 
275
    uniq_list = []
 
276
    for i in combined_list:
 
277
        if i not in uniq_list:
 
278
            uniq_list.append(i)
 
279
    return uniq_list
 
280
 
 
281
 
 
282
def clean_filename(fn):
 
283
    for (k, v) in FN_REPLACEMENTS.iteritems():
 
284
        fn = fn.replace(k, v)
 
285
    removals = []
 
286
    for k in fn:
 
287
        if k not in FN_ALLOWED:
 
288
            removals.append(k)
 
289
    for k in removals:
 
290
        fn = fn.replace(k, '')
 
291
    fn = fn.strip()
 
292
    return fn
 
293
 
 
294
 
 
295
def decomp_gzip(data, quiet=True):
 
296
    try:
 
297
        buf = StringIO(str(data))
 
298
        with contextlib.closing(gzip.GzipFile(None, "rb", 1, buf)) as gh:
 
299
            return gh.read()
 
300
    except Exception as e:
 
301
        if quiet:
 
302
            return data
 
303
        else:
 
304
            raise DecompressionError(str(e))
 
305
 
 
306
 
 
307
def extract_usergroup(ug_pair):
 
308
    if not ug_pair:
 
309
        return (None, None)
 
310
    ug_parted = ug_pair.split(':', 1)
 
311
    u = ug_parted[0].strip()
 
312
    if len(ug_parted) == 2:
 
313
        g = ug_parted[1].strip()
 
314
    else:
 
315
        g = None
 
316
    if not u or u == "-1" or u.lower() == "none":
 
317
        u = None
 
318
    if not g or g == "-1" or g.lower() == "none":
 
319
        g = None
 
320
    return (u, g)
 
321
 
 
322
 
 
323
def find_modules(root_dir):
 
324
    entries = dict()
 
325
    for fname in glob.glob(os.path.join(root_dir, "*.py")):
 
326
        if not os.path.isfile(fname):
 
327
            continue
 
328
        modname = os.path.basename(fname)[0:-3]
 
329
        modname = modname.strip()
 
330
        if modname and modname.find(".") == -1:
 
331
            entries[fname] = modname
 
332
    return entries
 
333
 
 
334
 
 
335
def multi_log(text, console=True, stderr=True,
 
336
              log=None, log_level=logging.DEBUG):
 
337
    if stderr:
 
338
        sys.stderr.write(text)
 
339
    if console:
 
340
        # Don't use the write_file since
 
341
        # this might be 'sensitive' info (not debug worthy?)
 
342
        with open('/dev/console', 'wb') as wfh:
 
343
            wfh.write(text)
 
344
            wfh.flush()
 
345
    if log:
 
346
        if text[-1] == "\n":
 
347
            log.log(log_level, text[:-1])
 
348
        else:
 
349
            log.log(log_level, text)
 
350
 
 
351
 
 
352
def is_ipv4(instr):
 
353
    """determine if input string is a ipv4 address. return boolean."""
 
354
    toks = instr.split('.')
 
355
    if len(toks) != 4:
 
356
        return False
 
357
 
 
358
    try:
 
359
        toks = [x for x in toks if (int(x) < 256 and int(x) > 0)]
 
360
    except:
 
361
        return False
 
362
 
 
363
    return (len(toks) == 4)
 
364
 
 
365
 
 
366
def get_cfg_option_bool(yobj, key, default=False):
 
367
    if key not in yobj:
 
368
        return default
 
369
    return translate_bool(yobj[key])
 
370
 
 
371
 
 
372
def get_cfg_option_str(yobj, key, default=None):
 
373
    if key not in yobj:
 
374
        return default
 
375
    val = yobj[key]
 
376
    if not isinstance(val, (str, basestring)):
 
377
        val = str(val)
 
378
    return val
 
379
 
 
380
 
 
381
def system_info():
 
382
    return {
 
383
        'platform': platform.platform(),
 
384
        'release': platform.release(),
 
385
        'python': platform.python_version(),
 
386
        'uname': platform.uname(),
 
387
    }
 
388
 
 
389
 
 
390
def get_cfg_option_list(yobj, key, default=None):
 
391
    """
 
392
    Gets the C{key} config option from C{yobj} as a list of strings. If the
 
393
    key is present as a single string it will be returned as a list with one
 
394
    string arg.
 
395
 
 
396
    @param yobj: The configuration object.
 
397
    @param key: The configuration key to get.
 
398
    @param default: The default to return if key is not found.
 
399
    @return: The configuration option as a list of strings or default if key
 
400
        is not found.
 
401
    """
 
402
    if not key in yobj:
 
403
        return default
 
404
    if yobj[key] is None:
 
405
        return []
 
406
    val = yobj[key]
 
407
    if isinstance(val, (list)):
 
408
        # Should we ensure they are all strings??
 
409
        cval = [str(v) for v in val]
 
410
        return cval
 
411
    if not isinstance(val, (str, basestring)):
 
412
        val = str(val)
 
413
    return [val]
 
414
 
 
415
 
 
416
# get a cfg entry by its path array
 
417
# for f['a']['b']: get_cfg_by_path(mycfg,('a','b'))
 
418
def get_cfg_by_path(yobj, keyp, default=None):
 
419
    cur = yobj
 
420
    for tok in keyp:
 
421
        if tok not in cur:
 
422
            return default
 
423
        cur = cur[tok]
 
424
    return cur
 
425
 
 
426
 
 
427
def fixup_output(cfg, mode):
 
428
    (outfmt, errfmt) = get_output_cfg(cfg, mode)
 
429
    redirect_output(outfmt, errfmt)
 
430
    return (outfmt, errfmt)
 
431
 
 
432
 
 
433
# redirect_output(outfmt, errfmt, orig_out, orig_err)
 
434
#  replace orig_out and orig_err with filehandles specified in outfmt or errfmt
 
435
#  fmt can be:
 
436
#   > FILEPATH
 
437
#   >> FILEPATH
 
438
#   | program [ arg1 [ arg2 [ ... ] ] ]
 
439
#
 
440
#   with a '|', arguments are passed to shell, so one level of
 
441
#   shell escape is required.
 
442
#
 
443
#   if _CLOUD_INIT_SAVE_STDOUT is set in environment to a non empty and true
 
444
#   value then output input will not be closed (useful for debugging).
 
445
#
 
446
def redirect_output(outfmt, errfmt, o_out=None, o_err=None):
 
447
 
 
448
    if is_true(os.environ.get("_CLOUD_INIT_SAVE_STDOUT")):
 
449
        LOG.debug("Not redirecting output due to _CLOUD_INIT_SAVE_STDOUT")
 
450
        return
 
451
 
 
452
    if not o_out:
 
453
        o_out = sys.stdout
 
454
    if not o_err:
 
455
        o_err = sys.stderr
 
456
 
 
457
    if outfmt:
 
458
        LOG.debug("Redirecting %s to %s", o_out, outfmt)
 
459
        (mode, arg) = outfmt.split(" ", 1)
 
460
        if mode == ">" or mode == ">>":
 
461
            owith = "ab"
 
462
            if mode == ">":
 
463
                owith = "wb"
 
464
            new_fp = open(arg, owith)
 
465
        elif mode == "|":
 
466
            proc = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE)
 
467
            new_fp = proc.stdin
 
468
        else:
 
469
            raise TypeError("Invalid type for output format: %s" % outfmt)
 
470
 
 
471
        if o_out:
 
472
            os.dup2(new_fp.fileno(), o_out.fileno())
 
473
 
 
474
        if errfmt == outfmt:
 
475
            LOG.debug("Redirecting %s to %s", o_err, outfmt)
 
476
            os.dup2(new_fp.fileno(), o_err.fileno())
 
477
            return
 
478
 
 
479
    if errfmt:
 
480
        LOG.debug("Redirecting %s to %s", o_err, errfmt)
 
481
        (mode, arg) = errfmt.split(" ", 1)
 
482
        if mode == ">" or mode == ">>":
 
483
            owith = "ab"
 
484
            if mode == ">":
 
485
                owith = "wb"
 
486
            new_fp = open(arg, owith)
 
487
        elif mode == "|":
 
488
            proc = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE)
 
489
            new_fp = proc.stdin
 
490
        else:
 
491
            raise TypeError("Invalid type for error format: %s" % errfmt)
 
492
 
 
493
        if o_err:
 
494
            os.dup2(new_fp.fileno(), o_err.fileno())
 
495
 
 
496
 
 
497
def make_url(scheme, host, port=None,
 
498
                path='', params='', query='', fragment=''):
 
499
 
 
500
    pieces = []
 
501
    pieces.append(scheme or '')
 
502
 
 
503
    netloc = ''
 
504
    if host:
 
505
        netloc = str(host)
 
506
 
 
507
    if port is not None:
 
508
        netloc += ":" + "%s" % (port)
 
509
 
 
510
    pieces.append(netloc or '')
 
511
    pieces.append(path or '')
 
512
    pieces.append(params or '')
 
513
    pieces.append(query or '')
 
514
    pieces.append(fragment or '')
 
515
 
 
516
    return urlparse.urlunparse(pieces)
 
517
 
 
518
 
 
519
def obj_name(obj):
 
520
    if isinstance(obj, (types.TypeType,
 
521
                        types.ModuleType,
 
522
                        types.FunctionType,
 
523
                        types.LambdaType)):
 
524
        return str(obj.__name__)
 
525
    return obj_name(obj.__class__)
 
526
 
 
527
 
 
528
def mergemanydict(srcs, reverse=False):
 
529
    if reverse:
 
530
        srcs = reversed(srcs)
 
531
    m_cfg = {}
 
532
    for a_cfg in srcs:
 
533
        if a_cfg:
 
534
            m_cfg = mergedict(m_cfg, a_cfg)
 
535
    return m_cfg
 
536
 
 
537
 
 
538
def mergedict(src, cand):
 
539
    """
 
540
    Merge values from C{cand} into C{src}.
 
541
    If C{src} has a key C{cand} will not override.
 
542
    Nested dictionaries are merged recursively.
 
543
    """
 
544
    if isinstance(src, dict) and isinstance(cand, dict):
 
545
        for (k, v) in cand.iteritems():
 
546
            if k not in src:
 
547
                src[k] = v
 
548
            else:
 
549
                src[k] = mergedict(src[k], v)
 
550
    return src
 
551
 
 
552
 
 
553
@contextlib.contextmanager
 
554
def chdir(ndir):
 
555
    curr = os.getcwd()
 
556
    try:
 
557
        os.chdir(ndir)
 
558
        yield ndir
 
559
    finally:
 
560
        os.chdir(curr)
 
561
 
 
562
 
 
563
@contextlib.contextmanager
 
564
def umask(n_msk):
 
565
    old = os.umask(n_msk)
 
566
    try:
 
567
        yield old
 
568
    finally:
 
569
        os.umask(old)
 
570
 
 
571
 
 
572
@contextlib.contextmanager
 
573
def tempdir(**kwargs):
 
574
    # This seems like it was only added in python 3.2
 
575
    # Make it since its useful...
 
576
    # See: http://bugs.python.org/file12970/tempdir.patch
 
577
    tdir = tempfile.mkdtemp(**kwargs)
 
578
    try:
 
579
        yield tdir
 
580
    finally:
 
581
        del_dir(tdir)
 
582
 
 
583
 
 
584
def center(text, fill, max_len):
 
585
    return '{0:{fill}{align}{size}}'.format(text, fill=fill,
 
586
                                            align="^", size=max_len)
 
587
 
 
588
 
 
589
def del_dir(path):
 
590
    LOG.debug("Recursively deleting %s", path)
 
591
    shutil.rmtree(path)
 
592
 
 
593
 
 
594
def runparts(dirp, skip_no_exist=True):
 
595
    if skip_no_exist and not os.path.isdir(dirp):
 
596
        return
 
597
 
 
598
    failed = []
 
599
    attempted = []
 
600
    for exe_name in sorted(os.listdir(dirp)):
 
601
        exe_path = os.path.join(dirp, exe_name)
 
602
        if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
 
603
            attempted.append(exe_path)
 
604
            try:
 
605
                subp([exe_path], capture=False)
 
606
            except ProcessExecutionError as e:
 
607
                logexc(LOG, "Failed running %s [%s]", exe_path, e.exit_code)
 
608
                failed.append(e)
 
609
 
 
610
    if failed and attempted:
 
611
        raise RuntimeError('Runparts: %s failures in %s attempted commands'
 
612
                           % (len(failed), len(attempted)))
 
613
 
 
614
 
 
615
# read_optional_seed
 
616
# returns boolean indicating success or failure (presense of files)
 
617
# if files are present, populates 'fill' dictionary with 'user-data' and
 
618
# 'meta-data' entries
 
619
def read_optional_seed(fill, base="", ext="", timeout=5):
 
620
    try:
 
621
        (md, ud) = read_seeded(base, ext, timeout)
 
622
        fill['user-data'] = ud
 
623
        fill['meta-data'] = md
 
624
        return True
 
625
    except OSError as e:
 
626
        if e.errno == errno.ENOENT:
 
627
            return False
 
628
        raise
 
629
 
 
630
 
 
631
def read_file_or_url(url, timeout=5, retries=10, file_retries=0):
 
632
    if url.startswith("/"):
 
633
        url = "file://%s" % url
 
634
    if url.startswith("file://"):
 
635
        retries = file_retries
 
636
    return uhelp.readurl(url, timeout=timeout, retries=retries)
 
637
 
 
638
 
 
639
def load_yaml(blob, default=None, allowed=(dict,)):
 
640
    loaded = default
 
641
    try:
 
642
        blob = str(blob)
 
643
        LOG.debug(("Attempting to load yaml from string "
 
644
                 "of length %s with allowed root types %s"),
 
645
                 len(blob), allowed)
 
646
        converted = safeyaml.load(blob)
 
647
        if not isinstance(converted, allowed):
 
648
            # Yes this will just be caught, but thats ok for now...
 
649
            raise TypeError(("Yaml load allows %s root types,"
 
650
                             " but got %s instead") %
 
651
                            (allowed, obj_name(converted)))
 
652
        loaded = converted
 
653
    except (yaml.YAMLError, TypeError, ValueError):
 
654
        if len(blob) == 0:
 
655
            LOG.debug("load_yaml given empty string, returning default")
 
656
        else:
 
657
            logexc(LOG, "Failed loading yaml blob")
 
658
    return loaded
 
659
 
 
660
 
 
661
def read_seeded(base="", ext="", timeout=5, retries=10, file_retries=0):
 
662
    if base.startswith("/"):
 
663
        base = "file://%s" % base
 
664
 
 
665
    # default retries for file is 0. for network is 10
 
666
    if base.startswith("file://"):
 
667
        retries = file_retries
 
668
 
 
669
    if base.find("%s") >= 0:
 
670
        ud_url = base % ("user-data" + ext)
 
671
        md_url = base % ("meta-data" + ext)
 
672
    else:
 
673
        ud_url = "%s%s%s" % (base, "user-data", ext)
 
674
        md_url = "%s%s%s" % (base, "meta-data", ext)
 
675
 
 
676
    md_resp = read_file_or_url(md_url, timeout, retries, file_retries)
 
677
    md = None
 
678
    if md_resp.ok():
 
679
        md_str = str(md_resp)
 
680
        md = load_yaml(md_str, default={})
 
681
 
 
682
    ud_resp = read_file_or_url(ud_url, timeout, retries, file_retries)
 
683
    ud = None
 
684
    if ud_resp.ok():
 
685
        ud_str = str(ud_resp)
 
686
        ud = ud_str
 
687
 
 
688
    return (md, ud)
 
689
 
 
690
 
 
691
def read_conf_d(confd):
 
692
    # Get reverse sorted list (later trumps newer)
 
693
    confs = sorted(os.listdir(confd), reverse=True)
 
694
 
 
695
    # Remove anything not ending in '.cfg'
 
696
    confs = [f for f in confs if f.endswith(".cfg")]
 
697
 
 
698
    # Remove anything not a file
 
699
    confs = [f for f in confs
 
700
             if os.path.isfile(os.path.join(confd, f))]
 
701
 
 
702
    # Load them all so that they can be merged
 
703
    cfgs = []
 
704
    for fn in confs:
 
705
        cfgs.append(read_conf(os.path.join(confd, fn)))
 
706
 
 
707
    return mergemanydict(cfgs)
 
708
 
 
709
 
 
710
def read_conf_with_confd(cfgfile):
 
711
    cfg = read_conf(cfgfile)
 
712
 
 
713
    confd = False
 
714
    if "conf_d" in cfg:
 
715
        confd = cfg['conf_d']
 
716
        if confd:
 
717
            if not isinstance(confd, (str, basestring)):
 
718
                raise TypeError(("Config file %s contains 'conf_d' "
 
719
                                 "with non-string type %s") %
 
720
                                 (cfgfile, obj_name(confd)))
 
721
            else:
 
722
                confd = str(confd).strip()
 
723
    elif os.path.isdir("%s.d" % cfgfile):
 
724
        confd = "%s.d" % cfgfile
 
725
 
 
726
    if not confd or not os.path.isdir(confd):
 
727
        return cfg
 
728
 
 
729
    # Conf.d settings override input configuration
 
730
    confd_cfg = read_conf_d(confd)
 
731
    return mergedict(confd_cfg, cfg)
 
732
 
 
733
 
 
734
def read_cc_from_cmdline(cmdline=None):
 
735
    # this should support reading cloud-config information from
 
736
    # the kernel command line.  It is intended to support content of the
 
737
    # format:
 
738
    #  cc: <yaml content here> [end_cc]
 
739
    # this would include:
 
740
    # cc: ssh_import_id: [smoser, kirkland]\\n
 
741
    # cc: ssh_import_id: [smoser, bob]\\nruncmd: [ [ ls, -l ], echo hi ] end_cc
 
742
    # cc:ssh_import_id: [smoser] end_cc cc:runcmd: [ [ ls, -l ] ] end_cc
 
743
    if cmdline is None:
 
744
        cmdline = get_cmdline()
 
745
 
 
746
    tag_begin = "cc:"
 
747
    tag_end = "end_cc"
 
748
    begin_l = len(tag_begin)
 
749
    end_l = len(tag_end)
 
750
    clen = len(cmdline)
 
751
    tokens = []
 
752
    begin = cmdline.find(tag_begin)
 
753
    while begin >= 0:
 
754
        end = cmdline.find(tag_end, begin + begin_l)
 
755
        if end < 0:
 
756
            end = clen
 
757
        tokens.append(cmdline[begin + begin_l:end].lstrip().replace("\\n",
 
758
                                                                    "\n"))
 
759
 
 
760
        begin = cmdline.find(tag_begin, end + end_l)
 
761
 
 
762
    return '\n'.join(tokens)
 
763
 
 
764
 
 
765
def dos2unix(contents):
 
766
    # find first end of line
 
767
    pos = contents.find('\n')
 
768
    if pos <= 0 or contents[pos - 1] != '\r':
 
769
        return contents
 
770
    return contents.replace('\r\n', '\n')
 
771
 
 
772
 
 
773
def get_hostname_fqdn(cfg, cloud):
 
774
    # return the hostname and fqdn from 'cfg'.  If not found in cfg,
 
775
    # then fall back to data from cloud
 
776
    if "fqdn" in cfg:
 
777
        # user specified a fqdn.  Default hostname then is based off that
 
778
        fqdn = cfg['fqdn']
 
779
        hostname = get_cfg_option_str(cfg, "hostname", fqdn.split('.')[0])
 
780
    else:
 
781
        if "hostname" in cfg and cfg['hostname'].find('.') > 0:
 
782
            # user specified hostname, and it had '.' in it
 
783
            # be nice to them.  set fqdn and hostname from that
 
784
            fqdn = cfg['hostname']
 
785
            hostname = cfg['hostname'][:fqdn.find('.')]
 
786
        else:
 
787
            # no fqdn set, get fqdn from cloud.
 
788
            # get hostname from cfg if available otherwise cloud
 
789
            fqdn = cloud.get_hostname(fqdn=True)
 
790
            if "hostname" in cfg:
 
791
                hostname = cfg['hostname']
 
792
            else:
 
793
                hostname = cloud.get_hostname()
 
794
    return (hostname, fqdn)
 
795
 
 
796
 
 
797
def get_fqdn_from_hosts(hostname, filename="/etc/hosts"):
 
798
    """
 
799
    For each host a single line should be present with
 
800
      the following information:
 
801
 
 
802
        IP_address canonical_hostname [aliases...]
 
803
 
 
804
      Fields of the entry are separated by any number of  blanks  and/or  tab
 
805
      characters.  Text  from   a "#" character until the end of the line is a
 
806
      comment, and is ignored.   Host  names  may  contain  only  alphanumeric
 
807
      characters, minus signs ("-"), and periods (".").  They must begin with
 
808
      an  alphabetic  character  and  end  with  an  alphanumeric  character.
 
809
      Optional aliases provide for name changes, alternate spellings, shorter
 
810
      hostnames, or generic hostnames (for example, localhost).
 
811
    """
 
812
    fqdn = None
 
813
    try:
 
814
        for line in load_file(filename).splitlines():
 
815
            hashpos = line.find("#")
 
816
            if hashpos >= 0:
 
817
                line = line[0:hashpos]
 
818
            line = line.strip()
 
819
            if not line:
 
820
                continue
 
821
 
 
822
            # If there there is less than 3 entries
 
823
            # (IP_address, canonical_hostname, alias)
 
824
            # then ignore this line
 
825
            toks = line.split()
 
826
            if len(toks) < 3:
 
827
                continue
 
828
 
 
829
            if hostname in toks[2:]:
 
830
                fqdn = toks[1]
 
831
                break
 
832
    except IOError:
 
833
        pass
 
834
    return fqdn
 
835
 
 
836
 
 
837
def get_cmdline_url(names=('cloud-config-url', 'url'),
 
838
                    starts="#cloud-config", cmdline=None):
 
839
    if cmdline is None:
 
840
        cmdline = get_cmdline()
 
841
 
 
842
    data = keyval_str_to_dict(cmdline)
 
843
    url = None
 
844
    key = None
 
845
    for key in names:
 
846
        if key in data:
 
847
            url = data[key]
 
848
            break
 
849
 
 
850
    if not url:
 
851
        return (None, None, None)
 
852
 
 
853
    resp = uhelp.readurl(url)
 
854
    if resp.contents.startswith(starts) and resp.ok():
 
855
        return (key, url, str(resp))
 
856
 
 
857
    return (key, url, None)
 
858
 
 
859
 
 
860
def is_resolvable(name):
 
861
    """determine if a url is resolvable, return a boolean
 
862
    This also attempts to be resilent against dns redirection.
 
863
 
 
864
    Note, that normal nsswitch resolution is used here.  So in order
 
865
    to avoid any utilization of 'search' entries in /etc/resolv.conf
 
866
    we have to append '.'.
 
867
 
 
868
    The top level 'invalid' domain is invalid per RFC.  And example.com
 
869
    should also not exist.  The random entry will be resolved inside
 
870
    the search list.
 
871
    """
 
872
    global _DNS_REDIRECT_IP  # pylint: disable=W0603
 
873
    if _DNS_REDIRECT_IP is None:
 
874
        badips = set()
 
875
        badnames = ("does-not-exist.example.com.", "example.invalid.",
 
876
                    rand_str())
 
877
        badresults = {}
 
878
        for iname in badnames:
 
879
            try:
 
880
                result = socket.getaddrinfo(iname, None, 0, 0,
 
881
                    socket.SOCK_STREAM, socket.AI_CANONNAME)
 
882
                badresults[iname] = []
 
883
                for (_fam, _stype, _proto, cname, sockaddr) in result:
 
884
                    badresults[iname].append("%s: %s" % (cname, sockaddr[0]))
 
885
                    badips.add(sockaddr[0])
 
886
            except socket.gaierror:
 
887
                pass
 
888
        _DNS_REDIRECT_IP = badips
 
889
        if badresults:
 
890
            LOG.debug("detected dns redirection: %s" % badresults)
 
891
 
 
892
    try:
 
893
        result = socket.getaddrinfo(name, None)
 
894
        # check first result's sockaddr field
 
895
        addr = result[0][4][0]
 
896
        if addr in _DNS_REDIRECT_IP:
 
897
            return False
 
898
        return True
 
899
    except socket.gaierror:
 
900
        return False
 
901
 
 
902
 
 
903
def get_hostname():
 
904
    hostname = socket.gethostname()
 
905
    return hostname
 
906
 
 
907
 
 
908
def is_resolvable_url(url):
 
909
    """determine if this url is resolvable (existing or ip)."""
 
910
    return (is_resolvable(urlparse.urlparse(url).hostname))
 
911
 
 
912
 
 
913
def search_for_mirror(candidates):
 
914
    """
 
915
    Search through a list of mirror urls for one that works
 
916
    This needs to return quickly.
 
917
    """
 
918
    for cand in candidates:
 
919
        try:
 
920
            if is_resolvable_url(cand):
 
921
                return cand
 
922
        except Exception:
 
923
            pass
 
924
    return None
 
925
 
 
926
 
 
927
def close_stdin():
 
928
    """
 
929
    reopen stdin as /dev/null so even subprocesses or other os level things get
 
930
    /dev/null as input.
 
931
 
 
932
    if _CLOUD_INIT_SAVE_STDIN is set in environment to a non empty and true
 
933
    value then input will not be closed (useful for debugging).
 
934
    """
 
935
    if is_true(os.environ.get("_CLOUD_INIT_SAVE_STDIN")):
 
936
        return
 
937
    with open(os.devnull) as fp:
 
938
        os.dup2(fp.fileno(), sys.stdin.fileno())
 
939
 
 
940
 
 
941
def find_devs_with(criteria=None, oformat='device',
 
942
                    tag=None, no_cache=False, path=None):
 
943
    """
 
944
    find devices matching given criteria (via blkid)
 
945
    criteria can be *one* of:
 
946
      TYPE=<filesystem>
 
947
      LABEL=<label>
 
948
      UUID=<uuid>
 
949
    """
 
950
    blk_id_cmd = ['blkid']
 
951
    options = []
 
952
    if criteria:
 
953
        # Search for block devices with tokens named NAME that
 
954
        # have the value 'value' and display any devices which are found.
 
955
        # Common values for NAME include  TYPE, LABEL, and UUID.
 
956
        # If there are no devices specified on the command line,
 
957
        # all block devices will be searched; otherwise,
 
958
        # only search the devices specified by the user.
 
959
        options.append("-t%s" % (criteria))
 
960
    if tag:
 
961
        # For each (specified) device, show only the tags that match tag.
 
962
        options.append("-s%s" % (tag))
 
963
    if no_cache:
 
964
        # If you want to start with a clean cache
 
965
        # (i.e. don't report devices previously scanned
 
966
        # but not necessarily available at this time), specify /dev/null.
 
967
        options.extend(["-c", "/dev/null"])
 
968
    if oformat:
 
969
        # Display blkid's output using the specified format.
 
970
        # The format parameter may be:
 
971
        # full, value, list, device, udev, export
 
972
        options.append('-o%s' % (oformat))
 
973
    if path:
 
974
        options.append(path)
 
975
    cmd = blk_id_cmd + options
 
976
    # See man blkid for why 2 is added
 
977
    (out, _err) = subp(cmd, rcs=[0, 2])
 
978
    entries = []
 
979
    for line in out.splitlines():
 
980
        line = line.strip()
 
981
        if line:
 
982
            entries.append(line)
 
983
    return entries
 
984
 
 
985
 
 
986
def load_file(fname, read_cb=None, quiet=False):
 
987
    LOG.debug("Reading from %s (quiet=%s)", fname, quiet)
 
988
    ofh = StringIO()
 
989
    try:
 
990
        with open(fname, 'rb') as ifh:
 
991
            pipe_in_out(ifh, ofh, chunk_cb=read_cb)
 
992
    except IOError as e:
 
993
        if not quiet:
 
994
            raise
 
995
        if e.errno != errno.ENOENT:
 
996
            raise
 
997
    contents = ofh.getvalue()
 
998
    LOG.debug("Read %s bytes from %s", len(contents), fname)
 
999
    return contents
 
1000
 
 
1001
 
 
1002
def get_cmdline():
 
1003
    if 'DEBUG_PROC_CMDLINE' in os.environ:
 
1004
        cmdline = os.environ["DEBUG_PROC_CMDLINE"]
 
1005
    else:
 
1006
        try:
 
1007
            cmdline = load_file("/proc/cmdline").strip()
 
1008
        except:
 
1009
            cmdline = ""
 
1010
    return cmdline
 
1011
 
 
1012
 
 
1013
def pipe_in_out(in_fh, out_fh, chunk_size=1024, chunk_cb=None):
 
1014
    bytes_piped = 0
 
1015
    while True:
 
1016
        data = in_fh.read(chunk_size)
 
1017
        if data == '':
 
1018
            break
 
1019
        else:
 
1020
            out_fh.write(data)
 
1021
            bytes_piped += len(data)
 
1022
            if chunk_cb:
 
1023
                chunk_cb(bytes_piped)
 
1024
    out_fh.flush()
 
1025
    return bytes_piped
 
1026
 
 
1027
 
 
1028
def chownbyid(fname, uid=None, gid=None):
 
1029
    if uid in [None, -1] and gid in [None, -1]:
 
1030
        # Nothing to do
 
1031
        return
 
1032
    LOG.debug("Changing the ownership of %s to %s:%s", fname, uid, gid)
 
1033
    os.chown(fname, uid, gid)
 
1034
 
 
1035
 
 
1036
def chownbyname(fname, user=None, group=None):
 
1037
    uid = -1
 
1038
    gid = -1
 
1039
    try:
 
1040
        if user:
 
1041
            uid = pwd.getpwnam(user).pw_uid
 
1042
        if group:
 
1043
            gid = grp.getgrnam(group).gr_gid
 
1044
    except KeyError as e:
 
1045
        raise OSError("Unknown user or group: %s" % (e))
 
1046
    chownbyid(fname, uid, gid)
 
1047
 
 
1048
 
 
1049
# Always returns well formated values
 
1050
# cfg is expected to have an entry 'output' in it, which is a dictionary
 
1051
# that includes entries for 'init', 'config', 'final' or 'all'
 
1052
#   init: /var/log/cloud.out
 
1053
#   config: [ ">> /var/log/cloud-config.out", /var/log/cloud-config.err ]
 
1054
#   final:
 
1055
#     output: "| logger -p"
 
1056
#     error: "> /dev/null"
 
1057
# this returns the specific 'mode' entry, cleanly formatted, with value
 
1058
def get_output_cfg(cfg, mode):
 
1059
    ret = [None, None]
 
1060
    if not cfg or not 'output' in cfg:
 
1061
        return ret
 
1062
 
 
1063
    outcfg = cfg['output']
 
1064
    if mode in outcfg:
 
1065
        modecfg = outcfg[mode]
 
1066
    else:
 
1067
        if 'all' not in outcfg:
 
1068
            return ret
 
1069
        # if there is a 'all' item in the output list
 
1070
        # then it applies to all users of this (init, config, final)
 
1071
        modecfg = outcfg['all']
 
1072
 
 
1073
    # if value is a string, it specifies stdout and stderr
 
1074
    if isinstance(modecfg, str):
 
1075
        ret = [modecfg, modecfg]
 
1076
 
 
1077
    # if its a list, then we expect (stdout, stderr)
 
1078
    if isinstance(modecfg, list):
 
1079
        if len(modecfg) > 0:
 
1080
            ret[0] = modecfg[0]
 
1081
        if len(modecfg) > 1:
 
1082
            ret[1] = modecfg[1]
 
1083
 
 
1084
    # if it is a dictionary, expect 'out' and 'error'
 
1085
    # items, which indicate out and error
 
1086
    if isinstance(modecfg, dict):
 
1087
        if 'output' in modecfg:
 
1088
            ret[0] = modecfg['output']
 
1089
        if 'error' in modecfg:
 
1090
            ret[1] = modecfg['error']
 
1091
 
 
1092
    # if err's entry == "&1", then make it same as stdout
 
1093
    # as in shell syntax of "echo foo >/dev/null 2>&1"
 
1094
    if ret[1] == "&1":
 
1095
        ret[1] = ret[0]
 
1096
 
 
1097
    swlist = [">>", ">", "|"]
 
1098
    for i in range(len(ret)):
 
1099
        if not ret[i]:
 
1100
            continue
 
1101
        val = ret[i].lstrip()
 
1102
        found = False
 
1103
        for s in swlist:
 
1104
            if val.startswith(s):
 
1105
                val = "%s %s" % (s, val[len(s):].strip())
 
1106
                found = True
 
1107
                break
 
1108
        if not found:
 
1109
            # default behavior is append
 
1110
            val = "%s %s" % (">>", val.strip())
 
1111
        ret[i] = val
 
1112
 
 
1113
    return ret
 
1114
 
 
1115
 
 
1116
def logexc(log, msg, *args):
 
1117
    # Setting this here allows this to change
 
1118
    # levels easily (not always error level)
 
1119
    # or even desirable to have that much junk
 
1120
    # coming out to a non-debug stream
 
1121
    if msg:
 
1122
        log.warn(msg, *args)
 
1123
    # Debug gets the full trace
 
1124
    log.debug(msg, exc_info=1, *args)
 
1125
 
 
1126
 
 
1127
def hash_blob(blob, routine, mlen=None):
 
1128
    hasher = hashlib.new(routine)
 
1129
    hasher.update(blob)
 
1130
    digest = hasher.hexdigest()
 
1131
    # Don't get to long now
 
1132
    if mlen is not None:
 
1133
        return digest[0:mlen]
 
1134
    else:
 
1135
        return digest
 
1136
 
 
1137
 
 
1138
def is_user(name):
 
1139
    try:
 
1140
        if pwd.getpwnam(name):
 
1141
            return True
 
1142
    except KeyError:
 
1143
        return False
 
1144
 
 
1145
 
 
1146
def is_group(name):
 
1147
    try:
 
1148
        if grp.getgrnam(name):
 
1149
            return True
 
1150
    except KeyError:
 
1151
        return False
 
1152
 
 
1153
 
 
1154
def rename(src, dest):
 
1155
    LOG.debug("Renaming %s to %s", src, dest)
 
1156
    # TODO(harlowja) use a se guard here??
 
1157
    os.rename(src, dest)
 
1158
 
 
1159
 
 
1160
def ensure_dirs(dirlist, mode=0755):
 
1161
    for d in dirlist:
 
1162
        ensure_dir(d, mode)
 
1163
 
 
1164
 
 
1165
def read_write_cmdline_url(target_fn):
 
1166
    if not os.path.exists(target_fn):
 
1167
        try:
 
1168
            (key, url, content) = get_cmdline_url()
 
1169
        except:
 
1170
            logexc(LOG, "Failed fetching command line url")
 
1171
            return
 
1172
        try:
 
1173
            if key and content:
 
1174
                write_file(target_fn, content, mode=0600)
 
1175
                LOG.debug(("Wrote to %s with contents of command line"
 
1176
                          " url %s (len=%s)"), target_fn, url, len(content))
 
1177
            elif key and not content:
 
1178
                LOG.debug(("Command line key %s with url"
 
1179
                          " %s had no contents"), key, url)
 
1180
        except:
 
1181
            logexc(LOG, "Failed writing url content to %s", target_fn)
 
1182
 
 
1183
 
 
1184
def yaml_dumps(obj):
 
1185
    formatted = yaml.dump(obj,
 
1186
                    line_break="\n",
 
1187
                    indent=4,
 
1188
                    explicit_start=True,
 
1189
                    explicit_end=True,
 
1190
                    default_flow_style=False,
 
1191
                    )
 
1192
    return formatted
 
1193
 
 
1194
 
 
1195
def ensure_dir(path, mode=None):
 
1196
    if not os.path.isdir(path):
 
1197
        # Make the dir and adjust the mode
 
1198
        with SeLinuxGuard(os.path.dirname(path), recursive=True):
 
1199
            os.makedirs(path)
 
1200
        chmod(path, mode)
 
1201
    else:
 
1202
        # Just adjust the mode
 
1203
        chmod(path, mode)
 
1204
 
 
1205
 
 
1206
@contextlib.contextmanager
 
1207
def unmounter(umount):
 
1208
    try:
 
1209
        yield umount
 
1210
    finally:
 
1211
        if umount:
 
1212
            umount_cmd = ["umount", '-l', umount]
 
1213
            subp(umount_cmd)
 
1214
 
 
1215
 
 
1216
def mounts():
 
1217
    mounted = {}
 
1218
    try:
 
1219
        # Go through mounts to see what is already mounted
 
1220
        mount_locs = load_file("/proc/mounts").splitlines()
 
1221
        for mpline in mount_locs:
 
1222
            # Format at: man fstab
 
1223
            try:
 
1224
                (dev, mp, fstype, opts, _freq, _passno) = mpline.split()
 
1225
            except:
 
1226
                continue
 
1227
            # If the name of the mount point contains spaces these
 
1228
            # can be escaped as '\040', so undo that..
 
1229
            mp = mp.replace("\\040", " ")
 
1230
            mounted[dev] = {
 
1231
                'fstype': fstype,
 
1232
                'mountpoint': mp,
 
1233
                'opts': opts,
 
1234
            }
 
1235
        LOG.debug("Fetched %s mounts from %s", mounted, "/proc/mounts")
 
1236
    except (IOError, OSError):
 
1237
        logexc(LOG, "Failed fetching mount points from /proc/mounts")
 
1238
    return mounted
 
1239
 
 
1240
 
 
1241
def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True):
 
1242
    """
 
1243
    Mount the device, call method 'callback' passing the directory
 
1244
    in which it was mounted, then unmount.  Return whatever 'callback'
 
1245
    returned.  If data != None, also pass data to callback.
 
1246
    """
 
1247
    mounted = mounts()
 
1248
    with tempdir() as tmpd:
 
1249
        umount = False
 
1250
        if device in mounted:
 
1251
            mountpoint = mounted[device]['mountpoint']
 
1252
        else:
 
1253
            try:
 
1254
                mountcmd = ['mount']
 
1255
                mountopts = []
 
1256
                if rw:
 
1257
                    mountopts.append('rw')
 
1258
                else:
 
1259
                    mountopts.append('ro')
 
1260
                if sync:
 
1261
                    # This seems like the safe approach to do
 
1262
                    # (ie where this is on by default)
 
1263
                    mountopts.append("sync")
 
1264
                if mountopts:
 
1265
                    mountcmd.extend(["-o", ",".join(mountopts)])
 
1266
                if mtype:
 
1267
                    mountcmd.extend(['-t', mtype])
 
1268
                mountcmd.append(device)
 
1269
                mountcmd.append(tmpd)
 
1270
                subp(mountcmd)
 
1271
                umount = tmpd  # This forces it to be unmounted (when set)
 
1272
                mountpoint = tmpd
 
1273
            except (IOError, OSError) as exc:
 
1274
                raise MountFailedError(("Failed mounting %s "
 
1275
                                        "to %s due to: %s") %
 
1276
                                       (device, tmpd, exc))
 
1277
        # Be nice and ensure it ends with a slash
 
1278
        if not mountpoint.endswith("/"):
 
1279
            mountpoint += "/"
 
1280
        with unmounter(umount):
 
1281
            if data is None:
 
1282
                ret = callback(mountpoint)
 
1283
            else:
 
1284
                ret = callback(mountpoint, data)
 
1285
            return ret
 
1286
 
 
1287
 
 
1288
def get_builtin_cfg():
 
1289
    # Deep copy so that others can't modify
 
1290
    return obj_copy.deepcopy(CFG_BUILTIN)
 
1291
 
 
1292
 
 
1293
def sym_link(source, link):
 
1294
    LOG.debug("Creating symbolic link from %r => %r" % (link, source))
 
1295
    os.symlink(source, link)
 
1296
 
 
1297
 
 
1298
def del_file(path):
 
1299
    LOG.debug("Attempting to remove %s", path)
 
1300
    try:
 
1301
        os.unlink(path)
 
1302
    except OSError as e:
 
1303
        if e.errno != errno.ENOENT:
 
1304
            raise e
 
1305
 
 
1306
 
 
1307
def copy(src, dest):
 
1308
    LOG.debug("Copying %s to %s", src, dest)
 
1309
    shutil.copy(src, dest)
 
1310
 
 
1311
 
 
1312
def time_rfc2822():
 
1313
    try:
 
1314
        ts = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.gmtime())
 
1315
    except:
 
1316
        ts = "??"
 
1317
    return ts
 
1318
 
 
1319
 
 
1320
def uptime():
 
1321
    uptime_str = '??'
 
1322
    try:
 
1323
        contents = load_file("/proc/uptime").strip()
 
1324
        if contents:
 
1325
            uptime_str = contents.split()[0]
 
1326
    except:
 
1327
        logexc(LOG, "Unable to read uptime from /proc/uptime")
 
1328
    return uptime_str
 
1329
 
 
1330
 
 
1331
def append_file(path, content):
 
1332
    write_file(path, content, omode="ab", mode=None)
 
1333
 
 
1334
 
 
1335
def ensure_file(path, mode=0644):
 
1336
    write_file(path, content='', omode="ab", mode=mode)
 
1337
 
 
1338
 
 
1339
def safe_int(possible_int):
 
1340
    try:
 
1341
        return int(possible_int)
 
1342
    except (ValueError, TypeError):
 
1343
        return None
 
1344
 
 
1345
 
 
1346
def chmod(path, mode):
 
1347
    real_mode = safe_int(mode)
 
1348
    if path and real_mode:
 
1349
        with SeLinuxGuard(path):
 
1350
            os.chmod(path, real_mode)
 
1351
 
 
1352
 
 
1353
def write_file(filename, content, mode=0644, omode="wb"):
 
1354
    """
 
1355
    Writes a file with the given content and sets the file mode as specified.
 
1356
    Resotres the SELinux context if possible.
 
1357
 
 
1358
    @param filename: The full path of the file to write.
 
1359
    @param content: The content to write to the file.
 
1360
    @param mode: The filesystem mode to set on the file.
 
1361
    @param omode: The open mode used when opening the file (r, rb, a, etc.)
 
1362
    """
 
1363
    ensure_dir(os.path.dirname(filename))
 
1364
    LOG.debug("Writing to %s - %s: [%s] %s bytes",
 
1365
               filename, omode, mode, len(content))
 
1366
    with SeLinuxGuard(path=filename):
 
1367
        with open(filename, omode) as fh:
 
1368
            fh.write(content)
 
1369
            fh.flush()
 
1370
    chmod(filename, mode)
 
1371
 
 
1372
 
 
1373
def delete_dir_contents(dirname):
 
1374
    """
 
1375
    Deletes all contents of a directory without deleting the directory itself.
 
1376
 
 
1377
    @param dirname: The directory whose contents should be deleted.
 
1378
    """
 
1379
    for node in os.listdir(dirname):
 
1380
        node_fullpath = os.path.join(dirname, node)
 
1381
        if os.path.isdir(node_fullpath):
 
1382
            del_dir(node_fullpath)
 
1383
        else:
 
1384
            del_file(node_fullpath)
 
1385
 
 
1386
 
 
1387
def subp(args, data=None, rcs=None, env=None, capture=True, shell=False,
 
1388
         logstring=False):
 
1389
    if rcs is None:
 
1390
        rcs = [0]
 
1391
    try:
 
1392
 
 
1393
        if not logstring:
 
1394
            LOG.debug(("Running command %s with allowed return codes %s"
 
1395
                       " (shell=%s, capture=%s)"), args, rcs, shell, capture)
 
1396
        else:
 
1397
            LOG.debug(("Running hidden command to protect sensitive "
 
1398
                       "input/output logstring: %s"), logstring)
 
1399
 
 
1400
        if not capture:
 
1401
            stdout = None
 
1402
            stderr = None
 
1403
        else:
 
1404
            stdout = subprocess.PIPE
 
1405
            stderr = subprocess.PIPE
 
1406
        stdin = subprocess.PIPE
 
1407
        sp = subprocess.Popen(args, stdout=stdout,
 
1408
                        stderr=stderr, stdin=stdin,
 
1409
                        env=env, shell=shell)
 
1410
        (out, err) = sp.communicate(data)
 
1411
    except OSError as e:
 
1412
        raise ProcessExecutionError(cmd=args, reason=e)
 
1413
    rc = sp.returncode
 
1414
    if rc not in rcs:
 
1415
        raise ProcessExecutionError(stdout=out, stderr=err,
 
1416
                                    exit_code=rc,
 
1417
                                    cmd=args)
 
1418
    # Just ensure blank instead of none?? (iff capturing)
 
1419
    if not out and capture:
 
1420
        out = ''
 
1421
    if not err and capture:
 
1422
        err = ''
 
1423
    return (out, err)
 
1424
 
 
1425
 
 
1426
def abs_join(*paths):
 
1427
    return os.path.abspath(os.path.join(*paths))
 
1428
 
 
1429
 
 
1430
# shellify, takes a list of commands
 
1431
#  for each entry in the list
 
1432
#    if it is an array, shell protect it (with single ticks)
 
1433
#    if it is a string, do nothing
 
1434
def shellify(cmdlist, add_header=True):
 
1435
    content = ''
 
1436
    if add_header:
 
1437
        content += "#!/bin/sh\n"
 
1438
    escaped = "%s%s%s%s" % ("'", '\\', "'", "'")
 
1439
    cmds_made = 0
 
1440
    for args in cmdlist:
 
1441
        # If the item is a list, wrap all items in single tick.
 
1442
        # If its not, then just write it directly.
 
1443
        if isinstance(args, list):
 
1444
            fixed = []
 
1445
            for f in args:
 
1446
                fixed.append("'%s'" % (str(f).replace("'", escaped)))
 
1447
            content = "%s%s\n" % (content, ' '.join(fixed))
 
1448
            cmds_made += 1
 
1449
        elif isinstance(args, (str, basestring)):
 
1450
            content = "%s%s\n" % (content, args)
 
1451
            cmds_made += 1
 
1452
        else:
 
1453
            raise RuntimeError(("Unable to shellify type %s"
 
1454
                                " which is not a list or string")
 
1455
                               % (obj_name(args)))
 
1456
    LOG.debug("Shellified %s commands.", cmds_made)
 
1457
    return content
 
1458
 
 
1459
 
 
1460
def is_container():
 
1461
    """
 
1462
    Checks to see if this code running in a container of some sort
 
1463
    """
 
1464
 
 
1465
    for helper in CONTAINER_TESTS:
 
1466
        try:
 
1467
            # try to run a helper program. if it returns true/zero
 
1468
            # then we're inside a container. otherwise, no
 
1469
            subp([helper])
 
1470
            return True
 
1471
        except (IOError, OSError):
 
1472
            pass
 
1473
 
 
1474
    # this code is largely from the logic in
 
1475
    # ubuntu's /etc/init/container-detect.conf
 
1476
    try:
 
1477
        # Detect old-style libvirt
 
1478
        # Detect OpenVZ containers
 
1479
        pid1env = get_proc_env(1)
 
1480
        if "container" in pid1env:
 
1481
            return True
 
1482
        if "LIBVIRT_LXC_UUID" in pid1env:
 
1483
            return True
 
1484
    except (IOError, OSError):
 
1485
        pass
 
1486
 
 
1487
    # Detect OpenVZ containers
 
1488
    if os.path.isdir("/proc/vz") and not os.path.isdir("/proc/bc"):
 
1489
        return True
 
1490
 
 
1491
    try:
 
1492
        # Detect Vserver containers
 
1493
        lines = load_file("/proc/self/status").splitlines()
 
1494
        for line in lines:
 
1495
            if line.startswith("VxID:"):
 
1496
                (_key, val) = line.strip().split(":", 1)
 
1497
                if val != "0":
 
1498
                    return True
 
1499
    except (IOError, OSError):
 
1500
        pass
 
1501
 
 
1502
    return False
 
1503
 
 
1504
 
 
1505
def get_proc_env(pid):
 
1506
    """
 
1507
    Return the environment in a dict that a given process id was started with.
 
1508
    """
 
1509
 
 
1510
    env = {}
 
1511
    fn = os.path.join("/proc/", str(pid), "environ")
 
1512
    try:
 
1513
        contents = load_file(fn)
 
1514
        toks = contents.split("\0")
 
1515
        for tok in toks:
 
1516
            if tok == "":
 
1517
                continue
 
1518
            (name, val) = tok.split("=", 1)
 
1519
            if name:
 
1520
                env[name] = val
 
1521
    except (IOError, OSError):
 
1522
        pass
 
1523
    return env
 
1524
 
 
1525
 
 
1526
def keyval_str_to_dict(kvstring):
 
1527
    ret = {}
 
1528
    for tok in kvstring.split():
 
1529
        try:
 
1530
            (key, val) = tok.split("=", 1)
 
1531
        except ValueError:
 
1532
            key = tok
 
1533
            val = True
 
1534
        ret[key] = val
 
1535
    return ret