def supported(fstab="/etc/fstab"):
    """ verify that the system supports apt btrfs snapshots
        by checking if the right fs layout is used etc
    """
    # check for the helper binary
    if not os.path.exists("/sbin/btrfs"):
        return False
    # check the fstab
    fstab = Fstab(fstab)
    entry = fstab.get_supported_btrfs_root_fstab_entry()
    return entry is not None
Ejemplo n.º 2
0
def fstab_entry():
    os.system('sudo blkid > data.txt')
    hdds = open(
        'data.txt',
        "r+",
    )
    y = ""
    finalstring = ""
    for line in hdds:
        if line.startswith("/dev/sda1"):
            if "PIHDD" and "ntfs" in line:
                j = line.find('UUID')
                i = line.find('"', j)
                b = line.find('"', i + 1)
                y = line[i:b].replace('"', '')
                finalstring = "UUID=" + y + " /opt/PIHDD " + "ntfs" + " defaults,nofail,x-systemd.device-timeout=1,noatime 0 0"

            elif "PIHDD" and "ext4" in line:
                j = line.find('UUID')
                i = line.find('"', j)
                b = line.find('"', i + 1)
                y = line[i:b].replace('"', '')
                finalstring = "UUID=" + y + " /opt/PIHDD " + "ext4" + " defaults,nofail,x-systemd.device-timeout=1,noatime 0 0"

    hdds.close()

    if os.path.exists('/opt/PIHDD/KOLIBRI_DATA/content/storage'):
        pass
    else:
        os.system('sudo mkdir /opt/PIHDD/KOLIBRI_DATA/content')

    fstab = Fstab()
    other_file = open('data1.txt', 'r+')
    other_file.truncate()

    fs_file = open('/etc/fstab', 'r+')
    fs_file.seek(0)
    t = fs_file.readlines()

    for line in t:
        if "PIHDD" in line:
            pass
        else:
            other_file.write(line)

    fs_file.seek(0)
    fs_file.truncate()
    fs_file.close()
    other_file.close()

    fs_file1 = open('/etc/fstab', 'r+')
    other_files = open('data1.txt', 'r+')

    for x in other_files.readlines():
        fs_file1.write(x)

    fs_file1.write(finalstring)

    other_files.close()
    fs_file1.close()
Ejemplo n.º 3
0
    def __init__(self):
        self.fstab = Fstab(FSTAB_PATH)
        builder = Gtk.Builder()
        builder.add_from_file("/usr/lib/snowlinux/snowMount/snowMount.ui")
        window = builder.get_object("main_window")
        self.disk_treeview = builder.get_object("disk_treeview")
        self.disk_label = builder.get_object("disk_label")
        self.disk_label2 = builder.get_object("disk_label2")

        self.part_treeview = builder.get_object("part_treeview")
        self.part_store = Gtk.ListStore(str, str, str, str)
        self.part_treeview.set_model(self.part_store)
        renderer_text = Gtk.CellRendererText()
        renderer_filesystem_text = Gtk.CellRendererText()
        renderer_filesystem_text.set_property("editable", True)
        renderer_filesystem_text.connect("edited", self.onFileSystemEdited)
        renderer_mountpoint_text = Gtk.CellRendererText()
        renderer_mountpoint_text.set_property("editable", True)
        renderer_mountpoint_text.connect("edited", self.onMountpointEdited)
        renderer_mountoptions_text = Gtk.CellRendererText()
        renderer_mountoptions_text.set_property("editable", True)
        renderer_mountoptions_text.connect("edited", self.onMountoptionsEdited)
        column = Gtk.TreeViewColumn("Partition", renderer_text, text=0)
        self.part_treeview.append_column(column)
        column = Gtk.TreeViewColumn("Filesystem", renderer_filesystem_text, text=1)
        self.part_treeview.append_column(column)
        column = Gtk.TreeViewColumn("Mountpoint", renderer_mountpoint_text, text=2)
        self.part_treeview.append_column(column)
        column = Gtk.TreeViewColumn("Options", renderer_mountoptions_text, text=3)
        self.part_treeview.append_column(column)

        self.part_filesystem = builder.get_object("part_filesystem")
        self.part_size = builder.get_object("part_size")
        self.part_label = builder.get_object("part_label")

        self.disk_store = Gtk.ListStore(str)
        self.disk_treeview.set_model(self.disk_store)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Disks", renderer, text=0)
        self.disk_treeview.append_column(column)
        self.createDiskStore()

        self.aboutdialog = builder.get_object("aboutdialog")
        self.aboutdialog.set_version(VERSION)
        self.aboutdialog.set_license(LICENSE)
        self.aboutdialog.set_copyright(COPYRIGHT)
        self.aboutdialog.set_comments("A tool to manage mountpoints and options of devices.")

        handlers = {
            "onDeleteWindow": Gtk.main_quit,
            "onButtonRefreshClicked": self.onButtonRefreshClicked,
            "onButtonSaveClicked": self.onButtonSaveClicked,
            "onButtonAboutClicked": self.onButtonAboutClicked,
            "onDiskCursorChanged": self.onDiskCursorChanged,
            "onPartCursorChanged": self.onPartCursorChanged,
        }

        builder.connect_signals(handlers)
        window.show_all()
Ejemplo n.º 4
0
def setupDisks(numDisks):
    print "Setup Disks"
    devLetters = list(string.ascii_lowercase)
    for x in range(1, int(numDisks) + 1):
        try:
            mkfs("/dev/xvd" + devLetters[x], "-text4", "-E lazy_itable_init=1")
        except Exception as e:
            pass
        if not os.path.exists("/mnt/data" + str(x)):
            os.makedirs("/mnt/data" + str(x))
        try:
            mount("/dev/xvd" + devLetters[x], "/mnt/data" + str(x), "-text4")
        except Exception as e:
            pass
        try:
            Fstab.add("/dev/xvd" + devLetters[x], "/mnt/data" + str(x), filesystem="ext4")
        except Exception as e:
            pass
 def __init__(self, fstab="/etc/fstab", sandbox=None):
     self.fstab = Fstab(fstab)
     self.commands = LowLevelCommands()
     # if we haven't been given a testing ground to play in, mount the real
     # root volume
     self.test = sandbox is not None
     self.mp = sandbox
     if self.mp is None:
         uuid = self.fstab.uuid_for_mountpoint("/")
         mountpoint = tempfile.mkdtemp(prefix="apt-btrfs-snapshot-mp-")
         if not self.commands.mount(uuid, mountpoint):
             os.rmdir(mountpoint)
             raise Exception("Unable to mount root volume")
         self.mp = mountpoint
     snapshots.setup(self.mp)
