class IPCWin32Socket(IPCSocketBase): def __init__(self, *args): IPCSocketBase.__init__(self, *args) self.socket_filename = os.path.join(self.config['data_dir'], self.name) self.mutex = None self.master = 0 def _get_sic_path(self): configdir = get_dot_dir() filename = os.path.join(configdir, ".btcontrol") return filename def create(self): obtain_mutex = 1 mutex = win32event.CreateMutex(None, obtain_mutex, app_name) # prevent the PyHANDLE from going out of scope, ints are fine self.mutex = int(mutex) mutex.Detach() lasterror = win32api.GetLastError() if lasterror == winerror.ERROR_ALREADY_EXISTS: takeover = 0 try: # if the mutex already exists, discover which port to connect to. # if something goes wrong with that, tell us to take over the # role of master takeover = self.discover_sic_socket() except: pass if not takeover: raise BTFailure(_("Global mutex already created.")) self.master = 1 # lazy free port code port_limit = 50000 while self.port < port_limit: try: controlsocket = self.rawserver.create_serversocket( self.port, '127.0.0.1') self.controlsocket = controlsocket break except socket.error, e: self.port += 1 if self.port >= port_limit: raise BTFailure(_("Could not find an open port!")) filename = self._get_sic_path() (path, name) = os.path.split(filename) try: os.makedirs(path) except OSError, e: # 17 is dir exists if e.errno != 17: BTFailure(_("Could not create application data directory!"))
def check_fastresume(self, resumefile, return_filelist=False, piece_size=None, numpieces=None, allfiles=None): filenames = [name for x, x, name in self.ranges] if resumefile is not None: version = resumefile.readline() if version != 'BitTorrent resume state file, version 1\n': raise BTFailure( _("Unsupported fastresume file format, " "maybe from another client version?")) amount_done = int(resumefile.readline()) else: amount_done = size = mtime = 0 for filename in filenames: if resumefile is not None: line = resumefile.readline() size, mtime = line.split()[:2] # allow adding extra fields size = int(size) mtime = int(mtime) if os.path.exists(filename): fsize = os.path.getsize(filename) else: raise BTFailure( _("Another program appears to have moved, renamed, or deleted the file, " "or %s may have crashed last time it was run.") % app_name) if fsize > 0 and mtime != os.path.getmtime(filename): raise BTFailure( _("Another program appears to have modified the file, " "or %s may have crashed last time it was run.") % app_name) if size != fsize: raise BTFailure( _("Another program appears to have changed the file size, " "or %s may have crashed last time it was run.") % app_name) if not return_filelist: return amount_done if resumefile is None: return None if numpieces < 32768: typecode = 'h' else: typecode = 'l' try: r = array(typecode) r.fromfile(resumefile, numpieces) except Exception, e: raise BTFailure( _("Couldn't read fastresume data: ") + str(e) + '.')
def create_socket_unix(self): filename = self.socket_filename if os.path.exists(filename): try: self.send_command_unix('no-op') except BTFailure: pass else: raise BTFailure("Could not create control socket: already in use") try: os.unlink(filename) except OSError, e: raise BTFailure("Could not remove old control socket filename:" + str(e))
def _get_available(self, url): self.debug('Updater.get_available() hitting url %s' % url) try: u = zurllib.urlopen(url) s = u.read() s = s.strip() except: raise BTFailure(_("Could not get latest version from %s") % url) try: assert len(s) == 5 availableversion = Version.from_str(s) except: raise BTFailure( _("Could not parse new version string from %s") % url) return availableversion
def run(self, scrwin): def reread(): self.multitorrent.rawserver.external_add_task( self.reread_config, 0) self.d = CursesDisplayer(scrwin, self.errlist, self.doneflag, reread) try: self.multitorrent = Multitorrent(self.config, self.doneflag, self.global_error) # raises BTFailure if bad metainfo = ConvertedMetainfo(bdecode(self.metainfo)) torrent_name = metainfo.name_fs if config['save_as']: if config['save_in']: raise BTFailure('You cannot specify both --save_as and ' '--save_in') saveas = config['save_as'] elif config['save_in']: saveas = os.path.join(config['save_in'], torrent_name) else: saveas = torrent_name self.d.set_torrent_values(metainfo.name, os.path.abspath(saveas), metainfo.total_bytes, len(metainfo.hashes)) self.torrent = self.multitorrent.start_torrent( metainfo, self.config, self, saveas) except BTFailure, e: errlist.append(str(e)) return
def _restore_state(self): def decode_line(line): hashtext = line[:40] try: infohash = hashtext.decode('hex') except: raise BTFailure("Invalid state file contents") if len(infohash) != 20: raise BTFailure("Invalid state file contents") try: path = os.path.join(self.config['data_dir'], 'metainfo', hashtext) f = file(path, 'rb') data = f.read() f.close() except Exception, e: try: f.close() except: pass self.global_error(ERROR,"Error reading file "+path+" ("+str(e)+ "), cannot restore state completely") return None if infohash in self.torrents: raise BTFailure("Invalid state file (duplicate entry)") t = TorrentInfo() self.torrents[infohash] = t try: t.metainfo = ConvertedMetainfo(bdecode(data)) except Exception, e: self.global_error(ERROR, "Corrupt data in "+path+ " , cannot restore torrent ("+str(e)+")") return None
def makeinfo(path, piece_length, flag, progress, name=None, content_type=None): # HEREDAVE. If path is directory, # how do we assign content type? def to_utf8(name): if isinstance(name, unicode): u = name else: try: u = decode_from_filesystem(name) except Exception, e: s = str_exc(e) raise BTFailure( _('Could not convert file/directory name %r to ' 'Unicode (%s). Either the assumed filesystem ' 'encoding "%s" is wrong or the filename contains ' 'illegal bytes.') % (name, s, get_filesystem_encoding())) if u.translate(noncharacter_translate) != u: raise BTFailure( _('File/directory name "%s" contains reserved ' 'unicode values that do not correspond to ' 'characters.') % name) return u.encode('utf-8')
def run(self): try: self.multitorrent = Multitorrent(self.config, self.doneflag, self.global_error) # raises BTFailure if bad metainfo = ConvertedMetainfo(bdecode(self.metainfo)) torrent_name = metainfo.name_fs if self.config['save_as']: if self.config['save_in']: raise BTFailure( _("You cannot specify both --save_as and " "--save_in")) saveas = self.config['save_as'] elif self.config['save_in']: saveas = os.path.join(self.config['save_in'], torrent_name) else: saveas = torrent_name self.d.set_torrent_values(metainfo.name, os.path.abspath(saveas), metainfo.total_bytes, len(metainfo.hashes)) self.torrent = self.multitorrent.start_torrent( metainfo, Preferences(self.config), self, saveas) except BTFailure, e: print str(e) return
def main(self): print "TorrentClient.run" """Main loop""" uiname = 'bittorrent-console' defaults = get_defaults(uiname) defaults.append(( 'twisted', 0, _("Use Twisted network libraries for network connections. 1 means use twisted, 0 means do not use twisted, -1 means autodetect, and prefer twisted" ))) metainfo = None config, args = configfile.parse_configuration_and_args( defaults, uiname) try: metainfo, errors = GetTorrent.get(self.torrentfilename) if errors: raise BTFailure( _("Error reading .torrent file: ") + '\n'.join(errors)) else: self.dl = DLKamaelia(metainfo, config, self) self.dl.run() except BTFailure, e: print str(e) sys.exit(1)
def send_command_unix(self, rawserver, action, data = ''): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) filename = self.socket_filename try: s.connect(filename) except socket.error, e: raise BTFailure('Could not send command: ' + str(e))
class IPCUnixSocket(IPCSocketBase): def __init__(self, *args): IPCSocketBase.__init__(self, *args) self.socket_filename = os.path.join(self.config['data_dir'], 'ui_socket') def create(self): filename = self.socket_filename if os.path.exists(filename): try: self.send_command('no-op') except BTFailure: pass else: raise BTFailure( _("Could not create control socket: already in use")) try: os.unlink(filename) except OSError, e: raise BTFailure( _("Could not remove old control socket filename:") + str(e)) try: controlsocket = RawServer.create_unixserversocket(filename) except socket.error, e: raise BTFailure(_("Could not create control socket: ") + str(e))
def _batch_read(self, pos, amount): dfs = [] r = [] # queue all the reads for filename, pos, end in self._intervals(pos, amount): df = self.filepool.read(self._read, filename, pos, end - pos) dfs.append(df) # yield on all the reads in order - they complete in any order exc = None for df in dfs: yield df try: r.append(df.getResult()) except: exc = exc or sys.exc_info() if exc: raise exc[0], exc[1], exc[2] r = ''.join(r) if len(r) != amount: raise BTFailure(_("Short read (%d of %d) - something truncated files?") % (len(r), amount)) yield r
def add_files(self, files, torrent): for filename in files: if filename in self.file_to_torrent: raise BTFailure(_("File %s belongs to another running torrent") % filename) for filename in files: self.file_to_torrent[filename] = torrent
class IPCUnixSocket(IPCSocketBase): def __init__(self, *args): IPCSocketBase.__init__(self, *args) data_dir, bad = encode_for_filesystem(self.config['data_dir']) if bad: raise BTFailure(_("Invalid path encoding.")) self.socket_filename = os.path.join(data_dir, self.name) def create(self): filename = self.socket_filename if os.path.exists(filename): try: self.send_command('no-op') except BTFailure: pass else: raise BTFailure( _("Could not create control socket: already in use")) try: os.unlink(filename) except OSError, e: raise BTFailure( _("Could not remove old control socket filename:") + str_exc(e)) try: controlsocket = self.rawserver.create_unixserversocket(filename) except socket.error, e: raise BTFailure( _("Could not create control socket: ") + str_exc(e))
def _read_torrent_config(self, infohash): path = os.path.join(self.data_dir, 'torrents', infohash.encode('hex')) if not os.path.exists(path): raise BTFailure, _("Coult not open the torrent config: " + infohash.encode('hex')) f = file(path, 'rb') data = f.read() f.close() try: torrent_config = cPickle.loads(data) except: # backward compatibility with <= 4.9.3 torrent_config = bdecode(data) for k, v in torrent_config.iteritems(): try: torrent_config[k] = v.decode('utf8') if k in ('destination_path', 'working_path'): torrent_config[k] = encode_for_filesystem( torrent_config[k])[0] except: pass if not torrent_config.get('destination_path'): raise BTFailure(_("Invalid torrent config file")) if not torrent_config.get('working_path'): raise BTFailure(_("Invalid torrent config file")) if get_filesystem_encoding() == None: # These paths should both be unicode. If they aren't, they are the # broken product of some old version, and probably are in the # encoding we used to use in config files. Attempt to recover. dp = torrent_config['destination_path'] if isinstance(dp, str): try: dp = dp.decode(old_broken_config_subencoding) torrent_config['destination_path'] = dp except: raise BTFailure(_("Invalid torrent config file")) wp = torrent_config['working_path'] if isinstance(wp, str): try: wp = wp.decode(old_broken_config_subencoding) torrent_config['working_path'] = wp except: raise BTFailure(_("Invalid torrent config file")) return torrent_config
def to_utf8(name): try: u = name.decode(encoding) except Exception, e: raise BTFailure(_('Could not convert file/directory name "%s" to ' 'utf-8 (%s). Either the assumed filesystem ' 'encoding "%s" is wrong or the filename contains ' 'illegal bytes.') % (name, str(e), encoding))
def _version_thread(self): def error(level, text): def f(): self.global_error(level, text) self.rawserver.external_add_task(f, 0) def splitversion(text): return [int(t) for t in text.split('.')] try: try: a = dns.resolver.query('version.bittorrent.com', 'TXT') except: # the exceptions from the library have empty str(), # just different classes... raise BTFailure('DNS query failed') if len(a) != 1: raise BTFailure('number of received TXT fields is not 1') value = iter(a).next() # the object doesn't support a[0] if len(value.strings) != 1: raise BTFailure('number of strings in reply is not 1?') s = value.strings[0].split(None, 2) myversion = splitversion(BitTorrent.version) if myversion[1] % 2 and len(s) > 1: s = s[1] else: s = s[0] try: latest = splitversion(s) except ValueError: raise BTFailure("Could not parse new version string") for my, new in zip(myversion, latest): if my > new: break if my < new: download_url = 'http://bittorrent.com/download.html' if hasattr(self.ui, 'new_version'): self.run_ui_task(self.ui.new_version, s, download_url) else: error( ERROR, "A newer version of BitTorrent is " "available.\nYou can always get the latest " "version from\n%s." % download_url) except Exception, e: error(WARNING, "Version check failed: " + str(e))
def create_socket_unix(self): filename = self.socket_filename if os.path.exists(filename): try: os.unlink(filename) except OSError, e: raise BTFailure("Could not remove old control socket filename:" + str(e))
def parse_options(defaults, newvalues): for key, value in newvalues.iteritems(): if not defaults.has_key(key): raise BTFailure('unknown key ' + format_key(key)) try: t = type(defaults[key]) if t is NoneType or t is StringType: newvalues[key] = value elif t in (IntType, LongType): newvalues[key] = int(value) elif t is FloatType: newvalues[key] = float(value) else: assert False except ValueError, e: raise BTFailure('wrong format of %s - %s' % (format_key(key), str(e)))
def to_utf8(name): try: u = name.decode(encoding) except Exception, e: raise BTFailure('Could not convert file/directory name "' + name + '" to utf-8 (' + str(e) + '). Either the assumed ' 'filesystem encoding "' + encoding + '" is wrong or ' 'the filename contains illegal bytes.')
def check_fastresume(self, resumefile, return_filelist=False, piece_size=None, numpieces=None, allfiles=None): filenames = [name for _, _, name in self.ranges] if resumefile is not None: version = resumefile.readline() if version != 'BitTorrent resume state file, version 1\n': raise BTFailure('Unsupported fastresume file format, ' 'maybe from another client version') amount_done = int(resumefile.readline()) else: amount_done = size = mtime = 0 for filename in filenames: if resumefile is not None: line = resumefile.readline() size, mtime = line.split()[:2] # allow adding extra fields size = int(size) mtime = int(mtime) if os.path.exists(filename): fsize = os.path.getsize(filename) else: fsize = 0 if fsize > 0 and mtime != os.path.getmtime(filename): raise BTFailure("Fastresume info doesn't match file " "modification time") if size != fsize: raise BTFailure("Fastresume data doesn't match actual " "filesize") if not return_filelist: return amount_done if resumefile is None: return None if numpieces < 32768: typecode = 'h' else: typecode = 'l' try: r = array(typecode) r.fromfile(resumefile, numpieces) except Exception, e: raise BTFailure("Couldn't read fastresume data: " + str(e))
def check_info(info, check_paths=True): if type(info) != dict: raise BTFailure, _("bad metainfo - not a dictionary") pieces = info.get('pieces') if type(pieces) != str or len(pieces) % 20 != 0: raise BTFailure, _("bad metainfo - bad pieces key") piecelength = info.get('piece length') if type(piecelength) not in ints or piecelength <= 0: raise BTFailure, _("bad metainfo - illegal piece length") name = info.get('name') if type(name) != str: raise BTFailure, _("bad metainfo - bad name") if not allowed_path_re.match(name): raise BTFailure, _("name %s disallowed for security reasons") % name if info.has_key('files') == info.has_key('length'): raise BTFailure, _("single/multiple file mix") if info.has_key('length'): length = info.get('length') if type(length) not in ints or length < 0: raise BTFailure, _("bad metainfo - bad length") else: files = info.get('files') if type(files) != list: raise BTFailure, _('bad metainfo - "files" is not a list of files') for f in files: if type(f) != dict: raise BTFailure, _("bad metainfo - file entry must be a dict") length = f.get('length') if type(length) not in ints or length < 0: raise BTFailure, _("bad metainfo - bad length") path = f.get('path') if type(path) != list or path == []: raise BTFailure, _("bad metainfo - bad path") for p in path: if type(p) != str: raise BTFailure, _("bad metainfo - bad path dir") if check_paths and not allowed_path_re.match(p): raise BTFailure, _( "path %s disallowed for security reasons") % p f = ['/'.join(x['path']) for x in files] f.sort() i = iter(f) try: name2 = i.next() while True: name1 = name2 name2 = i.next() if name2.startswith(name1): if name1 == name2: raise BTFailure, _("bad metainfo - duplicate path") elif name2[len(name1)] == '/': raise BTFailure( _("bad metainfo - name used as both" "file and subdirectory name")) except StopIteration: pass
def add_files(self, files, torrent): for filename in files: if filename in self.allfiles: raise BTFailure( _("File %s belongs to another running torrent") % filename) for filename in files: self.allfiles[filename] = torrent if self.handlebuffer is None and \ len(self.allfiles) > self.max_files_open: self.handlebuffer = self.handles.keys()
def read(self, pos, amount): r = [] for filename, pos, end in self._intervals(pos, amount): h = self._get_file_handle(filename, False) h.seek(pos) r.append(h.read(end - pos)) r = ''.join(r) if len(r) != amount: raise BTFailure('Short read - something truncated files?') return r
def check_info(info): if type(info) != dict: raise BTFailure, 'bad metainfo - not a dictionary' pieces = info.get('pieces') if type(pieces) != str or len(pieces) % 20 != 0: raise BTFailure, 'bad metainfo - bad pieces key' piecelength = info.get('piece length') if type(piecelength) not in ints or piecelength <= 0: raise BTFailure, 'bad metainfo - illegal piece length' name = info.get('name') if type(name) != str: raise BTFailure, 'bad metainfo - bad name' if not reg.match(name): raise BTFailure, 'name %s disallowed for security reasons' % name if info.has_key('files') == info.has_key('length'): raise BTFailure, 'single/multiple file mix' if info.has_key('length'): length = info.get('length') if type(length) not in ints or length < 0: raise BTFailure, 'bad metainfo - bad length' else: files = info.get('files') if type(files) != list: raise BTFailure, 'bad metainfo - "files" is not a list of files' for f in files: if type(f) != dict: raise BTFailure, 'bad metainfo - bad file value' length = f.get('length') if type(length) not in ints or length < 0: raise BTFailure, 'bad metainfo - bad length' path = f.get('path') if type(path) != list or path == []: raise BTFailure, 'bad metainfo - bad path' for p in path: if type(p) != str: raise BTFailure, 'bad metainfo - bad path dir' if not reg.match(p): raise BTFailure, 'path %s disallowed for security reasons' % p f = ['/'.join(x['path']) for x in files] f.sort() i = iter(f) try: name2 = i.next() while True: name1 = name2 name2 = i.next() if name2.startswith(name1): if name1 == name2: raise BTFailure, 'bad metainfo - duplicate path' elif name2[len(name1)] == '/': raise BTFailure('bad metainfo - name used as both ' 'file and subdirectory name') except StopIteration: pass
def add_torrent(self, infohash, connection_manager): if infohash in self.torrents: raise BTFailure(_("Can't start two separate instances of the same " "torrent")) self.torrents[infohash] = connection_manager key = sha('req2' + infohash).digest() self.obfuscated_torrents[key] = connection_manager if self.local_discovery: service = self.local_discovery.announce(infohash.encode('hex'), connection_manager.my_id.encode('hex')) self.ld_services[infohash] = service
def send_command_inet(self, action, data=''): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect(('127.0.0.1', 56881)) s.send(tobinary(len(action))) s.send(action) s.send(tobinary(len(data))) s.send(data) s.close() except socket.error, e: s.close() raise BTFailure('Could not send command: ' + str(e))
def discover_sic_socket(self): takeover = 0 # mutex exists and has been opened (not created, not locked). # wait for it so we can read the file r = win32event.WaitForSingleObject(self.mutex, win32event.INFINITE) # WAIT_OBJECT_0 means the mutex was obtained # WAIT_ABANDONED means the mutex was obtained, and it had previously been abandoned if (r != win32event.WAIT_OBJECT_0) and (r != win32event.WAIT_ABANDONED): raise BTFailure( _("Could not acquire global mutex lock for controlsocket file!" )) filename = self._get_sic_path() try: f = open(filename, "r") self.port = int(f.read()) f.close() except: if (r == win32event.WAIT_ABANDONED): self.log( WARNING, _("A previous instance of BT was not cleaned up properly. Continuing." )) # take over the role of master takeover = 1 else: self.log(WARNING, (_( "Another instance of BT is running, but \"%s\" does not exist.\n" ) % filename) + _("I'll guess at the port.")) try: self.port = CONTROL_SOCKET_PORT self.send_command('no-op') self.log(WARNING, _("Port found: %d") % self.port) try: f = open(filename, "w") f.write(str(self.port)) f.close() except: traceback.print_exc() except: # this is where this system falls down. # There's another copy of BitTorrent running, or something locking the mutex, # but I can't communicate with it. self.log(WARNING, _("Could not find port.")) # we're done reading the control file, release the mutex so other instances can lock it and read the file win32event.ReleaseMutex(self.mutex) return takeover
def _get_available(self, url): """Get the available version from the version site. The command line option --new_version X.Y.Z overrides this method and returns 'X.Y.Z' instead.""" self.debug('_get_available() run#%d: hitting url %s' % (self.runs, url)) try: u = zurllib.urlopen(url) s = u.read() s = s.strip() except: raise BTFailure(_("Could not get latest version from %s") % url) try: # we're luck asserts are turned off in production. # this assert is false for 4.20.X #assert len(s) == 5 available_version = Version.from_str(s) except: raise BTFailure( _("Could not parse new version string from %s") % url) return available_version
def decode_line(line): hashtext = line[:40] try: infohash = InfoHashType(hashtext.decode('hex')) except: raise BTFailure(_("Invalid state file contents")) if len(infohash) != 20: raise BTFailure(_("Invalid state file contents")) if infohash in self.torrents: raise BTFailure(_("Invalid state file (duplicate entry)")) try: metainfo = self._read_metainfo(infohash) except OSError, e: try: f.close() except: pass self.logger.error((_("Error reading metainfo file \"%s\".") % hashtext) + " (" + str_exc(e) + "), " + _("cannot restore state completely")) return None