#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)args.c 8.3 (Berkeley) 4/2/94";
#else
__RCSID("$NetBSD: args.c,v 1.44 2026/01/26 08:37:29 kre Exp $");
#endif
#endif
#include <sys/types.h>
#include <sys/time.h>
#ifndef NO_IOFLAG
#include <fcntl.h>
#endif
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dd.h"
#include "extern.h"
static int c_arg(const void *, const void *);
#ifdef NO_MSGFMT
static void f_msgfmt(char *) __dead;
#else
static void f_msgfmt(char *);
#endif
#ifdef NO_CONV
static void f_conv(char *) __dead;
#else
static void f_conv(char *);
static int c_conv(const void *, const void *);
#endif
#ifdef NO_IOFLAG
static void f_iflag(char *) __dead;
static void f_oflag(char *) __dead;
#else
static void f_iflag(char *);
static void f_oflag(char *);
static u_int f_ioflag(char *, u_int);
static int c_ioflag(const void *, const void *);
#endif
static void f_bs(char *);
static void f_cbs(char *);
static void f_count(char *);
static void f_files(char *);
static void f_ibs(char *);
static void f_if(char *);
static void f_obs(char *);
static void f_of(char *);
static void f_seek(char *);
static void f_skip(char *);
static void f_progress(char *);
static const struct arg {
const char *name;
void (*f)(char *);
u_int set, noset;
} args[] = {
{ "bs", f_bs, C_BS, C_BS|C_IBS|C_OBS|C_OSYNC },
{ "cbs", f_cbs, C_CBS, C_CBS },
{ "conv", f_conv, 0, 0 },
{ "count", f_count, C_COUNT, C_COUNT },
{ "files", f_files, C_FILES, C_FILES },
{ "ibs", f_ibs, C_IBS, C_BS|C_IBS },
{ "if", f_if, C_IF, C_IF },
{ "iflag", f_iflag, C_IFLAG, C_IFLAG },
{ "iseek", f_skip, C_SKIP, C_SKIP },
{ "msgfmt", f_msgfmt, 0, 0 },
{ "obs", f_obs, C_OBS, C_BS|C_OBS },
{ "of", f_of, C_OF, C_OF },
{ "oflag", f_oflag, C_OFLAG, C_OFLAG },
{ "oseek", f_seek, C_SEEK, C_SEEK },
{ "progress", f_progress, 0, 0 },
{ "seek", f_seek, C_SEEK, C_SEEK },
{ "skip", f_skip, C_SKIP, C_SKIP },
};
void
jcl(char **argv)
{
const struct arg *ap;
struct arg tmp;
char *oper, *arg;
in.dbsz = out.dbsz = 512;
while ((oper = *++argv) != NULL) {
if ((oper = strdup(oper)) == NULL) {
errx(EXIT_FAILURE,
"unable to allocate space for the argument %s",
*argv);
}
if ((arg = strchr(oper, '=')) == NULL) {
errx(EXIT_FAILURE, "unknown operand %s", oper);
}
*arg++ = '\0';
if (!*arg) {
errx(EXIT_FAILURE, "no value specified for %s", oper);
}
tmp.name = oper;
if (!(ap = bsearch(&tmp, args,
__arraycount(args), sizeof(*args), c_arg))) {
errx(EXIT_FAILURE, "unknown operand %s", tmp.name);
}
if (ddflags & ap->noset) {
errx(EXIT_FAILURE,
"%s: illegal argument combination or already set",
tmp.name);
}
ddflags |= ap->set;
ap->f(arg);
}
if (ddflags & C_BS) {
if (ddflags & (C_BLOCK | C_LCASE | C_SWAB | C_UCASE |
C_UNBLOCK | C_OSYNC | C_ASCII | C_EBCDIC | C_SPARSE)) {
ddflags &= ~C_BS;
ddflags |= C_IBS|C_OBS;
}
if (ddflags & C_BS && ddflags & (C_IBS|C_OBS))
warnx("bs supersedes ibs and obs");
}
if (ddflags & (C_BLOCK|C_UNBLOCK)) {
if (!(ddflags & C_CBS)) {
errx(EXIT_FAILURE, "record operations require cbs");
}
cfunc = ddflags & C_BLOCK ? block : unblock;
} else if (ddflags & C_CBS) {
if (ddflags & (C_ASCII|C_EBCDIC)) {
if (ddflags & C_ASCII) {
ddflags |= C_UNBLOCK;
cfunc = unblock;
} else {
ddflags |= C_BLOCK;
cfunc = block;
}
} else {
errx(EXIT_FAILURE,
"cbs meaningless if not doing record operations");
}
} else
cfunc = def;
}
static int
c_arg(const void *a, const void *b)
{
return (strcmp(((const struct arg *)a)->name,
((const struct arg *)b)->name));
}
static void
f_bs(char *arg)
{
in.dbsz = out.dbsz = strsuftoll("block size", arg, 1, UINT_MAX);
}
static void
f_cbs(char *arg)
{
cbsz = strsuftoll("conversion record size", arg, 1, UINT_MAX);
}
static void
f_count(char *arg)
{
cpy_cnt = strsuftoll("block count", arg, 0, LLONG_MAX);
}
static void
f_files(char *arg)
{
files_cnt = (u_int)strsuftoll("file count", arg, 0, UINT_MAX);
if (!files_cnt) {
summary();
exit(0);
}
}
static void
f_ibs(char *arg)
{
if (!(ddflags & C_BS))
in.dbsz = strsuftoll("input block size", arg, 1, UINT_MAX);
}
static void
f_if(char *arg)
{
in.name = arg;
}
#ifdef NO_MSGFMT
static void
f_msgfmt(char *arg)
{
errx(EXIT_FAILURE, "msgfmt option disabled");
}
#else
static void
f_msgfmt(char *arg)
{
dd_write_msg(arg, 0);
msgfmt = arg;
}
#endif
static void
f_obs(char *arg)
{
if (!(ddflags & C_BS))
out.dbsz = strsuftoll("output block size", arg, 1, UINT_MAX);
}
static void
f_of(char *arg)
{
out.name = arg;
}
static void
f_seek(char *arg)
{
out.offset = strsuftoll("seek blocks", arg, 0, LLONG_MAX);
}
static void
f_skip(char *arg)
{
in.offset = strsuftoll("skip blocks", arg, 0, LLONG_MAX);
}
static void
f_progress(char *arg)
{
progress = strsuftoll("progress blocks", arg, 0, LLONG_MAX);
}
#ifdef NO_CONV
static void
f_conv(char *arg)
{
errx(EXIT_FAILURE, "conv option disabled");
}
#else
static const struct conv {
const char *name;
u_int set, noset;
const u_char *ctab;
} clist[] = {
{ "ascii", C_ASCII, C_EBCDIC, e2a_POSIX },
{ "block", C_BLOCK, C_UNBLOCK, NULL },
{ "ebcdic", C_EBCDIC, C_ASCII, a2e_POSIX },
{ "ibm", C_EBCDIC, C_ASCII, a2ibm },
{ "lcase", C_LCASE, C_UCASE, NULL },
{ "noerror", C_NOERROR, 0, NULL },
{ "notrunc", C_NOTRUNC, 0, NULL },
{ "oldascii", C_ASCII, C_EBCDIC, e2a_32V },
{ "oldebcdic", C_EBCDIC, C_ASCII, a2e_32V },
{ "oldibm", C_EBCDIC, C_ASCII, a2ibm },
{ "osync", C_OSYNC, C_BS, NULL },
{ "sparse", C_SPARSE, 0, NULL },
{ "swab", C_SWAB, 0, NULL },
{ "sync", C_SYNC, 0, NULL },
{ "ucase", C_UCASE, C_LCASE, NULL },
{ "unblock", C_UNBLOCK, C_BLOCK, NULL },
};
static void
f_conv(char *arg)
{
const struct conv *cp;
struct conv tmp;
while (arg != NULL) {
tmp.name = strsep(&arg, ",");
if (!(cp = bsearch(&tmp, clist,
__arraycount(clist), sizeof(*clist), c_conv))) {
errx(EXIT_FAILURE, "unknown conversion %s", tmp.name);
}
if (ddflags & cp->noset) {
errx(EXIT_FAILURE,
"%s: illegal conversion combination", tmp.name);
}
ddflags |= cp->set;
if (cp->ctab)
ctab = cp->ctab;
}
}
static int
c_conv(const void *a, const void *b)
{
return (strcmp(((const struct conv *)a)->name,
((const struct conv *)b)->name));
}
#endif
static void
f_iflag(char *arg)
{
#ifdef NO_IOFLAG
errx(EXIT_FAILURE, "iflag option disabled");
#else
iflag = f_ioflag(arg, C_IFLAG);
return;
#endif
}
static void
f_oflag(char *arg)
{
#ifdef NO_IOFLAG
errx(EXIT_FAILURE, "oflag option disabled");
#else
oflag = f_ioflag(arg, C_OFLAG);
return;
#endif
}
#ifndef NO_IOFLAG
static const struct ioflag {
const char *name;
u_int set;
u_int allowed;
} olist[] = {
{ "alt_io", O_ALT_IO, C_IFLAG|C_OFLAG },
{ "append", O_APPEND, C_OFLAG },
{ "async", O_ASYNC, C_IFLAG|C_OFLAG },
{ "cloexec", O_CLOEXEC, C_IFLAG|C_OFLAG },
{ "creat", O_CREAT, C_OFLAG },
{ "direct", O_DIRECT, C_IFLAG|C_OFLAG },
{ "directory", O_DIRECTORY, C_NONE },
{ "dsync", O_DSYNC, C_OFLAG },
{ "excl", O_EXCL, C_OFLAG },
{ "exlock", O_EXLOCK, C_IFLAG|C_OFLAG },
{ "noctty", O_NOCTTY, C_IFLAG|C_OFLAG },
{ "nofollow", O_NOFOLLOW, C_IFLAG|C_OFLAG },
{ "nonblock", O_NONBLOCK, C_IFLAG|C_OFLAG },
{ "nosigpipe", O_NOSIGPIPE, C_IFLAG|C_OFLAG },
{ "rdonly", O_RDONLY, C_IFLAG },
{ "rdwr", O_RDWR, C_IFLAG },
{ "rsync", O_RSYNC, C_IFLAG },
{ "shlock", O_SHLOCK, C_IFLAG|C_OFLAG },
{ "sync", O_SYNC, C_IFLAG|C_OFLAG },
{ "trunc", O_TRUNC, C_OFLAG },
{ "wronly", O_WRONLY, C_OFLAG },
};
static u_int
f_ioflag(char *arg, u_int flagtype)
{
u_int ioflag = 0;
const struct ioflag *cp;
struct ioflag tmp;
const char *flagstr = (flagtype == C_IFLAG) ? "iflag" : "oflag";
while (arg != NULL) {
tmp.name = strsep(&arg, ",");
if (!(cp = bsearch(&tmp, olist,
__arraycount(olist), sizeof(*olist), c_ioflag))) {
errx(EXIT_FAILURE, "unknown %s %s", flagstr, tmp.name);
}
if ((cp->set & O_ACCMODE) && (flagtype == C_OFLAG)) {
warnx("rdonly, rdwr and wronly are ignored for oflag");
continue;
}
if ((cp->allowed & flagtype) == 0) {
warnx("%s set for %s but makes no sense",
cp->name, flagstr);
}
ioflag |= cp->set;
}
return ioflag;
}
static int
c_ioflag(const void *a, const void *b)
{
return (strcmp(((const struct ioflag *)a)->name,
((const struct ioflag *)b)->name));
}
#endif