Michał Górny avatar Michał Górny committed fa26a52

Introduce friendly descriptions of various issues.

Comments (0)

Files changed (2)

ecleankernel/cli.py

 	def __init__(self):
 		Exception.__init__(self, 'Unable to mount /boot')
 
+	@property
+	def friendly_desc(self):
+		return '''The program is unable to mount /boot.
+
+This usually indicates that you have insufficient permissions to run
+eclean-kernel, or your fstab is incorrect. Improperly mounted file-
+systems can result in wrong files being removed, and therefore
+eclean-kernel will refuse to proceed. Please either run the program
+as root, or preferably mount /boot before using it.'''
+
 class NullDebugger(object):
 	def __init__(self):
 		self._indent = 1
 			bootfs = pymountboot.BootMountpoint()
 
 	try:
-		bootfs.mount()
-	except RuntimeError:
-		raise MountError()
+		try:
+			bootfs.mount()
+		except RuntimeError:
+			raise MountError()
 
-	try:
-		kernels = find_kernels(exclusions = exclusions)
-
-		if opts.listkern:
-			for k in kernels:
-				print('%s [%s]:' % (k.version, k.real_kv))
-				for key in k.parts:
-					val = getattr(k, key)
-					if val is not None:
-						print('- %s: %s' % (key, val))
-			return 0
+		try:
+			kernels = find_kernels(exclusions = exclusions)
+
+			if opts.listkern:
+				for k in kernels:
+					print('%s [%s]:' % (k.version, k.real_kv))
+					for key in k.parts:
+						val = getattr(k, key)
+						if val is not None:
+							print('- %s: %s' % (key, val))
+				return 0
+
+			bootloader = get_bootloader(requested = opts.bootloader,
+					debug = debug)
+			removals = get_removal_list(kernels,
+					limit = None if opts.all else opts.num,
+					bootloader = bootloader,
+					destructive = opts.destructive,
+					debug = debug)
+
+			if not removals:
+				print('No outdated kernels found.')
+			elif opts.pretend:
+				print('These are the kernels which would be removed:')
+
+				for k, reason in removals:
+					print('- %s: %s' % (k.version, ', '.join(reason)))
+				if removals and hasattr(bootloader, 'postrm'):
+					print('Bootloader %s config will be updated.' % bootloader.name)
+			else:
+				bootfs.rwmount()
+				for k, reason in removals:
+					k.check_writable()
+
+				nremoved = 0
+
+				for k, reason in removals:
+					remove = True
+					while opts.ask:
+						try:
+							input_f = raw_input
+						except NameError:
+							input_f = input
+
+						ans = input_f('Remove %s (%s)? [Yes/No]'
+								% (k.version, ', '.join(reason))).lower()
+						if 'yes'.startswith(ans):
+							break
+						elif 'no'.startswith(ans):
+							remove = False
+							break
+						else:
+							print('Invalid answer (%s).' % ans)
+
+					if remove:
+						print('* Removing kernel %s (%s)' % (k.version, ', '.join(reason)))
+						del kernels[k.version]
+						nremoved += 1
+
+				if nremoved:
+					print('Removed %d kernels' % nremoved)
+					if hasattr(bootloader, 'postrm'):
+						bootloader.postrm()
 
-		bootloader = get_bootloader(requested = opts.bootloader,
-				debug = debug)
-		removals = get_removal_list(kernels,
-				limit = None if opts.all else opts.num,
-				bootloader = bootloader,
-				destructive = opts.destructive,
-				debug = debug)
-
-		if not removals:
-			print('No outdated kernels found.')
-		elif opts.pretend:
-			print('These are the kernels which would be removed:')
-
-			for k, reason in removals:
-				print('- %s: %s' % (k.version, ', '.join(reason)))
-			if removals and hasattr(bootloader, 'postrm'):
-				print('Bootloader %s config will be updated.' % bootloader.name)
+			return 0
+		finally:
+			try:
+				bootfs.umount()
+			except RuntimeError:
+				print('Note: unmounting /boot failed')
+	except Exception as e:
+		if opts.debug:
+			raise
+		print('eclean-kernel has met the following issue:\n')
+
+		if hasattr(e, 'friendly_desc'):
+			print(e.friendly_desc)
 		else:
-			bootfs.rwmount()
-			for k, reason in removals:
-				k.check_writable()
-
-			nremoved = 0
-
-			for k, reason in removals:
-				remove = True
-				while opts.ask:
-					try:
-						input_f = raw_input
-					except NameError:
-						input_f = input
-
-					ans = input_f('Remove %s (%s)? [Yes/No]'
-							% (k.version, ', '.join(reason))).lower()
-					if 'yes'.startswith(ans):
-						break
-					elif 'no'.startswith(ans):
-						remove = False
-						break
-					else:
-						print('Invalid answer (%s).' % ans)
-
-				if remove:
-					print('* Removing kernel %s (%s)' % (k.version, ', '.join(reason)))
-					del kernels[k.version]
-					nremoved += 1
-
-			if nremoved:
-				print('Removed %d kernels' % nremoved)
-				if hasattr(bootloader, 'postrm'):
-					bootloader.postrm()
-
-		return 0
-	finally:
-		try:
-			bootfs.umount()
-		except RuntimeError:
-			print('Note: unmounting /boot failed')
+			print('  %s' % e)
+
+		print('''
+If you believe that the mentioned issue is a bug, please report it
+to https://bitbucket.org/mgorny/eclean-kernel/issues. If possible,
+please attach the output of 'eclean-kernel --list-kernels' and your
+regular eclean-kernel call with additional '--debug' argument.''')

ecleankernel/kernel.py

 		self._path = path
 		Exception.__init__(self, '%s not readable, unable to proceed.' % path)
 
+	@property
+	def friendly_desc(self):
+		return '''The following file is not readable:
+  %s
+
+This usually indicates that you have insufficient permissions to run
+eclean-kernel. The program needs to be able to read all kernel-related
+files in order to properly associate them. Lack of access to some
+of the files may result in wrong kernels being removed and therefore
+the program will refuse to proceed.''' % self._path
+
 class WriteAccessError(Exception):
 	def __init__(self, path):
 		self._path = path
 		Exception.__init__(self, '%s not writable, refusing to proceed.' % path)
 
+	@property
+	def friendly_desc(self):
+		return '''The following file is not writable:
+  %s
+
+This usually indicates that you have insufficient permissions to run
+eclean-kernel. The program needs to be able to remove all the files
+associated with removed kernels. Lack of write access to some of them
+will result in orphan files and therefore the program will refuse
+to proceed.''' % self._path
+
 class PathRef(str):
 	def __init__(self, path):
 		str.__init__(self)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.