Browse code

Reworked Upload and Download errors to be more consistent. Add detection and handling of different OSErrors like "File name too long."

Florent Viard authored on 2015/09/16 03:36:07
Showing 4 changed files
... ...
@@ -544,7 +544,7 @@ class S3(object):
544 544
             raise ValueError("Expected URI type 's3', got '%s'" % uri.type)
545 545
 
546 546
         if filename != "-" and not os.path.isfile(deunicodise(filename)):
547
-            raise InvalidFileError(u"%s is not a regular file" % filename)
547
+            raise InvalidFileError(u"Not a regular file")
548 548
         try:
549 549
             if filename == "-":
550 550
                 file = sys.stdin
... ...
@@ -553,7 +553,7 @@ class S3(object):
553 553
                 file = open(deunicodise(filename), "rb")
554 554
                 size = os.stat(deunicodise(filename))[ST_SIZE]
555 555
         except (IOError, OSError), e:
556
-            raise InvalidFileError(u"%s: %s" % (filename, e.strerror))
556
+            raise InvalidFileError(u"%s" % e.strerror)
557 557
 
558 558
         headers = SortedDict(ignore_case = True)
559 559
         if extra_headers:
... ...
@@ -263,10 +263,10 @@ def mkdir_with_parents(dir_name):
263 263
             debug("mkdir(%s)" % cur_dir)
264 264
             os.mkdir(deunicodise(cur_dir))
265 265
         except (OSError, IOError), e:
266
-            warning("%s: can not make directory: %s" % (cur_dir, e.strerror))
266
+            debug("Can not make directory '%s' (Reason: %s)" % (cur_dir, e.strerror))
267 267
             return False
268 268
         except Exception, e:
269
-            warning("%s: %s" % (cur_dir, e))
269
+            debug("Can not make directory '%s' (Reason: %s)" % (cur_dir, e))
270 270
             return False
271 271
     return True
272 272
 __all__.append("mkdir_with_parents")
