LCOV - code coverage report
Current view: top level - lib/formats - py.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 16 20 80.0 %
Date: 2015-09-30 14:09:30 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  This file is part of rmlint.
       3             :  *
       4             :  *  rmlint is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  rmlint is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with rmlint.  If not, see <http://www.gnu.org/licenses/>.
      16             :  *
      17             :  * Authors:
      18             :  *
      19             :  *  - Christopher <sahib> Pahl 2010-2014 (https://github.com/sahib)
      20             :  *  - Daniel <SeeSpotRun> T.   2014-2014 (https://github.com/SeeSpotRun)
      21             :  *
      22             :  * Hosted on http://github.com/sahib/rmlint
      23             :  *
      24             :  */
      25             : 
      26             : #include "../formats.h"
      27             : #include "../utilities.h"
      28             : #include "../preprocess.h"
      29             : 
      30             : #include <glib.h>
      31             : #include <stdio.h>
      32             : #include <string.h>
      33             : 
      34             : static const char *PY_SOURCE = "#!/usr/bin/env python3\n"
      35             : "# encoding: utf-8\n"
      36             : "\n"
      37             : "\"\"\" This file is part of rmlint.\n"
      38             : "\n"
      39             : "rmlint is free software: you can redistribute it and/or modify\n"
      40             : "it under the terms of the GNU General Public License as published by\n"
      41             : "the Free Software Foundation, either version 3 of the License, or\n"
      42             : "(at your option) any later version.\n"
      43             : "\n"
      44             : "rmlint is distributed in the hope that it will be useful,\n"
      45             : "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
      46             : "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
      47             : "GNU General Public License for more details.\n"
      48             : "\n"
      49             : "You should have received a copy of the GNU General Public License\n"
      50             : "along with rmlint.  If not, see <http://www.gnu.org/licenses/>.\n"
      51             : "\n"
      52             : "Authors:\n"
      53             : "\n"
      54             : "- Christopher <sahib> Pahl 2010-2014 (https://github.com/sahib)\n"
      55             : "- Daniel <SeeSpotRun> T.   2014-2014 (https://github.com/SeeSpotRun)\n"
      56             : "\"\"\"\n"
      57             : "\n"
      58             : "# This is the python remover utility shipped inside the rmlint binary.\n"
      59             : "# The 200 lines source presented below is meant to be clean and hackable.\n"
      60             : "# It is intented to be used for corner cases where the built-in sh formatter\n"
      61             : "# is not enough or as an alternative to it. By default it works the same.\n"
      62             : "\n"
      63             : "# Python2 compat:\n"
      64             : "from __future__ import print_function\n"
      65             : "\n"
      66             : "import os\n"
      67             : "import sys\n"
      68             : "import pwd\n"
      69             : "import json\n"
      70             : "import shutil\n"
      71             : "import filecmp\n"
      72             : "import argparse\n"
      73             : "import subprocess\n"
      74             : "\n"
      75             : "\n"
      76             : "USE_COLOR = sys.stdout.isatty() and sys.stderr.isatty()\n"
      77             : "COLORS = {\n"
      78             : "    'red':    \"\x1b[31;01m\" if USE_COLOR else \"\",\n"
      79             : "    'yellow': \"\x1b[33;01m\" if USE_COLOR else \"\",\n"
      80             : "    'reset':  \"\x1b[0m\" if USE_COLOR else \"\",\n"
      81             : "    'green':  \"\x1b[32;01m\" if USE_COLOR else \"\",\n"
      82             : "    'blue':   \"\x1b[34;01m\" if USE_COLOR else \"\"\n"
      83             : "}\n"
      84             : "\n"
      85             : "\n"
      86             : "def original_check(path, original, be_paranoid=True):\n"
      87             : "    try:\n"
      88             : "        stat_p, stat_o = os.stat(path), os.stat(original)\n"
      89             : "        if (stat_p.st_dev, stat_p.st_ino) == (stat_o.st_dev, stat_o.st_ino):\n"
      90             : "            print('{c[red]}Same inode; ignoring:{c[reset]} {o} <=> {p}'.format(\n"
      91             : "                c=COLORS, o=original, p=path\n"
      92             : "            ))\n"
      93             : "            return False\n"
      94             : "\n"
      95             : "        if stat_p.st_size != stat_o.st_size:\n"
      96             : "            print('{c[red]}Size differs; ignoring:{c[reset]} {o} <=> {p}'.format(\n"
      97             : "                c=COLORS, o=original, p=path\n"
      98             : "            ))\n"
      99             : "            return False\n"
     100             : "\n"
     101             : "        if be_paranoid and not filecmp.cmp(path, original):\n"
     102             : "            print('{c[red]}Content differs; ignoring:{c[reset]} {o} <=> {p}'.format(\n"
     103             : "                c=COLORS, o=original, p=path\n"
     104             : "            ))\n"
     105             : "            return False\n"
     106             : "\n"
     107             : "        return True\n"
     108             : "    except OSError as exc:\n"
     109             : "        print('{c[red]}{exc}{c[reset]}'.format(c=COLORS, exc=exc))\n"
     110             : "        return False\n"
     111             : "\n"
     112             : "\n"
     113             : "def handle_duplicate_dir(path, original, **kwargs):\n"
     114             : "    shutil.rmtree(path)\n"
     115             : "\n"
     116             : "\n"
     117             : "def handle_duplicate_file(path, original, args, **kwargs):\n"
     118             : "    if original_check(path, original['path'], be_paranoid=args.paranoid):\n"
     119             : "        os.remove(path)\n"
     120             : "\n"
     121             : "\n"
     122             : "def handle_unfinished_cksum(path, **kwargs):\n"
     123             : "    pass  # doesn't need any handling.\n"
     124             : "\n"
     125             : "\n"
     126             : "def handle_empty_dir(path, **kwargs):\n"
     127             : "    os.rmdir(path)\n"
     128             : "\n"
     129             : "\n"
     130             : "def handle_empy_file(path, **kwargs):\n"
     131             : "    os.remove(path)\n"
     132             : "\n"
     133             : "\n"
     134             : "def handle_nonstripped(path, **kwargs):\n"
     135             : "    subprocess.call([\"strip\", \"--strip-debug\", path])\n"
     136             : "\n"
     137             : "\n"
     138             : "def handle_badlink(path, **kwargs):\n"
     139             : "    os.remove(path)\n"
     140             : "\n"
     141             : "\n"
     142             : "CURRENT_UID = os.geteuid()\n"
     143             : "CURRENT_GID = pwd.getpwuid(CURRENT_UID).pw_gid\n"
     144             : "\n"
     145             : "\n"
     146             : "def handle_baduid(path, **kwargs):\n"
     147             : "    os.chmod(path, CURRENT_UID, -1)\n"
     148             : "\n"
     149             : "\n"
     150             : "def handle_badgid(path, **kwargs):\n"
     151             : "    os.chmod(path, -1, CURRENT_GID)\n"
     152             : "\n"
     153             : "\n"
     154             : "def handle_badugid(path, **kwargs):\n"
     155             : "    os.chmod(path, CURRENT_UID, CURRENT_GID)\n"
     156             : "\n"
     157             : "\n"
     158             : "OPERATIONS = {\n"
     159             : "    \"duplicate_dir\": handle_duplicate_dir,\n"
     160             : "    \"duplicate_file\": handle_duplicate_file,\n"
     161             : "    \"unfinished_cksum\": handle_unfinished_cksum,\n"
     162             : "    \"emptydir\": handle_empty_dir,\n"
     163             : "    \"emptyfile\": handle_empy_file,\n"
     164             : "    \"nonstripped\": handle_nonstripped,\n"
     165             : "    \"badlink\": handle_badlink,\n"
     166             : "    \"baduid\": handle_baduid,\n"
     167             : "    \"badgid\": handle_badgid,\n"
     168             : "    \"badugid\": handle_badugid,\n"
     169             : "}\n"
     170             : "\n"
     171             : "MESSAGES = {\n"
     172             : "    \"duplicate_dir\": \"removing tree\",\n"
     173             : "    \"duplicate_file\": \"removing\",\n"
     174             : "    \"unfinished_cksum\": \"checking\",\n"
     175             : "    \"emptydir\": \"removing\",\n"
     176             : "    \"emptyfile\": \"removing\",\n"
     177             : "    \"nonstripped\": \"stripping\",\n"
     178             : "    \"badlink\": \"removing\",\n"
     179             : "    \"baduid\": \"changing uid\",\n"
     180             : "    \"badgid\": \"changing gid\",\n"
     181             : "    \"badugid\": \"changing uid & gid\",\n"
     182             : "}\n"
     183             : "\n"
     184             : "\n"
     185             : "def exec_operation(item, original=None, args=None):\n"
     186             : "    try:\n"
     187             : "        OPERATIONS[item['type']](item['path'], original=original, item=item, args=args)\n"
     188             : "    except OSError as err:\n"
     189             : "        print(\n"
     190             : "            '{c[red]}#{c[reset]} Error on `{item[path]}`:\\n{c[red]}#{c[reset]}    {err}'.format(\n"
     191             : "                item=item, err=err, c=COLORS\n"
     192             : "            ),\n"
     193             : "            file=sys.stderr\n"
     194             : "        )\n"
     195             : "\n"
     196             : "\n"
     197             : "def main(args, header, data, footer):\n"
     198             : "    seen_cksums = set()\n"
     199             : "    last_original_item = None\n"
     200             : "\n"
     201             : "    for item in data:\n"
     202             : "        if item['type'].startswith('duplicate_') and item['is_original']:\n"
     203             : "            print(\n"
     204             : "                \"\\n{c[green]}#{c[reset]} Deleting twins of {item[path]} \".format(\n"
     205             : "                    item=item, c=COLORS\n"
     206             : "                )\n"
     207             : "            )\n"
     208             : "            last_original_item = item\n"
     209             : "\n"
     210             : "            # Do not handle originals.\n"
     211             : "            continue\n"
     212             : "\n"
     213             : "        if not args.dry_run:\n"
     214             : "            exec_operation(item, original=last_original_item, args=args)\n"
     215             : "\n"
     216             : "        print('{c[blue]}#{c[reset]} Handling ({t} -> {v}): {p}'.format(\n"
     217             : "            c=COLORS, t=item['type'], v=MESSAGES[item['type']], p=item['path'])\n"
     218             : "        )\n"
     219             : "\n"
     220             : "\n"
     221             : "if __name__ == '__main__':\n"
     222             : "    parser = argparse.ArgumentParser(\n"
     223             : "        description='Handle the files stored in rmlints json output'\n"
     224             : "    )\n"
     225             : "\n"
     226             : "    parser.add_argument(\n"
     227             : "        'json_docs', metavar='json_doc', type=open, nargs='*',\n"
     228             : "        help='A json output of rmlint to handle (can be given many times)'\n"
     229             : "    )\n"
     230             : "    parser.add_argument(\n"
     231             : "        '-n', '--dry-run', action='store_true',\n"
     232             : "        help='Only print what would be done.'\n"
     233             : "    )\n"
     234             : "    parser.add_argument(\n"
     235             : "        '-d', '--no-ask', action='store_true', default=False,\n"
     236             : "        help='ask for confirmation before running (does nothing for -n)'\n"
     237             : "    )\n"
     238             : "    parser.add_argument(\n"
     239             : "        '-p', '--paranoid', action='store_true', default=False,\n"
     240             : "        help='Do an extra byte-by-byte compare before deleting duplicates'\n"
     241             : "    )\n"
     242             : "\n"
     243             : "    try:\n"
     244             : "        args = parser.parse_args()\n"
     245             : "    except OSError as err:\n"
     246             : "        print(err)\n"
     247             : "        sys.exit(-1)\n"
     248             : "\n"
     249             : "    if not args.json_docs:\n"
     250             : "        # None given on the commandline\n"
     251             : "        try:\n"
     252             : "            args.json_docs.append(open('.rmlint.json', 'r'))\n"
     253             : "        except OSError as err:\n"
     254             : "            print('Cannot load default json document: ', str(err), file=sys.stderr)\n"
     255             : "            sys.exit(-2)\n"
     256             : "\n"
     257             : "    json_docus = [json.load(doc) for doc in args.json_docs]\n"
     258             : "    json_elems = [item for sublist in json_docus for item in sublist]\n"
     259             : "\n"
     260             : "    try:\n"
     261             : "        if not args.no_ask and not args.dry_run:\n"
     262             : "            print('\\nPlease hit any key before continuing to shredder your data.', file=sys.stderr)\n"
     263             : "            sys.stdin.read(1)\n"
     264             : "\n"
     265             : "        for json_doc in json_docus:\n"
     266             : "            main(args, json_doc[0], json_doc[1:-1], json_doc[-1])\n"
     267             : "\n"
     268             : "        if args.dry_run:\n"
     269             : "            print(\n"
     270             : "                '\\n{c[green]}#{c[reset]} This was a dry run. Nothing modified.'.format(\n"
     271             : "                    c=COLORS\n"
     272             : "                )\n"
     273             : "            )\n"
     274             : "    except KeyboardInterrupt:\n"
     275             : "        print('canceled.')";
     276             : 
     277             : typedef struct RmFmtHandlerPy {
     278             :     /* must be first */
     279             :     RmFmtHandler parent;
     280             : 
     281             :     /* HACK: Actual handler might be bigger, add padding. */
     282             :     char _dummy[1024];
     283             : 
     284             :     /* Filehandle to pass to the JSON Formatter. */
     285             :     FILE *json_out;
     286             : 
     287             : } RmFmtHandlerPy;
     288             : 
     289             : /////////////////////////
     290             : //  ACTUAL CALLBACKS   //
     291             : /////////////////////////
     292             : 
     293         206 : static void rm_fmt_head(RmSession *session, RmFmtHandler *parent, FILE *out) {
     294         206 :     RmFmtHandlerPy *self = (RmFmtHandlerPy *)parent;
     295             : 
     296         206 :     fprintf(out, "%s", PY_SOURCE);
     297         206 :     if(fchmod(fileno(out), S_IRUSR | S_IWUSR | S_IXUSR) == -1) {
     298           0 :         rm_log_perror("Could not chmod +x python-script");
     299             :     }
     300             : 
     301         206 :     self->json_out = fopen(".rmlint.json", "w");
     302         206 :     if(self->json_out == NULL)  {
     303           0 :         return;
     304             :     }
     305             : 
     306             :     /* Delegate */
     307             :     extern RmFmtHandler *JSON_HANDLER;
     308         206 :     JSON_HANDLER->head(session, (RmFmtHandler *)self, self->json_out);
     309             : }
     310             : 
     311         206 : static void rm_fmt_foot(RmSession *session, RmFmtHandler *parent, _U FILE *out) {
     312         206 :     RmFmtHandlerPy *self = (RmFmtHandlerPy *)parent;
     313         206 :     if(self->json_out == NULL) {
     314           0 :         return;
     315             :     }
     316             : 
     317             :     /* Delegate */
     318             :     extern RmFmtHandler *JSON_HANDLER;
     319         206 :     JSON_HANDLER->foot(session, (RmFmtHandler *)self, self->json_out);
     320             : 
     321         206 :     fclose(self->json_out);
     322             : }
     323             : 
     324         580 : static void rm_fmt_elem(
     325             :     RmSession *session,
     326             :     RmFmtHandler *parent,
     327             :     _U FILE *out, RmFile *file
     328             : ) {
     329         580 :     RmFmtHandlerPy *self = (RmFmtHandlerPy *)parent;
     330         580 :     if(self->json_out == NULL) {
     331           0 :         return;
     332             :     }
     333             : 
     334             :     /* Delegate */
     335             :     extern RmFmtHandler *JSON_HANDLER;
     336         580 :     JSON_HANDLER->elem(session, (RmFmtHandler *)self, self->json_out, file);
     337             : }
     338             : 
     339             : static RmFmtHandlerPy PY_HANDLER_IMPL = {
     340             :     /* Initialize parent */
     341             :     .parent = {
     342             :         .size = sizeof(PY_HANDLER_IMPL),
     343             :         .name = "py",
     344             :         .head = rm_fmt_head,
     345             :         .elem = rm_fmt_elem,
     346             :         .prog = NULL,
     347             :         .foot = rm_fmt_foot,
     348             :         .valid_keys = {NULL},
     349             :     }
     350             : };
     351             : 
     352             : RmFmtHandler *PY_HANDLER = (RmFmtHandler *) &PY_HANDLER_IMPL;

Generated by: LCOV version 1.11