Skip to content

Commit

Permalink
init: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tshakalekholoane committed Jan 26, 2025
0 parents commit d350b92
Show file tree
Hide file tree
Showing 8 changed files with 276 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"
21 changes: 21 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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: |
touch x
bin/can x
if [[ -f x ]]; then
exit 1
fi
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.
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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

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

src/main.o:

.PHONY: clean
clean:
-rm bin/can src/main.o
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# `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.

> [!WARNING]
> GCC 14 incorrectly uses `__STDC_VERSION__ == 202000` for C23 [^1], which will produce an error even when the `-std=c23` flag is set.
```shell
git clone https://github.com/tshakalekholoane/can && cd can
make
```

[^1]: See https://stackoverflow.com/a/78582932.
130 changes: 130 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#ifndef __APPLE__
#error "This program is intended to only run on macOS."
#endif

#if __STDC_VERSION__ < 202000
#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

#define shift(n) \
do { \
argc -= (n); \
argv += (n); \
} while (false)

#define unlikely(c) __builtin_expect(!!(c), false)

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://tshaka.dev/a/7hq9i9

static id file_manager_default_manager(void) {
auto file_manager = objc_getClass("NSFileManager");
typedef id (*send_type)(Class, SEL);
auto func = (send_type)objc_msgSend;
return func(file_manager, sel_registerName("defaultManager"));
}

static id file_manager_string_with_file_system_representation(id self, const char string[static 1]) {
typedef id (*send_type)(id, SEL, 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(id self, id url, id* err) {
typedef bool (*send_type)(id, SEL, id, id, id*);
auto func = (send_type)objc_msgSend;
return func(self, sel_registerName("trashItemAtURL:resultingItemURL:error:"), url, nullptr, err);
}

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

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

static id url_file_url_with_path(id string) {
auto url = objc_getClass("NSURL");
typedef id (*send_type)(Class, SEL, id);
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 (unlikely(argc == 1)) {
fprintf(stderr, "%s\n", usage);
return EXIT_FAILURE;
}
shift(1);

for (ssize_t i = 0; i < (ssize_t)argc; i++) {
auto name = argv[i];
if (unlikely(strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || strcmp(name, "/") == 0)) {
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 (unlikely(superuser != nullptr)) {
auto entry = getpwnam(superuser);
if (unlikely(!entry || seteuid(entry->pw_uid))) {
fprintf(stderr, "%s\n", strerror(errno));
return EXIT_FAILURE;
}
}

// Ignore flag separator.
if (unlikely(strcmp(argv[0], "--") == 0))
shift(1);

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);
id err;
if (unlikely(!file_manager_trash_item_at_url(file_manager, url, &err))) {
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 d350b92

Please sign in to comment.