#!/usr/bin/python3 """fpaste - a cli frontend for the paste.fedoraproject.org pastebin.""" # Copyright 2008, 2010 Fedora Unity Project (http://fedoraunity.org) # Author: Jason 'zcat' Farrell # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys import urllib.request import urllib.parse import urllib.error import subprocess from optparse import OptionParser, OptionGroup, SUPPRESS_HELP from subprocess import CalledProcessError VERSION = '0.5.0.0' USER_AGENT = 'fpaste/' + VERSION APIKEY = urllib.parse.urlencode({'apikey': '5uZ30dTZE1a5V0WYhNwcMddBRDpk6UzuzMu-APKM38iMHacxdA0n4vCqA34avNyt'}) SERVER_URL = 'https://paste.centos.org' FPASTE_URL = 'https://paste.centos.org/api/create?' + APIKEY # cmd name, command, command2 fallback, command3 fallback, ... release_cmdlist = [ ('OS Release', '''lsb_release -ds''', '''cat /etc/*-release | uniq''', 'cat /etc/issue', 'cat /etc/motd' ), ] kernel_cmdlist = [ ('Kernel', '''uname -r''' ), ('Kernel cmdline', '''cat /proc/cmdline''' ), ] desktop_cmdlist = [ ('Desktop(s) Running', '''ps -eo comm= | grep -E '(gnome-session|startkde|startactive|xfce.?-session|fluxbox|blackbox|hackedbox|ratpoison|enlightenment|icewm-session|od-session|wmaker|wmx|openbox-lxde|openbox-gnome-session|openbox-kde-session|mwm|e16|fvwm|xmonad|sugar-session|mate-session|lxqt-session|cinnamon|lxdm-session|awesome|phosh|sway|Hyperland)' ''' ), ('Desktop(s) Installed', '''ls -m /usr/share/{xsessions,wayland-sessions}/ | sed 's/\\.desktop//g' ''' ), ('Session Type', '''env | grep 'XDG_SESSION_TYPE' | sed 's/.*=//' ''' ), ] cpu_cmdlist = [ ('CPU Model', '''grep 'model name' /proc/cpuinfo | awk -F: '{print $2}' | uniq -c | sed -re 's/^ +//' ''', '''grep 'model name' /proc/cpuinfo''' ), ('64-bit Support', '''grep -q ' lm ' /proc/cpuinfo && echo Yes || echo No''' ), ('Hardware Virtualization Support', '''grep -Eq '(vmx|svm)' /proc/cpuinfo && echo Yes || echo No''' ), ] selinux_cmdlist = [ ('SELinux Status', '''sestatus''', '''/usr/sbin/sestatus''', '''getenforce''', '''grep -v '^#' /etc/sysconfig/selinux''' ), ('SELinux Errors', '''selinuxenabled && journalctl --no-hostname --since yesterday |grep avc: | grep -Eo comm="[^ ]+" | sort |uniq -c |sort -rn''' ), ] mem_cmdlist = [ ('Memory usage', '''free -hm''', 'free' ), ('ZRAM usage', '''zramctl --output-all''' ), ] perf_cmdlist = [ ('Load average', '''uptime''' ), ('Pressure Stall Information', '''grep -R . /proc/pressure/''' ), ('Top 5 CPU hogs', '''ps axuScnh | awk '$2!=''' + str(os.getpid()) + '''' | sort -rnk3 | head -5''' ), ('Top 5 Memory hogs', '''ps axuScnh | sort -rnk4 | head -5''' ), ] storage_cmdlist = [ ('block devices', '''lsblk -o NAME,FSTYPE,SIZE,FSUSE%,MOUNTPOINT,UUID,MIN-IO,SCHED,DISC-GRAN,MODEL''' ), ] pci_cmdlist = [ ('PCI devices', '''lspci -nn''', '''/sbin/lspci -nn''', '''lspci''', '''/sbin/lspci''' ), ] pci_verbose_cmdlist = [ ('PCI devices', '''lspci -vvnnk''', '''/sbin/lspci -vvnnkk''' ), ] usb_cmdlist = [ ('USB devices', '''lsusb''', '''/sbin/lsusb''' ), ] usb_verbose_cmdlist = [ ('USB devices', '''lsusb -vv -t''', '''/sbin/lsusb -vv -t''' ), ] audio_cmdlist = [ ('PCI Audio devices', '''lspci | grep -i -E 'audio' | cut -b1-7 | xargs -i lspci -vnnks {} | grep -v ""''' ), ('Audio devices', '''cat /proc/asound/cards''' ), ('User audio services', '''systemctl --user --no-pager status wireplumber pipewire* | sed "s/$(hostname)/ahost/"''' ), ] gpu_cmdlist = [ ('PCI Video Card', '''lspci | grep -i -E 'vga' | cut -b1-7 | xargs -i lspci -vnnks {} | grep -v ""''' ), ('GL Support', '''glxinfo -B | grep -E "OpenGL version|OpenGL renderer"''' ), ] drm_cmdlist = [ ('DRM Information', '''journalctl -k -b --no-hostname | grep -o 'kernel:.*drm.*$' | cut -d ' ' -f 2- ''' ), ] xorg_cmdlist = [ ('Xorg modules', '''grep LoadModule /var/log/Xorg.0.log ~/.local/share/xorg/Xorg.0.log | cut -d \\" -f 2 | xargs''' ), ('Xorg errors', '''grep '^\\[.*(EE)' /var/log/Xorg.0.log ~/.local/share/xorg/Xorg.0.log | cut -d ':' -f 2- ''' ), ] dnf_cmdlist = [ ('DNF Repositories', '''dnf repolist''', '''ls -l /etc/yum.repos.d''', '''grep -v '^#' /etc/yum.conf''' ), ('DNF Extras', '''dnf -C list extras''' ), ('Last 20 packages installed', '''rpm -qa --nodigest --nosignature --last | head -20'''), # ('Installed packages', '''rpm -qa --nodigest --nosignature | sort''', '''dpkg -l''') ] ] efiboot_cmdlist = [ ('EFI boot manager output', '''efibootmgr -v''' ), ] reboot_cmdlist = [ ('Last few reboots', '''last -x -n10 reboot runlevel''' ), ] kernelbuffer_cmdlist = [ ('Kernel buffer tail', '''journalctl --no-hostname -k --lines 50''' ), ] net_cmdlist = [ ('PCI Network devices', '''lspci | grep -i -E 'net' | cut -b1-7 | xargs -i lspci -vnnks {} | grep -v ""''' ), ('Network status', '''ip -br addr | awk '{print $1" " $2}' | column -t''' ), ] btrfs_cmdlist = [ ('btrfs usage', '''btrfs filesystem usage -T /''' ), ('btrfs-progs', '''rpm -q btrfs-progs''' ), ('btrfs mounts', '''grep btrfs /proc/mounts''' ), ('FSUUID', '''findmnt -n -o UUID $(stat -c '%m' "/")''' ), ('btrfs allocations', '''FSUUID=$(findmnt -n -o UUID $(stat -c '%m' "/")); grep -R . /sys/fs/btrfs/"$FSUUID"/allocation/ | sed "s/^.*allocation//"''' ), ('btrfs features', '''FSUUID=$(findmnt -n -o UUID $(stat -c '%m' "/")); grep -R . /sys/fs/btrfs/"$FSUUID"/features/ | sed "s/^.*features//"''' ), ('btrfs checksum', '''FSUUID=$(findmnt -n -o UUID $(stat -c '%m' "/")); cat /sys/fs/btrfs/"$FSUUID"/checksum''' ), ('Kernel messages', '''journalctl -k -o short-monotonic --no-hostname | grep "Linux version\\| ata\\|Btrfs\\|BTRFS\\|] hd\\| scsi\\| sd\\| sdhci\\| mmc\\| nvme\\| usb\\| vd"''' ), ] def is_text(text, maxCheck=100, pctPrintable=0.75): """ Check to see if a majority of the characters are printable. Returns true if maxCheck evenly distributed chars in text are >= pctPrintable% text chars. """ # e.g.: /bin/* ranges between 19% and 42% printable from string import printable if type(text) == bytes: text = text.decode("utf-8", "replace") nchars = len(text) if nchars == 0: return False ncheck = min(nchars, maxCheck) inc = float(nchars) / ncheck i = 0.0 nprintable = 0 while i < nchars: if text[int(i)] in printable: nprintable += 1 i += inc pct = float(nprintable) / ncheck return (pct >= pctPrintable) def confirm(prompt="OK?"): """Prompt user for yes/no input and return True or False.""" prompt += " [y/N]: " try: ans = input(prompt) except EOFError: # already read sys.stdin and hit EOF # rebind sys.stdin to user tty (unix-only) try: mytty = os.ttyname(sys.stdout.fileno()) sys.stdin = open(mytty) ans = input() except: # noqa print( "could not rebind sys.stdin to %s after sys.stdin EOF" % mytty, file=sys.stderr) return False if ans.lower().startswith("y"): return True else: return False def paste(text, options): """Send text to paste server and return the URL.""" if not text: print("No text to send.", file=sys.stderr) return False # if sent data exceeds maxlength, server dies without error returned, so, # we'll truncate the input here, until the server decides to truncate # instead of die data = urllib.parse.urlencode( {'lang': options.lang, 'text': text, 'title': options.title, 'name': options.author, 'private': options.private, 'expire': options.life }) pasteSizeKiB = len(data) / 1024.0 # 512KiB appears to be the current hard limit (20110404); old limit was # 16MiB if pasteSizeKiB >= 512: print( "WARNING: your paste size (%.1fKiB) is very large and may be rejected by the server. A pastebin is NOT a file hosting service!" % (pasteSizeKiB), file=sys.stderr) # verify that it's most likely *non-binary* data being sent. if not is_text(text): print( "WARNING: your paste looks a lot like binary data instead of text.", file=sys.stderr) if not confirm("Send binary data anyway?"): return False req = urllib.request.Request( url=FPASTE_URL, data=data.encode('ascii'), headers={ 'User-agent': USER_AGENT}) if options.proxy: if options.debug: print("Using proxy: %s" % options.proxy, file=sys.stderr) req.set_proxy(options.proxy, 'http') print("Uploading (%.1fKiB)..." % pasteSizeKiB, file=sys.stderr) try: f = urllib.request.urlopen(req) except urllib.error.URLError as e: if hasattr(e, 'reason'): print("Error Uploading: %s" % e.reason, file=sys.stderr) elif hasattr(e, 'code'): print("Server Error: %d - %s" % (e.code, e.msg), file=sys.stderr) if options.debug: print(f.read()) return False try: response = f.read().decode("utf-8", "replace") except ValueError as e: print( "Error: Server did not return a correct response: " + str(e), file=sys.stderr) return False return response def btrfsinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get btrfs related info. """ return getinfo(release_cmdlist + kernel_cmdlist + storage_cmdlist + btrfs_cmdlist) def sysinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get general system related info. """ return getinfo(release_cmdlist + cpu_cmdlist + kernel_cmdlist + desktop_cmdlist + selinux_cmdlist + mem_cmdlist + perf_cmdlist + storage_cmdlist + pci_cmdlist + usb_cmdlist + gpu_cmdlist + drm_cmdlist + xorg_cmdlist + audio_cmdlist + net_cmdlist + kernelbuffer_cmdlist + reboot_cmdlist + dnf_cmdlist + efiboot_cmdlist) def sysinfo_short(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get shortened system related info. """ return getinfo(release_cmdlist + cpu_cmdlist + kernel_cmdlist + desktop_cmdlist + mem_cmdlist + storage_cmdlist + pci_cmdlist + usb_cmdlist + gpu_cmdlist + audio_cmdlist + net_cmdlist) def pci_verbose_info(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get verbose listing of PCI devices. """ return getinfo(release_cmdlist + kernel_cmdlist + pci_verbose_cmdlist) def usb_verbose_info(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get verbose listing of USB devices. """ return getinfo(release_cmdlist + kernel_cmdlist + usb_verbose_cmdlist) def audioinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get general audio info. """ return getinfo(release_cmdlist + kernel_cmdlist + audio_cmdlist) def videoinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get general video info. """ return getinfo(release_cmdlist + kernel_cmdlist + desktop_cmdlist + xorg_cmdlist + gpu_cmdlist) def netinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get general network device info. """ return getinfo(release_cmdlist + kernel_cmdlist + net_cmdlist) def diskinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get general disk related info. """ return getinfo(release_cmdlist + kernel_cmdlist + storage_cmdlist) def dnfinfo(show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """ Get repository and package information. """ return getinfo(release_cmdlist + dnf_cmdlist) def getinfo(cmdlist, show_stderr=False, show_successful_cmds=True, show_failed_cmds=True): """Return commonly requested system info.""" # 'ps' output below has been anonymized: -n for uid vs username, and -c for # short processname si = [] print("Gathering system info", end=' ', file=sys.stderr) for cmds in cmdlist: cmdname = cmds[0] cmd = "" for cmd in cmds[1:]: sys.stderr.write('.') # simple progress feedback p = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: (out, err) = p.communicate(timeout=300) except subprocess.TimeoutExpired: p.kill() (out, err) = p.communicate() if not p.returncode == 0: if show_stderr: if err: print( "sysinfo Error: the cmd \"%s\" returned %d with stderr: %s" % (cmd, p.returncode, err), file=sys.stderr) else: print( "sysinfo Error: the cmd \"%s\" returned %d without errors" % (cmd, p.returncode), file=sys.stderr) print("Trying next fallback cmd...", file=sys.stderr) if p.returncode == 0 and out: break if out: if show_successful_cmds: si.append(('%s (%s)' % (cmdname, cmd), out)) else: si.append(('%s' % cmdname, out)) else: if show_failed_cmds: si.append( ('%s (without results: "%s")' % (cmdname, '" AND "'.join( cmds[ 1:])), out)) else: si.append(('%s' % cmdname, out)) print(" ", end='\n', file=sys.stderr) # return in readable indented format sistr = "=== fpaste %s System Information ===\n" % VERSION for cmdname, output in si: sistr += "* %s:\n" % cmdname if not output: sistr += " N/A\n\n" else: for line in output.decode("utf-8", "replace").split('\n'): sistr += " %s\n" % line return sistr def summarize_text(text): # use beginning/middle/end content snippets as a description summary. 120 char limit # "36chars ... 36chars ... 36chars" == 118 chars # TODO: nuking whitespace in huge text files might be expensive; optimize # for b/m/e segments only sniplen = 36 seplen = len(" ... ") tsum = "" text = " ".join(text.split()) # nuke whitespace tlen = len(text) if tlen < sniplen + seplen: tsum += text if tlen >= sniplen + seplen: tsum += text[0:sniplen] + " ..." if tlen >= (sniplen * 2) + seplen: tsum += " " + text[tlen / 2 - (sniplen / 2):(tlen / 2) + (sniplen / 2)] + " ..." if tlen >= (sniplen * 3) + (seplen * 2): tsum += " " + text[-sniplen:] # print >> sys.stderr, str(len(tsum)) + ": " + tsum return tsum def main(): """Main work function.""" validSyntaxOpts = [ "text", "html5", "css", "javascript", "php", "python", "ruby", "lua", "bash", "erlang", "go", "c", "cpp", "diff", "latex", "sql", "xml", "0", "4cs", "6502acme", "6502kickass", "6502tasm", "68000devpac", "abap", "actionscript", "actionscript3", "ada", "aimms", "algol68", "apache", "applescript", "apt_sources", "arm", "asm", "asymptote", "asp", "autoconf", "autohotkey", "autoit", "avisynth", "awk", "bascomavr", "basic4gl", "bbcode", "bf", "bibtex", "blitzbasic", "bnf", "boo", "c_loadrunner", "c_mac", "c_winapi", "caddcl", "cadlisp", "cfdg", "cfm", "chaiscript", "chapel", "cil", "clojure", "cmake", "cobol", "coffeescript", "cpp-winapi", "csharp", "cuesheet", "d", "dart", "dcs", "dcl", "dcpu16", "delphi", "div", "dos", "dot", "e", "ecmascript", "eiffel", "email", "epc", "euphoria", "ezt", "f1", "falcon", "fo", "fortran", "freebasic", "freeswitch", "fsharp", "gambas", "gdb", "genero", "genie", "gettext", "glsl", "gml", "gnuplot", "groovy", "gwbasic", "haskell", "haxe", "hicest", "hq9plus", "html4strict", "icon", "idl", "ini", "inno", "intercal", "io", "ispfpanel", "j", "java", "java5", "jcl", "jquery", "klonec", "klonecpp", "kotlin", "lb", "ldif", "lisp", "llvm", "locobasic", "logcat", "logtalk", "lolcode", "lotusformulas", "lotusscript", "lscript", "lsl2", "m68k", "magiksf", "make", "mapbasic", "matlab", "mirc", "mmix", "modula2", "modula3", "mpasm", "mxml", "mysql", "nagios", "netrexx", "newlisp", "nginx", "nimrod", "nsis", "oberon2", "objc", "objeck", "ocaml", "octave", "oobas", "oorexx", "oracle11", "oracle8", "oxygene", "oz", "parasail", "parigp", "pascal", "pcre", "per", "perl", "perl6", "pf", "pic16", "pike", "pixelbender", "pli", "plsql", "postgresql", "postscript", "povray", "powerbuilder", "powershell", "proftpd", "progress", "prolog", "properties", "providex", "purebasic", "pys60", "q", "qbasic", "qml", "racket", "rails", "rbs", "rebol", "reg", "rexx", "robots", "rpmspec", "rsplus", "rust", "sas", "scala", "scheme", "scilab", "scl", "sdlbasic", "smalltalk", "smarty", "spark", "sparql", "standardml", "stonescript", "systemverilog", "tcl", "teraterm", "thinbasic", "tsql", "typoscript", "unicon", "uscript", "upc", "urbi", "vala", "vb", "vbnet", "vbscript", "vedit", "verilog", "vhdl", "vim", "visualfoxpro", "visualprolog", "whitespace", "whois", "winbatch", "xbasic", "xorg_conf", "xpp", "yaml", "z80", "zxbasic" ] validClipboardSelectionOpts = ['primary', 'secondary', 'clipboard'] ext2lang_map = { 'sh': 'bash', 'bash': 'bash', 'bib': 'bibtex', 'c': 'c', 'h': 'c', 'hpp': 'cpp', 'cls': 'latex', 'cpp': 'cpp', 'css': 'css', 'diff': 'diff', 'html': 'html5', 'htm': 'html5', 'ini': 'ini', 'java': 'java', 'js': 'javascript', 'jsp': 'html5', 'lua': 'lua', 'm': 'octave', 'mat': 'matlab', 'mbox': 'email', 'pl': 'perl', 'plt': 'gnuplot', 'php': 'php', 'php3': 'php', 'py': 'python', 'rb': 'ruby', 'rhtml': 'html', 'spec': 'rpmspec', 'sql': 'sql', 'sqlite': 'sql', 'sty': 'latex', 'tcl': 'tcl', 'tex': 'latex', 'xml': 'xml', 'yaml': 'yaml' } usage = """\ Usage: %%prog [OPTION]... [FILE]... Send text file(s), stdin, or clipboard to the Fedora community pastebin at %s and return the URL. It is often useful to be able to easily paste text to the Fedora Pastebin at http://paste.fedoraproject.org and this simple utility will do that and return the resulting URL so that people may examine the output. This can hopefully help folks who are for some reason stuck without a graphical interface, working remotely, or any other reason they may be unable to paste something into the pastebin using a web browser. Examples: %%prog file1.txt file2.txt dmesg | %%prog (prog1; prog2; prog3) | fpaste %%prog --sysinfo --confirm %%prog -t "debug output" -l python foo.py""" % SERVER_URL parser = OptionParser(usage=usage, version='%prog ' + VERSION) parser.add_option( '', '--debug', dest='debug', help=SUPPRESS_HELP, action="store_true", default=False) parser.add_option('', '--proxy', dest='proxy', help=SUPPRESS_HELP) # pastebin-specific options first fpasteOrg_group = OptionGroup(parser, "paste.fedoraproject.org Options") fpasteOrg_group.add_option( '-t', '--title', dest='title', help='title of paste; defaults to UNTITLED', metavar='"TITLE"') fpasteOrg_group.add_option( '-a', '--author', dest='author', help='author name; empty by default', metavar='"AUTHOR"') fpasteOrg_group.add_option( '-r', '--private', dest='private', help='make paste private; defaults to 1', metavar='PRIVATE') fpasteOrg_group.add_option( '-l', dest='lang', help='language of content for syntax highlighting; default is "%default"; use "list" to show all ' + str(len(validSyntaxOpts)) + ' supported langs', metavar='"LANGUAGE"') fpasteOrg_group.add_option( '-x', dest='life', help='life of paste in minutes; default is 1 day (maximum)', metavar='LIFE') parser.add_option_group(fpasteOrg_group) # other options fpasteProg_group = OptionGroup(parser, "Input/Output Options") fpasteProg_group.add_option( '-i', '--clipin', dest='clipin', help='read paste text from current X clipboard selection [requires: xsel]', action="store_true", default=False) fpasteProg_group.add_option( '-w', '--wayland-clipin', dest='wclipin', help='read paste text from Wayland selection [requires the wl-clipboard package]', action="store_true", default=False) fpasteProg_group.add_option( '-o', '--clipout', dest='clipout', help='save returned paste URL to all available clipboards', action="store_true", default=False) fpasteProg_group.add_option( '', '--input-selection', dest='selection', help='specify which X clipboard to use. valid options: "primary" (default; middle-mouse-button paste), "secondary" (uncommon), or "clipboard" (ctrl-v paste)', metavar='CLIP') fpasteProg_group.add_option( '', '--fullpath', dest='fullpath', help='use pathname VS basename for file description(s)', action="store_true", default=False) fpasteProg_group.add_option( '', '--pasteself', dest='pasteself', help='paste this script itself', action="store_true", default=False) fpasteProg_group.add_option( '', '--sysinfo', dest='sysinfo', help='paste system information', action="store_true", default=False) fpasteProg_group.add_option( '', '--sysinfo-short', dest='sysinfo_short', help='paste shortened system information', action="store_true", default=False) fpasteProg_group.add_option( '', '--audioinfo', '--sysinfo-audio', dest='audioinfo', help='paste general audio information', action="store_true", default=False) fpasteProg_group.add_option( '', '--videoinfo', '--sysinfo-video', dest='videoinfo', help='paste general video information', action="store_true", default=False) fpasteProg_group.add_option( '', '--diskinfo', '--sysinfo-disk', dest='diskinfo', help='paste general disk information', action="store_true", default=False) fpasteProg_group.add_option( '', '--netinfo', '--sysinfo-net', dest='netinfo', help='paste general network information', action="store_true", default=False) fpasteProg_group.add_option( '', '--dnfinfo', '--sysinfo-dnf', dest='dnfinfo', help='paste repository and package information', action="store_true", default=False) fpasteProg_group.add_option( '', '--pci-verbose', '--sysinfo-pci-verbose', dest='pci_verbose_info', help='paste verbose pci information', action="store_true", default=False) fpasteProg_group.add_option( '', '--usb-verbose', '--sysinfo-usb-verbose', dest='usb_verbose_info', help='paste verbose usb information', action="store_true", default=False) fpasteProg_group.add_option( '', '--btrfsinfo', '--sysinfo-btrfs', dest='btrfsinfo', help='paste btrfs related system information', action="store_true", default=False) fpasteProg_group.add_option( '', '--printonly', dest='printonly', help='print paste, but do not send', action="store_true", default=False) fpasteProg_group.add_option( '', '--confirm', dest='confirm', help='print paste, and prompt for confirmation before sending', action="store_true", default=False) fpasteProg_group.add_option( '', '--raw-url', dest='rawurl', help='print raw url', action="store_true", default=False) parser.add_option_group(fpasteProg_group) # Let default be anonymous. # p = subprocess.Popen('whoami', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # (out, err) = p.communicate () # if p.returncode == 0 and out: # user = out[0:-1] # else: # print >> sys.stderr, "WARNING Could not run whoami. Posting anonymously." parser.set_defaults( lang='text', life='1440', selection='primary', title="UNTITLED", private="1", author="" ) (options, args) = parser.parse_args() if options.lang.lower() == 'list': print('Valid language syntax options:') for opt in validSyntaxOpts: print(opt) sys.exit(0) if options.clipin: if not os.access('/usr/bin/xsel', os.X_OK): # TODO: try falling back to xclip or dbus parser.error( 'OOPS - the clipboard options currently depend on "/usr/bin/xsel", which does not appear to be installed') if options.wclipin: if not os.access('/usr/bin/wl-paste', os.X_OK): parser.error( 'OOPS - the Wayland clipboard option currently depends on "/usr/bin/wl-paste", which does not appear to be installed') if (options.clipin or options.wclipin) and args: parser.error( "Sending both clipboard contents AND files is not supported. Use -i OR -w OR filename(s)") for optk, optv, opts in [('language', options.lang, validSyntaxOpts), ('clipboard selection', options.selection, validClipboardSelectionOpts)]: if optv not in opts: parser.error( "'%s' is not a valid %s option.\n\tVALID OPTIONS: %s" % (optv, optk, ', '.join(opts))) fileargs = args if options.fullpath: fileargs = [os.path.abspath(x) for x in args] else: # remove potentially non-anonymous path info from file path # descriptions fileargs = [os.path.basename(x) for x in args] # guess lang for some common file extensions, if all file exts similar, # and lang not changed from default if options.lang == 'text': all_exts_similar = False ext_prev = "" for i in range(0, len(args)): all_exts_similar = True ext = os.path.splitext(args[i])[1].lstrip(os.extsep) if i > 0 and ext != ext_prev: all_exts_similar = False break ext_prev = ext if all_exts_similar and ext in list(ext2lang_map.keys()): options.lang = ext2lang_map[ext] # get input from mutually exclusive sources, though they *could* be # combined text = "" if options.clipin: xselcmd = 'xsel -o --%s' % options.selection # text = os.popen(xselcmd).read() p = subprocess.Popen( xselcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (text, err) = p.communicate() text = text.decode("utf-8", "replace") if p.returncode != 0: if options.debug: print(err, file=sys.stderr) parser.error( "'xsel' failure. this usually means you're not running X") if not text: parser.error("%s clipboard is empty" % options.selection) if options.wclipin: cmd = 'wl-paste' # text = os.popen(xselcmd).read() p = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (text, err) = p.communicate() text = text.decode("utf-8", "replace") if p.returncode != 0: if options.debug: print(err, file=sys.stderr) parser.error( "'wl-paste' failure. this usually means you're not running Wayland") if not text: parser.error("%s clipboard is empty" % options.selection) elif options.pasteself: text = open(sys.argv[0]).read() options.lang = 'python' elif options.sysinfo: text = sysinfo(options.debug) elif options.sysinfo_short: text = sysinfo_short(options.debug) elif options.pci_verbose_info: text = pci_verbose_info(options.debug) elif options.usb_verbose_info: text = usb_verbose_info(options.debug) elif options.audioinfo: text = audioinfo(options.debug) elif options.videoinfo: text = videoinfo(options.debug) elif options.diskinfo: text = diskinfo(options.debug) elif options.netinfo: text = netinfo(options.debug) elif options.dnfinfo: text = dnfinfo(options.debug) elif options.btrfsinfo: text = btrfsinfo(options.debug) elif not args: # read from stdin if no file args supplied try: input_text = sys.stdin.buffer.read() text += input_text.decode("utf-8", "replace") except KeyboardInterrupt: print( "\nUSAGE REMINDER:\n fpaste waits for input when run without file arguments.\n Paste your text, then press on a new line to upload.\n Try `fpaste --help' for more information.\nExiting...", file=sys.stderr) sys.exit(1) else: for i, f in enumerate(args): if not os.access(f, os.R_OK): parser.error("file '%s' is not readable" % f) if (len(args) > 1): # separate multiple files with header text += '#' * 78 + '\n' text += '### file %d of %d: %s\n' % (i + 1, len(args), fileargs[i]) text += '#' * 78 + '\n' text += open(f).read() if options.debug: print('lang: "%s"' % options.lang) print('text (%d): "%s ..."' % (len(text), text[:80])) if options.printonly or options.confirm: try: if is_text(text): # when piped to less, sometimes fails with [Errno 32] Broken # pipe print(text) else: print("DATA") except IOError: pass if options.printonly: # print only what would be sent, and exit sys.exit(0) elif options.confirm: # print what would be sent, and ask for permission if not confirm("OK to send?"): sys.exit(1) url = paste(text, options) if url: # Try to save URL in clipboard, and warn but don't throw error if options.clipout: if not os.access('/usr/bin/xsel', os.X_OK): print( 'OOPS - the clipboard options currently depend on "/usr/bin/xsel", which does not appear to be installed', file=sys.stderr) else: # Copy the url in all the valid clipboard options for selection in validClipboardSelectionOpts: xselcmd = 'xsel -i --%s' % selection p = subprocess.Popen( xselcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) (out, err) = p.communicate(input=url.encode('utf-8')) if p.returncode != 0: if options.debug: print(err, file=sys.stderr) print( "WARNING: URL not saved to %s" % selection, file=sys.stderr) # Copy to Wayland clipboard if not os.access('/usr/bin/wl-copy', os.X_OK): print( 'OOPS - the clipboard options currently depend on "/usr/bin/wl-copy", which does not appear to be installed', file=sys.stderr) else: wcmd = ['wl-copy', url] try: subprocess.run(wcmd, check=True) except CalledProcessError as cpe: if options.debug: print("{} errored with return code {}".format( cpe.cmd, cpe.returncode), file=sys.stderr) print("\n" + cpe.stderr.decode(), file=sys.stderr) print( "WARNING: URL not saved to Wayland clipboard", file=sys.stderr) if options.rawurl: rawurl = url.replace("view/", "view/raw/") print(rawurl) else: print(url) else: sys.exit(1) if options.pasteself: print( "install fpaste to local ~/bin dir by running: mkdir -p ~/bin; curl " + url + "/raw -o ~/bin/fpaste && chmod +x ~/bin/fpaste", file=sys.stderr) sys.exit(0) if __name__ == '__main__': try: main() except KeyboardInterrupt: print("\ninterrupted.") sys.exit(1)