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

Reply via email to