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-2015 (https://github.com/sahib)
20 : * - Daniel <SeeSpotRun> T. 2014-2015 (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 : #include "../checksums/spooky-c.h"
30 :
31 : #include <glib.h>
32 : #include <stdio.h>
33 : #include <string.h>
34 :
35 : typedef struct RmFmtHandlerJSON {
36 : /* must be first */
37 : RmFmtHandler parent;
38 :
39 : /* More human readable output? */
40 : bool pretty;
41 :
42 : /* set of already existing ids */
43 : GHashTable *id_set;
44 : } RmFmtHandlerJSON;
45 :
46 : //////////////////////////////////////////
47 : // FILE ID GENERATOR //
48 : //////////////////////////////////////////
49 :
50 443715 : static guint32 rm_fmt_json_generate_id(RmFmtHandlerJSON *self, RmFile *file,
51 : const char *file_path, char *cksum) {
52 443715 : guint32 hash = 0;
53 443715 : hash = file->inode ^ file->dev;
54 443715 : hash ^= file->file_size;
55 :
56 444267 : for(int i = 0; i < 8192; ++i) {
57 444267 : hash ^= spooky_hash32(file_path, strlen(file_path), i);
58 444267 : hash ^= spooky_hash32(cksum, strlen(cksum), i);
59 :
60 444267 : if(!g_hash_table_contains(self->id_set, GUINT_TO_POINTER(hash))) {
61 443715 : break;
62 : }
63 : }
64 :
65 443715 : g_hash_table_add(self->id_set, GUINT_TO_POINTER(hash));
66 443715 : return hash;
67 : }
68 :
69 : //////////////////////////////////////////
70 : // POOR MAN'S JSON FORMATTING TOOLBOX //
71 : //////////////////////////////////////////
72 :
73 1301718 : static void rm_fmt_json_key(FILE *out, const char *key, const char *value) {
74 1301718 : fprintf(out, "\"%s\": \"%s\"", key, value);
75 1301718 : }
76 :
77 512213 : static void rm_fmt_json_key_bool(FILE *out, const char *key, bool value) {
78 512213 : fprintf(out, "\"%s\": %s", key, value ? "true" : "false");
79 512213 : }
80 :
81 4103538 : static void rm_fmt_json_key_int(FILE *out, const char *key, RmOff value) {
82 4103538 : fprintf(out, "\"%s\": %" LLU "", key, value);
83 4103538 : }
84 :
85 443219 : static bool rm_fmt_json_fix(const char *string, char *fixed, size_t fixed_len) {
86 : /* More information here:
87 : *
88 : * http://stackoverflow.com/questions/4901133/json-and-escaping-characters/4908960#4908960
89 : */
90 :
91 443219 : int n = strlen(string);
92 443219 : char *safe_iter = fixed;
93 :
94 14756723 : for(int i = 0; i < n && (size_t)(safe_iter - fixed) < fixed_len; ++i) {
95 14313504 : unsigned char *curr = (unsigned char *)&string[i];
96 :
97 : char text[20];
98 14313504 : memset(text, 0, sizeof(text));
99 :
100 14313504 : if(*curr == '"' || *curr == '\\') {
101 : /* Printable, but needs to be escaped */
102 168 : text[0] = '\\';
103 168 : text[1] = *curr;
104 14313336 : } else if((*curr > 0 && *curr < 0x1f) || *curr >= 0x7f) {
105 : /* Something unprintable */
106 336 : switch(*curr) {
107 : case '\b':
108 56 : g_snprintf(text, sizeof(text), "\\b");
109 56 : break;
110 : case '\f':
111 56 : g_snprintf(text, sizeof(text), "\\f");
112 56 : break;
113 : case '\n':
114 56 : g_snprintf(text, sizeof(text), "\\n");
115 56 : break;
116 : case '\r':
117 56 : g_snprintf(text, sizeof(text), "\\r");
118 56 : break;
119 : case '\t':
120 112 : g_snprintf(text, sizeof(text), "\\t");
121 112 : break;
122 : default:
123 0 : g_snprintf(text, sizeof(text), "\\u00%02x", (guint)*curr);
124 0 : break;
125 : }
126 336 : } else {
127 : /* Take it unmodified */
128 14313000 : text[0] = *curr;
129 : }
130 :
131 14313504 : safe_iter = g_stpcpy(safe_iter, text);
132 : }
133 :
134 443219 : return (size_t)(safe_iter - fixed) < fixed_len;
135 : }
136 :
137 443219 : static void rm_fmt_json_key_unsafe(FILE *out, const char *key, const char *value) {
138 : char safe_value[PATH_MAX + 4 + 1];
139 443219 : memset(safe_value, 0, sizeof(safe_value));
140 :
141 443219 : if(rm_fmt_json_fix(value, safe_value, sizeof(safe_value))) {
142 443219 : fprintf(out, "\"%s\": \"%s\"", key, safe_value);
143 : } else {
144 : /* This should never happen but give at least means of debugging */
145 0 : fprintf(out, "\"%s\": \"<BROKEN PATH>\"", key);
146 : }
147 443219 : }
148 :
149 581879 : static void rm_fmt_json_open(RmFmtHandlerJSON *self, FILE *out) {
150 581879 : fprintf(out, "{%s", self->pretty ? "\n " : "");
151 581879 : }
152 :
153 512549 : static void rm_fmt_json_close(RmFmtHandlerJSON *self, FILE *out) {
154 512549 : if(self->pretty) {
155 32 : fprintf(out, "\n}, ");
156 : } else {
157 512517 : fprintf(out, "},\n");
158 : }
159 512549 : }
160 :
161 5778809 : static void rm_fmt_json_sep(RmFmtHandlerJSON *self, FILE *out) {
162 5778809 : fprintf(out, ", %s", self->pretty ? "\n " : "");
163 5778809 : }
164 :
165 : /////////////////////////
166 : // ACTUAL CALLBACKS //
167 : /////////////////////////
168 :
169 69330 : static void rm_fmt_head(RmSession *session, _U RmFmtHandler *parent, FILE *out) {
170 69330 : fprintf(out, "[\n");
171 :
172 69330 : RmFmtHandlerJSON *self = (RmFmtHandlerJSON *)parent;
173 69330 : self->id_set = g_hash_table_new(NULL, NULL);
174 :
175 69330 : if(rm_fmt_get_config_value(session->formats, "json", "oneline")) {
176 69317 : self->pretty = false;
177 : }
178 :
179 69330 : if(!rm_fmt_get_config_value(session->formats, "json", "no_header")) {
180 69330 : rm_fmt_json_open(self, out);
181 : {
182 69330 : rm_fmt_json_key(out, "description", "rmlint json-dump of lint files");
183 69330 : rm_fmt_json_sep(self, out);
184 69330 : rm_fmt_json_key(out, "cwd", session->cfg->iwd);
185 69330 : rm_fmt_json_sep(self, out);
186 69330 : rm_fmt_json_key(out, "args", session->cfg->joined_argv);
187 69330 : rm_fmt_json_sep(self, out);
188 69330 : rm_fmt_json_key(out, "version", RM_VERSION);
189 69330 : rm_fmt_json_sep(self, out);
190 69330 : rm_fmt_json_key(out, "rev", RM_VERSION_GIT_REVISION);
191 69330 : rm_fmt_json_sep(self, out);
192 69330 : rm_fmt_json_key_int(out, "progress", 0); /* Header is always first. */
193 69330 : rm_fmt_json_sep(self, out);
194 69330 : rm_fmt_json_key(out, "checksum_type",
195 69330 : rm_digest_type_to_string(session->cfg->checksum_type));
196 69330 : if(session->hash_seed1 && session->hash_seed2) {
197 2459 : rm_fmt_json_sep(self, out);
198 2459 : rm_fmt_json_key_int(out, "hash_seed1", session->hash_seed1);
199 2459 : rm_fmt_json_sep(self, out);
200 2459 : rm_fmt_json_key_int(out, "hash_seed2", session->hash_seed2);
201 : }
202 : }
203 69330 : rm_fmt_json_close(self, out);
204 : }
205 69330 : }
206 :
207 69330 : static void rm_fmt_foot(_U RmSession *session, RmFmtHandler *parent, FILE *out) {
208 69330 : RmFmtHandlerJSON *self = (RmFmtHandlerJSON *)parent;
209 :
210 69330 : if(rm_fmt_get_config_value(session->formats, "json", "no_footer")) {
211 0 : fprintf(out, "{}");
212 : } else {
213 69330 : rm_fmt_json_open(self, out);
214 : {
215 69330 : rm_fmt_json_key_bool(out, "aborted", rm_session_was_aborted(session));
216 69330 : rm_fmt_json_sep(self, out);
217 69330 : rm_fmt_json_key_int(out, "progress", 100); /* Footer is always last. */
218 69330 : rm_fmt_json_sep(self, out);
219 69330 : rm_fmt_json_key_int(out, "total_files", session->total_files);
220 69330 : rm_fmt_json_sep(self, out);
221 69330 : rm_fmt_json_key_int(out, "ignored_files", session->ignored_files);
222 69330 : rm_fmt_json_sep(self, out);
223 69330 : rm_fmt_json_key_int(out, "ignored_folders", session->ignored_folders);
224 69330 : rm_fmt_json_sep(self, out);
225 69330 : rm_fmt_json_key_int(out, "duplicates", session->dup_counter);
226 69330 : rm_fmt_json_sep(self, out);
227 69330 : rm_fmt_json_key_int(out, "duplicate_sets", session->dup_group_counter);
228 69330 : rm_fmt_json_sep(self, out);
229 69330 : rm_fmt_json_key_int(out, "total_lint_size", session->total_lint_size);
230 : }
231 69330 : if(self->pretty) {
232 13 : fprintf(out, "\n}");
233 : } else {
234 69317 : fprintf(out, "}\n");
235 : }
236 : }
237 :
238 69330 : fprintf(out, "]\n");
239 69330 : g_hash_table_unref(self->id_set);
240 69330 : }
241 :
242 443715 : static void rm_fmt_json_cksum(RmFile *file, char *checksum_str, size_t size) {
243 443715 : memset(checksum_str, '0', size);
244 443715 : checksum_str[size - 1] = 0;
245 443715 : rm_digest_hexstring(file->digest, checksum_str);
246 443715 : }
247 :
248 443219 : static void rm_fmt_elem(RmSession *session, _U RmFmtHandler *parent, FILE *out,
249 443219 : RmFile *file) {
250 443219 : if(rm_fmt_get_config_value(session->formats, "json", "no_body")) {
251 0 : return;
252 : }
253 443219 : char checksum_str[rm_digest_get_bytes(file->digest) * 2 + 1];
254 443219 : rm_fmt_json_cksum(file, checksum_str, sizeof(checksum_str));
255 :
256 443219 : RmFmtHandlerJSON *self = (RmFmtHandlerJSON *)parent;
257 :
258 : /* Make it look like a json element */
259 443219 : rm_fmt_json_open(self, out);
260 : {
261 443219 : RM_DEFINE_PATH(file);
262 :
263 443219 : rm_fmt_json_key_int(out, "id",
264 443219 : rm_fmt_json_generate_id(self, file, file_path, checksum_str));
265 :
266 443219 : rm_fmt_json_sep(self, out);
267 443219 : rm_fmt_json_key(out, "type", rm_file_lint_type_to_string(file->lint_type));
268 443219 : rm_fmt_json_sep(self, out);
269 1329657 : rm_fmt_json_key_int(
270 : out, "progress",
271 1329657 : CLAMP(100 -
272 : 100 * ((gdouble)session->shred_bytes_remaining /
273 : (gdouble)session->shred_bytes_after_preprocess),
274 : 0, 100));
275 443219 : rm_fmt_json_sep(self, out);
276 :
277 443219 : if(file->digest) {
278 442519 : rm_fmt_json_key(out, "checksum", checksum_str);
279 442519 : rm_fmt_json_sep(self, out);
280 : }
281 :
282 443219 : rm_fmt_json_key_unsafe(out, "path", file_path);
283 443219 : rm_fmt_json_sep(self, out);
284 443219 : if(file->lint_type != RM_LINT_TYPE_UNFINISHED_CKSUM) {
285 442883 : rm_fmt_json_key_int(out, "size", file->file_size);
286 442883 : rm_fmt_json_sep(self, out);
287 442883 : if(file->twin_count >= 0) {
288 442295 : rm_fmt_json_key_int(out, "twins", file->twin_count);
289 442295 : rm_fmt_json_sep(self, out);
290 : }
291 442883 : rm_fmt_json_key_int(out, "depth", file->depth);
292 442883 : rm_fmt_json_sep(self, out);
293 442883 : rm_fmt_json_key_int(out, "inode", file->inode);
294 442883 : rm_fmt_json_sep(self, out);
295 442883 : rm_fmt_json_key_int(out, "disk_id", file->dev);
296 442883 : rm_fmt_json_sep(self, out);
297 442883 : rm_fmt_json_key_bool(out, "is_original", file->is_original);
298 442883 : rm_fmt_json_sep(self, out);
299 :
300 442883 : if(session->cfg->find_hardlinked_dupes) {
301 442827 : RmFile *hardlink_head = file->hardlinks.hardlink_head;
302 :
303 442827 : if(hardlink_head && hardlink_head != file) {
304 496 : char orig_checksum_str[rm_digest_get_bytes(file->digest) * 2 + 1];
305 496 : rm_fmt_json_cksum(hardlink_head, orig_checksum_str,
306 496 : sizeof(orig_checksum_str));
307 :
308 496 : RM_DEFINE_PATH(hardlink_head);
309 :
310 496 : guint32 orig_id = rm_fmt_json_generate_id(
311 496 : self, hardlink_head, hardlink_head_path, orig_checksum_str);
312 :
313 496 : rm_fmt_json_key_int(out, "hardlink_of", orig_id);
314 496 : rm_fmt_json_sep(self, out);
315 : }
316 : }
317 : }
318 443219 : rm_fmt_json_key_int(out, "mtime", file->mtime);
319 : }
320 443219 : rm_fmt_json_close(self, out);
321 : }
322 :
323 : static RmFmtHandlerJSON JSON_HANDLER_IMPL = {
324 : /* Initialize parent */
325 : .parent =
326 : {
327 : .size = sizeof(JSON_HANDLER_IMPL),
328 : .name = "json",
329 : .head = rm_fmt_head,
330 : .elem = rm_fmt_elem,
331 : .prog = NULL,
332 : .foot = rm_fmt_foot,
333 : .valid_keys = {"no_header", "no_footer", "no_body", "oneline", NULL},
334 : },
335 : .pretty = true};
336 :
337 : RmFmtHandler *JSON_HANDLER = (RmFmtHandler *)&JSON_HANDLER_IMPL;
|