From 2d9f728e2793b0cc723a2768aaf37bcae59bc827 Mon Sep 17 00:00:00 2001 From: Zooko O'Whielacronx Date: Sat, 14 Apr 2007 17:48:32 -0700 Subject: [PATCH] pyfec: add -f option to fec, add more user-friendly handling of filesystem errors and user errors --- pyfec/bin/fec | 20 +++++++++++++++----- pyfec/bin/unfec | 6 +++++- pyfec/fec/filefec.py | 42 +++++++++++++++++++++++++++++++----------- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/pyfec/bin/fec b/pyfec/bin/fec index a70cff4b..19e7daa6 100644 --- a/pyfec/bin/fec +++ b/pyfec/bin/fec @@ -22,8 +22,9 @@ parser.add_argument('inputfile', help='file to encode or "-" for stdin', type=ar parser.add_argument('-d', '--output-dir', help='directory in which share file names will be created (default ".")', default='.', metavar='D') parser.add_argument('-p', '--prefix', help='prefix for share file names; If omitted, the name of the input file will be used.', metavar='P') parser.add_argument('-s', '--suffix', help='suffix for share file names (default ".fec")', default='.fec', metavar='S') -parser.add_argument('-m', '--totalshares', help='the total number of share files created (default 16)', default=16, metavar='M') -parser.add_argument('-k', '--requiredshares', help='the number of share files required to reconstruct (default 4)', default=4, metavar='K') +parser.add_argument('-m', '--totalshares', help='the total number of share files created (default 16)', default=16, type=int, metavar='M') +parser.add_argument('-k', '--requiredshares', help='the number of share files required to reconstruct (default 4)', default=4, type=int, metavar='K') +parser.add_argument('-f', '--force', help='overwrite any file which already in place an output file (share file)', action='store_true') parser.add_argument('-v', '--verbose', help='print out messages about progress', action='store_true') parser.add_argument('-V', '--version', help='print out version number and exit', action='store_true') args = parser.parse_args() @@ -33,13 +34,22 @@ if args.prefix is None: if args.prefix == "": args.prefix = "" -if args.totalshares < 3 or args.totalshares > 256 or args.requiredshares < 2 or args.requiredshares >= args.totalshares: - print "Invalid parameters, requiredshares: %s, totalshares:%s\nPlease see the accompanying documentation." % (args.requiredshares, args.totalshares,) +if args.totalshares < 3: + print "Invalid parameters, totalshares is required to be >= 3\nPlease see the accompanying documentation." + sys.exit(1) +if args.totalshares > 256: + print "Invalid parameters, totalshares is required to be <= 256\nPlease see the accompanying documentation." + sys.exit(1) +if args.requiredshares < 2: + print "Invalid parameters, requiredshares is required to be >= 2\nPlease see the accompanying documentation." + sys.exit(1) +if args.requiredshares >= args.totalshares: + print "Invalid parameters, requiredshares is required to be < totalshares\nPlease see the accompanying documentation." sys.exit(1) args.inputfile.seek(0, 2) fsize = args.inputfile.tell() args.inputfile.seek(0, 0) -ret = filefec.encode_to_files(args.inputfile, fsize, args.output_dir, args.prefix, args.requiredshares, args.totalshares, args.suffix, args.verbose) +ret = filefec.encode_to_files(args.inputfile, fsize, args.output_dir, args.prefix, args.requiredshares, args.totalshares, args.suffix, args.force, args.verbose) sys.exit(ret) diff --git a/pyfec/bin/unfec b/pyfec/bin/unfec index 9f212377..9d9fcade 100644 --- a/pyfec/bin/unfec +++ b/pyfec/bin/unfec @@ -40,6 +40,10 @@ else: sys.exit(2) outf = os.fdopen(outfd, "wb") -ret = filefec.decode_from_files(outf, args.sharefiles, args.verbose) +try: + ret = filefec.decode_from_files(outf, args.sharefiles, args.verbose) +except filefec.InsufficientShareFilesError, e: + print str(e) + sys.exit(3) sys.exit(ret) diff --git a/pyfec/fec/filefec.py b/pyfec/fec/filefec.py index e83ab2d8..c8e3258c 100644 --- a/pyfec/fec/filefec.py +++ b/pyfec/fec/filefec.py @@ -31,6 +31,21 @@ import array, os, re, struct, traceback CHUNKSIZE = 4096 +class InsufficientShareFilesError(fec.Error): + def __init__(self, k, kb, *args, **kwargs): + fec.Error.__init__(self, *args, **kwargs) + self.k = k + self.kb = kb + + def __repr__(self): + return "Insufficient share files -- %d share files are required to recover this file, but only %d were given" % (self.k, self.kb,) + + def __str__(self): + return self.__repr__() + +class CorruptedShareFilesError(fec.Error): + pass + def _build_header(m, k, pad, sh): """ @param m: the total number of shares; 3 <= m <= 256 @@ -106,7 +121,7 @@ def _parse_header(inf): # The first 8 bits always encode m. ch = inf.read(1) if not ch: - raise fec.Error("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) byte = ord(ch) m = byte + 3 @@ -116,7 +131,7 @@ def _parse_header(inf): kbitmask = MASK(kbits) << b2_bits_left ch = inf.read(1) if not ch: - raise fec.Error("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) byte = ord(ch) k = ((byte & kbitmask) >> b2_bits_left) + 2 @@ -129,7 +144,7 @@ def _parse_header(inf): if needed_padbits > 0: ch = inf.read(1) if not ch: - raise fec.Error("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) byte = struct.unpack(">B", ch)[0] val <<= 8 val |= byte @@ -143,7 +158,7 @@ def _parse_header(inf): if needed_shbits > 0: ch = inf.read(1) if not ch: - raise fec.Error("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r didn't have a complete metadata header at the front. Perhaps the file was truncated." % (inf.name,)) byte = struct.unpack(">B", ch)[0] val <<= 8 val |= byte @@ -158,7 +173,7 @@ def _parse_header(inf): FORMAT_FORMAT = "%%s.%%0%dd_%%0%dd%%s" RE_FORMAT = "%s.[0-9]+_[0-9]+%s" -def encode_to_files(inf, fsize, dirname, prefix, k, m, suffix=".fec", verbose=False): +def encode_to_files(inf, fsize, dirname, prefix, k, m, suffix=".fec", overwrite=False, verbose=False): """ Encode inf, writing the shares to specially named, newly created files. @@ -181,8 +196,11 @@ def encode_to_files(inf, fsize, dirname, prefix, k, m, suffix=".fec", verbose=Fa fn = os.path.join(dirname, format % (prefix, shnum, m, suffix,)) if verbose: print "Creating share file %r..." % (fn,) - fd = os.open(fn, os.O_WRONLY|os.O_CREAT|os.O_EXCL) - f = os.fdopen(fd, "wb") + if overwrite: + f = open(fn, "wb") + else: + fd = os.open(fn, os.O_WRONLY|os.O_CREAT|os.O_EXCL) + f = os.fdopen(fd, "wb") f.write(hdr) fs.append(f) fns.append(fn) @@ -242,13 +260,15 @@ def decode_from_files(outf, infiles, verbose=False): for f in infiles: (nm, nk, npadlen, shnum,) = _parse_header(f) if not (m is None or m == nm): - raise fec.Error("Share files were corrupted -- share file %r said that m was %s but another share file previously said that m was %s" % (f.name, nm, m,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r said that m was %s but another share file previously said that m was %s" % (f.name, nm, m,)) m = nm if not (k is None or k == nk): - raise fec.Error("Share files were corrupted -- share file %r said that k was %s but another share file previously said that k was %s" % (f.name, nk, k,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r said that k was %s but another share file previously said that k was %s" % (f.name, nk, k,)) + if k > len(infiles): + raise InsufficientShareFilesError(k, len(infiles)) k = nk if not (padlen is None or padlen == npadlen): - raise fec.Error("Share files were corrupted -- share file %r said that pad length was %s but another share file previously said that pad length was %s" % (f.name, npadlen, padlen,)) + raise CorruptedShareFilesError("Share files were corrupted -- share file %r said that pad length was %s but another share file previously said that pad length was %s" % (f.name, npadlen, padlen,)) padlen = npadlen infs.append(f) @@ -262,7 +282,7 @@ def decode_from_files(outf, infiles, verbose=False): while True: chunks = [ inf.read(CHUNKSIZE) for inf in infs ] if [ch for ch in chunks if len(ch) != len(chunks[-1])]: - raise fec.Error("Share files were corrupted -- all share files are required to be the same length, but they weren't.") + raise CorruptedShareFilesError("Share files were corrupted -- all share files are required to be the same length, but they weren't.") if len(chunks[-1]) == CHUNKSIZE: # Then this was a full read, so we're still in the sharefiles. -- 2.45.2