... ...
@@ -317,7 +317,7 @@ test_s3cmd("Buckets list", ["ls"],
317 317
 
318 318
 ## ====== Sync to S3
319 319
 test_s3cmd("Sync to S3", ['sync', 'testsuite/', pbucket(1) + '/xyz/', '--exclude', 'demo/*', '--exclude', '*.png', '--no-encrypt', '--exclude-from', 'testsuite/exclude.encodings' ],
320
-           must_find = [ "WARNING: File can not be uploaded: testsuite/permission-tests/permission-denied.txt: Permission denied",
320
+           must_find = [ "ERROR: Upload of 'testsuite/permission-tests/permission-denied.txt' is not possible (Reason: Permission denied)",
321 321
                          "WARNING: 32 non-printable characters replaced in: crappy-file-name/non-printables",
322 322
            ],
323 323
            must_not_find_re = [ "demo/", "\.png$", "permission-denied-dir" ],
... ...
@@ -392,7 +392,7 @@ test_mkdir("Create file-dir dir", "testsuite-out/xyz/dir-test/file-dir")
392 392
 
393 393
 ## ====== Skip dst dirs
394 394
 test_s3cmd("Skip over dir", ['sync', '%s/xyz' % pbucket(1), 'testsuite-out'],
395
-           must_find = "WARNING: testsuite-out/xyz/dir-test/file-dir is a directory - skipping over",
395
+           must_find = "ERROR: Download of 'xyz/dir-test/file-dir' failed (Reason: testsuite-out/xyz/dir-test/file-dir is a directory)",
396 396
            retcode = EX_PARTIAL)
397 397
 
398 398
 
... ...
@@ -383,25 +383,30 @@ def cmd_object_put(args):
383 383
         try:
384 384
             response = s3.object_put(full_name, uri_final, extra_headers, extra_label = seq_label)
385 385
         except S3UploadError, exc:
386
+            error(u"Upload of '%s' failed too many times (Last reason: %s)" % (full_name_orig, exc))
386 387
             if cfg.stop_on_error:
387 388
                 ret = EX_DATAERR
388
-                error(u"Upload of '%s' failed too many times. Exiting. (Last reason: %s)" % (full_name_orig, exc))
389
+                error(u"Exiting now because of --stop-on-error")
389 390
                 break
390 391
             ret = EX_PARTIAL
391
-            error(u"Upload of '%s' failed too many times. Skipping that file. (Last reason: %s)" % (full_name_orig, exc))
392 392
             continue
393 393
         except InvalidFileError, exc:
394
-            warning(u"File can not be uploaded: %s" % exc)
394
+            error(u"Upload of '%s' is not possible (Reason: %s)" % (full_name_orig, exc))
395 395
             ret = EX_PARTIAL
396 396
             if cfg.stop_on_error:
397 397
                 ret = EX_OSFILE
398
+                error(u"Exiting now because of --stop-on-error")
398 399
                 break
399 400
             continue
400 401
         if response is not None:
401 402
             speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
402 403
             if not Config().progress_meter:
404
+                if full_name_orig != "-":
405
+                    nicekey = full_name_orig
406
+                else:
407
+                    nicekey = "<stdin>"
403 408
                 output(u"upload: '%s' -> '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
404
-                       (full_name_orig, uri_final, response["size"], response["elapsed"],
409
+                       (nicekey, uri_final, response["size"], response["elapsed"],
405 410
                         speed_fmt[0], speed_fmt[1], seq_label))
406 411
         if Config().acl_public:
407 412
             output(u"Public URL of the object is: %s" %
... ...
@@ -1121,135 +1126,171 @@ def cmd_sync_remote2local(args):
1121 1121
             dst_file = item['local_filename']
1122 1122
             is_empty_directory = dst_file.endswith('/')
1123 1123
             seq_label = "[%d of %d]" % (seq, total)
1124
+
1125
+            dst_dir = unicodise(os.path.dirname(deunicodise(dst_file)))
1126
+            if not dir_cache.has_key(dst_dir):
1127
+                dir_cache[dst_dir] = Utils.mkdir_with_parents(dst_dir)
1128
+            if dir_cache[dst_dir] == False:
1129
+                if cfg.stop_on_error:
1130
+                    error(u"Exiting now because of --stop-on-error")
1131
+                    raise OSError("Download of '%s' failed (Reason: %s destination directory is not writable)" % (file, dst_dir))
1132
+                error(u"Download of '%s' failed (Reason: %s destination directory is not writable)" % (file, dst_dir))
1133
+                ret = EX_PARTIAL
1134
+                continue
1135
+
1124 1136
             try:
1125
-                dst_dir = unicodise(os.path.dirname(deunicodise(dst_file)))
1126
-                if not dir_cache.has_key(dst_dir):
1127
-                    dir_cache[dst_dir] = Utils.mkdir_with_parents(dst_dir)
1128
-                if dir_cache[dst_dir] == False:
1129
-                    if cfg.stop_on_error:
1130
-                        raise OSError("%s: destination directory not writable: %s" % (file, dst_dir))
1131
-                    warning(u"%s: destination directory not writable: %s" % (file, dst_dir))
1132
-                    ret = EX_PARTIAL
1133
-                    continue
1137
+                chkptfname = ''
1138
+                if not is_empty_directory: # ignore empty directory at S3:
1139
+                    debug(u"dst_file=%s" % dst_file)
1140
+                    # create temporary files (of type .s3cmd.XXXX.tmp) in the same directory
1141
+                    # for downloading and then rename once downloaded
1142
+                    chkptfd, chkptfname = tempfile.mkstemp(".tmp",".s3cmd.",os.path.dirname(deunicodise(dst_file)))
1143
+                    chkptfname = unicodise(chkptfname)
1144
+                    debug(u"created chkptfname=%s" % chkptfname)
1145
+                    dst_stream = os.fdopen(chkptfd, "wb")
1146
+                    response = s3.object_get(uri, dst_stream, dst_file, extra_label = seq_label)
1147
+                    dst_stream.close()
1148
+                    # download completed, rename the file to destination
1149
+                    os.rename(deunicodise(chkptfname), deunicodise(dst_file))
1150
+                    debug(u"renamed chkptfname=%s to dst_file=%s" % (chkptfname, dst_file))
1151
+            except OSError, exc:
1152
+                if (exc.errno == errno.EISDIR or
1153
+                      exc.errno == errno.ETXTBSY or
1154
+                      exc.errno == errno.EPERM or
1155
+                      exc.errno == errno.EACCES or
1156
+                      exc.errno == errno.EBUSY or
1157
+                      exc.errno == errno.EFBIG or
1158
+                      exc.errno == errno.ENAMETOOLONG):
1159
+
1160
+                    if exc.errno == errno.EISDIR:
1161
+                        error(u"Download of '%s' failed (Reason: %s is a directory)" % (file, dst_file))
1162
+                    elif exc.errno == errno.ETXTBSY:
1163
+                        error(u"Download of '%s' failed (Reason: %s is currently open for execute, cannot be overwritten)" % (file, dst_file))
1164
+                    elif (exc.errno == errno.EPERM or
1165
+                          exc.errno == errno.EACCES):
1166
+                        error(u"Download of '%s' failed (Reason: %s permission denied)" % (file, dst_file))
1167
+                    elif exc.errno == errno.EBUSY:
1168
+                        error(u"Download of '%s' failed (Reason: %s is busy)" % (file, dst_file))
1169
+                    elif exc.errno == errno.EFBIG:
1170
+                        error(u"Download of '%s' failed (Reason: %s is too big)" % (file, dst_file))
1171
+                    elif exc.errno == errno.ENAMETOOLONG:
1172
+                        error(u"Download of '%s' failed (Reason: File Name is too long)" % file)
1134 1173
 
1135
-                try:
1136
-                    if not is_empty_directory: # ignore empty directory at S3:
1137
-                        debug(u"dst_file=%s" % dst_file)
1138
-                        # create temporary files (of type .s3cmd.XXXX.tmp) in the same directory
1139
-                        # for downloading and then rename once downloaded
1140
-                        chkptfd, chkptfname = tempfile.mkstemp(".tmp",".s3cmd.",os.path.dirname(deunicodise(dst_file)))
1141
-                        chkptfname = unicodise(chkptfname)
1142
-                        debug(u"created chkptfname=%s" % chkptfname)
1143
-                        dst_stream = os.fdopen(chkptfd, "wb")
1144
-                        response = s3.object_get(uri, dst_stream, dst_file, extra_label = seq_label)
1145
-                        dst_stream.close()
1146
-                        # download completed, rename the file to destination
1147
-                        os.rename(deunicodise(chkptfname), deunicodise(dst_file))
1148
-                        debug(u"renamed chkptfname=%s to dst_file=%s" % (chkptfname, dst_file))
1149
-                except OSError, e:
1150
-                    ret = EX_PARTIAL
1151
-                    if e.errno == errno.EISDIR:
1152
-                        warning(u"%s is a directory - skipping over" % dst_file)
1153
-                        continue
1154
-                    elif e.errno == errno.ETXTBSY:
1155
-                        warning(u"%s is currently open for execute, cannot be overwritten.  Skipping over." % dst_file)
1156
-                        os.unlink(deunicodise(chkptfname))
1157
-                        continue
1158
-                    else:
1159
-                        raise
1160
-                except S3DownloadError, e:
1161
-                    error(u"%s: Skipping that file.  This is usually a transient error, please try again later." % e)
1162
-                    os.unlink(deunicodise(chkptfname))
1163
-                    if cfg.stop_on_error:
1164
-                        raise
1165
-                    ret = EX_PARTIAL
1166
-                    continue
1167
-                except S3Error, e:
1168
-                    warning(u"Remote file %s S3Error: %s" % (e.resource, e))
1174
+                    try:
1175
+                        # Try to remove the temp file if it exists
1176
+                        if chkptfname:
1177
+                            os.unlink(deunicodise(chkptfname))
1178
+                    except:
1179
+                        pass
1169 1180
                     if cfg.stop_on_error:
1181
+                        ret = EX_OSFILE
1182
+                        error(u"Exiting now because of --stop-on-error")
1170 1183
                         raise
1171 1184
                     ret = EX_PARTIAL
1172 1185
                     continue
1186
+                elif (exc.errno == errno.ENOSPC or
1187
+                      exc.errno == errno.EDQUOT):
1188
+                    error(u"Download of '%s' failed (Reason: No space left)" % file)
1189
+                    try:
1190
+                        # Try to remove the temp file if it exists
1191
+                        if chkptfname:
1192
+                            os.unlink(deunicodise(chkptfname))
1193
+                    except:
1194
+                        pass
1195
+                    raise
1173 1196
 
1174
-                try:
1175
-                    # set permissions on destination file
1176
-                    if not is_empty_directory: # a normal file
1177
-                        mode = 0777 - original_umask;
1178
-                    else: # an empty directory, make them readable/executable
1179
-                        mode = 0775
1180
-                    debug(u"mode=%s" % oct(mode))
1181
-                    os.chmod(deunicodise(dst_file), mode);
1182
-                except:
1197
+                raise
1198
+            except S3DownloadError, exc:
1199
+                error(u"Download of '%s' failed too many times (Last Reason: %s). "
1200
+                      "This is usually a transient error, please try again "
1201
+                      "later." % (file, exc))
1202
+                os.unlink(deunicodise(chkptfname))
1203
+                if cfg.stop_on_error:
1204
+                    ret = EX_DATAERR
1205
+                    error(u"Exiting now because of --stop-on-error")
1206
+                    raise
1207
+                ret = EX_PARTIAL
1208
+                continue
1209
+            except S3Error, exc:
1210
+                warning(u"Remote file '%s'. S3Error: %s" % (exc.resource, exc))
1211
+                if cfg.stop_on_error:
1183 1212
                     raise
1213
+                ret = EX_PARTIAL
1214
+                continue
1184 1215
 
1185
-                # because we don't upload empty directories,
1186
-                # we can continue the loop here, we won't be setting stat info.
1187
-                # if we do start to upload empty directories, we'll have to reconsider this.
1188
-                if is_empty_directory:
1189
-                    continue
1216
+            try:
1217
+                # set permissions on destination file
1218
+                if not is_empty_directory: # a normal file
1219
+                    mode = 0777 - original_umask;
1220
+                else: # an empty directory, make them readable/executable
1221
+                    mode = 0775
1222
+                debug(u"mode=%s" % oct(mode))
1223
+                os.chmod(deunicodise(dst_file), mode);
1224
+            except:
1225
+                raise
1226
+
1227
+            # because we don't upload empty directories,
1228
+            # we can continue the loop here, we won't be setting stat info.
1229
+            # if we do start to upload empty directories, we'll have to reconsider this.
1230
+            if is_empty_directory:
1231
+                continue
1190 1232
 
1233
+            try:
1234
+                if response.has_key('s3cmd-attrs') and cfg.preserve_attrs:
1235
+                    attrs = response['s3cmd-attrs']
1236
+                    if attrs.has_key('mode'):
1237
+                        os.chmod(deunicodise(dst_file), int(attrs['mode']))
1238
+                    if attrs.has_key('mtime') or attrs.has_key('atime'):
1239
+                        mtime = attrs.has_key('mtime') and int(attrs['mtime']) or int(time.time())
1240
+                        atime = attrs.has_key('atime') and int(attrs['atime']) or int(time.time())
1241
+                        os.utime(deunicodise(dst_file), (atime, mtime))
1242
+                    if attrs.has_key('uid') and attrs.has_key('gid'):
1243
+                        uid = int(attrs['uid'])
1244
+                        gid = int(attrs['gid'])
1245
+                        os.lchown(deunicodise(dst_file),uid,gid)
1246
+                elif response["headers"].has_key("last-modified"):
1247
+                    last_modified = time.mktime(time.strptime(response["headers"]["last-modified"], "%a, %d %b %Y %H:%M:%S GMT"))
1248
+                    os.utime(deunicodise(dst_file), (last_modified, last_modified))
1249
+                    debug("set mtime to %s" % last_modified)
1250
+            except OSError, e:
1191 1251
                 try:
1192
-                    if response.has_key('s3cmd-attrs') and cfg.preserve_attrs:
1193
-                        attrs = response['s3cmd-attrs']
1194
-                        if attrs.has_key('mode'):
1195
-                            os.chmod(deunicodise(dst_file), int(attrs['mode']))
1196
-                        if attrs.has_key('mtime') or attrs.has_key('atime'):
1197
-                            mtime = attrs.has_key('mtime') and int(attrs['mtime']) or int(time.time())
1198
-                            atime = attrs.has_key('atime') and int(attrs['atime']) or int(time.time())
1199
-                            os.utime(deunicodise(dst_file), (atime, mtime))
1200
-                        if attrs.has_key('uid') and attrs.has_key('gid'):
1201
-                            uid = int(attrs['uid'])
1202
-                            gid = int(attrs['gid'])
1203
-                            os.lchown(deunicodise(dst_file),uid,gid)
1204
-                    elif response["headers"].has_key("last-modified"):
1205
-                        last_modified = time.mktime(time.strptime(response["headers"]["last-modified"], "%a, %d %b %Y %H:%M:%S GMT"))
1206
-                        os.utime(deunicodise(dst_file), (last_modified, last_modified))
1207
-                        debug("set mtime to %s" % last_modified)
1208
-                except OSError, e:
1209
-                    try:
1210
-                        dst_stream.close()
1211
-                        os.remove(deunicodise(chkptfname))
1212
-                    except: pass
1213
-                    ret = EX_PARTIAL
1214
-                    if e.errno == errno.EEXIST:
1215
-                        warning(u"%s exists - not overwriting" % dst_file)
1216
-                        continue
1217
-                    if e.errno in (errno.EPERM, errno.EACCES):
1218
-                        warning(u"%s not writable: %s" % (dst_file, e.strerror))
1219
-                        if cfg.stop_on_error:
1220
-                            raise e
1221
-                        continue
1222
-                    raise e
1223
-                except KeyboardInterrupt:
1224
-                    try:
1225
-                        dst_stream.close()
1226
-                        os.remove(deunicodise(chkptfname))
1227
-                    except: pass
1228
-                    warning(u"Exiting after keyboard interrupt")
1229
-                    return
1230
-                except Exception, e:
1231
-                    try:
1232
-                        dst_stream.close()
1233
-                        os.remove(deunicodise(chkptfname))
1234
-                    except: pass
1235
-                    ret = EX_PARTIAL
1236
-                    error(u"%s: %s" % (file, e))
1252
+                    dst_stream.close()
1253
+                    os.remove(deunicodise(chkptfname))
1254
+                except: pass
1255
+                ret = EX_PARTIAL
1256
+                if e.errno == errno.EEXIST:
1257
+                    warning(u"%s exists - not overwriting" % dst_file)
1258
+                    continue
1259
+                if e.errno in (errno.EPERM, errno.EACCES):
1260
+                    warning(u"%s not writable: %s" % (dst_file, e.strerror))
1237 1261
                     if cfg.stop_on_error:
1238
-                        raise OSError, e
1262
+                        raise e
1239 1263
                     continue
1240
-                # We have to keep repeating this call because
1241
-                # Python 2.4 doesn't support try/except/finally
1242
-                # construction :-(
1264
+                raise e
1265
+            except KeyboardInterrupt:
1266
+                try:
1267
+                    dst_stream.close()
1268
+                    os.remove(deunicodise(chkptfname))
1269
+                except: pass
1270
+                warning(u"Exiting after keyboard interrupt")
1271
+                return
1272
+            except Exception, e:
1243 1273
                 try:
1244 1274
                     dst_stream.close()
1245 1275
                     os.remove(deunicodise(chkptfname))
1246 1276
                 except: pass
1247
-            except S3DownloadError, e:
1248 1277
                 ret = EX_PARTIAL
1249
-                error(u"%s: download failed too many times. Skipping that file.  This is usually a transient error, please try again later." % file)
1278
+                error(u"%s: %s" % (file, e))
1250 1279
                 if cfg.stop_on_error:
1251
-                    raise
1280
+                    raise OSError, e
1252 1281
                 continue
1282
+            # We have to keep repeating this call because
1283
+            # Python 2.4 doesn't support try/except/finally
1284
+            # construction :-(
1285
+            try:
1286
+                dst_stream.close()
1287
+                os.remove(deunicodise(chkptfname))
1288
+            except: pass
1253 1289
             speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
1254 1290
             if not Config().progress_meter:
1255 1291
                 output(u"download: '%s' -> '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
... ...
@@ -1307,7 +1348,7 @@ def local_copy(copy_pairs, destination_base):
1307 1307
             debug(u"Copying %s to %s" % (src_file, dst_file))
1308 1308
             shutil.copy2(deunicodise(src_file), deunicodise(dst_file))
1309 1309
         except (IOError, OSError), e:
1310
-            warning(u'Unable to hardlink or copy files %s -> %s: %s' % (src_file, dst_file, e))
1310
+            warning(u'Unable to hardlink or copy files %s -> %s (Reason: %s)' % (src_file, dst_file, e))
1311 1311
             failed_copy_list[relative_file] = src_obj
1312 1312
     return failed_copy_list
1313 1313
 
... ...
@@ -1436,16 +1477,20 @@ def cmd_sync_local2remote(args):
1436 1436
                         debug(u"attr_header: %s" % attr_header)
1437 1437
                         extra_headers.update(attr_header)
1438 1438
                     response = s3.object_put(src, uri, extra_headers, extra_label = seq_label)
1439
-                except InvalidFileError, e:
1440
-                    warning(u"File can not be uploaded: %s" % e)
1441
-                    ret = EX_PARTIAL
1439
+                except S3UploadError, exc:
1440
+                    error(u"Upload of '%s' failed too many times (Last reason: %s)" % (item['full_name'], exc))
1442 1441
                     if cfg.stop_on_error:
1442
+                        ret = EX_DATAERR
1443
+                        error(u"Exiting now because of --stop-on-error")
1443 1444
                         raise
1445
+                    ret = EX_PARTIAL
1444 1446
                     continue
1445
-                except S3UploadError, e:
1446
-                    error(u"%s: upload failed too many times. Skipping that file." % item['full_name'])
1447
+                except InvalidFileError, exc:
1448
+                    error(u"Upload of '%s' is not possible (Reason: %s)" % (item['full_name'], exc))
1447 1449
                     ret = EX_PARTIAL
1448 1450
                     if cfg.stop_on_error:
1451
+                        ret = EX_OSFILE
1452
+                        error(u"Exiting now because of --stop-on-error")
1449 1453
                         raise
1450 1454
                     continue
1451 1455
                 speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)