logo

deblob

remove binary executables from a directory git clone https://hacktivis.me/git/deblob.git
commit: 08538feb54dedd434d95911f831dc783d0a9eb71
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Thu, 17 Feb 2022 20:20:01 +0100

Initial Commit

Diffstat:

A.gitignore2++
Amain.ha153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 155 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +/deblob +\ No newline at end of file diff --git a/main.ha b/main.ha @@ -0,0 +1,153 @@ +// Copyright © 2019-2022 Haelwenn (lanodan) Monnier <contact+deblob-notice@hacktivis.me> +// SPDX-License-Identifier: BSD-3-Clause + +use getopt; +use fmt; +use os; +use fs; +use io; +use path; +use bytes; +use fnmatch; + +type invalid = !void; +type ok = !void; + +let excludes: []str = []; +let noop: bool = false; + +fn isBlob(filename: str) (bool|invalid) = { + static let buffer: [512]u8 = [0...]; + static const elf: []u8 = [0x7F, 'E', 'L', 'F']; + static const ar: []u8 = ['!', '<', 'a', 'r', 'c', 'h', '>', '\n']; // <arch>! + static const bios: []u8 = [0x55, 0xAA]; + + let file = match(os::open(filename)) { + case let f: io::file => yield f; + case let e: fs::error => + fmt::errorf("os::open({}): {}\n", filename, fs::strerror(e))!; + return invalid; + }; + defer io::close(file); + + const n = match(io::read(file, buffer)) { + case let s: size => yield s; + case io::EOF => + fmt::errorf("EOF reading: {}\n", filename)!; + return invalid; + case let e: io::error => + fmt::errorf("Error reading `{}`: {}\n", filename, io::strerror(e))!; + return invalid; + }; + + return bytes::hasprefix(buffer, elf) || bytes::hasprefix(buffer, ar) || bytes::hasprefix(buffer, bios); +}; + +fn is_excluded(filename: str) bool = { + for (let i = 0z; i < len(excludes); i += 1) { + if (fnmatch::fnmatch(excludes[i], filename, fnmatch::flags::NONE)) { + return true; + }; + }; + + return false; +}; + +fn checkDir(dirname: str, ignoring: bool) (ok|invalid) = { + let dir_ls = match(os::readdir(dirname)) { + case let d: []fs::dirent => yield d; + case let e: fs::error => + fmt::errorf("os::readdir({}): {}\n", dirname, fs::strerror(e))!; + return invalid; + }; + + for (let i = 0z; i < len(dir_ls); i += 1) { + let file = dir_ls[i]; + let filename = path::join(dirname, file.name); + + if(file.name == "." || file.name == "..") { + continue; + }; + + if(!ignoring) { + ignoring = is_excluded(filename); + }; + + if(fs::isdir(file.ftype)) { + match(checkDir(filename, ignoring)) { + case => continue; + }; + } else { + let is_blob = match(isBlob(filename)) { + case let b: bool => yield b; + case invalid => continue; + }; + + if(!is_blob) { + continue; + }; + + if(ignoring) { + fmt::printf("ignoring: {}\n", filename)!; + continue; + }; + + if(noop) { + fmt::printf("detected: {}\n", filename)!; + continue; + }; + + fmt::printf("removing: {}\n", filename)!; + + match(os::remove(filename)) { + case void => continue; + case let e: fs::error => + fmt::errorf("os::remove({}): {}\n", filename, fs::strerror(e))!; + }; + }; + }; + + return ok; +}; + +fn fn_noop() void = { + return; +}; + +export fn main() void = { + let workdir = "."; + + const cmd = getopt::parse(os::args, + "Remove binary executable files", + ('e', "NAME", "Exclude filename from removal (defaults to none)"), + ('d', "PATH", "Set working directory (default to current dir)"), + ('n', "No actual removal, only scan and log") + ); + defer getopt::finish(&cmd); + + for (let i = 0z; i < len(cmd.opts); i += 1) { + let opt = cmd.opts[i]; + switch(opt.0) { + case 'e' => append(excludes, opt.1); + case 'd' => workdir = opt.1; + case 'n' => noop = true; + }; + }; + + match(os::chdir(workdir)) { + case void => fn_noop; + case let e: fs::error => + fmt::fatal("os::chdir({}): {}", workdir, fs::strerror(e)); + }; + + fmt::println(":: Checking for blobs")!; + + let ret = checkDir(".", false); + + fmt::println(":: Done checking for blobs")!; + + match(ret) { + case ok => os::exit(0); + case invalid => os::exit(1); + }; +};