aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCharles Cabergs <me@cacharle.xyz>2020-08-22 19:43:03 +0200
committerCharles Cabergs <me@cacharle.xyz>2020-08-22 19:43:03 +0200
commitc40f79737a4ce6ae0cd8bd6ea7f302217333e486 (patch)
tree5afe1af26880bd7f8b16ee33fb8e14f99f665938 /src
parentf204fe59bd5fa537bf84cb522339c92b16f5a909 (diff)
downloadcoreutils-c40f79737a4ce6ae0cd8bd6ea7f302217333e486.tar.gz
coreutils-c40f79737a4ce6ae0cd8bd6ea7f302217333e486.tar.bz2
coreutils-c40f79737a4ce6ae0cd8bd6ea7f302217333e486.zip
Added Makefile
Diffstat (limited to 'src')
-rw-r--r--src/basename.c83
-rw-r--r--src/chown.c194
-rw-r--r--src/cut.c225
-rw-r--r--src/head.c166
-rw-r--r--src/mkdir.c86
-rw-r--r--src/mv.c37
-rw-r--r--src/rm.c130
-rw-r--r--src/seq.c126
-rw-r--r--src/shuf.c237
-rw-r--r--src/tee.c68
-rw-r--r--src/tr.c230
11 files changed, 1582 insertions, 0 deletions
diff --git a/src/basename.c b/src/basename.c
new file mode 100644
index 0000000..83f3b8f
--- /dev/null
+++ b/src/basename.c
@@ -0,0 +1,83 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+char *g_name = "basename";
+
+void fatal(char *message)
+{
+ fprintf(stderr, "%s: %s\n", g_name, message);
+ exit(EXIT_FAILURE);
+}
+
+char *truncate_path(char *s, char *suffix)
+{
+ if (*s == '\0')
+ return s;
+
+ size_t last = strlen(s) - 1;
+ while (s[last] == '/' && last != 0)
+ {
+ s[last] = '\0';
+ last--;
+ }
+
+ char *last_slash = strrchr(s, '/');
+ if (last_slash != NULL && last_slash != s)
+ s = last_slash + 1;
+
+ if (suffix != NULL)
+ {
+ char *end = s + strlen(s) - strlen(suffix);
+ if (end > s && strcmp(end, suffix) == 0)
+ *end = '\0';
+ }
+ return s;
+}
+
+int main(int argc, char **argv)
+{
+ int option;
+ char *suffix = NULL;
+ bool multiple = false;
+ char line_delim = '\n';
+
+ g_name = argv[0];
+ while ((option = getopt(argc, argv, "as:z")) != -1)
+ {
+ switch (option)
+ {
+ case 's':
+ suffix = optarg;
+ case 'a':
+ multiple = true;
+ break;
+ case 'z':
+ line_delim = '\0';
+ break;
+ default:
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (optind == argc)
+ fatal("missing operand");
+
+ if (!multiple)
+ {
+ argv[optind] = truncate_path(argv[optind], argv[optind + 1]);
+ fputs(argv[optind], stdout);
+ putchar(line_delim);
+ return EXIT_SUCCESS;
+ }
+
+ for (; optind < argc; optind++)
+ {
+ argv[optind] = truncate_path(argv[optind], suffix);
+ fputs(argv[optind], stdout);
+ putchar(line_delim);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/src/chown.c b/src/chown.c
new file mode 100644
index 0000000..c181d37
--- /dev/null
+++ b/src/chown.c
@@ -0,0 +1,194 @@
+#define _POSIX_C_SOURCE 200809L
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <limits.h>
+#include <dirent.h>
+#include <errno.h>
+#include <ctype.h>
+
+typedef enum
+{
+ RMODE_FOLLOW_ROOT,
+ RMODE_FOLLOW_ALL,
+ RMODE_NO_FOLLOW,
+} t_recursive_mode;
+
+typedef enum
+{
+ VLEVEL_CHANGE,
+ VLEVEL_QUIET,
+ VLEVEL_VERBOSE,
+} t_verbose_level;
+
+static t_verbose_level g_verbosity = VLEVEL_QUIET;
+static bool g_follow_link = true;
+static bool g_recursive = false;
+static t_recursive_mode g_recursive_mode = RMODE_NO_FOLLOW;
+static char *g_name = "chown";
+static uid_t g_user_id = -1;
+static gid_t g_group_id = -1;
+
+void verror_log(const char *format, va_list ap)
+{
+ fprintf(stderr, "%s: ", g_name);
+ vfprintf(stderr, format, ap);
+ fputc('\n', stderr);
+}
+
+void error_log(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ verror_log(format, ap);
+ va_end(ap);
+}
+
+void fatal(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ verror_log(format, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+bool parse_id(char *id_str, id_t *id_ptr, bool group)
+{
+ char *end;
+
+ if (isdigit(*id_str))
+ {
+ errno = 0;
+ long tmp = strtol(id_str, &end, 10);
+ if (errno != 0 || tmp > UINT_MAX || *end != '\0')
+ return false;
+ *id_ptr = (id_t)tmp;
+ }
+ else
+ {
+ struct passwd *pw = getpwnam(id_str);
+ if (pw == NULL)
+ return false;
+ if (group)
+ *id_ptr = pw->pw_gid;
+ else
+ *id_ptr = pw->pw_uid;
+ }
+ return true;
+}
+
+bool file_chown(char *path, bool root)
+{
+ char recursive_path[PATH_MAX + 1] = {'\0'};
+ struct stat statbuf;
+
+
+ if (stat(path, &statbuf) == -1)
+ {
+ error_log("cannot access '%s': %s", path, strerror(errno));
+ return false;
+ }
+
+ errno = 0;
+ if ((!g_recursive && g_follow_link)
+ || (g_recursive && (g_recursive_mode == RMODE_FOLLOW_ALL
+ || (root && g_recursive_mode == RMODE_FOLLOW_ROOT))))
+ chown(path, g_user_id, g_group_id);
+ else
+ lchown(path, g_user_id, g_group_id);
+ if (errno != 0)
+ {
+ error_log("changing ownership of '%s': %s", path, strerror(errno));
+ return false;
+ }
+
+ if (g_recursive)
+ {
+ errno = 0;
+ DIR *directory = opendir(path);
+
+ if (directory == NULL && errno == ENOTDIR)
+ return true;
+ if (directory == NULL)
+ {
+ error_log("cannot access '%s': %s", path, strerror(errno));
+ return false;
+ }
+
+ strcpy(recursive_path, path);
+ if (path[strlen(path) - 1] != '/')
+ strcat(recursive_path, "/");
+
+ struct dirent *entry;
+ while ((entry = readdir(directory)) != NULL)
+ {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+ strcat(recursive_path, entry->d_name);
+ file_chown(recursive_path, false);
+ strrchr(recursive_path, '/')[1] = '\0';
+ }
+ closedir(directory);
+ }
+ return true;
+}
+
+/*
+**
+** TODO
+** don't change group/user if not asked
+** change group to login group is trailling : after user in cmd
+**
+*/
+
+int main(int argc, char **argv)
+{
+ int option;
+
+ while ((option = getopt(argc, argv, "cfvhRHLP")) != -1)
+ {
+ switch (option)
+ {
+ case 'c': g_verbosity = VLEVEL_CHANGE; break;
+ case 'f': g_verbosity = VLEVEL_QUIET; break;
+ case 'v': g_verbosity = VLEVEL_VERBOSE; break;
+ case 'h': g_follow_link = false; break;
+ case 'R': g_recursive = true; break;
+ case 'H': g_recursive_mode = RMODE_FOLLOW_ROOT; break;
+ case 'L': g_recursive_mode = RMODE_FOLLOW_ALL; break;
+ case 'P': g_recursive_mode = RMODE_NO_FOLLOW; break;
+ }
+ }
+
+ if (argc - optind == 0)
+ fatal("missing operand");
+ if (argc - optind == 1)
+ fatal("missing operand after '%s'", argv[optind]);
+
+ char *user_str = argv[optind];
+ char *group_str = strchr(argv[optind], ':');
+ if (group_str != NULL)
+ {
+ *group_str = '\0';
+ group_str++;
+ }
+
+ if (!parse_id(user_str, &g_user_id, false))
+ fatal("invalid user '%s'", user_str);
+ if (group_str != NULL && !parse_id(group_str, &g_group_id, true))
+ fatal("invalid group '%s:%s'", user_str, group_str);
+
+ for (optind++; optind < argc; optind++)
+ file_chown(argv[optind], true);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/cut.c b/src/cut.c
new file mode 100644
index 0000000..2a7990d
--- /dev/null
+++ b/src/cut.c
@@ -0,0 +1,225 @@
+#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE 1
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#define LIST_VALUE_INFINITY -1
+#define LIST_VALUE_SINGLETON -2
+
+#define LINE_BUFFER_SIZE 2024
+
+typedef enum
+{
+ LIST_BYTE,
+ LIST_CHAR,
+ LIST_FIELD,
+ LIST_UNDEFINED,
+} t_list_type;
+
+char *g_name = "cut";
+
+void fatal_msg(char *message)
+{
+ fprintf(stderr, "%s: %s\n", g_name, message);
+ exit(EXIT_FAILURE);
+}
+
+void fatal_errno(void)
+{
+ perror(g_name);
+ exit(EXIT_FAILURE);
+}
+
+int parse_uint(char *s, char **endptr)
+{
+ if (!isdigit(*s))
+ return -1;
+ unsigned long x = strtoul(s, endptr, 10);
+ return (int)x;
+}
+
+int main(int argc, char **argv)
+{
+ int option;
+ int list_start = LIST_VALUE_INFINITY;
+ int list_end = LIST_VALUE_INFINITY;
+ t_list_type list_type = LIST_UNDEFINED;
+ wchar_t delimiter = '\t';
+ bool print_only_delimiter = false;
+ char line_delimiter = '\n';
+
+ g_name = argv[0];
+ while ((option = getopt(argc, argv, "b:c:d:f:sz")) > 0)
+ {
+
+ switch (option)
+ {
+ case 'b':
+ case 'c':
+ case 'f':
+ if (list_type != LIST_UNDEFINED)
+ fatal_msg("only one type of list may be specified");
+
+ switch (option)
+ {
+ case 'b': list_type = LIST_BYTE; break;
+ case 'c': list_type = LIST_CHAR; break;
+ case 'f': list_type = LIST_FIELD; break;
+ }
+
+ char *ptr = optarg;
+
+ char *hyphen = strchr(ptr, '-');
+ if (hyphen == NULL) // N
+ {
+ list_start = parse_uint(ptr, &ptr);
+ if (*ptr != '\0')
+ fatal_msg("");
+ list_end = LIST_VALUE_SINGLETON;
+ }
+ else
+ {
+ if (ptr == hyphen) // -M
+ {
+ list_end = parse_uint(ptr + 1, &ptr);
+ printf(">> %d %d |%s|\n", list_start, list_end, ptr);
+ if (*ptr != '\0')
+ fatal_msg("");
+ }
+ else
+ {
+ list_start = parse_uint(ptr, &ptr);
+ if (*ptr != '-')
+ fatal_msg("");
+ ptr++;
+ if (*ptr != '\0') // N-M
+ {
+ list_end = parse_uint(ptr, &ptr);
+ }
+ if (*ptr != '\0')
+ fatal_msg("");
+ }
+ }
+ break;
+
+ case 'd':
+ delimiter = optarg[0];
+ if (delimiter != '\0' && optarg[1] != '\0')
+ fatal_msg("the delimiter must be a single character");
+ break;
+
+ case 's':
+ print_only_delimiter = true;
+ break;
+ case 'z':
+ line_delimiter = '\0';
+ break;
+ }
+ }
+ if (list_type == LIST_UNDEFINED)
+ fatal_msg("you must specify a list of bytes, characters, or fields");
+ if (list_type != LIST_FIELD && delimiter != '\t')
+ fatal_msg("an input delimiter may be specified only when operating on fields");
+ if (list_type != LIST_FIELD && print_only_delimiter)
+ fatal_msg("suppressing non-delimited lines makes sense only when operating on fields");
+
+ if (list_start == 0)
+ fatal_msg("list are numbered from 1");
+ if (list_end > 0 && list_start > list_end)
+ fatal_msg("invalid decreasing range");
+
+ char *line = NULL;
+ size_t line_buffer_size = LINE_BUFFER_SIZE;
+ ssize_t read_size = -1;
+
+ if ((line = malloc(LINE_BUFFER_SIZE)) == NULL)
+ exit(1);
+
+ char *field;
+ int counter = 1;
+ char delimiter_buf[2] = {delimiter, '\0'};
+
+ if (optarg == NULL)
+ {
+ errno = 0;
+ while ((read_size = getdelim(&line, &line_buffer_size, line_delimiter, stdin)) > 0)
+ {
+ switch (list_type)
+ {
+ case LIST_BYTE:
+ case LIST_CHAR: // TODO unicode
+ if (list_end == LIST_VALUE_SINGLETON)
+ {
+ if (read_size - 1 >= list_start)
+ fputc(line[list_start - 1], stdout);
+ }
+ else if (list_start == LIST_VALUE_INFINITY)
+ {
+ line[list_end] = '\0';
+ fputs(line, stdout);
+ }
+ else if (list_end == LIST_VALUE_INFINITY)
+ {
+ fputs(&line[list_start - 1], stdout);
+ }
+ else
+ {
+ line[list_end] = '\0';
+ fputs(&line[list_start - 1], stdout);
+ }
+ break;
+ case LIST_FIELD:
+ while ((field = strsep(&line, delimiter_buf)) != NULL)
+ {
+ if (list_end == LIST_VALUE_SINGLETON && counter == list_start)
+ fputs(field, stdout);
+ else if (list_start == LIST_VALUE_INFINITY && counter <= list_end)
+ fputs(field, stdout);
+ else if (list_end == LIST_VALUE_INFINITY && counter >= list_start)
+ fputs(field, stdout);
+ else if (counter >= list_start && counter <= list_end)
+ fputs(field, stdout);
+ counter++;
+ }
+ break;
+ }
+ putchar('\n');
+ }
+ free(line);
+ if (errno != 0)
+ fatal_errno();
+ return EXIT_SUCCESS;
+ }
+
+
+ /* while (optarg != NULL) */
+ /* { */
+ /* if (strcmp(optarg, "-") == 0) */
+ /* { */
+ /* // read stdin */
+ /* } */
+ /* else */
+ /* { */
+ /* FILE *file; */
+ /* */
+ /* if ((file = fopen(optarg, "r")) == NULL) */
+ /* fatal_errno(); */
+ /* */
+ /* while (getdelim(&line, 0, line_delimiter, file)) */
+ /* { */
+ /* if (field) */
+ /* strsep(line, delimiter); */
+ /* } */
+ /* } */
+ /* } */
+
+
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/head.c b/src/head.c
new file mode 100644
index 0000000..f2201b5
--- /dev/null
+++ b/src/head.c
@@ -0,0 +1,166 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+
+typedef enum
+{
+ VLEVEL_QUIET,
+ VLEVEL_NORMAL,
+ VLEVEL_VERBOSE,
+} t_verbose_level;
+
+typedef enum
+{
+ UNIT_LINE,
+ UNIT_BYTE,
+} t_unit;
+
+char *g_name = "head";
+
+#define UNIT_BYTE_BUFFER_SIZE 1028
+
+void fatal_printf(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s: ", g_name);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+int file_head(FILE *file,
+ char *header, t_unit unit,
+ unsigned long count, char line_delimiter)
+{
+ char buf[UNIT_BYTE_BUFFER_SIZE] = {'\0'};
+ size_t read_size;
+ size_t write_size;
+ char *line = NULL;
+ size_t line_size = 0;
+ ssize_t ret;
+
+ if (header != NULL)
+ printf("==> %s <==\n", header);
+ switch (unit)
+ {
+ case UNIT_BYTE:
+ while (count > 0)
+ {
+ read_size = count > UNIT_BYTE_BUFFER_SIZE ? UNIT_BYTE_BUFFER_SIZE : count;
+ write_size = fread(buf, 1, read_size, file);
+ fwrite(buf, 1, write_size, stdout);
+ if (write_size < read_size)
+ break;
+ count -= read_size;
+ }
+ break;
+
+ case UNIT_LINE:
+ for (; count > 0 && (ret = getdelim(&line, &line_size, line_delimiter, file)) != -1; count--)
+ fwrite(line, 1, ret, stdout);
+ free(line);
+ break;
+ }
+ return 0;
+}
+
+int file_path_head(char *path,
+ char *header, t_unit unit,
+ unsigned long count, char line_delimiter)
+{
+ FILE *file;
+
+ if ((file = fopen(path, "r")) == NULL)
+ {
+ fprintf(stderr, "%s: cannot open '%s' for reading: %s\n", g_name, path, strerror(errno));
+ return -1;
+ }
+ file_head(file, header, unit, count, line_delimiter);
+ fclose(file);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int option;
+ t_verbose_level verbose_level = VLEVEL_NORMAL;
+ t_unit unit = UNIT_LINE;
+ unsigned long count = 10;
+ char line_delimiter = '\n';
+
+ g_name = argv[0];
+
+ while ((option = getopt(argc, argv, "c:n:qvz")) != -1)
+ {
+ switch (option)
+ {
+ case 'c':
+ case 'n':
+ if (strchr(optarg, '-') != NULL)
+ fatal_printf("invalid number of bytes: '%s'\n", optarg);
+ char *endptr;
+ errno = 0;
+ // TODO '-' prefix result in tail
+ // TODO human readable size suffix
+ count = strtoul(optarg, &endptr, 10);
+ if (*endptr != '\0')
+ fatal_printf("invalid number of bytes: '%s'\n", optarg);
+ if (errno != 0 || *endptr != '\0')
+ fatal_printf("invalid number of bytes: '%s': %s\n", optarg, strerror(errno));
+ switch (option)
+ {
+ case 'c': unit = UNIT_BYTE; break;
+ case 'n': unit = UNIT_LINE; break;
+ }
+ break;
+
+ case 'q': verbose_level = VLEVEL_QUIET; break;
+ case 'v': verbose_level = VLEVEL_VERBOSE; break;
+ case 'z': line_delimiter = '\0'; break;
+ }
+ }
+
+ if (argv[optind] == NULL)
+ {
+ file_head(
+ stdin, verbose_level == VLEVEL_VERBOSE ? "standard input" : NULL,
+ unit, count, line_delimiter);
+ return EXIT_SUCCESS;
+ }
+
+ if (argv[optind + 1] == NULL)
+ {
+ if (strcmp(argv[optind], "-") == 0)
+ file_head(
+ stdin, verbose_level == VLEVEL_VERBOSE ? "standard input" : NULL,
+ unit, count, line_delimiter);
+ else
+ file_path_head(
+ argv[optind], verbose_level == VLEVEL_VERBOSE ? argv[optind] : NULL,
+ unit, count, line_delimiter);
+ return EXIT_SUCCESS;
+ }
+
+ for (; optind < argc; optind++)
+ {
+ int ret = 0;
+
+ if (strcmp(argv[optind], "-") == 0)
+ ret = file_head(
+ stdin, verbose_level != VLEVEL_QUIET ? "standard input" : NULL,
+ unit, count, line_delimiter);
+ else
+ ret = file_path_head(
+ argv[optind], verbose_level != VLEVEL_QUIET ? argv[optind] : NULL,
+ unit, count, line_delimiter);
+ if (argv[optind + 1] != NULL && ret != -1)
+ putchar('\n');
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/src/mkdir.c b/src/mkdir.c
new file mode 100644
index 0000000..3ab73d1
--- /dev/null
+++ b/src/mkdir.c
@@ -0,0 +1,86 @@
+#define _POSIX_C_SOURCE 2
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+char *g_name = "mkdir";
+
+int mkdir_wrapper(const char *path, mode_t mode, bool verbose)
+{
+ int ret;
+
+ if ((ret = mkdir(path, mode)) == -1 && (verbose || errno != EEXIST))
+ fprintf(stderr, "%s: cannot create directory '%s': %s\n", g_name, path, strerror(errno));
+ else if (verbose)
+ printf("%s: created directory '%s'\n", g_name, path);
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int option;
+ mode_t mode = 0755;
+ bool parent = false;
+ bool verbose = false;
+
+ g_name = argv[0];
+ if (argc == 1)
+ {
+ fprintf(stderr, "%s: missing operand\n", g_name);
+ return EXIT_FAILURE;
+ }
+ while ((option = getopt(argc, argv, "m:pv")) != -1)
+ {
+ switch (option)
+ {
+ case 'm':
+ sscanf(optarg, "%o", &mode); // TODO mode not working
+ break;
+ case 'p':
+ parent = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ }
+ }
+ for (; optind < argc; optind++)
+ {
+ // remove duplicate slash
+ for (size_t i = 0; argv[optind][i] != '\0'; i++)
+ {
+ if (argv[optind][i] == '/' && argv[optind][i + 1] == '/')
+ {
+ memmove(&argv[optind][i], &argv[optind][i + 1], strlen(&argv[optind][i + 1]) + 1);
+ i--;
+ }
+ }
+
+ if (parent)
+ {
+ char *tmp;
+ char *searched = argv[optind];
+
+ if (*searched == '/')
+ searched++;
+ for (; (tmp = strchr(searched, '/')) != NULL; searched = tmp + 1)
+ {
+ if (tmp[1] == '\0')
+ continue;
+ *tmp = '\0';
+ mkdir_wrapper(argv[optind], mode, verbose);
+ *tmp = '/';
+ }
+ mkdir_wrapper(argv[optind], mode, verbose);
+ }
+ else
+ mkdir_wrapper(argv[optind], mode, verbose);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/src/mv.c b/src/mv.c
new file mode 100644
index 0000000..46eab14
--- /dev/null
+++ b/src/mv.c
@@ -0,0 +1,37 @@
+#define _POSIX_C_SOURCE 2
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+int main(int argc, char **argv)
+{
+ int option;
+
+ while ((option = getopt(argc, argv, "bfinS:t:Tuv")) != -1)
+ {
+ switch (option)
+ {
+ case 'b':
+ break;
+ case 'f':
+ break;
+ case 'i':
+ break;
+ case 'n':
+ break;
+ case 'S':
+ break;
+ case 't':
+ break;
+ case 'T':
+ break;
+ case 'u':
+ break;
+ case 'v':
+ break;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/rm.c b/src/rm.c
new file mode 100644
index 0000000..2bcf68f
--- /dev/null
+++ b/src/rm.c
@@ -0,0 +1,130 @@
+#define _POSIX_C_SOURCE 200112L
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <dirent.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+typedef enum
+{
+ FLAG_FORCE = 1 << 0,
+ FLAG_INTERACTIVE = 1 << 1,
+ /* FLAG_INTERACTIVE_ONCE = 1 << 2, */ // TODO
+ FLAG_RECURSIVE = 1 << 3,
+ FLAG_DIRECTORY = 1 << 4,
+ FLAG_VERBOSE = 1 << 5,
+} t_flags;
+
+char *g_name = "rm";
+
+void log_errno(char *path)
+{
+ fprintf(stderr, "%s: cannot remove '%s': %s\n", g_name, path, strerror(errno));
+}
+
+void fatal_errno(char *path)
+{
+ log_errno(path);
+ exit(EXIT_FAILURE);
+}
+
+int rm_file(char *path, t_flags flags)
+{
+ struct stat statbuf;
+ char recursive_path[PATH_MAX + 1] = {'\0'};
+
+ if (lstat(path, &statbuf) == -1)
+ fatal_errno(path);
+ if (flags & FLAG_INTERACTIVE
+ || (!(flags & FLAG_FORCE)
+ && !((statbuf.st_mode & S_IWUSR && statbuf.st_uid == getuid()) ||
+ (statbuf.st_mode & S_IWGRP && statbuf.st_gid == getgid()) ||
+ statbuf.st_mode & S_IWOTH)))
+ {
+ printf("%s: remove regular file '%s'? ", g_name, path);
+ if (tolower(getchar()) != 'y')
+ return 0;
+ }
+
+ if (S_ISDIR(statbuf.st_mode) && flags & FLAG_RECURSIVE)
+ {
+ DIR *directory;
+ struct dirent *entry;
+
+ if ((directory = opendir(path)) == NULL)
+ {
+ log_errno(path);
+ return 0;
+ }
+ strcpy(recursive_path, path);
+ if (path[strlen(path) - 1] != '/')
+ strcat(recursive_path, "/");
+ while ((entry = readdir(directory)) != NULL)
+ {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+ strcat(recursive_path, entry->d_name);
+ rm_file(recursive_path, flags);
+ strrchr(recursive_path, '/')[1] = '\0';
+ }
+ closedir(directory);
+ if (rmdir(path) == -1)
+ log_errno(path);
+ else if (flags & FLAG_VERBOSE)
+ printf("removed directory '%s'\n", path);
+ }
+ else if (S_ISDIR(statbuf.st_mode) && flags & FLAG_DIRECTORY)
+ {
+ if (rmdir(path) == -1)
+ log_errno(path);
+ else if (flags & FLAG_VERBOSE)
+ printf("removed directory '%s'\n", path);
+ }
+ else
+ {
+ if (unlink(path) == -1)
+ log_errno(path);
+ else if (flags & FLAG_VERBOSE)
+ printf("removed '%s'\n", path);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc == 1)
+ {
+ fprintf(stderr, "%s: missing operand\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ g_name = argv[0];
+
+ int option;
+ t_flags flags = 0;
+
+ while ((option = getopt(argc, argv, "firRdv")) != -1)
+ {
+ switch (option)
+ {
+ case 'f': flags |= FLAG_FORCE; break;
+ case 'i': flags |= FLAG_INTERACTIVE; break;
+ /* case 'I': flags |= FLAG_INTERACTIVE_ONCE; break; */
+ case 'd': flags |= FLAG_DIRECTORY; break;
+ case 'v': flags |= FLAG_VERBOSE; break;
+
+ case 'r':
+ case 'R':
+ flags |= FLAG_RECURSIVE; break;
+ break;
+ }
+ }
+
+ for (; optind < argc; optind++)
+ rm_file(argv[optind], flags);
+ return EXIT_SUCCESS;
+}
diff --git a/src/seq.c b/src/seq.c
new file mode 100644
index 0000000..8307fc4
--- /dev/null
+++ b/src/seq.c
@@ -0,0 +1,126 @@
+#define _POSIX_C_SOURCE 2
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+typedef struct
+{
+ long double value;
+ int precision;
+ int integer_len;
+} t_num;
+
+char *g_name = "seq";
+
+int parse_num(char *s, t_num *num)
+{
+ char *tmp;
+
+ errno = 0;
+ num->value = strtold(s, &tmp);
+ if (errno != 0 || *tmp != '\0')
+ {
+ fprintf(stderr, "%s: invalid floating point argument: '%s'\n", g_name, s);
+ exit(EXIT_FAILURE);
+ }
+ num->precision = 0;
+ num->integer_len = strlen(s);
+ if ((tmp = strchr(s, '.')) != NULL)
+ {
+ num->precision = strlen(tmp + 1);
+ num->integer_len = tmp - s;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc == 1)
+ {
+ fprintf(stderr, "%s: missing operand\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ int option;
+ char *separator = "\n";
+ bool padding = false;
+
+ g_name = argv[0];
+
+ while ((option = getopt(argc, argv, "f:s:w0123456789")) != -1)
+ {
+ if (isdigit(option))
+ break;
+ switch (option)
+ {
+ case 'f':
+ exit(EXIT_FAILURE); // TODO
+ break;
+ case 's':
+ separator = optarg;
+ break;
+ case 'w':
+ padding = true;
+ break;
+ }
+ }
+
+ t_num first;
+ t_num increment;
+ t_num last;
+
+ first.value = 1.0;
+ first.precision = 0;
+ increment.value = 1.0;
+ increment.precision = 0;
+
+ switch (argc - optind)
+ {
+ case 1:
+ parse_num(argv[optind], &last);
+ break;
+ case 2:
+ parse_num(argv[optind], &first);
+ parse_num(argv[optind + 1], &last);
+ break;
+ case 3:
+ parse_num(argv[optind], &first);
+ parse_num(argv[optind + 1], &increment);
+ if (increment.value == 0)
+ {
+ fprintf(stderr, "%s: invalid Zero increment value '%s'\n", argv[0], argv[optind + 3]);
+ exit(EXIT_FAILURE);
+ }
+ parse_num(argv[optind + 2], &last);
+ break;
+ default:
+ fprintf(stderr, "%s: extra operand '%s'\n", argv[0], argv[optind + 3]);
+ exit(EXIT_FAILURE);
+ break;
+ }
+
+ while (increment.value > 0 ? (first.value <= last.value) : (first.value >= last.value))
+ {
+ int precision = MAX(first.precision, increment.precision);
+ if (!padding)
+ printf("%.*llf", precision, first.value);
+ else
+ {
+ int width = MAX(last.integer_len, first.integer_len);
+ if (precision != 0)
+ width += precision + 1;
+ printf("%0*.*llf", width, precision, first.value);
+ }
+ first.value += increment.value;
+ if (increment.value > 0 ? (first.value <= last.value) : (first.value >= last.value))
+ fputs(separator, stdout);
+ }
+ putchar('\n');
+ return EXIT_SUCCESS;
+}
diff --git a/src/shuf.c b/src/shuf.c
new file mode 100644
index 0000000..5b4808a
--- /dev/null
+++ b/src/shuf.c
@@ -0,0 +1,237 @@
+#define _POSIX_C_SOURCE 200809L
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#define NUMBER_LEN(x) strlen(#x);
+
+static char *g_name = "shuf";
+
+void fatal(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s: ", g_name);
+ vfprintf(stderr, format, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+long parse_ulong(char *str, char *error_msg)
+{
+ char *end;
+ long ret;
+
+ errno = 0;
+ ret = strtol(str, &end, 10);
+ if (errno != 0 || ret < 0 || *end != '\0')
+ fatal("%s '%s'", error_msg, optarg);
+ return ret;
+}
+
+typedef struct
+{
+ char **data;
+ size_t len;
+ size_t cap;
+} t_lines;
+
+#define INITIAL_CAPACITY 64
+
+void lines_init(t_lines *lines)
+{
+ lines->len = 0;
+ lines->cap = INITIAL_CAPACITY;
+ lines->data = malloc(lines->cap * sizeof(char*));
+ if (lines->data == NULL)
+ fatal("%s", strerror(errno));
+}
+
+void lines_destroy(t_lines *lines)
+{
+ for (size_t i = 0; i < lines->len; i++)
+ free(lines->data[i]);
+ free(lines->data);
+}
+
+#define GROWTH_FACTOR 2
+
+void lines_push(t_lines *lines, char *line)
+{
+ if (lines->len >= lines->cap)
+ {
+ lines->cap *= GROWTH_FACTOR;
+ lines->data = realloc(lines->data, lines->cap * sizeof(char*));
+ if (lines->data == NULL)
+ fatal("%s", strerror(errno));
+ }
+ lines->data[lines->len] = line;
+ lines->len++;
+}
+
+#define RANDOM_FILEPATH "/dev/random"
+
+int main(int argc, char **argv)
+{
+ int option;
+ long max_output = -1;
+ char *output_filepath = NULL;
+ bool repeat = false;
+ bool echo = false;
+ char delimiter = '\n';
+ long range_start = -1;
+ long range_stop = -1;
+
+ while ((option = getopt(argc, argv, "ei:n:o:rz")) != -1)
+ {
+
+ switch (option)
+ {
+ case 'e': echo = true; break;
+ case 'r': repeat = true; break;
+ case 'o': output_filepath = optarg; break;
+ case 'z': delimiter = '\0'; break;
+
+ case 'i':
+ {
+ char *hyphen = strchr(optarg, '-');
+ if (hyphen == NULL)
+ fatal("invalid input range '%s'", optarg);
+ *hyphen = '\0';
+ range_start = parse_ulong(optarg, "invalid input range");
+ *hyphen = '-';
+ range_stop = parse_ulong(hyphen + 1, "invalid input range");
+ if (range_start > range_stop)
+ fatal("invalid input range '%s'", optarg);
+ break;
+ }
+
+ case 'n':
+ max_output = parse_ulong(optarg, "invalid line count");
+ break;
+ }
+
+ }
+ if (range_start != -1 && echo)
+ fatal("cannot combine -e and -i options");
+ if (range_start != -1 && optind != argc)
+ fatal("extra operand '%s'", argv[optind]);
+ if (!echo && argc - optind > 1)
+ fatal("extra operand '%s'", argv[optind]);
+
+ unsigned int seed;
+ FILE *seed_file = fopen(RANDOM_FILEPATH, "r");
+ if (seed_file == NULL)
+ fatal("%s: %s", RANDOM_FILEPATH, strerror(errno));
+ fread(&seed, sizeof(seed), 1, seed_file);
+ srand(seed);
+
+ t_lines lines;
+ lines_init(&lines);
+
+ if (range_start != -1)
+ {
+ size_t buf_len = NUMBER_LEN(LONG_MAX);
+ for (; range_start <= range_stop; range_start++)
+ {
+ char *line = malloc(buf_len * sizeof(char));
+ if (line == NULL)
+ fatal("%s", strerror(errno));
+ sprintf(line, "%ld", range_start);
+ lines_push(&lines, line);
+ }
+ }
+ else if (echo)
+ {
+ for (; optind < argc; optind++)
+ {
+ char *line = strdup(argv[optind]);
+ if (line == NULL)
+ fatal("%s", strerror(errno));
+ lines_push(&lines, line);
+ }
+ }
+ else
+ {
+ FILE *input = stdin;
+ if (optind != argc)
+ {
+ input = fopen(argv[optind], "r");
+ if (input == NULL)
+ fatal("%s: %s", argv[optind], strerror(errno));
+ }
+
+ char *line = NULL;
+ size_t line_size = 0;
+ errno = 0;
+ while (getdelim(&line, &line_size, delimiter, input) != -1)
+ {
+ lines_push(&lines, line);
+ line = NULL;
+ line_size = 0;
+ }
+ if (errno != 0)
+ {
+ fclose(input);
+ fatal("%s: %s", argv[optind], strerror(errno));
+ }
+ fclose(input);
+ }
+
+ if (lines.len == 0)
+ {
+ lines_destroy(&lines);
+ return EXIT_SUCCESS;
+ }
+ for (size_t i = lines.len - 1; i > 0; i--)
+ {
+ size_t j = rand() % i;
+ char *tmp = lines.data[i];
+ lines.data[i] = lines.data[j];
+ lines.data[j] = tmp;
+ }
+
+ FILE *output = stdout;
+ if (output_filepath != NULL)
+ {
+ output = fopen(output_filepath, "w");
+ if (output == NULL)
+ fatal("%s: %s", output_filepath, strerror(errno));
+ }
+
+ if (repeat)
+ {
+ for (size_t i = 0; true; i = (i + 1) % lines.len)
+ {
+ if (fputs(lines.data[i], output) == EOF)
+ break;
+ if (echo || range_start != -1)
+ if (fputc('\n', output) == EOF)
+ break;
+ }
+ }
+ else
+ {
+ for (size_t i = 0;
+ max_output == -1 ? (i < lines.len) : (i < (unsigned long)max_output);
+ i++)
+ {
+ if (fputs(lines.data[i], output) == EOF)
+ break;
+ if (echo || range_start != -1)
+ if (fputc('\n', output) == EOF)
+ break;
+ }
+ }
+ fclose(output);
+
+ lines_destroy(&lines);
+ return EXIT_SUCCESS;
+}
diff --git a/src/tee.c b/src/tee.c
new file mode 100644
index 0000000..4df5074
--- /dev/null
+++ b/src/tee.c
@@ -0,0 +1,68 @@
+#define _POSIX_C_SOURCE 2 // getopt
+#define _XOPEN_SOURCE 500 // sigaction
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+
+#define BUFFER_SIZE 2048
+
+char *g_name = "tee";
+
+int main(int argc, char **argv)
+{
+ int option;
+ bool append = false;
+
+ g_name = argv[0];
+
+ struct sigaction action;
+ action.sa_handler = SIG_IGN;
+ action.sa_flags = SA_NODEFER;
+
+ while ((option = getopt(argc, argv, "ai")) != -1)
+ {
+ switch (option)
+ {
+ case 'a':
+ append = true;
+ break;
+ case 'i':
+ sigaction(SIGINT, &action, NULL);
+ break;
+ }
+ }
+
+ size_t files_num = argc - optind;
+ FILE **files = calloc(files_num, sizeof(FILE*));
+ if (files == NULL)
+ return EXIT_FAILURE;
+
+ for (size_t i = 0; i < files_num; i++)
+ {
+ files[i] = fopen(argv[optind + i], append ? "a" : "w");
+ if (files[i] == NULL)
+ {
+ fprintf(stderr, "%s: %s: %s\n", g_name, argv[optind + i], strerror(errno));
+ i--;
+ files_num--;
+ }
+ }
+
+ char buf[BUFFER_SIZE + 1] = {0};
+ size_t read_size = 0;
+ while ((read_size = fread(buf, 1, BUFFER_SIZE, stdin)) > 0)
+ {
+ fwrite(buf, 1, read_size, stdout);
+ for (size_t i = 0; i < files_num; i++)
+ fwrite(buf, 1, read_size, files[i]);
+ }
+
+ for (size_t i = 0; i < files_num; i++)
+ fclose(files[i]);
+ free(files);
+ return EXIT_SUCCESS;
+}
diff --git a/src/tr.c b/src/tr.c
new file mode 100644
index 0000000..a423794
--- /dev/null
+++ b/src/tr.c
@@ -0,0 +1,230 @@
+#define _POSIX_C_SOURCE 2
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+
+typedef enum
+{
+ FLAG_COMPLEMENT = 1 << 0,
+ FLAG_DELETE = 1 << 1,
+ FLAG_SQUEEZE = 1 << 2,
+} t_flags;
+
+
+typedef enum
+{
+ MTAG_CHAR,
+ MTAG_CLASS,
+ MTAG_RANGE,
+ /* MTAG_REPEAT, */
+ /* MTAG_EQUIVALENT, */ // e.g [=e=] == eéèê
+} t_matcher_tag;
+
+typedef struct
+{
+ t_matcher_tag tag;
+ union
+ {
+ char c;
+ int (*class)(int c);
+ char range[2];
+ struct
+ {
+ char c;
+ size_t count;
+ } repeat;
+ };
+} t_matcher;
+
+struct char_class_entry {
+ char *id;
+ int (*func)(int c);
+};
+
+struct char_class_entry char_classes[] = {
+ {"[:alnum:]", isalnum},
+ {"[:alpha:]", isalpha},
+ {"[:blank:]", isblank},
+ {"[:cntrl:]", iscntrl},
+ {"[:digit:]", isdigit},
+ {"[:graph:]", isgraph},
+ {"[:lower:]", islower},
+ {"[:print:]", isprint},
+ {"[:punct:]", ispunct},
+ {"[:space:]", isspace},
+ {"[:upper:]", isupper},
+ {"[:xdigit:]", isxdigit},
+};
+
+typedef struct
+{
+ t_matcher *array;
+ size_t count;
+} t_matchers;
+
+void matchers_push(t_matchers *matchers, t_matcher_tag tag, void *data)
+{
+ t_matcher *pushed;
+
+ matchers->count++;
+ matchers->array = realloc(matchers->array, matchers->count);
+ assert(matchers->array != NULL);
+ pushed = &matchers->array[matchers->count - 1];
+ pushed->tag = tag;
+ switch (tag)
+ {
+ case MTAG_CHAR:
+ pushed->c = *(char*)data;
+ break;
+ case MTAG_CLASS:
+ pushed->class = (int (*)(int))data;
+ break;
+ case MTAG_RANGE:
+ pushed->range[0] = ((short)data & 0x00ff) >> 0;
+ pushed->range[1] = ((short)data & 0xff00) >> 8;
+ break;
+ default:
+ abort();
+ }
+}
+
+bool isodigit(char c) { return c >= '0' && c <= '7'; }
+
+void parse(t_matchers *matchers, char *s)
+{
+ for (size_t i = 0; s[i] != '\0'; i++)
+ {
+ if (s[i] == '\\')
+ {
+ memmove(&s[i], &s[i + 1], strlen(&s[i + 1]));
+
+ switch (s[i])
+ {
+ case 'a': s[i] = '\a'; break;
+ case 'b': s[i] = '\b'; break;
+ case 'f': s[i] = '\f'; break;
+ case 'n': s[i] = '\n'; break;
+ case 'r': s[i] = '\r'; break;
+ case 't': s[i] = '\t'; break;
+ case 'v': s[i] = '\v'; break;
+ }
+
+ if (isodigit(s[i]))
+ {
+ char num[4] = {'\0'};
+ strncpy(num, &s[i], 3);
+ char *end;
+ s[i] = strtol(num, &end, 8);
+ memmove(&s[i + 1], &s[i + 1 + (end - num)], strlen(&s[i + 1 + (end - num)]));
+ }
+ matchers_push(matchers, MTAG_CHAR, (void*)s[i]);
+ }
+ else if (s[i + 1] == '-' && s[i + 2] != '\0')
+ matchers_push(matchers, MTAG_RANGE, (void*)(s[i] | (s[i] << 8)));
+ else if (s[i] == '[' && s[i + 1] == ':')
+ {
+ /* for (size_t j = 0; j < sizeof(char_classes) / sizeof(char_class_entry); j++) */
+ /* { */
+ /* if (strncmp( */
+ /* } */
+ }
+ else
+ matchers_push(matchers, MTAG_CHAR, (void*)s[i]);
+ }
+}
+
+char *g_name = "tr";
+
+void fatal(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s: ", g_name);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+#define BUFFER_SIZE 256
+
+/*
+** no 't' flag, truncate is the default behavior
+*/
+
+int main(int argc, char **argv)
+{
+ int option;
+ t_flags flags = 0;
+
+ while ((option = getopt(argc, argv, "cCds")) != -1)
+ {
+ switch (option)
+ {
+ case 'c':
+ case 'C':
+ flags |= FLAG_COMPLEMENT;
+ break;
+ case 'd':
+ flags |= FLAG_DELETE;
+ break;
+ case 's':
+ flags |= FLAG_SQUEEZE;
+ break;
+ }
+ }
+ if (optind == argc)
+ fatal("missing operand\n");
+ if (argc - optind == 3)
+ fatal("extra operand '%s'\n", argv[argc - 1]);
+
+ if (flags & FLAG_DELETE && flags & FLAG_SQUEEZE && argc - optind != 2)
+ fatal("missing operand after '%s'\n"
+ "Two strings must be given when both deleting and squezzing repeats.\n",
+ argv[optind]);
+
+ if (flags & FLAG_DELETE && argc - optind != 1)
+ fatal("extra operand '%s'\n",
+ "Only one string may be given when deleting without squeezing repeats.\n",
+ argv[optind + 1]);
+
+ t_matchers set1 = { .array = NULL, .count = 0 };
+ t_matchers set2 = { .array = NULL, .count = 0 };
+
+ char buf[BUFFER_SIZE + 1] = {'\0'};
+ size_t read_size;
+
+ while ((read_size = fread(buf, sizeof(char), BUFFER_SIZE, stdin)) > 0)
+ {
+ for (size_t i = 0; i < read_size; i++)
+ {
+ /* if (flags & FLAG_DELETE && flags & FLAG_SQUEEZE) */
+ /* { */
+ /* */
+ /* } */
+ /* else if (flags & FLAG_DELETE && match()) */
+ /* { */
+ /* memmove(&buf[i], &buf[i + 1], read_size - i); */
+ /* */
+ /* } */
+ /* else if (flags & FLAG_SQUEEZE) */
+ /* { */
+ /* while (match(s[i + 1])) */
+ /* */
+ /* memmove(&buf[i + 1], &buf[i + 2], read_size - i); */
+ /* } */
+ /* else */
+ /* { */
+ /* if (match()) */
+ /* buf[i] = set2 */
+ /* } */
+ }
+ }
+
+ return EXIT_SUCCESS;
+}