1    	#!@PYTHON@ -tt
2    	
3    	import sys
4    	import stat
5    	import re
6    	import os
7    	import time
8    	import logging
9    	import atexit
10   	import hashlib
11   	import ctypes
12   	sys.path.append("@FENCEAGENTSLIBDIR@")
13   	from fencing import fail_usage, run_command, atexit_handler, check_input, process_input, show_docs, fence_action, all_opt
14   	from fencing import run_delay
15   	
16   	STORE_PATH = "@STORE_PATH@"
17   	
18   	
19   	def get_status(conn, options):
20   		del conn
21   		status = "off"
22   		for dev in options["devices"]:
23   			is_block_device(dev)
24   			reset_dev(options, dev)
25   			if options["--key"] in get_registration_keys(options, dev):
26   				status = "on"
27   			else:
28   				logging.debug("No registration for key "\
29   					+ options["--key"] + " on device " + dev + "\n")
30   				if options["--action"] == "on":
31   					status = "off"
32   					break
33   		return status
34   	
35   	
36   	def set_status(conn, options):
37   		del conn
38   		count = 0
39   		if options["--action"] == "on":
40   			set_key(options)
41   			for dev in options["devices"]:
42   				is_block_device(dev)
43   	
44   				register_dev(options, dev, options["--key"])
45   				if options["--key"] not in get_registration_keys(options, dev):
46   					count += 1
47   					logging.debug("Failed to register key "\
48   						+ options["--key"] + "on device " + dev + "\n")
49   					continue
50   				dev_write(dev, options)
51   	
52   				if get_reservation_key(options, dev) is None \
53   				and not reserve_dev(options, dev) \
54   				and get_reservation_key(options, dev) is None:
55   					count += 1
56   					logging.debug("Failed to create reservation (key="\
57   						+ options["--key"] + ", device=" + dev + ")\n")
58   	
59   		else:
60   			host_key = get_key()
61   			if host_key == options["--key"].lower():
62   				fail_usage("Failed: keys cannot be same. You can not fence yourself.")
63   			for dev in options["devices"]:
64   				is_block_device(dev)
65   				register_dev(options, dev, host_key)
66   				if options["--key"] in get_registration_keys(options, dev):
67   					preempt_abort(options, host_key, dev)
68   	
69   			for dev in options["devices"]:
70   				if options["--key"] in get_registration_keys(options, dev):
71   					count += 1
72   					logging.debug("Failed to remove key "\
73   						+ options["--key"] + " on device " + dev + "\n")
74   					continue
75   	
76   				if not get_reservation_key(options, dev):
77   					count += 1
78   					logging.debug("No reservation exists on device " + dev + "\n")
79   		if count:
80   			logging.error("Failed to verify " + str(count) + " device(s)")
81   			sys.exit(1)
82   	
83   	
84   	# check if host is ready to execute actions
85   	def do_action_monitor(options):
86   		# Check if required binaries are installed
87   		if bool(run_cmd(options, options["--sg_persist-path"] + " -V")["rc"]):
88   			logging.error("Unable to run " + options["--sg_persist-path"])
89   			return 1
90   		elif bool(run_cmd(options, options["--sg_turs-path"] + " -V")["rc"]):
91   			logging.error("Unable to run " + options["--sg_turs-path"])
92   			return 1
93   		elif ("--devices" not in options and 
94   				bool(run_cmd(options, options["--vgs-path"] + " --version")["rc"])):
95   			logging.error("Unable to run " + options["--vgs-path"])
96   			return 1
97   	
98   		# Keys have to be present in order to fence/unfence
99   		get_key()
100  		dev_read()
101  	
102  		return 0
103  	
104  	
105  	# run command, returns dict, ret["rc"] = exit code; ret["out"] = output;
106  	# ret["err"] = error
107  	def run_cmd(options, cmd):
108  		ret = {}
109  		(ret["rc"], ret["out"], ret["err"]) = run_command(options, cmd)
110  		ret["out"] = "".join([i for i in ret["out"] if i is not None])
111  		ret["err"] = "".join([i for i in ret["err"] if i is not None])
112  		return ret
113  	
114  	
115  	# check if device exist and is block device
116  	def is_block_device(dev):
117  		if not os.path.exists(dev):
118  			fail_usage("Failed: device \"" + dev + "\" does not exist")
119  		if not stat.S_ISBLK(os.stat(dev).st_mode):
120  			fail_usage("Failed: device \"" + dev + "\" is not a block device")
121  	
122  	
123  	# cancel registration
124  	def preempt_abort(options, host, dev):
125  		reset_dev(options,dev)
126  		cmd = options["--sg_persist-path"] + " -n -o -A -T 5 -K " + host + " -S " + options["--key"] + " -d " + dev
127  		return not bool(run_cmd(options, cmd)["rc"])
128  	
129  	
130  	def reset_dev(options, dev):
131  		return run_cmd(options, options["--sg_turs-path"] + " " + dev)["rc"]
132  	
133  	
134  	def register_dev(options, dev, key, do_preempt=True):
135  		dev = os.path.realpath(dev)
136  		if options.get("--mpath-register-method") == "multi" and re.search(r"^dm", dev[5:]):
137  			devices = get_mpath_slaves(dev)
138  			register_dev(options, devices[0], key)
139  			for device in devices[1:]:
140  				register_dev(options, device, key, False)
141  			return True
142  	
143  		# Check if any registration exists for the key already. We track this in
144  		# order to decide whether the existing registration needs to be cleared.
145  		# This is needed since the previous registration could be for a
146  		# different I_T nexus (different ISID).
147  		registration_key_exists = False
148  		if key in get_registration_keys(options, dev):
149  			logging.debug("Registration key exists for device " + dev)
150  			registration_key_exists = True
151  		if not register_helper(options, dev, key):
152  			return False
153  	
154  		if registration_key_exists:
155  			# If key matches, make sure it matches with the connection that
156  			# exists right now. To do this, we can issue a preempt with same key
157  			# which should replace the old invalid entries from the target.
158  			if do_preempt and not preempt(options, key, dev, key):
159  				return False
160  	
161  			# If there was no reservation, we need to issue another registration
162  			# since the previous preempt would clear registration made above.
163  			if get_reservation_key(options, dev, False) != key:
164  				return register_helper(options, dev, key)
165  		return True
166  	
167  	# helper function to preempt host with 'key' using 'host_key' without aborting tasks
168  	def preempt(options, host_key, dev, key):
169  		reset_dev(options,dev)
170  		cmd = options["--sg_persist-path"] + " -n -o -P -T 5 -K " + host_key + " -S " + key + " -d " + dev
171  		return not bool(run_cmd(options, cmd)["rc"])
172  	
173  	# helper function to send the register command
174  	def register_helper(options, dev, key):
175  		reset_dev(options, dev)
176  		cmd = options["--sg_persist-path"] + " -n -o -I -S " + key + " -d " + dev
177  		cmd += " -Z" if "--aptpl" in options else ""
178  		return not bool(run_cmd(options, cmd)["rc"])
179  	
180  	
181  	def reserve_dev(options, dev):
182  		reset_dev(options,dev)
183  		cmd = options["--sg_persist-path"] + " -n -o -R -T 5 -K " + options["--key"] + " -d " + dev
184  		return not bool(run_cmd(options, cmd)["rc"])
185  	
186  	
187  	def get_reservation_key(options, dev, fail=True):
188  		reset_dev(options,dev)
189  		opts = ""
190  		if "--readonly" in options:
191  			opts = "-y "
192  		cmd = options["--sg_persist-path"] + " -n -i " + opts + "-r -d " + dev
193  		out = run_cmd(options, cmd)
194  		if out["rc"] and fail:
195  			fail_usage('Cannot get reservation key on device "' + dev
196  	                        + '": ' + out["err"])
197  		match = re.search(r"\s+key=0x(\S+)\s+", out["out"], re.IGNORECASE)
198  		return match.group(1) if match else None
199  	
200  	
201  	def get_registration_keys(options, dev, fail=True):
202  		reset_dev(options,dev)
203  		keys = []
204  		opts = ""
205  		if "--readonly" in options:
206  			opts = "-y "
207  		cmd = options["--sg_persist-path"] + " -n -i " + opts + "-k -d " + dev
208  		out = run_cmd(options, cmd)
209  		if out["rc"]:
210  			fail_usage('Cannot get registration keys on device "' + dev
211  	                        + '": ' + out["err"], fail)
212  			if not fail:
213  				return []
214  		for line in out["out"].split("\n"):
215  			match = re.search(r"\s+0x(\S+)\s*", line)
216  			if match:
217  				keys.append(match.group(1))
218  		return keys
219  	
220  	
221  	def get_cluster_id(options):
222  		cmd = options["--corosync-cmap-path"] + " totem.cluster_name"
223  	
224  		match = re.search(r"\(str\) = (\S+)\n", run_cmd(options, cmd)["out"])
225  	
226  		if not match:
227  			fail_usage("Failed: cannot get cluster name")
228  	
229  		try:
(1) Event Sigma main event: This application uses a weak algorithm to generate hash digests in security context, which may lead to collision attacks.
(2) Event remediation: Use strong algorithms such as `sha256` to prevent collision attacks or explicitly set `usedforsecurity` parameter to `false` to make it clear that it is not being used in a security context.
230  			return hashlib.md5(match.group(1).encode('ascii')).hexdigest()
231  		except ValueError:
232  			# FIPS requires usedforsecurity=False and might not be
233  			# available on all distros: https://bugs.python.org/issue9216
234  			return hashlib.md5(match.group(1).encode('ascii'), usedforsecurity=False).hexdigest()
235  	
236  	
237  	def get_node_id(options):
238  		cmd = options["--corosync-cmap-path"] + " nodelist"
239  		out = run_cmd(options, cmd)["out"]
240  	
241  		match = re.search(r".(\d+).name \(str\) = " + options["--plug"] + r"\n", out)
242  	
243  		# try old format before failing
244  		if not match:
245  			match = re.search(r".(\d+).ring._addr \(str\) = " + options["--plug"] + r"\n", out)
246  	
247  		return match.group(1) if match else fail_usage("Failed: unable to parse output of corosync-cmapctl or node does not exist")
248  	
249  	def get_node_hash(options):
250  		try:
251  			return hashlib.md5(options["--plug"].encode('ascii')).hexdigest()
252  		except ValueError:
253  			# FIPS requires usedforsecurity=False and might not be
254  			# available on all distros: https://bugs.python.org/issue9216
255  			return hashlib.md5(options["--plug"].encode('ascii'), usedforsecurity=False).hexdigest()
256  	
257  	
258  	def generate_key(options):
259  		if options["--key-value"] == "hash":
260  			return "%.4s%.4s" % (get_cluster_id(options), get_node_hash(options))
261  		else:
262  			return "%.4s%.4d" % (get_cluster_id(options), int(get_node_id(options)))
263  	
264  	
265  	# save node key to file
266  	def set_key(options):
267  		file_path = options["store_path"] + ".key"
268  		if not os.path.isdir(os.path.dirname(options["store_path"])):
269  			os.makedirs(os.path.dirname(options["store_path"]))
270  		try:
271  			f = open(file_path, "w")
272  		except IOError:
273  			fail_usage("Failed: Cannot open file \""+ file_path + "\"")
274  		f.write(options["--key"].lower() + "\n")
275  		f.close()
276  	
277  	
278  	# read node key from file
279  	def get_key(fail=True):
280  		file_path = STORE_PATH + ".key"
281  		try:
282  			f = open(file_path, "r")
283  		except IOError:
284  			fail_usage("Failed: Cannot open file \""+ file_path + "\"", fail)
285  			if not fail:
286  				return None
287  		return f.readline().strip().lower()
288  	
289  	
290  	def dev_write(dev, options):
291  		file_path = options["store_path"] + ".dev"
292  		if not os.path.isdir(os.path.dirname(options["store_path"])):
293  			os.makedirs(os.path.dirname(options["store_path"]))
294  		try:
295  			f = open(file_path, "a+")
296  		except IOError:
297  			fail_usage("Failed: Cannot open file \""+ file_path + "\"")
298  		f.seek(0)
299  		out = f.read()
300  		if not re.search(r"^" + dev + r"\s+", out, flags=re.MULTILINE):
301  			f.write(dev + "\n")
302  		f.close()
303  	
304  	
305  	def dev_read(fail=True, opt=None):
306  		file_path = STORE_PATH + ".dev"
307  		try:
308  			f = open(file_path, "r")
309  		except IOError:
310  			if "--suppress-errors" not in opt:
311  				fail_usage("Failed: Cannot open file \"" + file_path + "\"", fail)
312  			if not fail:
313  				return None
314  		# get not empty lines from file
315  		devs = [line.strip() for line in f if line.strip()]
316  		f.close()
317  		return devs
318  	
319  	
320  	def get_shared_devices(options):
321  		devs = []
322  		cmd = options["--vgs-path"] + " " +\
323  		"--noheadings " +\
324  		"--separator : " +\
325  		"--sort pv_uuid " +\
326  		"--options vg_attr,pv_name "+\
327  		"--config 'global { locking_type = 0 } devices { preferred_names = [ \"^/dev/dm\" ] }'"
328  		out = run_cmd(options, cmd)
329  		if out["rc"]:
330  			fail_usage("Failed: Cannot get shared devices")
331  		for line in out["out"].splitlines():
332  			vg_attr, pv_name = line.strip().split(":")
333  			if vg_attr[5] in "cs":
334  				devs.append(pv_name)
335  		return devs
336  	
337  	
338  	def get_mpath_slaves(dev):
339  		if dev[:5] == "/dev/":
340  			dev = dev[5:]
341  		slaves = [i for i in os.listdir("/sys/block/" + dev + "/slaves/") if i[:1] != "."]
342  		if slaves[0][:2] == "dm":
343  			slaves = get_mpath_slaves(slaves[0])
344  		else:
345  			slaves = ["/dev/" + x for x in slaves]
346  		return slaves
347  	
348  	
349  	def define_new_opts():
350  		all_opt["devices"] = {
351  			"getopt" : "d:",
352  			"longopt" : "devices",
353  			"help" : "-d, --devices=[devices]        List of devices to use for current operation",
354  			"required" : "0",
355  			"shortdesc" : "List of devices to use for current operation. Devices can \
356  	be comma or space separated list of raw devices (eg. /dev/sdc). Each device must support SCSI-3 \
357  	persistent reservations. Optional if cluster is configured with clvm or lvmlockd.",
358  			"order": 1
359  		}
360  		all_opt["nodename"] = {
361  			"getopt" : ":",
362  			"longopt" : "nodename",
363  			"help" : "",
364  			"required" : "0",
365  			"shortdesc" : "",
366  			"order": 1
367  		}
368  		all_opt["key"] = {
369  			"getopt" : "k:",
370  			"longopt" : "key",
371  			"help" : "-k, --key=[key]                Key to use for the current operation",
372  			"required" : "0",
373  			"shortdesc" : "Key to use for the current operation. This key should be \
374  	unique to a node. For the \"on\" action, the key specifies the key use to \
375  	register the local node. For the \"off\" action, this key specifies the key to \
376  	be removed from the device(s).",
377  			"order": 1
378  		}
379  		all_opt["aptpl"] = {
380  			"getopt" : "a",
381  			"longopt" : "aptpl",
382  			"help" : "-a, --aptpl                    Use the APTPL flag for registrations",
383  			"required" : "0",
384  			"shortdesc" : "Use the APTPL flag for registrations. This option is only used for the 'on' action.",
385  			"order": 1
386  		}
387  		all_opt["mpath_register_method"] = {
388  			"getopt" : ":",
389  			"longopt" : "mpath-register-method",
390  			"help" : "--mpath-register-method=[multi|single]  Register key to all multipath sub devices (multi), or directly to the main multipath device (single).",
391  			"required" : "0",
392  			"shortdesc" : "Multipath register method (multi/single).",
393  			"default": "multi",
394  			"order": 3
395  		}
396  		all_opt["readonly"] = {
397  			"getopt" : "",
398  			"longopt" : "readonly",
399  			"help" : "--readonly                     Open DEVICE read-only. May be useful with PRIN commands if there are unwanted side effects with the default read-write open.",
400  			"required" : "0",
401  			"shortdesc" : "Open DEVICE read-only.",
402  			"order": 4
403  		}
404  		all_opt["suppress-errors"] = {
405  			"getopt" : "",
406  			"longopt" : "suppress-errors",
407  			"help" : "--suppress-errors              Suppress error log. Suppresses error logging when run from the watchdog service before pacemaker starts.",
408  			"required" : "0",
409  			"shortdesc" : "Error log suppression.",
410  			"order": 5
411  		}
412  		all_opt["logfile"] = {
413  			"getopt" : ":",
414  			"longopt" : "logfile",
415  			"help" : "-f, --logfile                  Log output (stdout and stderr) to file",
416  			"required" : "0",
417  			"shortdesc" : "Log output (stdout and stderr) to file",
418  			"order": 6
419  		}
420  		all_opt["corosync_cmap_path"] = {
421  			"getopt" : ":",
422  			"longopt" : "corosync-cmap-path",
423  			"help" : "--corosync-cmap-path=[path]    Path to corosync-cmapctl binary",
424  			"required" : "0",
425  			"shortdesc" : "Path to corosync-cmapctl binary",
426  			"default" : "@COROSYNC_CMAPCTL_PATH@",
427  			"order": 300
428  		}
429  		all_opt["sg_persist_path"] = {
430  			"getopt" : ":",
431  			"longopt" : "sg_persist-path",
432  			"help" : "--sg_persist-path=[path]       Path to sg_persist binary",
433  			"required" : "0",
434  			"shortdesc" : "Path to sg_persist binary",
435  			"default" : "@SG_PERSIST_PATH@",
436  			"order": 300
437  		}
438  		all_opt["sg_turs_path"] = {
439  			"getopt" : ":",
440  			"longopt" : "sg_turs-path",
441  			"help" : "--sg_turs-path=[path]          Path to sg_turs binary",
442  			"required" : "0",
443  			"shortdesc" : "Path to sg_turs binary",
444  			"default" : "@SG_TURS_PATH@",
445  			"order": 300
446  		}
447  		all_opt["vgs_path"] = {
448  			"getopt" : ":",
449  			"longopt" : "vgs-path",
450  			"help" : "--vgs-path=[path]              Path to vgs binary",
451  			"required" : "0",
452  			"shortdesc" : "Path to vgs binary",
453  			"default" : "@VGS_PATH@",
454  			"order": 300
455  		}
456  		all_opt["key_value"] = {
457  			"getopt" : ":",
458  			"longopt" : "key-value",
459  			"help" : "--key-value=<id|hash>          SCSI key node generation method",
460  			"required" : "0",
461  			"shortdesc" : "Method used to generate the SCSI key. \"id\" (default) \
462  	uses the positional ID from \"corosync-cmactl nodelist\" output which can get inconsistent \
463  	when nodes are removed from cluster without full cluster restart. \"hash\" uses part of hash \
464  	made out of node names which is not affected over time but there is theoretical chance that \
465  	hashes can collide as size of SCSI key is quite limited.",
466  			"default" : "id",
467  			"order": 300
468  		}
469  	
470  	
471  	def scsi_check_get_options(options):
472  		try:
473  			f = open("/etc/sysconfig/stonith", "r")
474  		except IOError:
475  			return options
476  	
477  		match = re.findall(r"^\s*(\S*)\s*=\s*(\S*)\s*", "".join(f.readlines()), re.MULTILINE)
478  	
479  		for m in match:
480  			options[m[0].lower()] = m[1].lower()
481  	
482  		f.close()
483  	
484  		return options
485  	
486  	
487  	def scsi_check(hardreboot=False):
488  		if len(sys.argv) >= 3 and sys.argv[1] == "repair":
489  			return int(sys.argv[2])
490  		options = {}
491  		options["--sg_turs-path"] = "@SG_TURS_PATH@"
492  		options["--sg_persist-path"] = "@SG_PERSIST_PATH@"
493  		options["--power-timeout"] = "5"
494  		options["retry"] = "0"
495  		options["retry-sleep"] = "1"
496  		options = scsi_check_get_options(options)
497  		if "verbose" in options and options["verbose"] == "yes":
498  			logging.getLogger().setLevel(logging.DEBUG)
499  		devs = dev_read(fail=False,opt=options)
500  		if not devs:
501  			if "--suppress-errors" not in options:
502  				logging.error("No devices found")
503  			return 0
504  		key = get_key(fail=False)
505  		if not key:
506  			logging.error("Key not found")
507  			return 0
508  		for dev in devs:
509  			for n in range(int(options["retry"]) + 1):
510  				if n > 0:
511  					logging.debug("retry: " + str(n) + " of " + options["retry"])
512  				if key in get_registration_keys(options, dev, fail=False):
513  					logging.debug("key " + key + " registered with device " + dev)
514  					return 0
515  				else:
516  					logging.debug("key " + key + " not registered with device " + dev)
517  	
518  				if n < int(options["retry"]):
519  					time.sleep(float(options["retry-sleep"]))
520  	
521  		logging.debug("key " + key + " registered with any devices")
522  	
523  		if hardreboot == True:
524  			libc = ctypes.cdll['libc.so.6']
525  			libc.reboot(0x1234567)
526  		return 2
527  	
528  	
529  	def main():
530  	
531  		atexit.register(atexit_handler)
532  	
533  		device_opt = ["no_login", "no_password", "devices", "nodename", "port",\
534  		"no_port", "key", "aptpl", "fabric_fencing", "on_target", "corosync_cmap_path",\
535  		"sg_persist_path", "sg_turs_path", "mpath_register_method", "readonly", \
536  		"suppress-errors", "logfile", "vgs_path","force_on", "key_value"]
537  	
538  		define_new_opts()
539  	
540  		all_opt["delay"]["getopt"] = "H:"
541  	
542  		all_opt["port"]["help"] = "-n, --plug=[nodename]          Name of the node to be fenced"
543  		all_opt["port"]["shortdesc"] = "Name of the node to be fenced. The node name is used to \
544  	generate the key value used for the current operation. This option will be \
545  	ignored when used with the -k option."
546  	
547  		#fence_scsi_check
548  		if os.path.basename(sys.argv[0]) == "fence_scsi_check":
549  			sys.exit(scsi_check())
550  		elif os.path.basename(sys.argv[0]) == "fence_scsi_check_hardreboot":
551  			sys.exit(scsi_check(True))
552  	
553  		options = check_input(device_opt, process_input(device_opt), other_conditions=True)
554  	
555  		# hack to remove list/list-status actions which are not supported
556  		options["device_opt"] = [ o for o in options["device_opt"] if o != "separator" ]
557  	
558  		docs = {}
559  		docs["shortdesc"] = "Fence agent for SCSI persistent reservation"
560  		docs["longdesc"] = "fence_scsi is an I/O Fencing agent that uses SCSI-3 \
561  	persistent reservations to control access to shared storage devices. These \
562  	devices must support SCSI-3 persistent reservations (SPC-3 or greater) as \
563  	well as the \"preempt-and-abort\" subcommand.\nThe fence_scsi agent works by \
564  	having each node in the cluster register a unique key with the SCSI \
565  	device(s). Reservation key is generated from \"node id\" (default) or from \
566  	\"node name hash\" (RECOMMENDED) by adjusting \"key_value\" option. \
567  	Using hash is recommended to prevent issues when removing nodes \
568  	from cluster without full cluster restart. \
569  	Once registered, a single node will become the reservation holder \
570  	by creating a \"write exclusive, registrants only\" reservation on the \
571  	device(s). The result is that only registered nodes may write to the \
572  	device(s). When a node failure occurs, the fence_scsi agent will remove the \
573  	key belonging to the failed node from the device(s). The failed node will no \
574  	longer be able to write to the device(s). A manual reboot is required.\
575  	\n.P\n\
576  	When used as a watchdog device you can define e.g. retry=1, retry-sleep=2 and \
577  	verbose=yes parameters in /etc/sysconfig/stonith if you have issues with it \
578  	failing."
579  		docs["vendorurl"] = ""
580  		show_docs(options, docs)
581  	
582  		run_delay(options)
583  	
584  		# backward compatibility layer BEGIN
585  		if "--logfile" in options:
586  			try:
587  				logfile = open(options["--logfile"], 'w')
588  				sys.stderr = logfile
589  				sys.stdout = logfile
590  			except IOError:
591  				fail_usage("Failed: Unable to create file " + options["--logfile"])
592  		# backward compatibility layer END
593  	
594  		options["store_path"] = STORE_PATH
595  	
596  		# Input control BEGIN
597  		stop_after_error = False if options["--action"] == "validate-all" else True
598  	
599  		if options["--action"] == "monitor":
600  			sys.exit(do_action_monitor(options))
601  	
602  		# workaround to avoid regressions
603  		if "--nodename" in options and options["--nodename"]:
604  			options["--plug"] = options["--nodename"]
605  			del options["--nodename"]
606  	
607  		if not (("--plug" in options and options["--plug"])\
608  		or ("--key" in options and options["--key"])):
609  			fail_usage("Failed: nodename or key is required", stop_after_error)
610  	
611  		if options["--action"] != "validate-all":
612  			if not ("--key" in options and options["--key"]):
613  				options["--key"] = generate_key(options)
614  	
615  			if options["--key"] == "0" or not options["--key"]:
616  				fail_usage("Failed: key cannot be 0", stop_after_error)
617  	
618  		if "--key-value" in options\
619  		and (options["--key-value"] != "id" and options["--key-value"] != "hash"):
620  			fail_usage("Failed: key-value has to be 'id' or 'hash'", stop_after_error)
621  	
622  		if options["--action"] == "validate-all":
623  			sys.exit(0)
624  	
625  		options["--key"] = options["--key"].lstrip('0')
626  	
627  		if not ("--devices" in options and [d for d in re.split(r"\s*,\s*|\s+", options["--devices"].strip()) if d]):
628  			options["devices"] = get_shared_devices(options)
629  		else:
630  			options["devices"] = [d for d in re.split(r"\s*,\s*|\s+", options["--devices"].strip()) if d]
631  	
632  		if not options["devices"]:
633  			fail_usage("Failed: No devices found")
634  		# Input control END
635  	
636  		result = fence_action(None, options, set_status, get_status)
637  		sys.exit(result)
638  	
639  	if __name__ == "__main__":
640  		main()
641