Ejemplo n.º 6
0
def fstab_add(dev, mp, fs, options=None):
    """Adds the given device entry to the /etc/fstab file
    """
    return Fstab.add(dev, mp, fs, options=options)
Ejemplo n.º 7
0
def fstab_remove(mp):
    """Remove the given mountpoint entry from /etc/fstab
    """
    return Fstab.remove_by_mountpoint(mp)
Ejemplo n.º 8
0
def fstab_add(dev, mp, fs, options=None):
    """Adds the given device entry to the /etc/fstab file
    """
    return Fstab.add(dev, mp, fs, options=options)
Ejemplo n.º 9
0
def fstab_remove(mp):
    """Remove the given mountpoint entry from /etc/fstab
    """
    return Fstab.remove_by_mountpoint(mp)
Ejemplo n.º 10
0
def main():

    # to parse fdisk output, look for partitions after partitions header line
    fdisk_pars_begin_pattern = re.compile(
        r'^Device\s+Start\s+End\s+Sectors\s+Size\s+Type\s*$')
    # to parse partitions from fdisk output after parted creates partition table
    fdisk_par_pattern = re.compile(
        r'^(?P<device>\S+)\s+(?P<start>\d+)\s+(?P<end>\d+)\s+(?P<sectors>\d+)\s+(?P<size>\S+)\s+(?P<type>.*)$'
    )

    # extract arguments from the command line
    parser = argparse.ArgumentParser(
        description='sensor-capture-disk-config.py',
        add_help=False,
        usage='sensor-capture-disk-config.py [options]')
    parser.add_argument('-i',
                        '--interactive',
                        dest='interactive',
                        type=str2bool,
                        nargs='?',
                        const=True,
                        default=False,
                        help="Interactive")
    parser.add_argument(
        '-u',
        '--umount',
        dest='umount',
        type=str2bool,
        nargs='?',
        const=True,
        default=False,
        help="Unmount capture directories before determining candidate drives")
    parser.add_argument('-v',
                        '--verbose',
                        dest='debug',
                        type=str2bool,
                        nargs='?',
                        const=True,
                        default=False,
                        help="Verbose output")
    parser.add_argument('-n',
                        '--dry-run',
                        dest='dryrun',
                        type=str2bool,
                        nargs='?',
                        const=True,
                        default=False,
                        help="Dry run (don't perform actions)")
    parser.add_argument('-c',
                        '--crypto',
                        dest='encrypt',
                        type=str2bool,
                        nargs='?',
                        const=True,
                        default=False,
                        help="Encrypt formatted volumes")
    try:
        parser.error = parser.exit
        args = parser.parse_args()
    except SystemExit:
        parser.print_help()
        exit(2)

    debug = args.debug
    if debug: eprint(f"Arguments: {sys.argv[1:]}")
    if debug: eprint(f"Arguments: {args}")

    # unmount existing mounts if requested
    if args.umount and (not args.dryrun):
        if (not args.interactive
            ) or YesOrNo(f'Unmount any mounted capture path(s)?'):
            if debug: eprint(f"Attempting unmount of capture path(s)...")
            run_process(
                f"umount {os.path.join(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_MOUNT_PCAP_DIR)}"
            )
            run_process(
                f"umount {os.path.join(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_MOUNT_ZEEK_DIR)}"
            )
            run_process(f"umount {CAPTURE_MOUNT_ROOT_PATH}")
            # also luksClose any luks volumes devices we might have set up
            for cryptDev in [
                    remove_prefix(x, '/dev/mapper/') for x in glob.glob(
                        f"/dev/mapper/{CAPTURE_CRYPT_DEV_PREFIX}*")
            ]:
                if debug:
                    eprint(f"Running crypsetup luksClose on {cryptDev}...")
                _, cryptOut = run_process(
                    f"/sbin/cryptsetup --verbose luksClose {cryptDev}",
                    stdout=True,
                    stderr=True,
                    timeout=300)
                if debug:
                    for line in cryptOut:
                        eprint(f"\t{line}")
            _, reloadOut = run_process(f"systemctl daemon-reload")

    # check existing mounts, if the capture path(s) are already mounted, then abort
    with open('/proc/mounts', 'r') as f:
        for line in f.readlines():
            mountDetails = line.split()
            if (len(mountDetails) >= 2):
                mountPoint = mountDetails[1]
                if mountPoint.startswith(CAPTURE_MOUNT_ROOT_PATH):
                    eprint(
                        f"It appears there is already a device mounted under {CAPTURE_MOUNT_ROOT_PATH} at {mountPoint}."
                    )
                    eprint(
                        f"If you wish to continue, you may run this script with the '-u|--umount' option to umount first."
                    )
                    eprint()
                    parser.print_help()
                    exit(2)

    # get physical disks, partitions, device maps, and any mountpoints and UUID associated
    allDisks = defaultdict(list)
    if debug: eprint(f"Block devices:")
    for device in GetInternalDevices():
        ecode, deviceTree = run_process(
            f'/bin/lsblk -o name,uuid,mountpoint --paths --noheadings /dev/{device}',
            stdout=True,
            stderr=False)
        if (ecode == 0):
            currentDev = None
            currentPar = None
            currentMapper = None
            for line in deviceTree:
                line = line.rstrip()
                if (len(line) > 0):
                    if debug: eprint(f"\t{line}")
                    if (line == f"/dev/{device}"):
                        currentDev = line
                        currentPar = None
                        currentMapper = None
                        allDisks[currentDev].append(
                            PartitionInfo(device=currentDev))
                    elif (currentDev
                          is not None) and (line[2:2 + len(f"/dev/{device}")]
                                            == f"/dev/{device}"):
                        parInfo = f"/{line.split('/', 1)[-1]}".split()
                        if (len(parInfo) >= 2):
                            currentPar = PartitionInfo(
                                device=currentDev,
                                partition=parInfo[0],
                                uuid=parInfo[1],
                                mount=parInfo[2] if
                                (len(parInfo) > 2) else None)
                            currentMapper = None
                            allDisks[currentDev].append(currentPar)
                    elif (currentPar is not None) and ("/dev/mapper/" in line):
                        parInfo = f"/{line.split('/', 1)[-1]}".split()
                        if (len(parInfo) >= 2):
                            currentMapper = PartitionInfo(
                                device=currentDev,
                                partition=currentPar.partition,
                                mapper=parInfo[0],
                                uuid=parInfo[1],
                                mount=parInfo[2] if
                                (len(parInfo) > 2) else None)
                            allDisks[currentDev].append(currentMapper)

    # at this point allDisks might look like this:
    # defaultdict(<class 'list'>,
    #             {'/dev/sda': [PartitionInfo(device='/dev/sda', partition=None, mapper=None, uuid=None, mount=None),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda1', mapper=None, uuid='B42B-7414', mount=None),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda2', mapper=None, uuid='6DF8-D966', mount='/boot/efi'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda3', mapper=None, uuid='f6b575e4-0ec2-47ab-8d0a-9d677ac4fe3c', mount='/boot'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper=None, uuid='Lmx30A-U9qR-kDZF-WOju-zlOi-otrR-WNjh7j', mount=None),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-swap', uuid='00987200-7157-45d1-a233-90cbb22554aa', mount='[SWAP]'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-root', uuid='b53ea5c3-8771-4717-9d3d-ef9c5b18bbe4', mount='/'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-var', uuid='45aec3eb-68be-4eaa-bf79-de3f2a85c103', mount='/var'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-audit', uuid='339ee49c-0e45-4510-8447-55f46f2a3653', mount='/var/log/audit'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-tmp', uuid='b305d781-263f-4016-8422-301f61c11472', mount='/tmp'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-opt', uuid='5e7cbfb8-760e-4526-90d5-ab103ae626a5', mount='/opt'),
    #                           PartitionInfo(device='/dev/sda', partition='/dev/sda4', mapper='/dev/mapper/main-home', uuid='1b089fc0-f3a4-400b-955c-d3fa6b1e2a5f', mount='/home')],
    #              '/dev/sdb': [PartitionInfo(device='/dev/sdb', partition=None, mapper=None, uuid=None, mount=None)]})

    candidateDevs = []
    formattedDevs = []

    # determine candidate storage devices, which are any disks that do not have a mount point associated with
    # it in any way, (no partitions, mappings, etc. that are mounted) and is at least 100 gigabytes
    for device, entries in allDisks.items():
        deviceMounts = list(
            set([par.mount for par in entries if par.mount is not None]))
        if (len(deviceMounts) == 0) and (GetDeviceSize(device) >=
                                         MINIMUM_CAPTURE_DEVICE_BYTES):
            candidateDevs.append(device)

    # sort candidate devices largest to smallest
    candidateDevs = sorted(candidateDevs,
                           key=lambda x: GetDeviceSize(x),
                           reverse=True)
    if debug:
        eprint(
            f"Device candidates: {[(x, sizeof_fmt(GetDeviceSize(x))) for x in candidateDevs]}"
        )

    if len(candidateDevs) > 0:

        if args.encrypt:
            # create keyfile (will be on the encrypted system drive, and used to automatically unlock the encrypted capture drives)
            with open(CAPTURE_CRYPT_KEYFILE, 'wb') as f:
                f.write(os.urandom(4096))
            os.chown(CAPTURE_CRYPT_KEYFILE, 0, 0)
            os.chmod(CAPTURE_CRYPT_KEYFILE, CAPTURE_CRYPT_KEYFILE_PERMS)

        # partition/format each candidate device
        for device in candidateDevs:

            # we only need at most two drives (one for pcap, one for zeek), or at least one
            if (len(formattedDevs) >= 2): break

            if (not args.interactive) or YesOrNo(
                    f'Partition and format {device}{" (dry-run)" if args.dryrun else ""}?'
            ):

                if args.dryrun:
                    eprint(f"Partitioning {device} (dry run only)...")
                    eprint(
                        f'\t/sbin/parted --script --align optimal {device} -- mklabel gpt \\\n\t\tunit mib mkpart primary 1 100%'
                    )
                    ecode = 0
                    partedOut = []

                else:
                    # use parted to create a gpt partition table with a single partition consuming 100% of the disk minus one megabyte at the beginning
                    if debug: eprint(f"Partitioning {device}...")
                    ecode, partedOut = run_process(
                        f'/sbin/parted --script --align optimal {device} -- mklabel gpt \\\n unit mib mkpart primary 1 100%',
                        stdout=True,
                        stderr=True,
                        timeout=300)
                    if debug: eprint(partedOut)
                    if (ecode == 0):
                        if debug: eprint(f"Success partitioning {device}")

                        # get the list of partitions from the newly partitioned device (should be just one)
                        _, fdiskOut = run_process(f'fdisk -l {device}')
                        pars = []
                        parsList = False
                        for line in fdiskOut:
                            if debug: eprint(f"\t{line}")
                            if (not parsList
                                ) and fdisk_pars_begin_pattern.search(line):
                                parsList = True
                            elif parsList:
                                match = fdisk_par_pattern.search(line)
                                if match is not None:
                                    pars.append(match.group('device'))

                        if len(pars) == 1:

                            parDev = pars[0]
                            parUuid = str(uuid.uuid4())
                            parMapperDev = None
                            okToFormat = True

                            if args.encrypt:
                                okToFormat = False

                                # remove this device from /etc/crypttab
                                if os.path.isfile(CRYPTTAB_FILE):
                                    with fileinput.FileInput(
                                            CRYPTTAB_FILE,
                                            inplace=True,
                                            backup='.bak') as f:
                                        for line in f:
                                            line = line.rstrip("\n")
                                            if line.startswith(
                                                    f"{CreateMapperName(parDev)}"
                                            ):
                                                if debug:
                                                    eprint(
                                                        f"removed {line} from {CRYPTTAB_FILE}"
                                                    )
                                            else:
                                                print(line)

                                _, reloadOut = run_process(
                                    f"systemctl daemon-reload")

                                # for good measure, run luksErase in case it was a previous luks volume
                                if debug:
                                    eprint(
                                        f"Running crypsetup luksErase on {parDev}..."
                                    )
                                _, cryptOut = run_process(
                                    f"/sbin/cryptsetup --verbose --batch-mode luksErase {parDev}",
                                    stdout=True,
                                    stderr=True,
                                    timeout=600)
                                if debug:
                                    for line in cryptOut:
                                        eprint(f"\t{line}")

                                _, reloadOut = run_process(
                                    f"systemctl daemon-reload")

                                # luks volume creation

                                # format device as a luks volume
                                if debug:
                                    eprint(
                                        f"Running crypsetup luksFormat on {device}..."
                                    )
                                ecode, cryptOut = run_process(
                                    f"/sbin/cryptsetup --verbose --batch-mode luksFormat {parDev} --uuid='{parUuid}' --key-file {CAPTURE_CRYPT_KEYFILE}",
                                    stdout=True,
                                    stderr=True,
                                    timeout=3600)
                                if debug or (ecode != 0):
                                    for line in cryptOut:
                                        eprint(f"\t{line}")
                                if (ecode == 0):

                                    # open the luks volume in /dev/mapper/
                                    if debug:
                                        eprint(
                                            f"Running crypsetup luksOpen on {device}..."
                                        )
                                    parMapperDev = CreateMapperDeviceName(
                                        parDev)
                                    ecode, cryptOut = run_process(
                                        f"/sbin/cryptsetup --verbose luksOpen {parDev} {CreateMapperName(parDev)} --key-file {CAPTURE_CRYPT_KEYFILE}",
                                        stdout=True,
                                        stderr=True,
                                        timeout=180)
                                    if debug or (ecode != 0):
                                        for line in cryptOut:
                                            eprint(f"\t{line}")
                                    if (ecode == 0):
                                        # we have everything we need for luks
                                        okToFormat = True

                                    else:
                                        eprint(
                                            f"Error {ecode} opening LUKS on {parDev}, giving up on {device}"
                                        )
                                else:
                                    eprint(
                                        f"Error {ecode} formatting LUKS on {parDev}, giving up on {device}"
                                    )

                            # format the partition as an XFS file system
                            if okToFormat:
                                if debug:
                                    eprint(
                                        f'Created {parDev}, assigning {parUuid}'
                                    )
                                if args.encrypt:
                                    formatCmd = f"/sbin/mkfs.xfs -f {parMapperDev}"
                                else:
                                    formatCmd = f"/sbin/mkfs.xfs -f -m uuid='{parUuid}' {parDev}"
                                if debug: eprint(f"Formatting: {formatCmd}")
                                ecode, mkfsOut = run_process(formatCmd,
                                                             stdout=True,
                                                             stderr=True,
                                                             timeout=3600)
                                if debug:
                                    for line in mkfsOut:
                                        eprint(f"\t{line}")
                                if (ecode == 0):
                                    eprint(
                                        f"Success formatting {parMapperDev if args.encrypt else parDev}"
                                    )
                                    formattedDevs.append(
                                        PartitionInfo(device=device,
                                                      partition=parDev,
                                                      mapper=parMapperDev,
                                                      uuid=parUuid,
                                                      mount=None))

                                else:
                                    eprint(
                                        f"Error {ecode} formatting {formatPath}, giving up on {device}"
                                    )

                        else:
                            eprint(
                                f"Error partitioning {device}, unexpected partitions after running parted, giving up on {device}"
                            )

                    elif (ecode != 0):
                        eprint(
                            f"Error {ecode} partitioning {device}, giving up on {device}"
                        )

        # now that we have formatted our device(s), decide where they're going to mount (these are already sorted)
        if len(formattedDevs) >= 2:
            formattedDevs[0].mount = os.path.join(CAPTURE_MOUNT_ROOT_PATH,
                                                  CAPTURE_MOUNT_PCAP_DIR)
            formattedDevs[1].mount = os.path.join(CAPTURE_MOUNT_ROOT_PATH,
                                                  CAPTURE_MOUNT_ZEEK_DIR)

        elif len(formattedDevs) == 1:
            formattedDevs[0].mount = CAPTURE_MOUNT_ROOT_PATH

        if debug: eprint(formattedDevs)

        # mountpoints are probably not already mounted, but this will make sure
        run_process(
            f"umount {os.path.join(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_MOUNT_PCAP_DIR)}"
        )
        run_process(
            f"umount {os.path.join(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_MOUNT_ZEEK_DIR)}"
        )
        run_process(f"umount {CAPTURE_MOUNT_ROOT_PATH}")

        _, reloadOut = run_process(f"systemctl daemon-reload")

        # clean out any previous fstab entries that might be interfering from previous configurations
        if Fstab.remove_by_mountpoint(os.path.join(CAPTURE_MOUNT_ROOT_PATH,
                                                   CAPTURE_MOUNT_PCAP_DIR),
                                      path=FSTAB_FILE):
            if debug:
                eprint(
                    f"Removed previous {os.path.join(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_MOUNT_PCAP_DIR)} mount from {FSTAB_FILE}"
                )
        if Fstab.remove_by_mountpoint(os.path.join(CAPTURE_MOUNT_ROOT_PATH,
                                                   CAPTURE_MOUNT_ZEEK_DIR),
                                      path=FSTAB_FILE):
            if debug:
                eprint(
                    f"Removed previous {os.path.join(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_MOUNT_ZEEK_DIR)} mount from {FSTAB_FILE}"
                )
        if Fstab.remove_by_mountpoint(CAPTURE_MOUNT_ROOT_PATH,
                                      path=FSTAB_FILE):
            if debug:
                eprint(
                    f"Removed previous {CAPTURE_MOUNT_ROOT_PATH} mount from {FSTAB_FILE}"
                )

        # reload tab files with systemctl
        _, reloadOut = run_process(f"systemctl daemon-reload")

        # get the GID of the group of the user(s) that will be doing the capture
        try:
            ecode, guidGetOut = run_process(
                f"getent group {CAPTURE_GROUP_OWNER}",
                stdout=True,
                stderr=True)
            if (ecode == 0) and (len(guidGetOut) > 0):
                netdevGuid = int(guidGetOut[0].split(':')[2])
            else:
                netdevGuid = -1
        except:
            netdevGuid = -1

        # rmdir any mount directories that might be interfering from previous configurations
        if os.path.isdir(CAPTURE_MOUNT_ROOT_PATH):
            for root, dirs, files in os.walk(CAPTURE_MOUNT_ROOT_PATH,
                                             topdown=False):
                for name in dirs:
                    if debug: eprint(f"Removing {os.path.join(root, name)}")
                    os.rmdir(os.path.join(root, name))
            if debug: eprint(f"Removing {CAPTURE_MOUNT_ROOT_PATH}")
            os.rmdir(CAPTURE_MOUNT_ROOT_PATH)
            if debug: eprint(f"Creating {CAPTURE_MOUNT_ROOT_PATH}")
            os.makedirs(CAPTURE_MOUNT_ROOT_PATH, exist_ok=True)
            os.chown(CAPTURE_MOUNT_ROOT_PATH, -1, netdevGuid)
            os.chmod(CAPTURE_MOUNT_ROOT_PATH, CAPTURE_DIR_PERMS)

        # add crypttab entries
        if args.encrypt:
            with open(CRYPTTAB_FILE,
                      'a' if os.path.isfile(CRYPTTAB_FILE) else 'w') as f:
                for par in formattedDevs:
                    crypttabLine = f"{CreateMapperName(par.partition)} UUID={par.uuid} {CAPTURE_CRYPT_KEYFILE} luks\n"
                    f.write(crypttabLine)
                    if debug:
                        eprint(f'Added "{crypttabLine}" to {CRYPTTAB_FILE}')

        # recreate mount directories and add fstab entries
        for par in formattedDevs:
            if debug: eprint(f"Creating {par.mount}")
            os.makedirs(par.mount, exist_ok=True)
            if args.encrypt:
                entry = Fstab.add(
                    device=f"{par.mapper}",
                    mountpoint=par.mount,
                    options=
                    f"defaults,inode64,noatime,rw,auto,user,x-systemd.device-timeout=600s",
                    fs_passno=2,
                    filesystem='xfs',
                    path=FSTAB_FILE)
            else:
                entry = Fstab.add(
                    device=f"UUID={par.uuid}",
                    mountpoint=par.mount,
                    options=
                    f"defaults,inode64,noatime,rw,auto,user,x-systemd.device-timeout=600s",
                    fs_passno=2,
                    filesystem='xfs',
                    path=FSTAB_FILE)
            eprint(f'Added "{entry}" to {FSTAB_FILE} for {par.partition}')

        # reload tab files with systemctl
        _, reloadOut = run_process(f"systemctl daemon-reload")

        # mount the partitions and create a directory with user permissions
        for par in formattedDevs:

            ecode, mountOut = run_process(f"mount {par.mount}")
            if (ecode == 0):
                if debug: eprint(f'Mounted {par.partition} at {par.mount}')

                userDirs = []
                if par.mount == CAPTURE_MOUNT_ROOT_PATH:
                    # only one drive, so we're mounted at /capture, create user directories for CAPTURE_MOUNT_ZEEK_DIR and CAPTURE_MOUNT_PCAP_DIR
                    userDirs.append(
                        os.path.join(par.mount, CAPTURE_MOUNT_PCAP_DIR))
                    userDirs.append(
                        os.path.join(par.mount, CAPTURE_MOUNT_ZEEK_DIR))
                else:
                    # we're mounted somewhere *underneath* /capture, so create a user-writeable subdirectory where we are
                    userDirs.append(os.path.join(par.mount, 'capture'))

                # set permissions on user dirs
                pcapDir = None
                zeekDir = None
                for userDir in userDirs:
                    os.makedirs(userDir, exist_ok=True)
                    os.chown(userDir, CAPTURE_USER_UID, netdevGuid)
                    os.chmod(userDir, CAPTURE_SUBDIR_PERMS)
                    if debug:
                        eprint(
                            f'Created "{userDir}" for writing by capture user')
                    if f"{os.path.sep}{CAPTURE_MOUNT_PCAP_DIR}{os.path.sep}" in userDir:
                        pcapDir = userDir
                    elif f"{os.path.sep}{CAPTURE_MOUNT_ZEEK_DIR}{os.path.sep}" in userDir:
                        zeekDir = userDir

                # replace capture paths in-place in SENSOR_CAPTURE_CONFIG
                if os.path.isfile(SENSOR_CAPTURE_CONFIG):
                    capture_re = re.compile(
                        r"\b(?P<key>PCAP_PATH|ZEEK_LOG_PATH)\s*=\s*.*?$")
                    with fileinput.FileInput(SENSOR_CAPTURE_CONFIG,
                                             inplace=True,
                                             backup='.bak') as f:
                        for line in f:
                            line = line.rstrip("\n")
                            log_path_match = capture_re.search(line)
                            if (log_path_match is not None):
                                if (log_path_match.group('key')
                                        == 'PCAP_PATH') and (pcapDir
                                                             is not None):
                                    print(
                                        capture_re.sub(r"\1=%s" % pcapDir,
                                                       line))
                                elif (log_path_match.group('key')
                                      == 'ZEEK_LOG_PATH') and (zeekDir
                                                               is not None):
                                    print(
                                        capture_re.sub(r"\1=%s" % zeekDir,
                                                       line))
                                else:
                                    print(line)
                            else:
                                print(line)

            else:
                eprint(f"Error {ecode} mounting {par.partition}")

    else:
        eprint(
            f"Could not find any unmounted devices greater than 100GB, giving up"
        )
