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;
|