Browse code

add S3/ExitCodes, use them in all sys.exit() calls.

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.

Matt Domsch authored on 2014/04/20 12:24:10
Showing 4 changed files
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