Skip to content

Commit

Permalink
init: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tshakalekholoane committed Dec 14, 2024
0 parents commit 8dee7e1
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ColumnLimit: 0
PointerAlignment: Left
6 changes: 6 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: "weekly"
15 changes: 15 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: ci

on: [pull_request, push]

jobs:
check:
runs-on: macos-15
steps:
- uses: actions/checkout@v4

- name: build
run: make CC=$(brew --prefix llvm@18)/bin/clang

- name: run
run: ./bin/can -V
56 changes: 56 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# General.
.DS_Store
bin/

# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
15 changes: 15 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ISC License

Copyright (c) 2023 Tshaka Lekholoane

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
COMMIT = $(shell git rev-parse --short HEAD)
DATE = $(shell date -u +"%Y.%m.%d")
BUILD = $(shell printf "%s (%s)" "$(DATE)" "$(COMMIT)" )
CC = cc
CFLAGS = -DCAN_BUILD="\"$(BUILD)\"" -O3 -Wall -Wextra -Wno-c++98-compat \
-Wno-cast-function-type-strict -Wno-declaration-after-statement \
-Wno-format-nonliteral -Wno-incompatible-pointer-types-discards-qualifiers \
-Wno-poison-system-directories -Wno-vla -framework Foundation -march=native \
-pedantic -std=c23

.PHONY: all clean

all:
mkdir -p bin/
$(CC) $(CFLAGS) -o bin/can src/main.c

clean:
rm -rf bin/
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `can`

![Continuous Integration](https://github.com/tshakalekholoane/can/actions/workflows/ci.yaml/badge.svg)

`can` is a macOS command-line utility that provides an alternative to the `rm` command. Instead of permanently deleting files and directories, `can` moves them to the user's Trash, allowing for easy recovery if needed.

## Usage

```
usage: can [-h | -V] [--] file ...
```

## Installation

### Source

The application can be built from source by cloning the repository and running the following commands which require working versions of [Make](https://www.gnu.org/software/make/) and a C compiler with C23 support.

```shell
git clone https://github.com/tshakalekholoane/can && cd can
make
```
125 changes: 125 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#ifndef __APPLE__
#error "This program is intended to only run on macOS."
#endif

#if __STDC_VERSION__ < 202311L
#error "This code requires C23 or later."
#endif

#include <errno.h>
#include <getopt.h>
#include <objc/message.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#ifndef CAN_BUILD
#define CAN_BUILD "TIP"
#endif

static const char* usage = "usage: can [-h | -V] [--] file ...";

// Objective-C messaging primitives require that functions be cast to an
// appropriate function pointer type before being called [1].
//
// [1]: https://github.com/apple-oss-distributions/objc4/blob/89543e2c0f67d38ca5211cea33f42c51500287d5/runtime/message.h#L52-L53

static struct objc_object* file_manager_default_manager(void) {
auto file_manager = objc_getClass("NSFileManager");
typedef struct objc_object* (*send_type)(struct objc_class*, struct objc_selector*);
auto func = (send_type)objc_msgSend;
return func(file_manager, sel_registerName("defaultManager"));
}

static struct objc_object* file_manager_string_with_file_system_representation(struct objc_object* self, const char string[static 1]) {
typedef struct objc_object* (*send_type)(struct objc_object*, struct objc_selector*, const char*, unsigned long);
auto func = (send_type)objc_msgSend;
return func(self, sel_registerName("stringWithFileSystemRepresentation:length:"), string, strlen(string));
}

static bool file_manager_trash_item_at_url(struct objc_object* self, struct objc_object* url, struct objc_object** error) {
typedef bool (*send_type)(struct objc_object*, struct objc_selector*, struct objc_object*, struct objc_object*, struct objc_object**);
auto func = (send_type)objc_msgSend;
return func(self, sel_registerName("trashItemAtURL:resultingItemURL:error:"), url, nullptr, error);
}

static struct objc_object* error_localized_description(struct objc_object* self) {
typedef struct objc_object* (*send_type)(struct objc_object*, struct objc_selector*);
auto func = (send_type)objc_msgSend;
return func(self, sel_registerName("localizedDescription"));
}

static const char* string_utf8_string(struct objc_object* self) {
typedef const char* (*send_type)(struct objc_object*, struct objc_selector*);
auto func = (send_type)objc_msgSend;
return func(self, sel_registerName("UTF8String"));
}

static struct objc_object* url_file_url_with_path(struct objc_object* string) {
auto url = objc_getClass("NSURL");
typedef struct objc_object* (*send_type)(struct objc_class*, struct objc_selector*, struct objc_object*);
auto func = (send_type)objc_msgSend;
return func(url, sel_registerName("fileURLWithPath:"), string);
}

int main(int argc, char* argv[argc + 1]) {
int opt;
while ((opt = getopt(argc, argv, "hV")) != -1) {
switch (opt) {
case 'h':
printf("%s\n", usage);
return EXIT_SUCCESS;
case 'V':
printf("can %s\n", CAN_BUILD);
return EXIT_SUCCESS;
default:
fprintf(stderr, "%s\n", usage);
return EXIT_FAILURE;
}
}
if (__builtin_expect(argc == 1, false)) {
fprintf(stderr, "%s\n", usage);
return EXIT_FAILURE;
}
argc--;
argv++;

for (ssize_t i = 0; i < (ssize_t)argc; i++) {
auto name = argv[i];
if (__builtin_expect(strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || strcmp(name, "/") == 0, false)) {
fprintf(stderr, "\"/\", \".\", and \"..\" may not be removed.\n");
return EXIT_FAILURE;
}
}

// Avoid using the root user's trash when invoked with sudo.
auto superuser = getenv("SUDO_USER");
if (__builtin_expect(superuser != nullptr, false)) {
auto entry = getpwnam(superuser);
if (__builtin_expect(!entry || seteuid(entry->pw_uid), false)) {
fprintf(stderr, "%s\n", strerror(errno));
return EXIT_FAILURE;
}
}

// Ignore flag separator.
if (__builtin_expect(strcmp(argv[0], "--") == 0, false)) {
argc--;
argv++;
}

auto exit_code = EXIT_SUCCESS;
auto file_manager = file_manager_default_manager();
for (ssize_t i = 0; i < (ssize_t)argc; i++) {
auto path = file_manager_string_with_file_system_representation(file_manager, argv[i]);
auto url = url_file_url_with_path(path);
struct objc_object* err;
if (__builtin_expect(!file_manager_trash_item_at_url(file_manager, url, &err), false)) {
auto description = error_localized_description(err);
fprintf(stderr, "%s\n", string_utf8_string(description));
exit_code = EXIT_FAILURE;
}
}
return exit_code;
}

0 comments on commit 8dee7e1

Please sign in to comment.