Ejemplo n.º 11
0
 def test_fstab_get_uuid(self):
     fstab = Fstab(fstab=os.path.join(self.testdir, "data", "fstab"))
     self.assertEqual(fstab.uuid_for_mountpoint("/"), "UUID=fe63f598-1906-478e-acc7-f74740e78d1f")
Ejemplo n.º 12
0
class MainWindow(Gtk.Window):
    def __init__(self):
        self.fstab = Fstab(FSTAB_PATH)
        builder = Gtk.Builder()
        builder.add_from_file("/usr/lib/snowlinux/snowMount/snowMount.ui")
        window = builder.get_object("main_window")
        self.disk_treeview = builder.get_object("disk_treeview")
        self.disk_label = builder.get_object("disk_label")
        self.disk_label2 = builder.get_object("disk_label2")

        self.part_treeview = builder.get_object("part_treeview")
        self.part_store = Gtk.ListStore(str, str, str, str)
        self.part_treeview.set_model(self.part_store)
        renderer_text = Gtk.CellRendererText()
        renderer_filesystem_text = Gtk.CellRendererText()
        renderer_filesystem_text.set_property("editable", True)
        renderer_filesystem_text.connect("edited", self.onFileSystemEdited)
        renderer_mountpoint_text = Gtk.CellRendererText()
        renderer_mountpoint_text.set_property("editable", True)
        renderer_mountpoint_text.connect("edited", self.onMountpointEdited)
        renderer_mountoptions_text = Gtk.CellRendererText()
        renderer_mountoptions_text.set_property("editable", True)
        renderer_mountoptions_text.connect("edited", self.onMountoptionsEdited)
        column = Gtk.TreeViewColumn("Partition", renderer_text, text=0)
        self.part_treeview.append_column(column)
        column = Gtk.TreeViewColumn("Filesystem", renderer_filesystem_text, text=1)
        self.part_treeview.append_column(column)
        column = Gtk.TreeViewColumn("Mountpoint", renderer_mountpoint_text, text=2)
        self.part_treeview.append_column(column)
        column = Gtk.TreeViewColumn("Options", renderer_mountoptions_text, text=3)
        self.part_treeview.append_column(column)

        self.part_filesystem = builder.get_object("part_filesystem")
        self.part_size = builder.get_object("part_size")
        self.part_label = builder.get_object("part_label")

        self.disk_store = Gtk.ListStore(str)
        self.disk_treeview.set_model(self.disk_store)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Disks", renderer, text=0)
        self.disk_treeview.append_column(column)
        self.createDiskStore()

        self.aboutdialog = builder.get_object("aboutdialog")
        self.aboutdialog.set_version(VERSION)
        self.aboutdialog.set_license(LICENSE)
        self.aboutdialog.set_copyright(COPYRIGHT)
        self.aboutdialog.set_comments("A tool to manage mountpoints and options of devices.")

        handlers = {
            "onDeleteWindow": Gtk.main_quit,
            "onButtonRefreshClicked": self.onButtonRefreshClicked,
            "onButtonSaveClicked": self.onButtonSaveClicked,
            "onButtonAboutClicked": self.onButtonAboutClicked,
            "onDiskCursorChanged": self.onDiskCursorChanged,
            "onPartCursorChanged": self.onPartCursorChanged,
        }

        builder.connect_signals(handlers)
        window.show_all()

    def createDiskStore(self):
        disks = drivereader.get_disks()
        for disk in disks:
            self.disk_store.append(["{} ({})".format(disks[disk].getModel(), disk)])

    def onFileSystemEdited(self, widget, path, text):
        self.part_store[path][1] = text

    def onMountpointEdited(self, widget, path, text):
        self.part_store[path][2] = text

    def onMountoptionsEdited(self, widget, path, text):
        self.part_store[path][3] = text

    def updateFstab(self, path):
        device_path = path[0]
        filesystem = path[1]
        mountpoint = path[2]
        mountoptions = path[3]
        self.fstab.updateFstab(device_path, mountpoint, mountoptions, filesystem)

    def onButtonSaveClicked(self, button):
        model, path = self.current_part[0], self.current_part[1]
        self.updateFstab(model[path])
        self.fstab.writeFstab()

    def onButtonRefreshClicked(self, button):
        self.disk_store.clear()
        self.part_store.clear()
        self.part_filesystem.set_text("")
        self.part_size.set_text("")
        self.part_label.set_text("")
        self.createDiskStore()

    def onButtonAboutClicked(self, button):
        self.aboutdialog.run()
        self.aboutdialog.hide()

    def onDiskCursorChanged(self, selection):
        self.part_store.clear()
        self.part_filesystem.set_text("")
        self.part_size.set_text("")
        self.part_label.set_text("")
        model, treeiter = selection.get_selected()
        if treeiter is not None:
            device_path = model[treeiter][0].split()[-1][1:-1]
            disk = drivereader.get_disk(device_path)
            self.disk_label.set_text("{} ({})".format(disk.getModel(), disk.getSize()))
            self.disk_label2.set_text(device_path)
            for part in disk.getPartitions():
                self.part_store.append(
                    [
                        part,
                        self.fstab.getFilesystem(part),
                        self.fstab.getMountpoint(part),
                        self.fstab.getMountoptions(part),
                    ]
                )

    def onPartCursorChanged(self, selection):
        model, treeiter = selection.get_selected()
        if treeiter is not None:
            device_path = model[treeiter][0]
            part = drivereader.get_partition(device_path)
            self.part_filesystem.set_text(part.getFilesystem())
            self.part_size.set_text(part.getSize())
            self.part_label.set_text(part.getLabel())
            self.current_part = (model, treeiter)
