LCOV - code coverage report
Current view: top level - lib/formats - json.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 168 173 97.1 %
Date: 2015-09-30 14:09:30 Functions: 13 13 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-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;

Generated by: LCOV version 1.11