Beginning the process of returning useful exit codes. First we
add the ExitCodes themselves, and start using them in sys.exit() calls
throughout for consistency.
Some are still a little weird, like returning EX_SOFTWARE when a
request to move a file results in an S3Error(NoSuchKey). It's not
really a software failure, it's arguably a user input failure. But to
fix it we have to catch the S3Error lower down, and raise a
ParameterError or similar.
None of the commands (put, get, sync, ...) yet return any status code,
so main() always exits with EX_OK when it runs to completion.
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,16 @@ |
0 |
+# patterned on /usr/include/sysexits.h |
|
1 |
+ |
|
2 |
+EX_OK = 0 |
|
3 |
+EX_GENERAL = 1 |
|
4 |
+EX_SOMEFAILED = 2 # some parts of the command succeeded, while others failed |
|
5 |
+EX_USAGE = 64 # The command was used incorrectly (e.g. bad command line syntax) |
|
6 |
+EX_SOFTWARE = 70 # internal software error (e.g. S3 error of unknown specificity) |
|
7 |
+EX_OSERR = 71 # system error (e.g. out of memory) |
|
8 |
+EX_OSFILE = 72 # OS error (e.g. invalid Python version) |
|
9 |
+EX_IOERR = 74 # An error occurred while doing I/O on some file. |
|
10 |
+EX_TEMPFAIL = 75 # temporary failure (S3DownloadError or similar, retry later) |
|
11 |
+EX_NOPERM = 77 # Insufficient permissions to perform the operation on S3 |
|
12 |
+EX_CONFIG = 78 # Configuration file error |
|
13 |
+_EX_SIGNAL = 128 |
|
14 |
+_EX_SIGINT = 2 |
|
15 |
+EX_BREAK = _EX_SIGNAL + _EX_SIGINT # Control-C (KeyboardInterrupt raised) |
... | ... |
@@ -18,6 +18,7 @@ import errno |
18 | 18 |
import urllib |
19 | 19 |
from calendar import timegm |
20 | 20 |
from logging import debug, info, warning, error |
21 |
+from ExitCodes import EX_OSFILE |
|
21 | 22 |
try: |
22 | 23 |
import dateutil.parser |
23 | 24 |
except ImportError: |
... | ... |
@@ -33,7 +34,7 @@ $ pip install python-dateutil |
33 | 33 |
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
34 | 34 |
""") |
35 | 35 |
sys.stderr.flush() |
36 |
- sys.exit(1) |
|
36 |
+ sys.exit(EX_OSFILE) |
|
37 | 37 |
|
38 | 38 |
import Config |
39 | 39 |
import Exceptions |
... | ... |
@@ -15,6 +15,7 @@ import locale |
15 | 15 |
import getpass |
16 | 16 |
import S3.Exceptions |
17 | 17 |
import S3.Config |
18 |
+from S3.ExitCodes import * |
|
18 | 19 |
|
19 | 20 |
count_pass = 0 |
20 | 21 |
count_fail = 0 |
... | ... |
@@ -290,7 +291,7 @@ test_s3cmd("Create multiple buckets", ['mb', pbucket(2), pbucket(3)], |
290 | 290 |
|
291 | 291 |
## ====== Invalid bucket name |
292 | 292 |
test_s3cmd("Invalid bucket name", ["mb", "--bucket-location=EU", pbucket('EU')], |
293 |
- retcode = 1, |
|
293 |
+ retcode = EX_USAGE, |
|
294 | 294 |
must_find = "ERROR: Parameter problem: Bucket name '%s' contains disallowed character" % bucket('EU'), |
295 | 295 |
must_not_find_re = "Bucket.*created") |
296 | 296 |
|
... | ... |
@@ -421,7 +422,7 @@ test_s3cmd("Rename within S3", ['mv', '%s/xyz/etc/logo.png' % pbucket(1), '%s/xy |
421 | 421 |
|
422 | 422 |
## ====== Rename (NoSuchKey) |
423 | 423 |
test_s3cmd("Rename (NoSuchKey)", ['mv', '%s/xyz/etc/logo.png' % pbucket(1), '%s/xyz/etc2/Logo.PNG' % pbucket(1)], |
424 |
- retcode = 1, |
|
424 |
+ retcode = EX_SOFTWARE, |
|
425 | 425 |
must_find_re = [ 'ERROR:.*NoSuchKey' ], |
426 | 426 |
must_not_find = [ 'File %s/xyz/etc/logo.png moved to %s/xyz/etc2/Logo.PNG' % (pbucket(1), pbucket(1)) ]) |
427 | 427 |
|
... | ... |
@@ -443,7 +444,7 @@ test_rmdir("Remove dst dir for get", "testsuite-out") |
443 | 443 |
|
444 | 444 |
## ====== Get multiple files |
445 | 445 |
test_s3cmd("Get multiple files", ['get', '%s/xyz/etc2/Logo.PNG' % pbucket(1), '%s/xyz/etc/AtomicClockRadio.ttf' % pbucket(1), 'testsuite-out'], |
446 |
- retcode = 1, |
|
446 |
+ retcode = EX_USAGE, |
|
447 | 447 |
must_find = [ 'Destination must be a directory or stdout when downloading multiple sources.' ]) |
448 | 448 |
|
449 | 449 |
## ====== put/get non-ASCII filenames |
... | ... |
@@ -22,7 +22,7 @@ import sys |
22 | 22 |
|
23 | 23 |
if float("%d.%d" %(sys.version_info[0], sys.version_info[1])) < 2.4: |
24 | 24 |
sys.stderr.write(u"ERROR: Python 2.4 or higher required, sorry.\n") |
25 |
- sys.exit(1) |
|
25 |
+ sys.exit(EX_OSFILE) |
|
26 | 26 |
|
27 | 27 |
import logging |
28 | 28 |
import time |
... | ... |
@@ -358,7 +358,7 @@ def cmd_object_put(args): |
358 | 358 |
full_name = full_name_orig |
359 | 359 |
seq_label = "[%d of %d]" % (seq, local_count) |
360 | 360 |
if Config().encrypt: |
361 |
- exitcode, full_name, extra_headers["x-amz-meta-s3tools-gpgenc"] = gpg_encrypt(full_name_orig) |
|
361 |
+ gpg_exitcode, full_name, extra_headers["x-amz-meta-s3tools-gpgenc"] = gpg_encrypt(full_name_orig) |
|
362 | 362 |
if cfg.preserve_attrs or local_list[key]['size'] > (cfg.multipart_chunk_size_mb * 1024 * 1024): |
363 | 363 |
attr_header = _build_attr_header(local_list, key) |
364 | 364 |
debug(u"attr_header: %s" % attr_header) |
... | ... |
@@ -1398,7 +1398,7 @@ def cmd_sync_local2remote(args): |
1398 | 1398 |
error(u"S3cmd 'sync' doesn't yet support GPG encryption, sorry.") |
1399 | 1399 |
error(u"Either use unconditional 's3cmd put --recursive'") |
1400 | 1400 |
error(u"or disable encryption with --no-encrypt parameter.") |
1401 |
- sys.exit(1) |
|
1401 |
+ sys.exit(EX_USAGE) |
|
1402 | 1402 |
|
1403 | 1403 |
local_list, single_file_local, src_exclude_list = fetch_local_list(args[:-1], is_src = True, recursive = True) |
1404 | 1404 |
|
... | ... |
@@ -1837,14 +1837,14 @@ def run_configure(config_file, args): |
1837 | 1837 |
|
1838 | 1838 |
except IOError, e: |
1839 | 1839 |
error(u"Writing config file failed: %s: %s" % (config_file, e.strerror)) |
1840 |
- sys.exit(1) |
|
1840 |
+ sys.exit(EX_IOERR) |
|
1841 | 1841 |
|
1842 | 1842 |
def process_patterns_from_file(fname, patterns_list): |
1843 | 1843 |
try: |
1844 | 1844 |
fn = open(fname, "rt") |
1845 | 1845 |
except IOError, e: |
1846 | 1846 |
error(e) |
1847 |
- sys.exit(1) |
|
1847 |
+ sys.exit(EX_IOERR) |
|
1848 | 1848 |
for pattern in fn: |
1849 | 1849 |
pattern = pattern.strip() |
1850 | 1850 |
if re.match("^#", pattern) or re.match("^\s*$", pattern): |
... | ... |
@@ -2160,7 +2160,7 @@ def main(): |
2160 | 2160 |
|
2161 | 2161 |
if options.show_version: |
2162 | 2162 |
output(u"s3cmd version %s" % PkgInfo.version) |
2163 |
- sys.exit(0) |
|
2163 |
+ sys.exit(EX_OK) |
|
2164 | 2164 |
|
2165 | 2165 |
if options.quiet: |
2166 | 2166 |
try: |
... | ... |
@@ -2173,7 +2173,7 @@ def main(): |
2173 | 2173 |
## Now finally parse the config file |
2174 | 2174 |
if not options.config: |
2175 | 2175 |
error(u"Can't find a config file. Please use --config option.") |
2176 |
- sys.exit(1) |
|
2176 |
+ sys.exit(EX_CONFIG) |
|
2177 | 2177 |
|
2178 | 2178 |
try: |
2179 | 2179 |
cfg = Config(options.config, options.access_key, options.secret_key) |
... | ... |
@@ -2184,7 +2184,7 @@ def main(): |
2184 | 2184 |
error(u"%s: %s" % (options.config, e.strerror)) |
2185 | 2185 |
error(u"Configuration file not available.") |
2186 | 2186 |
error(u"Consider using --configure parameter to create one.") |
2187 |
- sys.exit(1) |
|
2187 |
+ sys.exit(EX_CONFIG) |
|
2188 | 2188 |
|
2189 | 2189 |
# allow commandline verbosity config to override config file |
2190 | 2190 |
if options.verbosity is not None: |
... | ... |
@@ -2320,20 +2320,20 @@ def main(): |
2320 | 2320 |
if cfg.encrypt and cfg.gpg_passphrase == "": |
2321 | 2321 |
error(u"Encryption requested but no passphrase set in config file.") |
2322 | 2322 |
error(u"Please re-run 's3cmd --configure' and supply it.") |
2323 |
- sys.exit(1) |
|
2323 |
+ sys.exit(EX_CONFIG) |
|
2324 | 2324 |
|
2325 | 2325 |
if options.dump_config: |
2326 | 2326 |
cfg.dump_config(sys.stdout) |
2327 |
- sys.exit(0) |
|
2327 |
+ sys.exit(EX_OK) |
|
2328 | 2328 |
|
2329 | 2329 |
if options.run_configure: |
2330 | 2330 |
# 'args' may contain the test-bucket URI |
2331 | 2331 |
run_configure(options.config, args) |
2332 |
- sys.exit(0) |
|
2332 |
+ sys.exit(EX_OK) |
|
2333 | 2333 |
|
2334 | 2334 |
if len(args) < 1: |
2335 | 2335 |
error(u"Missing command. Please run with --help for more information.") |
2336 |
- sys.exit(1) |
|
2336 |
+ sys.exit(EX_USAGE) |
|
2337 | 2337 |
|
2338 | 2338 |
## Unicodise all remaining arguments: |
2339 | 2339 |
args = [unicodise(arg) for arg in args] |
... | ... |
@@ -2347,17 +2347,17 @@ def main(): |
2347 | 2347 |
cmd_func = commands[command]["func"] |
2348 | 2348 |
except KeyError, e: |
2349 | 2349 |
error(u"Invalid command: %s" % e) |
2350 |
- sys.exit(1) |
|
2350 |
+ sys.exit(EX_USAGE) |
|
2351 | 2351 |
|
2352 | 2352 |
if len(args) < commands[command]["argc"]: |
2353 | 2353 |
error(u"Not enough parameters for command '%s'" % command) |
2354 |
- sys.exit(1) |
|
2354 |
+ sys.exit(EX_USAGE) |
|
2355 | 2355 |
|
2356 | 2356 |
try: |
2357 | 2357 |
cmd_func(args) |
2358 | 2358 |
except S3Error, e: |
2359 | 2359 |
error(u"S3 error: %s" % e) |
2360 |
- sys.exit(1) |
|
2360 |
+ sys.exit(EX_SOFTWARE) |
|
2361 | 2361 |
|
2362 | 2362 |
def report_exception(e, msg=''): |
2363 | 2363 |
sys.stderr.write(u""" |
... | ... |
@@ -2417,6 +2417,7 @@ if __name__ == '__main__': |
2417 | 2417 |
## Our modules |
2418 | 2418 |
## Keep them in try/except block to |
2419 | 2419 |
## detect any syntax errors in there |
2420 |
+ from S3.ExitCodes import * |
|
2420 | 2421 |
from S3.Exceptions import * |
2421 | 2422 |
from S3 import PkgInfo |
2422 | 2423 |
from S3.S3 import S3 |
... | ... |
@@ -2433,26 +2434,30 @@ if __name__ == '__main__': |
2433 | 2433 |
from S3.MultiPart import MultiPartUpload |
2434 | 2434 |
|
2435 | 2435 |
main() |
2436 |
- sys.exit(0) |
|
2436 |
+ sys.exit(EX_OK) |
|
2437 | 2437 |
|
2438 | 2438 |
except ImportError, e: |
2439 | 2439 |
report_exception(e) |
2440 |
- sys.exit(1) |
|
2440 |
+ sys.exit(EX_GENERAL) |
|
2441 | 2441 |
|
2442 | 2442 |
except ParameterError, e: |
2443 | 2443 |
error(u"Parameter problem: %s" % e) |
2444 |
- sys.exit(1) |
|
2444 |
+ sys.exit(EX_USAGE) |
|
2445 | 2445 |
|
2446 | 2446 |
except SystemExit, e: |
2447 | 2447 |
sys.exit(e.code) |
2448 | 2448 |
|
2449 | 2449 |
except KeyboardInterrupt: |
2450 | 2450 |
sys.stderr.write("See ya!\n") |
2451 |
- sys.exit(1) |
|
2451 |
+ sys.exit(EX_BREAK) |
|
2452 | 2452 |
|
2453 |
- except (OSError, IOError), e: |
|
2453 |
+ except IOError, e: |
|
2454 |
+ error(e) |
|
2455 |
+ sys.exit(EX_IOERR) |
|
2456 |
+ |
|
2457 |
+ except OSError, e: |
|
2454 | 2458 |
error(e) |
2455 |
- sys.exit(1) |
|
2459 |
+ sys.exit(EX_OSERR) |
|
2456 | 2460 |
|
2457 | 2461 |
except MemoryError: |
2458 | 2462 |
msg = """ |
... | ... |
@@ -2463,7 +2468,7 @@ The solutions to this are: |
2463 | 2463 |
2) use a 64-bit python on a 64-bit OS with >8GB RAM |
2464 | 2464 |
""" |
2465 | 2465 |
sys.stderr.write(msg) |
2466 |
- sys.exit(1) |
|
2466 |
+ sys.exit(EX_OSERR) |
|
2467 | 2467 |
|
2468 | 2468 |
except UnicodeEncodeError, e: |
2469 | 2469 |
lang = os.getenv("LANG") |
... | ... |
@@ -2474,10 +2479,10 @@ Please set LANG=en_US.UTF-8 or similar in your environment before |
2474 | 2474 |
invoking s3cmd. |
2475 | 2475 |
""" % lang |
2476 | 2476 |
report_exception(e, msg) |
2477 |
- sys.exit(1) |
|
2477 |
+ sys.exit(EX_GENERAL) |
|
2478 | 2478 |
|
2479 | 2479 |
except Exception, e: |
2480 | 2480 |
report_exception(e) |
2481 |
- sys.exit(1) |
|
2481 |
+ sys.exit(EX_GENERAL) |
|
2482 | 2482 |
|
2483 | 2483 |
# vim:et:ts=4:sts=4:ai |