LCOV - code coverage report
Current view: top level - lib/formats - sh.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 98 166 59.0 %
Date: 2015-09-30 14:09:30 Functions: 8 12 66.7 %

          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 "../preprocess.h"
      28             : 
      29             : #include <glib.h>
      30             : #include <stdio.h>
      31             : #include <string.h>
      32             : 
      33             : 
      34             : typedef struct RmFmtHandlerShScript {
      35             :     RmFmtHandler parent;
      36             :     RmFile *last_original;
      37             :     RmSession *session;
      38             : 
      39             :     bool allow_user_cmd : 1;
      40             :     bool allow_clone : 1;
      41             :     bool allow_reflink : 1;
      42             :     bool allow_symlink : 1;
      43             :     bool allow_hardlink : 1;
      44             :     bool allow_remove : 1;
      45             : 
      46             :     const char *user_cmd;
      47             : 
      48             :     GByteArray *order;
      49             : } RmFmtHandlerShScript;
      50             : 
      51             : static const char *SH_SCRIPT_TEMPLATE_HEAD = "#!/bin/sh\n"
      52             : "# This file was autowritten by rmlint\n"
      53             : "# rmlint was executed from: %s\n"
      54             : "# Your command line was: %s\n"
      55             : "\n"
      56             : "USER='%s'\n"
      57             : "GROUP='%s'\n"
      58             : "\n"
      59             : "# Set to true on -n\n"
      60             : "DO_DRY_RUN=\n"
      61             : "\n"
      62             : "# Set to true on -p\n"
      63             : "DO_PARANOID_CHECK=\n"
      64             : "\n"
      65             : "##################################\n"
      66             : "# GENERAL LINT HANDLER FUNCTIONS #\n"
      67             : "##################################\n"
      68             : "\n"
      69             : "\n"
      70             : "handle_emptyfile() {\n"
      71             : "    echo 'Deleting empty file:' \"$1\"\n"
      72             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
      73             : "        rm -f \"$1\"\n"
      74             : "    fi\n"
      75             : "}\n"
      76             : "\n"
      77             : "handle_emptydir() {\n"
      78             : "    echo 'Deleting empty directory:' \"$1\"\n"
      79             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
      80             : "        rmdir \"$1\"\n"
      81             : "    fi\n"
      82             : "}\n"
      83             : "\n"
      84             : "handle_bad_symlink() {\n"
      85             : "    echo 'Deleting symlink pointing nowhere:' \"$1\"\n"
      86             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
      87             : "        rm -f \"$1\"\n"
      88             : "    fi\n"
      89             : "}\n"
      90             : "\n"
      91             : "handle_unstripped_binary() {\n"
      92             : "    echo 'Stripping debug symbols of:' \"$1\"\n"
      93             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
      94             : "        strip -s \"$1\"\n"
      95             : "    fi\n"
      96             : "}\n"
      97             : "\n"
      98             : "handle_bad_user_id() {\n"
      99             : "    echo 'chown' \"$USER\" \"$1\"\n"
     100             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
     101             : "        chown \"$USER\" \"$1\"\n"
     102             : "    fi\n"
     103             : "}\n"
     104             : "\n"
     105             : "handle_bad_group_id() {\n"
     106             : "    echo 'chgrp' \"$GROUP\" \"$1\"\n"
     107             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
     108             : "        chgrp \"$GROUP\" \"$1\"\n"
     109             : "    fi\n"
     110             : "}\n"
     111             : "\n"
     112             : "handle_bad_user_and_group_id() {\n"
     113             : "    echo 'chown' \"$USER:$GROUP\" \"$1\"\n"
     114             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
     115             : "        chown \"$USER:$GROUP\" \"$1\"\n"
     116             : "    fi\n"
     117             : "}\n"
     118             : "\n"
     119             : "###############################\n"
     120             : "# DUPLICATE HANDLER FUNCTIONS #\n"
     121             : "###############################\n"
     122             : "\n"
     123             : "original_check() {\n"
     124             : "    if [ ! -e \"$2\" ]; then\n"
     125             : "        echo \"^^^^^^ Error: original has disappeared - cancelling.....\"\n"
     126             : "        return 1\n"
     127             : "    fi\n"
     128             : "\n"
     129             : "    if [ ! -e \"$1\" ]; then\n"
     130             : "        echo \"^^^^^^ Error: duplicate has disappeared - cancelling.....\"\n"
     131             : "        return 1\n"
     132             : "    fi\n"
     133             : "\n"
     134             : "    # Check they are not the exact same file (hardlinks allowed):\n"
     135             : "    if [ \"$1\" = \"$2\" ]; then\n"
     136             : "        echo \"^^^^^^ Error: original and duplicate point to the *same* path - cancelling.....\"\n"
     137             : "        return 1\n"
     138             : "    fi\n"
     139             : "\n"
     140             : "    # Do double-check if requested:\n"
     141             : "    if [ -z \"$DO_PARANOID_CHECK\" ]; then\n"
     142             : "        return 0\n"
     143             : "    else\n"
     144             : "        if cmp -s \"$1\" \"$2\"; then\n"
     145             : "            return 0\n"
     146             : "        else\n"
     147             : "            echo \"^^^^^^ Error: files no longer identical - cancelling.....\"\n"
     148             : "            return 1\n"
     149             : "        fi\n"
     150             : "    fi\n"
     151             : "}\n"
     152             : "\n"
     153             : "cp_hardlink() {\n"
     154             : "    echo 'Hardlinking to original:' \"$1\"\n"
     155             : "    if original_check \"$1\" \"$2\"; then\n"
     156             : "        if [ -z \"$DO_DRY_RUN\" ]; then\n"
     157             : "            cp --remove-destination --archive --link \"$2\" \"$1\"\n"
     158             : "        fi\n"
     159             : "    fi\n"
     160             : "}\n"
     161             : "\n"
     162             : "cp_symlink() {\n"
     163             : "    echo 'Symlinking to original:' \"$1\"\n"
     164             : "    if original_check \"$1\" \"$2\"; then\n"
     165             : "        if [ -z \"$DO_DRY_RUN\" ]; then\n"
     166             : "            touch -mr \"$1\" \"$0\"\n"
     167             : "            cp --remove-destination --archive --symbolic-link \"$2\" \"$1\"\n"
     168             : "            touch -mr \"$0\" \"$1\"\n"
     169             : "        fi\n"
     170             : "    fi\n"
     171             : "}\n"
     172             : "\n"
     173             : "cp_reflink() {\n"
     174             : "    # reflink $1 to $2's data, preserving $1's  mtime\n"
     175             : "    echo 'Reflinking to original:' \"$1\"\n"
     176             : "    if original_check \"$1\" \"$2\"; then\n"
     177             : "        if [ -z \"$DO_DRY_RUN\" ]; then\n"
     178             : "            touch -mr \"$1\" \"$0\"\n"
     179             : "            cp --reflink=always \"$2\" \"$1\"\n"
     180             : "            touch -mr \"$0\" \"$1\"\n"
     181             : "        fi\n"
     182             : "    fi\n"
     183             : "}\n"
     184             : "\n"
     185             : "clone() {\n"
     186             : "    # clone $1 from $2's data\n"
     187             : "    echo 'Cloning to: ' \"$1\"\n"
     188             : "    if [ -z \"$DO_DRY_RUN\" ]; then\n"
     189             : "        rmlint --btrfs-clone \"$2\" \"$1\"\n"
     190             : "    fi\n"
     191             : "}\n"
     192             : "\n"
     193             : "skip_hardlink() {\n"
     194             : "    echo 'Leaving as-is (already hardlinked to original):' \"$1\"\n"
     195             : "}\n"
     196             : "\n"
     197             : "skip_reflink() {\n"
     198             : "    echo 'Leaving as-is (already reflinked to original):' \"$1\"\n"
     199             : "}\n"
     200             : "\n"
     201             : "user_command() {\n"
     202             : "    # You can define this function to do what you want:\n"
     203             : "    %s\n"
     204             : "}\n"
     205             : "\n"
     206             : "remove_cmd() {\n"
     207             : "    echo 'Deleting:' \"$1\"\n"
     208             : "    if original_check \"$1\" \"$2\"; then\n"
     209             : "        if [ -z \"$DO_DRY_RUN\" ]; then\n"
     210             : "            rm -rf \"$1\"\n"
     211             : "        fi\n"
     212             : "    fi\n"
     213             : "}\n"
     214             : "\n"
     215             : "##################\n"
     216             : "# OPTION PARSING #\n"
     217             : "##################\n"
     218             : "\n"
     219             : "ask() {\n"
     220             : "    cat << EOF\n"
     221             : "\n"
     222             : "This script will delete certain files rmlint found.\n"
     223             : "It is highly advisable to view the script first!\n"
     224             : "\n"
     225             : "Rmlint was executed in the following way:\n"
     226             : "\n"
     227             : "   $ %s\n"
     228             : "\n"
     229             : "Execute this script with -d to disable this informational message.\n"
     230             : "Type any string to continue; CTRL-C, Enter or CTRL-D to abort immediately\n"
     231             : "EOF\n"
     232             : "    read eof_check\n"
     233             : "    if [ -z \"$eof_check\" ]\n"
     234             : "    then\n"
     235             : "        # Count Ctrl-D and Enter as aborted too.\n"
     236             : "        echo \"Aborted on behalf of the user.\"\n"
     237             : "        exit 1;\n"
     238             : "    fi\n"
     239             : "}\n"
     240             : "\n"
     241             : "usage() {\n"
     242             : "    cat << EOF\n"
     243             : "usage: $0 OPTIONS\n"
     244             : "\n"
     245             : "OPTIONS:\n"
     246             : "\n"
     247             : "  -h   Show this message.\n"
     248             : "  -d   Do not ask before running.\n"
     249             : "  -x   Keep rmlint.sh; do not autodelete it.\n"
     250             : "  -p   Recheck that files are still identical before removing duplicates.\n"
     251             : "  -n   Do not perform any modifications, just print what would be done.\n"
     252             : "EOF\n"
     253             : "}\n"
     254             : "\n"
     255             : "DO_REMOVE=\n"
     256             : "DO_ASK=\n"
     257             : "\n"
     258             : "while getopts \"dhxnp\" OPTION\n"
     259             : "do\n"
     260             : "  case $OPTION in\n"
     261             : "     h)\n"
     262             : "       usage\n"
     263             : "       exit 1\n"
     264             : "       ;;\n"
     265             : "     d)\n"
     266             : "       DO_ASK=false\n"
     267             : "       ;;\n"
     268             : "     x)\n"
     269             : "       DO_REMOVE=false\n"
     270             : "       ;;\n"
     271             : "     n)\n"
     272             : "       DO_DRY_RUN=true\n"
     273             : "       ;;\n"
     274             : "     p)\n"
     275             : "       DO_PARANOID_CHECK=true\n"
     276             : "  esac\n"
     277             : "done\n"
     278             : "\n"
     279             : "if [ -z $DO_ASK ]\n"
     280             : "then\n"
     281             : "  usage\n"
     282             : "  ask\n"
     283             : "fi\n"
     284             : "\n"
     285             : "######### START OF AUTOGENERATED OUTPUT #########\n"
     286             : "\n"
     287             : "";
     288             : static const char *SH_SCRIPT_TEMPLATE_FOOT =
     289             :     "                                               \n"
     290             :     "######### END OF AUTOGENERATED OUTPUT #########\n"
     291             :     "                                               \n"
     292             :     "if [ -z $DO_REMOVE ] && [ -z $DO_DRY_RUN ]     \n"
     293             :     "then                                           \n"
     294             :     "  echo \"Deleting script \" \"$0\"             \n" 
     295             :     "  %s '%s';                                     \n"
     296             :     "fi                                             \n"
     297             :     ;
     298             : 
     299             : typedef bool (* RmShOrderEmitFunc)(RmFmtHandlerShScript *self, char **out, RmFile *file, char *path, char *orig_path);
     300             : 
     301             : 
     302         176 : static bool rm_sh_emit_handler_user(RmFmtHandlerShScript *self, char **out, _U RmFile *file, char *path, char *orig_path) {
     303         176 :     if(self->user_cmd == NULL || !self->allow_user_cmd) {
     304         176 :         return false;
     305             :     }
     306             : 
     307           0 :     *out = g_strdup_printf("user_command '%s' '%s'", path, orig_path);
     308           0 :     return true;
     309             : }
     310             : 
     311           0 : static bool rm_sh_emit_handler_clone(RmFmtHandlerShScript *self, char **out, RmFile *file, char *path, char *orig_path) {
     312           0 :     if(!self->allow_clone) {
     313           0 :         return false;
     314             :     }
     315             : 
     316             :     /* Needs to have at least kernel 4.2 */
     317           0 :     if(!rm_session_check_kernel_version(self->session, 4, 2)) {
     318           0 :         return false;
     319             :     }
     320             : 
     321           0 :     bool offsets_match = rm_offsets_match(path, orig_path);
     322           0 :     char *reflink_type = g_hash_table_lookup(
     323           0 :             self->session->mounts->reflinkfs_table,
     324           0 :             GUINT_TO_POINTER(file->dev)
     325             :     );
     326             : 
     327           0 :     if (offsets_match) {
     328           0 :         *out = g_strdup_printf("skip_reflink '%s' '%s'", path, orig_path);
     329           0 :     } else if(!g_strcmp0("btrfs", reflink_type)) {
     330           0 :         *out = g_strdup_printf("clone '%s' '%s'", path, orig_path);
     331             :     } else {
     332           0 :         return false;
     333             :     }
     334             : 
     335           0 :     return true;
     336             : }
     337             : 
     338           0 : static bool rm_sh_emit_handler_reflink(RmFmtHandlerShScript *self, char **out, RmFile *file, char *path, char *orig_path) {
     339           0 :     if(!self->allow_reflink || !rm_mounts_can_reflink(self->session->mounts, self->last_original->dev, file->dev)) {
     340           0 :         return false;
     341             :     }
     342             : 
     343           0 :     if (rm_offsets_match(path, orig_path)) {
     344           0 :         *out = g_strdup_printf("skip_reflink '%s' '%s'", path, orig_path);
     345             :     } else {
     346           0 :         *out = g_strdup_printf("cp_reflink '%s' '%s'", path, orig_path);
     347             :     }
     348           0 :     return true;
     349             : }
     350             : 
     351           0 : static bool rm_sh_emit_handler_symlink(RmFmtHandlerShScript *self, char **out, RmFile *file, char *path, char *orig_path) {
     352           0 :     if(!self->allow_symlink || self->last_original->dev != file->dev) {
     353           0 :         return false;
     354             :     }
     355             : 
     356           0 :     *out = g_strdup_printf("cp_symlink '%s' '%s'", path, orig_path);
     357           0 :     return true;
     358             : }
     359             : 
     360           0 : static bool rm_sh_emit_handler_hardlink(RmFmtHandlerShScript *self, char **out, _U RmFile *file, char *path, char *orig_path) {
     361           0 :     if(!self->allow_hardlink) {
     362           0 :         return false;
     363             :     }
     364             : 
     365           0 :     if (self->last_original && file->hardlinks.hardlink_head == self->last_original) {
     366           0 :         *out = g_strdup_printf("skip_hardlink '%s' '%s'", path, orig_path);
     367             :     } else {
     368           0 :         *out = g_strdup_printf("cp_hardlink '%s' '%s'", path, orig_path);
     369             :     }
     370           0 :     return true;
     371             : }
     372             : 
     373         176 : static bool rm_sh_emit_handler_remove(RmFmtHandlerShScript *self, char **out, _U RmFile *file, char *path, char *orig_path) {
     374         176 :     if(!self->allow_remove) {
     375           0 :         return false;
     376             :     }
     377             : 
     378         176 :     *out = g_strdup_printf("remove_cmd '%s' '%s'", path, orig_path);
     379         176 :     return true;
     380             : }
     381             : 
     382             : typedef enum RmShHandler {
     383             :     RM_SH_HANDLER_UNKNOWN = 0,
     384             :     RM_SH_HANDLER_USER_COMMAND,
     385             :     RM_SH_HANDLER_CLONE,
     386             :     RM_SH_HANDLER_REFLINK,
     387             :     RM_SH_HANDLER_SYMLINK,
     388             :     RM_SH_HANDLER_HARDLINK,
     389             :     RM_SH_HANDLER_REMOVE,
     390             :     RM_SH_HANDLER_N
     391             : } RmShHandler;
     392             : 
     393             : static const char *ORDER_TO_STRING[] = {
     394             :     [RM_SH_HANDLER_UNKNOWN] = NULL,
     395             :     [RM_SH_HANDLER_USER_COMMAND] = "cmd",
     396             :     [RM_SH_HANDLER_CLONE] = "clone",
     397             :     [RM_SH_HANDLER_REFLINK] = "reflink",
     398             :     [RM_SH_HANDLER_SYMLINK] = "symlink",
     399             :     [RM_SH_HANDLER_HARDLINK] = "hardlink",
     400             :     [RM_SH_HANDLER_REMOVE] = "remove",
     401             :     [RM_SH_HANDLER_N] = NULL
     402             : };
     403             : 
     404             : static const RmShOrderEmitFunc ORDER_TO_FUNC[] = {
     405             :     [RM_SH_HANDLER_UNKNOWN] = NULL,
     406             :     [RM_SH_HANDLER_USER_COMMAND] = rm_sh_emit_handler_user,
     407             :     [RM_SH_HANDLER_CLONE] = rm_sh_emit_handler_clone,
     408             :     [RM_SH_HANDLER_REFLINK] = rm_sh_emit_handler_reflink,
     409             :     [RM_SH_HANDLER_SYMLINK] = rm_sh_emit_handler_symlink,
     410             :     [RM_SH_HANDLER_HARDLINK] = rm_sh_emit_handler_hardlink,
     411             :     [RM_SH_HANDLER_REMOVE] = rm_sh_emit_handler_remove
     412             : };
     413             : 
     414         124 : static void rm_sh_parse_handlers(RmFmtHandlerShScript *self, const char *handler_cfg) {
     415         124 :     self->order = g_byte_array_new();
     416             : 
     417         124 :     char **order_vec = g_strsplit(handler_cfg, ",", -1);
     418         372 :     for(int i = 0; order_vec && order_vec[i]; ++i) {
     419         248 :         bool found = false;
     420        1116 :         for(RmShHandler n = 0; n < RM_SH_HANDLER_N; ++n) {
     421        1116 :             if(ORDER_TO_STRING[n] == NULL) {
     422         248 :                 continue;
     423             :             }
     424             : 
     425         868 :             if(strcasecmp(order_vec[i], ORDER_TO_STRING[n]) == 0) {
     426         248 :                 switch(n) {
     427             :                 case RM_SH_HANDLER_USER_COMMAND:
     428         124 :                     self->allow_user_cmd = true;
     429         124 :                     break;
     430             :                 case RM_SH_HANDLER_CLONE:
     431           0 :                     self->allow_clone = true;
     432           0 :                     break;
     433             :                 case RM_SH_HANDLER_REFLINK:
     434           0 :                     self->allow_reflink = true;
     435           0 :                     break;
     436             :                 case RM_SH_HANDLER_SYMLINK:
     437           0 :                     self->allow_symlink = true;
     438           0 :                     break;
     439             :                 case RM_SH_HANDLER_HARDLINK:
     440           0 :                     self->allow_hardlink = true;
     441           0 :                     break;
     442             :                 case RM_SH_HANDLER_REMOVE:
     443         124 :                     self->allow_remove = true;
     444         124 :                     break;
     445             :                 default:
     446           0 :                     g_assert_not_reached();
     447             :                 }
     448             : 
     449             :                 /* we found the id */
     450         248 :                 g_byte_array_append(self->order, (guint8 *)&n, 1);
     451         248 :                 found = true;
     452         248 :                 break;
     453             :             }
     454             :         }
     455             : 
     456         248 :         if(!found) {
     457           0 :             rm_log_error_line(_("%s is an invalid handler."), order_vec[i]);
     458             :         }
     459             :     }
     460             : 
     461         124 :     g_strfreev(order_vec);
     462         124 : }
     463             : 
     464         124 : static void rm_fmt_head(RmSession *session, RmFmtHandler *parent, FILE *out) {
     465         124 :     RmFmtHandlerShScript *self = (RmFmtHandlerShScript *)parent;
     466         124 :     char *script_header = NULL;
     467             : 
     468         124 :     self->session = session;
     469         124 :     self->user_cmd = rm_fmt_get_config_value(session->formats, "sh", "cmd");
     470             : 
     471         124 :     const char *handler_cfg = rm_fmt_get_config_value(session->formats, "sh", "handler");
     472         124 :     if(handler_cfg != NULL) {
     473             :         /* user specified handlers */
     474           0 :         rm_sh_parse_handlers(self, handler_cfg);
     475         124 :     } else if(rm_fmt_get_config_value(session->formats, "sh", "link") != NULL) {
     476             :         /* Preset: try reflinks, then symlinks then hardlinks */
     477           0 :         rm_sh_parse_handlers(self, "clone,reflink,hardlink,symlink");
     478         124 :     } else if(rm_fmt_get_config_value(session->formats, "sh", "hardlink") != NULL) {
     479             :         /* Preset: try symlinks before using hardlinks */
     480           0 :         rm_sh_parse_handlers(self, "hardlink,symlink");
     481         124 :     } else if(rm_fmt_get_config_value(session->formats, "sh", "symlink") != NULL) {
     482             :         /* Preset: only do hardlinks */
     483           0 :         rm_sh_parse_handlers(self, "symlink");
     484             :     } else {
     485             :         /* Default: remove the file */
     486         124 :         rm_sh_parse_handlers(self, "cmd,remove");
     487             :     }
     488             : 
     489         124 :     if(fchmod(fileno(out), S_IRUSR | S_IWUSR | S_IXUSR) == -1) {
     490           0 :         rm_log_perror("Could not chmod +x sh script");
     491             :     }
     492             : 
     493             :     /* Fill in all placeholders in the script template */
     494         744 :     fprintf(
     495             :         out, SH_SCRIPT_TEMPLATE_HEAD,
     496         124 :         session->cfg->iwd,
     497         248 :         (session->cfg->joined_argv) ? (session->cfg->joined_argv) : "[unknown]",
     498             :         rm_util_get_username(),
     499             :         rm_util_get_groupname(),
     500         124 :         (self->user_cmd) ? self->user_cmd : "echo 'no user command defined.'",
     501         248 :         (session->cfg->joined_argv) ? (session->cfg->joined_argv) : "unknown_commandline"
     502             :     );
     503             : 
     504         124 :     g_free(script_header);
     505         124 : }
     506             : 
     507         948 : static char *rm_fmt_sh_escape_path(char *path) {
     508             :     /* See http://stackoverflow.com/questions/1250079/bash-escaping-single-quotes-inside-of-single-quoted-strings
     509             :      * for more info on this
     510             :      * */
     511         948 :     return rm_util_strsub(path, "'", "'\"'\"'");
     512             : }
     513             : 
     514         296 : static void rm_fmt_write_duplicate(RmFmtHandlerShScript *self, FILE *out, RmFile *file) {
     515         296 :     bool is_dir = (file->lint_type == RM_LINT_TYPE_DUPE_DIR_CANDIDATE);
     516             : 
     517         296 :     RM_DEFINE_PATH(file);
     518             : 
     519         296 :     char *path = rm_fmt_sh_escape_path(file_path);
     520         296 :     char *prefix = NULL;
     521         296 :     const char *comment = NULL;
     522             : 
     523         296 :     if(file->is_original) {
     524         120 :         if(is_dir) {
     525          28 :             comment = "# original directory";
     526             :         } else {
     527          92 :             comment = "# original";
     528             :         }
     529             : 
     530         120 :         prefix = g_strdup_printf("echo 'Keeping: ' '%s'", path);
     531         120 :         self->last_original = file;
     532         176 :     } else if(self->last_original) {
     533             : 
     534         176 :         RmFile *last_original = self->last_original;
     535         176 :         RM_DEFINE_PATH(last_original);
     536             : 
     537         176 :         char *orig_path = rm_fmt_sh_escape_path(last_original_path);
     538         176 :         if(is_dir) {
     539          28 :             comment = "# duplicate directory";
     540             :         } else {
     541         148 :             comment = "# duplicate";
     542             :         }
     543             : 
     544         352 :         for(gsize n = 0; n < self->order->len; ++n) {
     545         352 :             RmShOrderEmitFunc func = ORDER_TO_FUNC[self->order->data[n]];
     546         352 :             if(func == NULL) {
     547           0 :                 rm_log_error_line("null-func in sh formatter. Should not happen.");
     548           0 :                 continue;
     549             :             }
     550             : 
     551         352 :             if(func(self, &prefix, file, path, orig_path) && prefix) {
     552         176 :                 break;
     553             :             }
     554             :         }
     555             : 
     556         176 :         g_free(orig_path);
     557             :     }
     558             : 
     559         296 :     if(prefix != NULL) {
     560         296 :         fprintf(out, "%s %s\n", prefix, comment);
     561         296 :         g_free(prefix);
     562             :     }
     563             : 
     564         296 :     g_free(path);
     565         296 : }
     566             : 
     567         352 : static void rm_fmt_elem(_U RmSession *session, _U RmFmtHandler *parent, FILE *out, RmFile *file) {
     568         352 :     RmFmtHandlerShScript *self = (RmFmtHandlerShScript *)parent;
     569         352 :     if(file->lint_type == RM_LINT_TYPE_UNFINISHED_CKSUM) {
     570           0 :         return;
     571             :     }
     572             : 
     573         352 :     RM_DEFINE_PATH(file);
     574         352 :     char *path = rm_fmt_sh_escape_path(file_path);
     575             : 
     576         352 :     switch(file->lint_type) {
     577             :     case RM_LINT_TYPE_EMPTY_DIR:
     578           0 :         fprintf(out, "handle_emptydir '%s' # empty folder\n", path);
     579           0 :         break;
     580             :     case RM_LINT_TYPE_EMPTY_FILE:
     581          28 :         fprintf(out, "handle_emptyfile '%s' # empty file\n", path);
     582          28 :         break;
     583             :     case RM_LINT_TYPE_BADLINK:
     584          28 :         fprintf(out, "handle_bad_symlink '%s' # bad symlink pointing nowhere\n", path);
     585          28 :         break;
     586             :     case RM_LINT_TYPE_NONSTRIPPED:
     587           0 :         fprintf(out, "handle_unstripped_binary '%s' # binary with debugsymbols\n", path);
     588           0 :         break;
     589             :     case RM_LINT_TYPE_BADUID:
     590           0 :         fprintf(out, "handle_bad_user_id '%s' # bad uid\n", path);
     591           0 :         break;
     592             :     case RM_LINT_TYPE_BADGID:
     593           0 :         fprintf(out, "handle_bad_group_id '%s' # bad gid\n", path);
     594           0 :         break;
     595             :     case RM_LINT_TYPE_BADUGID:
     596           0 :         fprintf(out, "handle_bad_user_and_group_id '%s' # bad gid\n", path);
     597           0 :         break;
     598             :     case RM_LINT_TYPE_DUPE_DIR_CANDIDATE:
     599             :     case RM_LINT_TYPE_DUPE_CANDIDATE:
     600         296 :         rm_fmt_write_duplicate(self, out, file);
     601         296 :         break;
     602             :     default:
     603           0 :         rm_log_warning("Warning: unknown type in encountered: %d\n", file->lint_type);
     604           0 :         break;
     605             :     }
     606             : 
     607         352 :     g_free(path);
     608             : }
     609             : 
     610         124 : static void rm_fmt_foot(_U RmSession *session, RmFmtHandler *parent, FILE *out) {
     611         124 :     RmFmtHandlerShScript *self = (RmFmtHandlerShScript *)parent;
     612         124 :     g_byte_array_free(self->order, true);
     613             : 
     614         124 :     if(rm_fmt_is_stream(session->formats, parent)) {
     615             :         /* You will have a hard time deleting standard streams. */
     616           0 :         return;
     617             :     }
     618             : 
     619         124 :     char *escaped_path = rm_fmt_sh_escape_path(parent->path);
     620         124 :     fprintf(out, SH_SCRIPT_TEMPLATE_FOOT, "rm -f", escaped_path);
     621         124 :     g_free(escaped_path);
     622             : }
     623             : 
     624             : static RmFmtHandlerShScript SH_SCRIPT_HANDLER_IMPL = {
     625             :     .parent = {
     626             :         .size = sizeof(SH_SCRIPT_HANDLER_IMPL),
     627             :         .name = "sh",
     628             :         .head = rm_fmt_head,
     629             :         .elem = rm_fmt_elem,
     630             :         .prog = NULL,
     631             :         .foot = rm_fmt_foot,
     632             :         .valid_keys = {"handler", "cmd", "link", "hardlink", "symlink", NULL},
     633             :     },
     634             :     .last_original = NULL
     635             : };
     636             : 
     637             : RmFmtHandler *SH_SCRIPT_HANDLER = (RmFmtHandler *) &SH_SCRIPT_HANDLER_IMPL;

Generated by: LCOV version 1.11