Thank you Roberto for your proposal, I changed `isspace(3)` to `isblank(3)` as it is unlikely for a user to write leading `\v`, `\f`, `\r`, and it prevents checking `\n` twice. You are right `toupper(3)` is correct, however the direct comparison is the fastest and most efficient option:
1) c == 'y' || c == 'Y' Easily optimized and branch-predicted by the CPU, just a couple of instructions (compare + jump). No memory access, no function call. e.g: cmp c, 'y' ; compare c to 'y' (0x79) je matched ; if equal, jump cmp c, 'Y' ; compare c to 'Y' (0x59) je matched ; if equal, jump 2) toupper(c) == 'Y' This is usually involve a library call or locale table lookup (especially in locale-aware systems) unless the compiler can inline it. It often require checking if c is between 'a' and 'z', if so, subtracting 32 to make it uppercase. It is more instructions, more branches. e.g: movzx eax, byte ptr [c] ; zero-extend c into eax cmp al, 'a' jb not_lower cmp al, 'z' ja not_lower sub al, 0x20 ; convert to uppercase not_lower: cmp al, 'Y' je matched Finally, I took the liberty of exposing `xvprintf(3)` and moved the logic in a new `confirm.c` file in order to be reused (e.g: `mv(1)`, `rm(1)`). ---8< >From b391f3e2f6483d9986905688091f0cdec60251b7 Thu Apr 10 00:00:00 2025 From: Thibaut Aubin <t.aubi...@ejm.org> Date: Thu, 10 Apr 2025 00:00:00 +0000 Subject: [PATCH] cp: add -i flag --- Makefile | 1 + README | 2 +- cp.1 | 6 ++++-- cp.c | 6 +++++- fs.h | 1 + libutil/confirm.c | 21 +++++++++++++++++++++ libutil/cp.c | 8 ++++++++ libutil/eprintf.c | 2 -- util.h | 3 +++ 9 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 libutil/confirm.c diff --git a/Makefile b/Makefile index 165eeed..2d409ff 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,7 @@ LIBUTILOBJ =\ libutil/concat.o\ libutil/cp.o\ libutil/crypt.o\ + libutil/confirm.o\ libutil/ealloc.o\ libutil/enmasse.o\ libutil/eprintf.o\ diff --git a/README b/README index 698eae3..fd66095 100644 --- a/README +++ b/README @@ -56,7 +56,7 @@ The following tools are implemented: 0=*|o cmp . 0#*|x cols . 0=*|o comm . -0=*|o cp (-i) +0=*|o cp . 0=*|x cron . 0#*|o cut . 0=*|o date . diff --git a/cp.1 b/cp.1 index 93f2bc0..08c169a 100644 --- a/cp.1 +++ b/cp.1 @@ -1,4 +1,4 @@ -.Dd October 8, 2015 +.Dd April 10, 2025 .Dt CP 1 .Os sbase .Sh NAME @@ -6,7 +6,7 @@ .Nd copy files and directories .Sh SYNOPSIS .Nm -.Op Fl afpv +.Op Fl afipv .Oo .Fl R .Op Fl H | L | P @@ -37,6 +37,8 @@ and If an existing .Ar dest cannot be opened, remove it and try again. +.It Fl i +Interactive prompt before overwrite. .It Fl p Preserve mode, timestamp and permissions. .It Fl v diff --git a/cp.c b/cp.c index 6abe02c..c6b32f5 100644 --- a/cp.c +++ b/cp.c @@ -7,7 +7,7 @@ static void usage(void) { - eprintf("usage: %s [-afpv] [-R [-H | -L | -P]] source ... dest\n", argv0); + eprintf("usage: %s [-afipv] [-R [-H | -L | -P]] source ... dest\n", argv0); } int @@ -16,6 +16,9 @@ main(int argc, char *argv[]) struct stat st; ARGBEGIN { + case 'i': + cp_iflag = 1; + break; case 'a': cp_follow = 'P'; cp_aflag = cp_pflag = cp_rflag = 1; @@ -58,3 +61,4 @@ main(int argc, char *argv[]) return fshut(stdout, "<stdout>") || cp_status; } + diff --git a/fs.h b/fs.h index 00ecd3b..f8a9322 100644 --- a/fs.h +++ b/fs.h @@ -28,6 +28,7 @@ enum { extern int cp_aflag; extern int cp_fflag; +extern int cp_iflag; extern int cp_pflag; extern int cp_rflag; extern int cp_vflag; diff --git a/libutil/confirm.c b/libutil/confirm.c new file mode 100644 index 0000000..4fa48fb --- /dev/null +++ b/libutil/confirm.c @@ -0,0 +1,21 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <ctype.h> + +#include "../util.h" + +int confirm(const char *fmt, ...) { + int c, ans; + va_list ap; + + va_start(ap, fmt); + xvprintf(fmt, ap); + va_end(ap); + + while (isblank(c = getchar())) + ; + ans = c; + while (c != '\n' && c != EOF) + c = getchar(); + return ans == 'y' || ans == 'Y'; +} diff --git a/libutil/cp.c b/libutil/cp.c index 23275ac..cd94858 100644 --- a/libutil/cp.c +++ b/libutil/cp.c @@ -16,6 +16,7 @@ int cp_aflag = 0; int cp_fflag = 0; +int cp_iflag = 0; int cp_pflag = 0; int cp_rflag = 0; int cp_vflag = 0; @@ -42,6 +43,13 @@ cp(const char *s1, const char *s2, int depth) return 0; } + if (cp_iflag && access(s2, F_OK) == 0) { + if (!confirm("overwrite '%s'? ", s2)) { + cp_status = 1; + return 0; + } + } + if (cp_vflag) printf("%s -> %s\n", s1, s2); diff --git a/libutil/eprintf.c b/libutil/eprintf.c index 673523e..7197fbb 100644 --- a/libutil/eprintf.c +++ b/libutil/eprintf.c @@ -8,8 +8,6 @@ char *argv0; -static void xvprintf(const char *, va_list); - void eprintf(const char *fmt, ...) { diff --git a/util.h b/util.h index 346f6ca..845e7c0 100644 --- a/util.h +++ b/util.h @@ -43,6 +43,9 @@ int fshut(FILE *, const char *); void enprintf(int, const char *, ...); void eprintf(const char *, ...); void weprintf(const char *, ...); +void xvprintf(const char *, va_list); + +int confirm(const char*, ...); double estrtod(const char *); -- 2.48.1