| #!/usr/bin/python |
| # Copyright 2012 Google Inc. All Rights Reserved. |
| # |
| """A command-line tool for uploading to gfiber-dropbox.appspot.com.""" |
| |
| __author__ = 'apenwarr@google.com (Avery Pennarun)' |
| |
| import errno |
| import os |
| import re |
| import subprocess |
| import sys |
| import options |
| |
| |
| optspec = """ |
| upload-crash-log [options...] |
| -- |
| s,server= The server URL (default: whatever upload-logs uses) |
| a,all Upload the entire log, not just the most recent part |
| l,logtype= The logtype to send to the server |
| stdout Print to stdout instead of calling upload-logs |
| """ |
| |
| MARK_START = '*LOG_UPLOAD_START*' |
| MARK_END = '*LOG_UPLOAD_END*' |
| |
| |
| class SubprocError(Exception): |
| pass |
| |
| |
| def ReadPipe(argv, **kwargs): |
| p = subprocess.Popen(argv, stdout=subprocess.PIPE, **kwargs) |
| data = p.stdout.read() |
| retval = p.wait() |
| if retval: |
| raise SubprocError('%r returned exit code %d' % (argv, retval)) |
| return data |
| |
| |
| def TryReadPipe(argv, **kwargs): |
| try: |
| return ReadPipe(argv, **kwargs) |
| except SubprocError: |
| return '' |
| except OSError, e: |
| if e.errno == errno.ENOENT: |
| return '' |
| else: |
| raise |
| |
| |
| def ReFind(regex, key, data): |
| matches = re.findall(regex, data) |
| return ['%s=%s' % (key, i) for i in matches] |
| |
| |
| def IfconfigData(ifcname): |
| """Returns a list of key/value pairs for the given network interface.""" |
| data = TryReadPipe(['ip', 'addr', 'show', ifcname]) |
| out = [] |
| out += ReFind(r'inet ([\d.]+/\d+)', 'ip', data) |
| out += ReFind(r'inet6 ([\da-fA-F:/.]+/\d+)', 'ip6', data) |
| out += ReFind(r'link/ether ([\da-fA-F:]+)', 'hw', data) |
| return out |
| |
| |
| def NvramData(): |
| """Returns a list of key/value pairs based on NVRAM contents.""" |
| return ['serial=' + TryReadPipe(['/bin/serial']).rstrip()] |
| |
| |
| def ModelName(): |
| try: |
| model = open('/etc/platform').read().rstrip() |
| return ['model=' + model] |
| except IOError: |
| return None |
| |
| |
| def Marker(s): |
| """Print the given marker string to the dmesg log.""" |
| open('/dev/kmsg', 'w').write('<7>%s\n' % s) |
| |
| |
| def UploadLog(server, filename, content, keys): |
| """Upload a log file to the given server by calling upload-logs.""" |
| extra_args = [] |
| if server: |
| extra_args += ['--server', server] |
| p = subprocess.Popen(['upload-logs', '--stdin', filename] + |
| extra_args + |
| ['-k%s' % i for i in keys], |
| stdin=subprocess.PIPE) |
| p.stdin.write(content) |
| p.stdin.close() |
| retval = p.wait() |
| if retval: |
| raise Exception('upload-logs returned exit code %d' % retval) |
| |
| |
| def FirstStamp(s): |
| regex = re.compile(r'^(<[0-9]+>)?\[\s*([0-9.]+\.[0-9]+)\s*\]', re.M) |
| g = re.search(regex, s) |
| if g: |
| return float(g.group(2)) |
| return None |
| |
| |
| def main(): |
| o = options.Options(optspec) |
| (opt, flags, extra) = o.parse(sys.argv[1:]) # pylint: disable-msg=W0612 |
| if extra: |
| o.fatal('no filenames expected') |
| |
| subprocess.call('logmark-once') |
| Marker(MARK_START) |
| |
| # In case the system crashes after we finish uploading but before the next |
| # time this script runs, we want to make sure there's at least one logmark |
| # in the *next* segment as well. Otherwise the server won't be able to |
| # feel certain about what version that segment came from. So add it again |
| # now. |
| subprocess.call('logmark-once') |
| |
| p = subprocess.Popen(['toolbox', 'dmesg'], stdout=subprocess.PIPE) |
| dmesg = p.stdout.read() |
| retval = p.wait() |
| if retval: |
| raise Exception('dmesg returned exit code %d' % retval) |
| |
| interfaces = set(['br0', 'eth0', 'man', 'pon0']) |
| interfaces = interfaces.intersection(os.listdir('/sys/class/net')) |
| keys = [] |
| for iface in interfaces: |
| keys += IfconfigData(iface) |
| model = ModelName() |
| if model: |
| keys += model |
| if opt.logtype: |
| keys.append('logtype=' + opt.logtype) |
| keys = set(keys + NvramData()) |
| os.environ['PATH'] += ':.' # make sure PWD is searched for upload-logs |
| |
| if not opt.all: |
| # we only want the text from after the last MARK_START that came *before* |
| # the last MARK_END. If we tried uploading previously but failed, it |
| # will have printed a MARK_START but not a MARK_END, and we have to ignore |
| # that one and retry. However, we can't just use MARK_END, because |
| # messages might have been printed after retrieving the dmesg text |
| # but before printing MARK_END. |
| # |
| # And we don't want anything that came after the final MARK_START, because |
| # that will be uploaded next time, and we don't want duplicate data. |
| start = dmesg.rfind(MARK_END) |
| if start < 0: start = 0 |
| start = dmesg.rfind(MARK_START, 0, start) |
| if start < 0: start = 0 |
| end = dmesg.rfind(MARK_START) |
| if end < 0: |
| end = len(dmesg) |
| dmesg = dmesg[start:end] |
| if start == 0 and FirstStamp(dmesg) != 0.0: |
| dmesg = ('\n\nWARNING: log buffer overflow. ' |
| 'Some messages were lost at this position.\n\n' + |
| dmesg) |
| |
| if opt.stdout: |
| print dmesg |
| else: |
| UploadLog(opt.server, 'dmesg', dmesg, keys) |
| |
| Marker(MARK_END) |
| |
| if __name__ == '__main__': |
| main() |