diff --git a/doc/man/8/ceph-disk.rst b/doc/man/8/ceph-disk.rst index f1b8f1c1bf7e7..ed938b8b57071 100644 --- a/doc/man/8/ceph-disk.rst +++ b/doc/man/8/ceph-disk.rst @@ -56,6 +56,8 @@ zap Zap/erase/destroy a device's partition table (and contents) trigger Trigger an event (caled by udev) +fix + Fix SELinux labels and/or file permissions Description =========== diff --git a/src/ceph-disk/ceph_disk/main.py b/src/ceph-disk/ceph_disk/main.py index 4a5e1af518a7b..2352e3d356cc7 100755 --- a/src/ceph-disk/ceph_disk/main.py +++ b/src/ceph-disk/ceph_disk/main.py @@ -39,6 +39,7 @@ import pwd import grp import textwrap +import glob CEPH_OSD_ONDISK_MAGIC = 'ceph osd volume v026' CEPH_LOCKBOX_ONDISK_MAGIC = 'ceph lockbox volume v001' @@ -475,6 +476,40 @@ def _bytes2str(string): return string.decode('utf-8') if isinstance(string, bytes) else string +def command_init(arguments, **kwargs): + """ + Safely execute a non-blocking ``subprocess.Popen`` call + making sure that the executable exists and raising a helpful + error message if it does not. + + .. note:: This should be the preferred way of calling ``subprocess.Popen`` + since it provides the caller with the safety net of making sure that + executables *will* be found and will error nicely otherwise. + + This returns the process. + """ + + arguments = list(map(_bytes2str, _get_command_executable(arguments))) + + LOG.info('Running command: %s' % ' '.join(arguments)) + process = subprocess.Popen( + arguments, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs) + return process + + +def command_wait(process): + """ + Wait for the process finish and parse its output. + """ + + out, err = process.communicate() + + return _bytes2str(out), _bytes2str(err), process.returncode + + def command_check_call(arguments, exit=False): """ Safely execute a ``subprocess.check_call`` call making sure that the @@ -4690,6 +4725,151 @@ def main_trigger(args): LOG.debug(err) +def main_fix(args): + # A hash table containing 'path': ('uid', 'gid', blocking, recursive) + fix_table = [ + ('/var/lib/ceph', 'ceph', 'ceph', True, False), + ] + + # Relabel/chown all files under /var/lib/ceph/ recursively (except for osd) + for directory in glob.glob('/var/lib/ceph/*'): + if directory == '/var/lib/ceph/osd': + fix_table.append((directory, 'ceph', 'ceph', True, False)) + else: + fix_table.append((directory, 'ceph', 'ceph', True, True)) + + # Relabel/chown the osds recursively and in parallel + for directory in glob.glob('/var/lib/ceph/osd/*'): + fix_table.append((directory, 'ceph', 'ceph', False, True)) + + LOG.debug("fix_table: " + str(fix_table)) + + # The lists of background processes + all_processes = [] + permissions_processes = [] + selinux_processes = [] + + # Preliminary checks + if args.selinux or args.all: + out, err, ret = command(['selinuxenabled']) + if ret: + LOG.error('SELinux is not enabled, please enable it, first.') + raise Error('no SELinux') + + for daemon in ['ceph-mon', 'ceph-osd', 'ceph-mds', 'radosgw', 'ceph-mgr']: + out, err, ret = command(['pgrep', daemon]) + if ret == 0: + LOG.error(daemon + ' is running, please stop it, first') + raise Error(daemon + ' running') + + # Use find to relabel + chown ~simultaenously + if args.all: + for directory, uid, gid, blocking, recursive in fix_table: + c = [ + 'find', + directory, + '-exec', + 'chown', + ':'.join((uid, gid)), + '{}', + '+', + '-exec', + 'restorecon', + '{}', + '+', + ] + + # Just pass -maxdepth 0 for non-recursive calls + if not recursive: + c += ['-maxdepth', '0'] + + if blocking: + out, err, ret = command(c) + + if ret: + LOG.error("Failed to fix " + directory) + LOG.error(err) + raise Error(directory + " fix failed") + else: + all_processes.append(command_init(c)) + + LOG.debug("all_processes: " + str(all_processes)) + for process in all_processes: + out, err, ret = command_wait(process) + if ret: + LOG.error("A background find process failed") + LOG.error(err) + raise Error("background failed") + + # Fix permissions + if args.permissions: + for directory, uid, gid, blocking, recursive in fix_table: + if recursive: + c = [ + 'chown', + '-R', + ':'.join((uid, gid)), + directory + ] + else: + c = [ + 'chown', + ':'.join((uid, gid)), + directory + ] + + if blocking: + out, err, ret = command(c) + + if ret: + LOG.error("Failed to chown " + directory) + LOG.error(err) + raise Error(directory + " chown failed") + else: + permissions_processes.append(command_init(c)) + + LOG.debug("permissions_processes: " + str(permissions_processes)) + for process in permissions_processes: + out, err, ret = command_wait(process) + if ret: + LOG.error("A background permissions process failed") + LOG.error(err) + raise Error("background failed") + + # Fix SELinux labels + if args.selinux: + for directory, uid, gid, blocking, recursive in fix_table: + if recursive: + c = [ + 'restorecon', + '-R', + directory + ] + else: + c = [ + 'restorecon', + directory + ] + + if blocking: + out, err, ret = command(c) + + if ret: + LOG.error("Failed to restore labels for " + directory) + LOG.error(err) + raise Error(directory + " relabel failed") + else: + selinux_processes.append(command_init(c)) + + LOG.debug("selinux_processes: " + str(selinux_processes)) + for process in selinux_processes: + out, err, ret = command_wait(process) + if ret: + LOG.error("A background selinux process failed") + LOG.error(err) + raise Error("background failed") + + def setup_statedir(dir): # XXX The following use of globals makes linting # really hard. Global state in Python is iffy and @@ -4787,11 +4967,44 @@ def parse_args(argv): make_destroy_parser(subparsers) make_zap_parser(subparsers) make_trigger_parser(subparsers) + make_fix_parser(subparsers) args = parser.parse_args(argv) return args +def make_fix_parser(subparsers): + fix_parser = subparsers.add_parser( + 'fix', + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.fill(textwrap.dedent("""\ + """)), + help='fix SELinux labels and/or file permissions') + + fix_parser.add_argument( + '--selinux', + action='store_true', + default=False, + help='fix SELinux labels for ceph data' + ) + fix_parser.add_argument( + '--permissions', + action='store_true', + default=False, + help='fix file permissions for ceph data' + ) + fix_parser.add_argument( + '--all', + action='store_true', + default=False, + help='fix file permissions as well as SELinux labels for ceph data' + ) + fix_parser.set_defaults( + func=main_fix, + ) + return fix_parser + + def make_trigger_parser(subparsers): trigger_parser = subparsers.add_parser( 'trigger',