class AptBtrfsSnapshot(object):
    """ the high level object that interacts with the snapshot system """

    def __init__(self, fstab="/etc/fstab", sandbox=None):
        self.fstab = Fstab(fstab)
        self.commands = LowLevelCommands()
        # if we haven't been given a testing ground to play in, mount the real
        # root volume
        self.test = sandbox is not None
        self.mp = sandbox
        if self.mp is None:
            uuid = self.fstab.uuid_for_mountpoint("/")
            mountpoint = tempfile.mkdtemp(prefix="apt-btrfs-snapshot-mp-")
            if not self.commands.mount(uuid, mountpoint):
                os.rmdir(mountpoint)
                raise Exception("Unable to mount root volume")
            self.mp = mountpoint
        snapshots.setup(self.mp)

    def __del__(self):
        """ unmount root volume if necessary """
        # This will probably not get run if there are cyclic references.
        # check thoroughly because we get called even if __init__ fails
        if not self.test and self.mp is not None:
            res = self.commands.umount(self.mp)
            os.rmdir(self.mp)
            self.mp = None

    def _get_now_str(self):
        return datetime.datetime.now().replace(microsecond=0).isoformat(
            str('_'))

    def _parse_older_than_to_datetime(self, timefmt):
        if isinstance(timefmt, datetime.datetime):
            return timefmt
        now = datetime.datetime.now()
        if not timefmt.endswith("d"):
            raise Exception("Please specify time in days (e.g. 10d)")
        days = int(timefmt[:-1])
        return now - datetime.timedelta(days)
        
    def _get_last_snapshot_time(self):
        last_snapshot = datetime.datetime.fromtimestamp(0.0)
        if self.test:
            last_snapshot_file = '/tmp/apt_last_snapshot'
        else:
            last_snapshot_file = '/run/apt_last_snapshot'

        if os.path.exists(last_snapshot_file):
            try:
                t = open(last_snapshot_file)
                last_snapshot = \
                datetime.datetime.fromtimestamp(float(t.readline()))
            except:
                # If we fail to read the timestamp for some reason, just return
                # the default value silently
                pass
            finally:
                t.close()
        return last_snapshot

    def _save_last_snapshot_time(self):
        if self.test:
            last_snapshot_file = '/tmp/apt_last_snapshot'
        else:
            last_snapshot_file = '/run/apt_last_snapshot'
        f = open(last_snapshot_file, 'w')
        f.write(str(time.time()))
        f.close()

    def _get_status(self):
        
        parent = Snapshot("@").parent
        if parent is not None:
            date_parent = parent.date
        else:
            date_parent = None
        if self.test:
            testdir = os.path.dirname(os.path.abspath(__file__))
            if not testdir.endswith("test"):
                testdir = os.path.join(testdir, "test")
            var_location = os.path.join(testdir, "data/var")
            history = DpkgHistory(since = date_parent, 
                var_location = var_location)
        else:
            history = DpkgHistory(since = date_parent)
        return parent, history

    def _prettify_changes(self, history, i_indent="- ", s_indent="    "):
        if history == None or history == NO_HISTORY:
            return [i_indent + "No packages operations recorded"]
        output = []
        for op in ("install", "auto-install", "upgrade", "remove", "purge"):
            if len(history[op]) > 0:
                output.append("%s%ss (%d):" % (i_indent, op, len(history[op])))
                packages = []
                for p, v in history[op]:
                    packages.append(p)
                packages = ", ".join(packages)
                if sys.stdout.isatty():
                    # if we are in a terminal, wrap text to match its width
                    rows, columns = os.popen('stty size', 'r').read().split()
                    packages = textwrap.wrap(packages, width=int(columns), 
                        initial_indent=s_indent, subsequent_indent=s_indent,
                        break_on_hyphens=False)
                output.extend(packages)
        return output
    
    def status(self):
        """ show current root's parent and recent changes """
        return self.show("@")
    
    def show(self, snapshot, compact=False):
        """ show details pertaining to given snapshot """
        snapshot = Snapshot(snapshot)
        if snapshot.name == "@":
            parent, changes = self._get_status()
        else:
            parent, changes = snapshot.parent, snapshot.changes
        
        fca = snapshots.first_common_ancestor("@", snapshot)
        mainline = (fca == snapshot) and 'Is' or "Isn't"
        mainline = "%s an ancestor of @" % mainline
        
        pretty_history = self._prettify_changes(changes)

        if parent == None:
            parent = "unknown"
        else:
            parent = parent.name
        
        if not compact:
            title = "Snapshot %s" % snapshot.name
            print(title)
            if snapshot.name != "@":
                print(mainline)
            print("Parent: %s" % parent)
            if parent == "unknown" and snapshot.name == "@":
                print("dpkg history shown for the last 30 days")
            print("dpkg history:")
        else:
            print("dpkg history for %s" % snapshot.name)
        print("\n".join(pretty_history))
        
        return True
    
    def create(self, tag=""):
        """ create a new apt-snapshot of @, tagging it if a tag is given """
        if 'APT_NO_SNAPSHOTS' in os.environ and tag == "":
            print("Shell variable APT_NO_SNAPSHOTS found, skipping creation")
            return True
        elif 'APT_NO_SNAPSHOTS' in os.environ and tag != "":
            print("Shell variable APT_NO_SNAPSHOTS found, but tag supplied, "
                "creating snapshot")
        last = self._get_last_snapshot_time()

        # If there is a recent snapshot and no tag supplied, skip creation
        if tag == "" \
        and last > datetime.datetime.now() - datetime.timedelta(seconds=60):
            print("A recent snapshot already exists: %s" % last)
            return True
        
        # make snapshot
        snap_id = SNAP_PREFIX + self._get_now_str() + tag
        res = self.commands.btrfs_subvolume_snapshot(
            os.path.join(self.mp, "@"),
            os.path.join(self.mp, snap_id))
        
        # set root's new parent
        Snapshot("@").parent = snap_id
        
        # find and store dpkg changes
        parent, history = self._get_status()
        Snapshot(snap_id).changes = history
        
        self._save_last_snapshot_time()
        return res
    
    def tag(self, snapshot, tag):
        """ Adds/replaces the tag for the given snapshot """
        children = Snapshot(snapshot).children

        pos = len(SNAP_PREFIX)
        new_name = snapshot[:pos + 19] + tag
        old_snap = os.path.join(self.mp, snapshot)
        new_snap = os.path.join(self.mp, new_name)
        os.rename(old_snap, new_snap)
        
        tagged = Snapshot(new_name)
        for child in children:
            child.parent = tagged
        return True

    def list(self):
        # The function name will not clash with reserved keywords. It is only
        # accessible via self.list()
        print("Available snapshots:")
        print("  \n".join(snapshots.get_list()))
        return True

    def list_older_than(self, timefmt):
        older_than = self._parse_older_than_to_datetime(timefmt)
        print("Available snapshots older than '%s':" % timefmt)
        print("  \n".join(snapshots.get_list(older_than=older_than)))
        return True

    def _prompt_for_tag(self):
        print("You haven't specified a tag for the snapshot that will be created from the current state.")
        tag = raw_input("Please enter a tag: ")
        if tag:
            tag = "-" + tag
        return tag
    
    def set_default(self, snapshot, tag=""):
        """ backup @ and replace @ with a copy of given snapshot """
        if not tag:
            tag = self._prompt_for_tag()

        snapshot = Snapshot(snapshot)
        new_root = os.path.join(self.mp, snapshot.name)
        if (
                os.path.isdir(new_root) and
                snapshot.name.startswith(SNAP_PREFIX)):
            default_root = os.path.join(self.mp, "@")
            staging = os.path.join(self.mp, "@apt-btrfs-staging")
            if os.path.lexists(staging):
                raise Exception("Reserved directory @apt-btrfs-staging "
                    "exists\nPlease remove from btrfs volume root before "
                    "trying again")
            
            # find and store dpkg changes
            date, history = self._get_status()
            Snapshot("@").changes = history
                
            # snapshot the requested default so as not to remove it
            res = self.commands.btrfs_subvolume_snapshot(new_root, staging)
            if not res:
                raise Exception("Could not create snapshot")

            # make backup name
            backup = os.path.join(self.mp, 
                SNAP_PREFIX + self._get_now_str()) + tag
            # if backup name is already in use, wait a sec and try again
            if os.path.exists(backup):
                time.sleep(1)
                backup = os.path.join(self.mp, 
                    SNAP_PREFIX + self._get_now_str())

            # move everything into place
            os.rename(default_root, backup)
            os.rename(staging, default_root)
            
            # remove @/etc/apt-btrfs-changes & set root's new parent
            new_default = Snapshot("@")
            new_default.changes = None
            new_default.parent = snapshot.name
            
            print("Default changed to %s, please reboot for changes to take "
                  "effect." % snapshot.name)
        else:
            print("You have selected an invalid snapshot. Please make sure "
                  "that it exists, and that its name starts with "
                  "\"%s\"" % SNAP_PREFIX)
        return True

    def rollback(self, number=1, tag=""):
        back_to = Snapshot("@")
        for i in range(number):
            back_to = back_to.parent
            if back_to == None:
                raise Exception("Can't rollback that far")
                return False
        return self.set_default(back_to, tag)

    def delete(self, snapshot):
        snapshot = Snapshot(snapshot)
        to_delete = os.path.join(self.mp, snapshot.name)
        res = True
        if (
                os.path.isdir(to_delete) and
                snapshot.name.startswith(SNAP_PREFIX)):
            
            # correct parent links and combine change info
            parent = snapshot.parent
            children = snapshot.children
            old_history = snapshot.changes
            
            # clean-ups in the global vars of 
            snapshots.list_of.remove(snapshot)
            if parent != None and parent.name in snapshots.children:
                snapshots.children[parent.name].remove(snapshot)
            
            for child in children:
                child.parent = parent
                
                # and do the same again in the global vars of snapshots
                # messy but necessary for delete_older_than to work
                snapshots.parents[child.name] = parent
                if parent != None:
                    snapshots.children[parent.name].append(child) # necessary
                
                newer_history = child.changes
                if old_history == None:
                    combined = newer_history
                elif newer_history == None:
                    combined = None
                else:
                    combined = old_history + newer_history
                child.changes = combined
            
            res = self.commands.btrfs_delete_snapshot(to_delete)
        else:
            print("You have selected an invalid snapshot. Please make sure "
                  "that it exists, and that its name starts with "
                  "\"%s\"" % SNAP_PREFIX)
        return res
    
    def delete_older_than(self, timefmt):
        older_than = self._parse_older_than_to_datetime(timefmt)
        res = True
        list_of = snapshots.get_list(older_than=older_than)
        list_of.sort(key = lambda x: x.date, reverse = True)
        for snap in list_of:
            if len(snap.children) < 2 and snap.tag == "":
                res &= self.delete(snap)
        return res
    
    def prune(self, snapshot):
        snapshot = Snapshot(snapshot)
        res = True
        if len(snapshot.children) != 0:
            raise Exception("Snapshot is not the end of a branch")
        while True:
            parent = snapshot.parent
            res &= self.delete(snapshot)
            snapshot = parent
            if snapshot == None or len(snapshot.children) != 0:
                break
        return res
    
    def tree(self):
        date_parent, history = self._get_status()
        tree = TreeView(history)
        tree.print()
    
    def recent(self, number, snapshot):
        print("%s and its predecessors. Showing %d snapshots.\n" % (snapshot, 
            number))
        snapshot = Snapshot(snapshot)
        for i in range(number):
            self.show(snapshot, compact=True)
            snapshot = snapshot.parent
            if snapshot == None or i == number - 1:
                break
            else:
                print()
        return True
    
    def clean(self, what="apt-cache"):
        snapshot_list = snapshots.get_list()
        for snapshot in snapshot_list:
            path = os.path.join(self.mp, snapshot.name)
            if what == "apt-cache":
                path = os.path.join(path, "var/cache/apt/archives")
                if not os.path.exists(path):
                    continue
                dirlist = os.listdir(path)
                for f in dirlist:
                    fpath = os.path.join(path, f)
                    if f.endswith(".deb") and os.path.lexists(fpath):
                        os.remove(fpath)