~curtin-dev/curtin/yakkety

« back to all changes in this revision

Viewing changes to curtin/block/__init__.py

  • Committer: Scott Moser
  • Date: 2017-02-17 03:30:10 UTC
  • Revision ID: smoser@ubuntu.com-20170217033010-qi3li0lh5n7w6in3
* New upstream snapshot.
  - Install zipl in target on s390x arch. (LP: #1662346)
  - avoid UnicodeDecode error on passing non-utf8 into shlex
  - adjustments to version string handling, improved pack unit tests.
  - helpers/common: Add grub install debugging output
  - curtin: add version module and display in output and logs
  - content decoding in load_file, apply_net raise exception on errors
  - gpg: retry when recv'ing gpg keys fail (LP: #1661337)
  - Add clear_holders checks to disk and partition handlers (LP: #1659509)
  - net: add new lines after rendered static routes. (LP: #1649652)
  - multipath: don't run update-grub; setup_grub will handle this better.
    (LP: #1656369)
  - Test changes:
    - vmtest: Add tests for zesty and Trusty HWE-X kernels.
    - tests: fix tox tip-pycodestyle complaints
    - image-sync: add debugging output to help diagnose errors
    - vmtest: change get_curtin_version to use version subcommand.
    - Remove style checking during build and add latest style checks to tox
    - subp doc an unit test improvements.
    - flake8: remove unused variable.
    - vmtest: Add the ability to add extra config files to test execution.
    - vmtest: overhaul image sync
    - vmtest: skip apt-proxy test if not set
    - vmtest: add 'webserv' helper
    - vmtest: add CURTIN_VMTEST_CURTIN_EXE variable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#   You should have received a copy of the GNU Affero General Public License
16
16
#   along with Curtin.  If not, see <http://www.gnu.org/licenses/>.
17
17
 
 
18
from contextlib import contextmanager
18
19
import errno
 
20
import itertools
19
21
import os
 
22
import shlex
20
23
import stat
21
 
import shlex
 
24
import sys
22
25
import tempfile
23
 
import itertools
24
26
 
25
27
from curtin import util
26
28
from curtin.block import lvm
165
167
    return os.path.normpath(path)
166
168
 
167
169
 
 
170
def get_holders(device):
 
171
    """
 
172
    Look up any block device holders, return list of knames
 
173
    """
 
174
    # block.sys_block_path works when given a /sys or /dev path
 
175
    sysfs_path = sys_block_path(device)
 
176
    # get holders
 
177
    holders = os.listdir(os.path.join(sysfs_path, 'holders'))
 
178
    LOG.debug("devname '%s' had holders: %s", device, holders)
 
179
    return holders
 
180
 
 
181
 
 
182
def _shlex_split(str_in):
 
183
    # shlex.split takes a string
 
184
    # but in python2 if input here is a unicode, encode it to a string.
 
185
    # http://stackoverflow.com/questions/2365411/
 
186
    #     python-convert-unicode-to-ascii-without-errors
 
187
    if sys.version_info.major == 2:
 
188
        try:
 
189
            if isinstance(str_in, unicode):
 
190
                str_in = str_in.encode('utf-8')
 
191
        except NameError:
 
192
            pass
 
193
 
 
194
        return shlex.split(str_in)
 
195
    else:
 
196
        return shlex.split(str_in)
 
197
 
 
198
 
168
199
def _lsblock_pairs_to_dict(lines):
169
200
    """
170
201
    parse lsblock output and convert to dict
171
202
    """
172
203
    ret = {}
173
204
    for line in lines.splitlines():
174
 
        toks = shlex.split(line)
 
205
        toks = _shlex_split(line)
175
206
        cur = {}
176
207
        for tok in toks:
177
208
            k, v = tok.split("=", 1)
378
409
    except util.ProcessExecutionError as e:
379
410
        # FIXME: its less than ideal to swallow this error, but until
380
411
        # we fix LP: #1489521 we kind of need to.
381
 
        LOG.warn("rescanning devices failed: %s", e)
 
412
        LOG.warn("Error rescanning devices, possibly known issue LP: #1489521")
 
413
        # Reformatting the exception output so as to not trigger
 
414
        # vmtest scanning for Unexepected errors in install logfile
 
415
        LOG.warn("cmd: %s\nstdout:%s\nstderr:%s\nexit_code:%s", e.cmd,
 
416
                 e.stdout, e.stderr, e.exit_code)
382
417
 
383
418
    udevadm_settle()
384
419
 
407
442
    data = {}
408
443
    for line in out.splitlines():
409
444
        curdev, curdata = line.split(":", 1)
410
 
        data[curdev] = dict(tok.split('=', 1) for tok in shlex.split(curdata))
 
445
        data[curdev] = dict(tok.split('=', 1)
 
446
                            for tok in _shlex_split(curdata))
411
447
    return data
412
448
 
413
449
 
521
557
            if os.path.isdir(curtin_dir):
522
558
                target = dev_path
523
559
                break
524
 
        except:
 
560
        except Exception:
525
561
            pass
526
562
        finally:
527
563
            if mp:
688
724
    # this signature must be at 0x1fe
689
725
    # https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout
690
726
    return (is_block_device(device) and util.file_size(device) >= 0x200 and
691
 
            (util.load_file(device, mode='rb', read_len=2, offset=0x1fe) ==
 
727
            (util.load_file(device, decode=False, read_len=2, offset=0x1fe) ==
692
728
             b'\x55\xAA'))
693
729
 
694
730
 
706
742
    sector_size = get_blockdev_sector_size(device)[0]
707
743
    return (is_block_device(device) and
708
744
            util.file_size(device) >= 2 * sector_size and
709
 
            (util.load_file(device, mode='rb', read_len=8,
 
745
            (util.load_file(device, decode=False, read_len=8,
710
746
                            offset=sector_size) == b'EFI PART'))
711
747
 
712
748
 
723
759
            check_dos_signature(device))
724
760
 
725
761
 
 
762
@contextmanager
 
763
def exclusive_open(path):
 
764
    """
 
765
    Obtain an exclusive file-handle to the file/device specified
 
766
    """
 
767
    mode = 'rb+'
 
768
    fd = None
 
769
    if not os.path.exists(path):
 
770
        raise ValueError("No such file at path: %s" % path)
 
771
 
 
772
    try:
 
773
        fd = os.open(path, os.O_RDWR | os.O_EXCL)
 
774
        try:
 
775
            fd_needs_closing = True
 
776
            with os.fdopen(fd, mode) as fo:
 
777
                yield fo
 
778
            fd_needs_closing = False
 
779
        except OSError:
 
780
            LOG.exception("Failed to create file-object from fd")
 
781
            raise
 
782
        finally:
 
783
            # python2 leaves fd open if there os.fdopen fails
 
784
            if fd_needs_closing and sys.version_info.major == 2:
 
785
                os.close(fd)
 
786
    except OSError:
 
787
        LOG.exception("Failed to exclusively open path: %s", path)
 
788
        holders = get_holders(path)
 
789
        LOG.error('Device holders with exclusive access: %s', holders)
 
790
        mount_points = util.list_device_mounts(path)
 
791
        LOG.error('Device mounts: %s', mount_points)
 
792
        raise
 
793
 
 
794
 
726
795
def wipe_file(path, reader=None, buflen=4 * 1024 * 1024):
727
796
    """
728
797
    wipe the existing file at path.
742
811
    LOG.debug("%s is %s bytes. wiping with buflen=%s",
743
812
              path, size, buflen)
744
813
 
745
 
    with open(path, "rb+") as fp:
 
814
    with exclusive_open(path) as fp:
746
815
        while True:
747
816
            pbuf = readfunc(buflen)
748
817
            pos = fp.tell()
772
841
    if not (is_block or os.path.isfile(path)):
773
842
        raise ValueError("%s: not an existing file or block device", path)
774
843
 
 
844
    pt_names = []
775
845
    if partitions and is_block:
776
846
        ptdata = sysfs_partition_data(path)
777
847
        for kname, ptnum, start, size in ptdata:
778
 
            offsets.append(start)
779
 
            offsets.append(start + size - zero_size)
 
848
            pt_names.append((dev_path(kname), kname, ptnum))
 
849
        pt_names.reverse()
 
850
 
 
851
    for (pt, kname, ptnum) in pt_names:
 
852
        LOG.debug('Wiping path: dev:%s kname:%s partnum:%s',
 
853
                  pt, kname, ptnum)
 
854
        quick_zero(pt, partitions=False)
780
855
 
781
856
    LOG.debug("wiping 1M on %s at offsets %s", path, offsets)
782
857
    return zero_file_at_offsets(path, offsets, buflen=buflen, count=count)
796
871
    buf = b'\0' * buflen
797
872
    tot = buflen * count
798
873
    msg_vals = {'path': path, 'tot': buflen * count}
799
 
    with open(path, "rb+") as fp:
 
874
 
 
875
    with exclusive_open(path) as fp:
800
876
        # get the size by seeking to end.
801
877
        fp.seek(0, 2)
802
878
        size = fp.tell()