-#! /usr/bin/env python
-import os, subprocess, sys, signal, time
-from twisted.python import usage
-
-from twisted.python.procutils import which
-
-def testtwistd(loc):
- try:
- return subprocess.call(["python", loc,], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- except:
- return -1
-
-twistd = None
-if not twistd:
- for maybetwistd in which("twistd"):
- ret = testtwistd(maybetwistd)
- if ret == 0:
- twistd = maybetwistd
- break
-
-if not twistd:
- for maybetwistd in which("twistd.py"):
- ret = testtwistd(maybetwistd)
- if ret == 0:
- twistd = maybetwistd
- break
-
-if not twistd:
- maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd')
- ret = testtwistd(maybetwistd)
- if ret == 0:
- twistd = maybetwistd
-
-if not twistd:
- maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd.py')
- ret = testtwistd(maybetwistd)
- if ret == 0:
- twistd = maybetwistd
-
-if not twistd:
- print "Can't find twistd (it comes with Twisted). Aborting."
- sys.exit(1)
+import os, sys
+from cStringIO import StringIO
-class BasedirMixin:
- optFlags = [
- ["multiple", "m", "allow multiple basedirs to be specified at once"],
- ]
-
- def postOptions(self):
- if not self.basedirs:
- raise usage.UsageError("<basedir> parameter is required")
- if self['basedir']:
- del self['basedir']
- self['basedirs'] = [os.path.abspath(os.path.expanduser(b))
- for b in self.basedirs]
+from twisted.python import usage
- def parseArgs(self, *args):
- self.basedirs = []
- if self['basedir']:
- self.basedirs.append(self['basedir'])
- if self['multiple']:
- self.basedirs.extend(args)
- else:
- if len(args) == 0 and not self.basedirs:
- self.basedirs.append(".")
- if len(args) > 0:
- self.basedirs.append(args[0])
- if len(args) > 1:
- raise usage.UsageError("I wasn't expecting so many arguments")
+from allmydata.scripts.common import get_default_nodedir
+from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin
+from allmydata.util.encodingutil import quote_output, quote_local_unicode_path, get_io_encoding
-class NoDefaultBasedirMixin(BasedirMixin):
- def parseArgs(self, *args):
- # create-client won't default to --basedir=.
- self.basedirs = []
- if self['basedir']:
- self.basedirs.append(self['basedir'])
- if self['multiple']:
- self.basedirs.extend(args)
- else:
- if len(args) > 0:
- self.basedirs.append(args[0])
- if len(args) > 1:
- raise usage.UsageError("I wasn't expecting so many arguments")
- if not self.basedirs:
- raise usage.UsageError("--basedir must be provided")
+def GROUP(s):
+ # Usage.parseOptions compares argv[1] against command[0], so it will
+ # effectively ignore any "subcommand" that starts with a newline. We use
+ # these to insert section headers into the --help output.
+ return [("\n(%s)" % s, None, None, None)]
-class StartOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to start the node in"],
- ]
-class StopOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to stop the node in"],
- ]
+_default_nodedir = get_default_nodedir()
-class RestartOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to restart the node in"],
- ]
+NODEDIR_HELP = ("Specify which Tahoe node directory should be used. The "
+ "directory should either contain a full Tahoe node, or a "
+ "file named node.url that points to some other Tahoe node. "
+ "It should also contain a file named '"
+ + os.path.join('private', 'aliases') +
+ "' which contains the mapping from alias name to root "
+ "dirnode URI.")
+if _default_nodedir:
+ NODEDIR_HELP += " [default for most commands: " + quote_local_unicode_path(_default_nodedir) + "]"
-class CreateClientOptions(NoDefaultBasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to create the client in"],
- ]
- optFlags = [
- ["quiet", "q", "operate silently"],
- ]
+class Options(usage.Options):
+ # unit tests can override these to point at StringIO instances
+ stdin = sys.stdin
+ stdout = sys.stdout
+ stderr = sys.stderr
+
+ synopsis = "\nUsage: tahoe <command> [command options]"
+ subCommands = ( GROUP("Administration")
+ + create_node.subCommands
+ + keygen.subCommands
+ + stats_gatherer.subCommands
+ + admin.subCommands
+ + GROUP("Controlling a node")
+ + startstop_node.subCommands
+ + GROUP("Debugging")
+ + debug.subCommands
+ + GROUP("Using the filesystem")
+ + cli.subCommands
+ )
-class CreateIntroducerOptions(NoDefaultBasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to create the introducer in"],
- ]
optFlags = [
- ["quiet", "q", "operate silently"],
- ]
-
-class DumpOptions(usage.Options):
+ ["quiet", "q", "Operate silently."],
+ ["version", "V", "Display version numbers."],
+ ["version-and-path", None, "Display version numbers and paths to their locations."],
+ ]
optParameters = [
- ["filename", "f", None, "which file to dump"],
- ]
+ ["node-directory", "d", None, NODEDIR_HELP],
+ ]
- def parseArgs(self, filename=None):
- if filename:
- self['filename'] = filename
+ def opt_version(self):
+ import allmydata
+ print >>self.stdout, allmydata.get_package_versions_string(debug=True)
+ self.no_command_needed = True
- def postOptions(self):
- if not self['filename']:
- raise usage.UsageError("<filename> parameter is required")
+ def opt_version_and_path(self):
+ import allmydata
+ print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True)
+ self.no_command_needed = True
-class DumpRootDirnodeOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "the vdrive-server's base directory"],
- ]
+ def getSynopsis(self):
+ return "\nUsage: tahoe [global-opts] <command> [command-options]"
-class DumpDirnodeOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["uri", "u", None, "the URI of the dirnode to dump."],
- ["basedir", "C", None, "which directory to create the introducer in"],
- ]
- optFlags = [
- ["verbose", "v", "be extra noisy (show encrypted data)"],
- ]
- def parseArgs(self, *args):
- if len(args) == 1:
- self['uri'] = args[-1]
- args = args[:-1]
- BasedirMixin.parseArgs(self, *args)
+ def getUsage(self, **kwargs):
+ t = usage.Options.getUsage(self, **kwargs)
+ return t + "\nPlease run 'tahoe <command> --help' for more details on each command.\n"
def postOptions(self):
- BasedirMixin.postOptions(self)
- if not self['uri']:
- raise usage.UsageError("<uri> parameter is required")
-
-client_tac = """
-# -*- python -*-
-
-from allmydata import client
-from twisted.application import service
-
-c = client.Client()
-
-application = service.Application("allmydata_client")
-c.setServiceParent(application)
-"""
-
-introducer_tac = """
-# -*- python -*-
+ if not hasattr(self, 'subOptions'):
+ if not hasattr(self, 'no_command_needed'):
+ raise usage.UsageError("must specify a command")
+ sys.exit(0)
-from allmydata import introducer_and_vdrive
-from twisted.application import service
-c = introducer_and_vdrive.IntroducerAndVdrive()
+create_dispatch = {}
+for module in (create_node, keygen, stats_gatherer):
+ create_dispatch.update(module.dispatch)
-application = service.Application("allmydata_introducer")
-c.setServiceParent(application)
-"""
+def runner(argv,
+ run_by_human=True,
+ stdin=None, stdout=None, stderr=None,
+ install_node_control=True, additional_commands=None):
-class Options(usage.Options):
- synopsis = "Usage: allmydata <command> [command options]"
+ stdin = stdin or sys.stdin
+ stdout = stdout or sys.stdout
+ stderr = stderr or sys.stderr
- subCommands = [
- ["create-client", None, CreateClientOptions, "Create a client node."],
- ["create-introducer", None, CreateIntroducerOptions, "Create a introducer node."],
- ["start", None, StartOptions, "Start a node (of any type)."],
- ["stop", None, StopOptions, "Stop a node."],
- ["restart", None, RestartOptions, "Restart a node."],
- ["dump-uri-extension", None, DumpOptions,
- "Unpack and display the contents of a uri_extension file."],
- ["dump-root-dirnode", None, DumpRootDirnodeOptions,
- "Compute most of the URI for the vdrive server's root dirnode."],
- ["dump-dirnode", None, DumpDirnodeOptions,
- "Unpack and display the contents of a vdrive DirectoryNode."],
- ]
+ config = Options()
+ if install_node_control:
+ config.subCommands.extend(startstop_node.subCommands)
- def postOptions(self):
- if not hasattr(self, 'subOptions'):
- raise usage.UsageError("must specify a command")
+ ac_dispatch = {}
+ if additional_commands:
+ for ac in additional_commands:
+ config.subCommands.extend(ac.subCommands)
+ ac_dispatch.update(ac.dispatch)
-def runner(argv, run_by_human=True):
- config = Options()
try:
config.parseOptions(argv)
except usage.error, e:
if not run_by_human:
raise
- print "%s: %s" % (sys.argv[0], e)
- print
- c = getattr(config, 'subOptions', config)
- print str(c)
+ c = config
+ while hasattr(c, 'subOptions'):
+ c = c.subOptions
+ print >>stdout, str(c)
+ try:
+ msg = e.args[0].decode(get_io_encoding())
+ except Exception:
+ msg = repr(e)
+ print >>stdout, "%s: %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
return 1
command = config.subCommand
so = config.subOptions
- rc = 0
- if command == "create-client":
- for basedir in so.basedirs:
- rc = create_client(basedir, so) or rc
- elif command == "create-introducer":
- for basedir in so.basedirs:
- rc = create_introducer(basedir, so) or rc
- elif command == "start":
- for basedir in so.basedirs:
- rc = start(basedir, so) or rc
- elif command == "stop":
- for basedir in so.basedirs:
- rc = stop(basedir, so) or rc
- elif command == "restart":
- for basedir in so.basedirs:
- rc = stop(basedir, so) or rc
- if rc:
- print "not restarting"
- return rc
- for basedir in so.basedirs:
- rc = start(basedir, so) or rc
- elif command == "dump-uri-extension":
- rc = dump_uri_extension(so)
- elif command == "dump-root-dirnode":
- rc = dump_root_dirnode(so.basedirs[0], so)
- elif command == "dump-dirnode":
- rc = dump_directory_node(so.basedirs[0], so)
- return rc
-
-def run():
- rc = runner(sys.argv[1:])
- sys.exit(rc)
-
-def create_client(basedir, config):
- if os.path.exists(basedir):
- if os.listdir(basedir):
- print "The base directory already exists: %s" % basedir
- print "To avoid clobbering anything, I am going to quit now"
- print "Please use a different directory, or delete this one"
- return -1
- # we're willing to use an empty directory
+ if config['quiet']:
+ stdout = StringIO()
+
+ so.stdout = stdout
+ so.stderr = stderr
+ so.stdin = stdin
+
+ if command in create_dispatch:
+ rc = create_dispatch[command](so, stdout, stderr)
+ elif command in startstop_node.dispatch:
+ rc = startstop_node.dispatch[command](so, stdout, stderr)
+ elif command in debug.dispatch:
+ rc = debug.dispatch[command](so)
+ elif command in admin.dispatch:
+ rc = admin.dispatch[command](so)
+ elif command in cli.dispatch:
+ rc = cli.dispatch[command](so)
+ elif command in ac_dispatch:
+ rc = ac_dispatch[command](so, stdout, stderr)
else:
- os.mkdir(basedir)
- f = open(os.path.join(basedir, "client.tac"), "w")
- f.write(client_tac)
- f.close()
- if not config['quiet']:
- print "client created in %s" % basedir
- print " please copy introducer.furl and vdrive.furl into the directory"
-
-def create_introducer(basedir, config):
- if os.path.exists(basedir):
- if os.listdir(basedir):
- print "The base directory already exists: %s" % basedir
- print "To avoid clobbering anything, I am going to quit now"
- print "Please use a different directory, or delete this one"
- return -1
- # we're willing to use an empty directory
- else:
- os.mkdir(basedir)
- f = open(os.path.join(basedir, "introducer.tac"), "w")
- f.write(introducer_tac)
- f.close()
- if not config['quiet']:
- print "introducer created in %s" % basedir
-
-def start(basedir, config):
- print "STARTING", basedir
- if os.path.exists(os.path.join(basedir, "client.tac")):
- tac = "client.tac"
- type = "client"
- elif os.path.exists(os.path.join(basedir, "introducer.tac")):
- tac = "introducer.tac"
- type = "introducer"
- else:
- print "%s does not look like a node directory" % basedir
- if not os.path.isdir(basedir):
- print " in fact, it doesn't look like a directory at all!"
- sys.exit(1)
- rc = subprocess.call(["python", twistd, "-y", tac,], cwd=basedir)
- if rc == 0:
- print "%s node probably started" % type
- return 0
- else:
- print "%s node probably not started" % type
- return 1
-
-def stop(basedir, config):
- print "STOPPING", basedir
- pidfile = os.path.join(basedir, "twistd.pid")
- if not os.path.exists(pidfile):
- print "%s does not look like a running node directory (no twistd.pid)" % basedir
- return 1
- pid = open(pidfile, "r").read()
- pid = int(pid)
+ raise usage.UsageError()
- timer = 0
- os.kill(pid, signal.SIGTERM)
- time.sleep(0.1)
- while timer < 5:
- # poll once per second until twistd.pid goes away, up to 5 seconds
- try:
- os.kill(pid, 0)
- except OSError:
- print "process %d is dead" % pid
- return
- timer += 1
- time.sleep(1)
- print "never saw process go away"
- return 1
-
-def dump_uri_extension(config):
- from allmydata import uri
-
- filename = config['filename']
- unpacked = uri.unpack_extension_readable(open(filename,"rb").read())
- keys1 = ("size", "num_segments", "segment_size",
- "needed_shares", "total_shares")
- keys2 = ("codec_name", "codec_params", "tail_codec_params")
- keys3 = ("plaintext_hash", "plaintext_root_hash",
- "crypttext_hash", "crypttext_root_hash",
- "share_root_hash")
- for k in keys1:
- if k in unpacked:
- print "%19s: %s" % (k, unpacked[k])
- print
- for k in keys2:
- if k in unpacked:
- print "%19s: %s" % (k, unpacked[k])
- print
- for k in keys3:
- if k in unpacked:
- print "%19s: %s" % (k, unpacked[k])
-
- leftover = set(unpacked.keys()) - set(keys1 + keys2 + keys3)
- if leftover:
- print
- for k in sorted(leftover):
- print "%s: %s" % (k, unpacked[k])
-
- print
- return 0
+ return rc
-def dump_root_dirnode(basedir, config):
- from allmydata import uri
- root_dirnode_file = os.path.join(basedir, "vdrive", "root")
+def run(install_node_control=True):
try:
- f = open(root_dirnode_file, "rb")
- key = f.read()
- rooturi = uri.pack_dirnode_uri("fakeFURL", key)
- print rooturi
- return 0
- except EnvironmentError:
- print "unable to read root dirnode file from %s" % root_dirnode_file
- return 1
-
-def dump_directory_node(basedir, config):
- from allmydata import filetable, vdrive, uri
- from allmydata.util import hashutil, idlib
- dir_uri = config['uri']
- verbose = config['verbose']
+ if sys.platform == "win32":
+ from allmydata.windows.fixups import initialize
+ initialize()
- furl, key = uri.unpack_dirnode_uri(dir_uri)
- if uri.is_mutable_dirnode_uri(dir_uri):
- wk, we, rk, index = hashutil.generate_dirnode_keys_from_writekey(key)
- else:
- wk, we, rk, index = hashutil.generate_dirnode_keys_from_readkey(key)
-
- filename = os.path.join(basedir, "vdrive", idlib.b2a(index))
-
- print
- print "dirnode uri: %s" % dir_uri
- print "filename : %s" % filename
- print "index : %s" % idlib.b2a(index)
- if wk:
- print "writekey : %s" % idlib.b2a(wk)
- print "write_enabler: %s" % idlib.b2a(we)
- else:
- print "writekey : None"
- print "write_enabler: None"
- print "readkey : %s" % idlib.b2a(rk)
+ rc = runner(sys.argv[1:], install_node_control=install_node_control)
+ except Exception:
+ import traceback
+ traceback.print_exc()
+ rc = 1
- print
-
- vds = filetable.VirtualDriveServer(os.path.join(basedir, "vdrive"), False)
- data = vds._read_from_file(index)
- if we:
- if we != data[0]:
- print "ERROR: write_enabler does not match"
-
- for (H_key, E_key, E_write, E_read) in data[1]:
- if verbose:
- print " H_key %s" % idlib.b2a(H_key)
- print " E_key %s" % idlib.b2a(E_key)
- print " E_write %s" % idlib.b2a(E_write)
- print " E_read %s" % idlib.b2a(E_read)
- key = vdrive.decrypt(rk, E_key)
- print " key %s" % key
- if hashutil.dir_name_hash(rk, key) != H_key:
- print " ERROR: H_key does not match"
- if wk and E_write:
- if len(E_write) < 14:
- print " ERROR: write data is short:", idlib.b2a(E_write)
- write = vdrive.decrypt(wk, E_write)
- print " write: %s" % write
- read = vdrive.decrypt(rk, E_read)
- print " read: %s" % read
- print
-
- return 0
+ sys.exit(rc)