From 81fed70379578f4fe6255fdf33699675179ffe91 Mon Sep 17 00:00:00 2001 From: Nicholas Noll Date: Thu, 18 Jun 2020 19:49:01 -0700 Subject: feat: small coreutils added --- sys/cmd/cp/cp.c | 75 +++++ sys/cmd/cp/rules.mk | 26 ++ sys/cmd/echo/echo.c | 39 +++ sys/cmd/echo/rules.mk | 13 + sys/cmd/filter/filter.c | 104 ++++++ sys/cmd/filter/rules.mk | 13 + sys/cmd/ic/.gitignore | 3 + sys/cmd/ic/LICENSE | 23 ++ sys/cmd/ic/ic.1 | 100 ++++++ sys/cmd/ic/ic.c | 880 ++++++++++++++++++++++++++++++++++++++++++++++++ sys/cmd/ic/rules.mk | 14 + sys/cmd/ic/strlcpy.c | 32 ++ sys/cmd/ls/ls.c | 36 ++ sys/cmd/ls/rules.mk | 13 + sys/cmd/mv/mv.c | 130 +++++++ sys/cmd/mv/rules.mk | 13 + sys/cmd/rm/rm.c | 119 +++++++ sys/cmd/rm/rules.mk | 13 + 18 files changed, 1646 insertions(+) create mode 100644 sys/cmd/cp/cp.c create mode 100644 sys/cmd/cp/rules.mk create mode 100644 sys/cmd/echo/echo.c create mode 100644 sys/cmd/echo/rules.mk create mode 100644 sys/cmd/filter/filter.c create mode 100644 sys/cmd/filter/rules.mk create mode 100644 sys/cmd/ic/.gitignore create mode 100644 sys/cmd/ic/LICENSE create mode 100644 sys/cmd/ic/ic.1 create mode 100644 sys/cmd/ic/ic.c create mode 100644 sys/cmd/ic/rules.mk create mode 100644 sys/cmd/ic/strlcpy.c create mode 100644 sys/cmd/ls/ls.c create mode 100644 sys/cmd/ls/rules.mk create mode 100644 sys/cmd/mv/mv.c create mode 100644 sys/cmd/mv/rules.mk create mode 100644 sys/cmd/rm/rm.c create mode 100644 sys/cmd/rm/rules.mk (limited to 'sys') diff --git a/sys/cmd/cp/cp.c b/sys/cmd/cp/cp.c new file mode 100644 index 0000000..dfe2922 --- /dev/null +++ b/sys/cmd/cp/cp.c @@ -0,0 +1,75 @@ +#include +#include + +static struct Flags { + uchar a : 1; + uchar f : 1; + uchar p : 1; + uchar v : 1; + uchar r : 1; +} flag; +static char follow; + +static +int +cp(char *from, char *to) +{ + +} + +/* + * P -> never dereference + * L -> dereference links + * H -> dereference links on the first level + */ +static +void +usage(void) +{ + fputs("usage: cp [-afpv] [-[r|R] [-H | -L | -P]] source ... dest", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + io·Stat info; + + ARGBEGIN { + case 'a': + follow = 'P'; + flag.a = flag.p = flag.r = 1; + break; + case 'f': + flag.f = 1; + break; + case 'p': + flag.p = 1; + break; + case 'r': + /* fallthrough */ + case 'R': + flag.r = 1; + break; + case 'H': + case 'L': + case 'P': + follow = ARGC(); + break; + default: + usage(); + } ARGEND; + + if (argc < 2) + usage(); + + if (!follow) + follow = flag.r ? 'P' : 'L'; + + if (argc > 2) { + if (stat(argv[argc-1], &info) < 0) + errorf("stat %s: %s", argv[argc-1], strerror(errno)); + if (!S_ISDIR(info.st_mode)) + errorf("%s: is not a directory", argv[argc-1]); + } +} diff --git a/sys/cmd/cp/rules.mk b/sys/cmd/cp/rules.mk new file mode 100644 index 0000000..500b001 --- /dev/null +++ b/sys/cmd/cp/rules.mk @@ -0,0 +1,26 @@ +include share/push.mk +# Iterate through subdirectory tree + +# Local sources +SRCS_$(d) := +LIBS_$(d) := +BINS_$(d) := +TSTS_$(d) := + +include share/paths.mk + +# Local rules +# $(LIBS_$(d)) = TCFLAGS := +# $(LIBS_$(d)) = TCINCS := +# $(LIBS_$(d)) = TCLIBS := + +$(LIBS_$(d)): $(OBJS_$(d)) + $(ARCHIVE) + +$(BINS_$(d)): $(OBJS_$(d)) + $(LINK) + +$(UNTS_$(d)): $(TOBJS_$(d)) $(LIBS_$(d)) + $(LINK) + +include share/pop.mk diff --git a/sys/cmd/echo/echo.c b/sys/cmd/echo/echo.c new file mode 100644 index 0000000..adac611 --- /dev/null +++ b/sys/cmd/echo/echo.c @@ -0,0 +1,39 @@ +#include +#include + +int +main(int argc, char *argv[]) +{ + char *s, *b, *c; + int i, nnl, len; + static char buf[2*1024]; + + nnl = (argc>1) && strcmp(argv[1], "-n")==0; + + len = 1; + for(i = 1+nnl; i < argc; i++) + len += strlen(argv[i])+1; + + if (len >= arrlen(buf)) { + s = b = calloc(len, sizeof *s); + if (!s) + exits("no memory"); + } else + s = b = buf; + + for (i = 1+nnl; i < argc; i++) { + c = argv[i]; + while(*c) + *b++ = *c++; + if (i < argc-1) + *b++ = ' '; + } + + if (!nnl) + *b++ = '\n'; + + if (write(1, s, b-s) < 0) + exits("write error"); + + exit(0); +} diff --git a/sys/cmd/echo/rules.mk b/sys/cmd/echo/rules.mk new file mode 100644 index 0000000..5f531ff --- /dev/null +++ b/sys/cmd/echo/rules.mk @@ -0,0 +1,13 @@ +include share/push.mk + +# Local sources +SRCS_$(d) := $(d)/echo.c +BINS_$(d) := $(d)/echo + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/sys/cmd/filter/filter.c b/sys/cmd/filter/filter.c new file mode 100644 index 0000000..271c544 --- /dev/null +++ b/sys/cmd/filter/filter.c @@ -0,0 +1,104 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include + +#define FLAG(x) (flag[(x)-'a']) + +static void filter(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static +void +filter(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (n && line[n - 1] == '\n') + line[n - 1] = '\0'; + filter(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* filter directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + filter(path, d->d_name); + } + closedir(dir); + } else { + filter(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/sys/cmd/filter/rules.mk b/sys/cmd/filter/rules.mk new file mode 100644 index 0000000..4973772 --- /dev/null +++ b/sys/cmd/filter/rules.mk @@ -0,0 +1,13 @@ +include share/push.mk + +# Local sources +SRCS_$(d) := $(d)/filter.c +BINS_$(d) := $(d)/filter + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/sys/cmd/ic/.gitignore b/sys/cmd/ic/.gitignore new file mode 100644 index 0000000..43d0692 --- /dev/null +++ b/sys/cmd/ic/.gitignore @@ -0,0 +1,3 @@ +ii +*.a +*.o diff --git a/sys/cmd/ic/LICENSE b/sys/cmd/ic/LICENSE new file mode 100644 index 0000000..a5816a8 --- /dev/null +++ b/sys/cmd/ic/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +(C)opyright 2014-2018 Hiltjo Posthuma +(C)opyright 2005-2006 Anselm R. Garbe +(C)opyright 2005-2011 Nico Golde + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sys/cmd/ic/ic.1 b/sys/cmd/ic/ic.1 new file mode 100644 index 0000000..3302dad --- /dev/null +++ b/sys/cmd/ic/ic.1 @@ -0,0 +1,100 @@ +.TH II 1 ic\-VERSION +.SH NAME +ic \- irc it or irc improved +.SH DESCRIPTION +.B ic +is a minimalistic FIFO and filesystem based IRC client. +It creates an irc directory tree with server, channel and +nick name directories. +In every directory a FIFO file (in) and normal file (out) +is placed. This will be for example ~/irc/irc.freenode.net/. +The in file is used to communicate with the servers and the out +files includes the server messages. For every channel and every nick +name there will be new in and out files. +The basic idea of this is to be able to communicate with an IRC +server with basic command line tools. +For example if you will join a channel just do echo "/j #channel" > in +and ic creates a new channel directory with in and out file. +.SH SYNOPSIS +.B ic +.RB < \-s +.IR servername > +.RB [ \-p +.IR port ] +.RB [ \-k +.IR "environment variable" ] +.RB [ \-i +.IR prefix ] +.RB [ \-n +.IR nickname ] +.RB [ \-f +.IR realname ] +.RB < \-u +.IR sockname > +.SH OPTIONS +.TP +.BI \-s " servername" +server to connect to, for example: irc.freenode.net +.TP +.BI \-u " sockname" +connect to a UNIX domain socket instead of directly to a server. +.TP +.BI \-p " port" +lets you override the default port (6667) +.TP +.BI \-k " environment variable" +lets you specify an environment variable that contains your IRC password, e.g. IIPASS="foobar" ic -k IIPASS. +This is done in order to prevent other users from eavesdropping the server password via the process list. +.TP +.BI \-i " prefix" +lets you override the default irc path (~/irc) +.TP +.BI \-n " nickname" +lets you override the default nick ($USER) +.TP +.BI \-f " realname" +lets you specify your real name associated with your nick +.SH DIRECTORIES +.TP +.B ~/irc +In this directory the irc tree will be created. In this directory you +will find a directory for your server (default: irc.freenode.net) in +which the FIFO and the output file will be stored. +If you join a channel a new directory with the name of the channel +will be created in the ~/irc/$servername/ directory. +.SH COMMANDS +.TP +.BI /a " []" +mark yourself as away +.TP +.BI /j " #channel/nickname []" +join a channel or open private conversation with user +.TP +.BI /l " [reason]" +leave a channel or query +.TP +.BI /n " nick" +change the nick name +.TP +.BI /q " [reason]" +quit ic +.TP +.BI /t " topic" +set the topic of a channel +.SH RAW COMMANDS +.LP +Everything which is not a command will be posted into the channel or to the server. +So if you need /who just write /WHO as described in RFC#1459 to the server in FIFO. +.SH SSL PROTOCOL SUPPORT +.LP +For TLS/SSL protocol support you can connect to a local tunnel, for example with stunnel or socat. +.SH CONTACT +.LP +Subscribe to the mailinglist and write to dev (at) suckless (dot) org for suggestions, fixes, etc. +.SH AUTHORS +ic engineers, see LICENSE file +.SH SEE ALSO +.BR echo (1), +.BR tail (1) +.SH BUGS +Please report them! diff --git a/sys/cmd/ic/ic.c b/sys/cmd/ic/ic.c new file mode 100644 index 0000000..50ce8c1 --- /dev/null +++ b/sys/cmd/ic/ic.c @@ -0,0 +1,880 @@ +/* See LICENSE file for license details. */ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifdef NEED_STRLCPY +size_t strlcpy(char *, const char *, size_t); +#endif + +#define IRC_CHANNEL_MAX 200 +#define IRC_MSG_MAX 512 /* guaranteed to be <= than PIPE_BUF */ +#define PING_TIMEOUT 300 + +enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST }; + +typedef struct Channel Channel; +struct Channel { + int fdin; + char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */ + char inpath[PATH_MAX]; /* input path */ + char outpath[PATH_MAX]; /* output path */ + Channel *next; +}; + +static Channel * channel_add(const char *); +static Channel * channel_find(const char *); +static Channel * channel_join(const char *); +static void channel_leave(Channel *); +static Channel * channel_new(const char *); +static void channel_normalize_name(char *); +static void channel_normalize_path(char *); +static int channel_open(Channel *); +static void channel_print(Channel *, const char *); +static int channel_reopen(Channel *); +static void channel_rm(Channel *); + +static void create_dirtree(const char *); +static void create_filepath(char *, size_t, const char *, const char *, const char *); +static void ewritestr(int, const char *); +static void handle_channels_input(int, Channel *); +static void handle_server_output(int); +static int isnumeric(const char *); +static void loginkey(int, const char *); +static void loginuser(int, const char *, const char *); +static void proc_channels_input(int, Channel *, char *); +static void proc_channels_privmsg(int, Channel *, char *); +static void proc_server_cmd(int, char *); +static int read_line(int, char *, size_t); +static void run(int, const char *); +static void setup(void); +static void sighandler(int); +static int tcpopen(const char *, const char *); +static size_t tokenize(char **, size_t, char *, int); +static int udsopen(const char *); +static void usage(void); + +static int isrunning = 1; +static time_t last_response = 0; +static Channel *channels = nil; +static Channel *channelmaster = nil; +static char nick[32], _nick[arrlen(nick)]; /* active nickname at runtime */ +static char ircpath[PATH_MAX]; /* irc dir (-i) */ +static char msg[IRC_MSG_MAX]; /* message buf used for communication */ + +static +void +usage(void) +{ + fprintf(stderr, "usage: %s <-s host> [-i ] [-p ] " + "[-u ] [-n ] [-k ] " + "[-f ]\n", argv0); + exit(1); +} + +static +void +ewritestr(int fd, const char *s) +{ + size_t len, off = 0; + int w = -1; + + len = strlen(s); + for (off = 0; off < len; off += w) { + if ((w = write(fd, s + off, len - off)) == -1) + break; + off += w; + } + if (w == -1) { + fprintf(stderr, "%s: write: %s\n", argv0, strerror(errno)); + exit(1); + } +} + +/* creates directories bottom-up, if necessary */ +static +void +create_dirtree(const char *dir) +{ + char tmp[PATH_MAX], *p; + struct stat st; + size_t len; + + strlcpy(tmp, dir, sizeof(tmp)); + len = strlen(tmp); + if (len > 0 && tmp[len - 1] == '/') + tmp[len - 1] = '\0'; + + if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode)) + return; /* dir exists */ + + for (p = tmp + 1; *p; p++) { + if (*p != '/') + continue; + *p = '\0'; + mkdir(tmp, S_IRWXU); + *p = '/'; + } + mkdir(tmp, S_IRWXU); +} + +static +void +channel_normalize_path(char *s) +{ + for (; *s; s++) { + if (isalpha((unsigned char)*s)) + *s = tolower((unsigned char)*s); + else if (!isdigit((unsigned char)*s) && !strchr(".#&+!-", *s)) + *s = '_'; + } +} + +static +void +channel_normalize_name(char *s) +{ + char *p; + + while (*s == '&' || *s == '#') + s++; + for (p = s; *s; s++) { + if (!strchr(" ,&#\x07", *s)) { + *p = *s; + p++; + } + } + *p = '\0'; +} + +static +void +create_filepath(char *filepath, size_t len, const char *path, + const char *channel, const char *suffix) +{ + int r; + + if (channel[0]) { + r = snprintf(filepath, len, "%s/%s", path, channel); + if (r < 0 || (size_t)r >= len) + goto error; + create_dirtree(filepath); + r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix); + if (r < 0 || (size_t)r >= len) + goto error; + } else { + r = snprintf(filepath, len, "%s/%s", path, suffix); + if (r < 0 || (size_t)r >= len) + goto error; + } + return; + +error: + fprintf(stderr, "%s: path to irc directory too long\n", argv0); + exit(1); +} + +static +int +channel_open(Channel *c) +{ + int fd; + struct stat st; + + /* make "in" fifo if it doesn't exist already. */ + if (lstat(c->inpath, &st) != -1) { + if (!(st.st_mode & S_IFIFO)) + return -1; + } else if (mkfifo(c->inpath, S_IRWXU)) { + return -1; + } + c->fdin = -1; + fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0); + if (fd == -1) + return -1; + c->fdin = fd; + + return 0; +} + +static +int +channel_reopen(Channel *c) +{ + if (c->fdin > 2) { + close(c->fdin); + c->fdin = -1; + } + return channel_open(c); +} + +static +Channel * +channel_new(const char *name) +{ + Channel *c; + char channelpath[PATH_MAX]; + + strlcpy(channelpath, name, sizeof(channelpath)); + channel_normalize_path(channelpath); + + if (!(c = calloc(1, sizeof(Channel)))) { + fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno)); + exit(1); + } + + strlcpy(c->name, name, sizeof(c->name)); + channel_normalize_name(c->name); + + create_filepath(c->inpath, sizeof(c->inpath), ircpath, + channelpath, "in"); + create_filepath(c->outpath, sizeof(c->outpath), ircpath, + channelpath, "out"); + return c; +} + +static +Channel * +channel_find(const char *name) +{ + Channel *c; + char chan[IRC_CHANNEL_MAX]; + + strlcpy(chan, name, sizeof(chan)); + channel_normalize_name(chan); + for (c = channels; c; c = c->next) { + if (!strcmp(chan, c->name)) + return c; /* already handled */ + } + return nil; +} + +static +Channel * +channel_add(const char *name) +{ + Channel *c; + + c = channel_new(name); + if (channel_open(c) == -1) { + fprintf(stderr, "%s: cannot create channel: %s: %s\n", + argv0, name, strerror(errno)); + free(c); + return nil; + } + if (!channels) { + channels = c; + } else { + c->next = channels; + channels = c; + } + return c; +} + +static +Channel * +channel_join(const char *name) +{ + Channel *c; + + if (!(c = channel_find(name))) + c = channel_add(name); + return c; +} + +static +void +channel_rm(Channel *c) +{ + Channel *p; + + if (channels == c) { + channels = channels->next; + } else { + for (p = channels; p && p->next != c; p = p->next) + ; + if (p && p->next == c) + p->next = c->next; + } + free(c); +} + +static +void +channel_leave(Channel *c) +{ + if (c->fdin > 2) { + close(c->fdin); + c->fdin = -1; + } + /* remove "in" file on leaving the channel */ + unlink(c->inpath); + channel_rm(c); +} + +static +void +loginkey(int ircfd, const char *key) +{ + snprintf(msg, sizeof(msg), "PASS %s\r\n", key); + ewritestr(ircfd, msg); +} + +static +void +loginuser(int ircfd, const char *host, const char *fullname) +{ + snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n", + nick, nick, host, fullname); + puts(msg); + ewritestr(ircfd, msg); +} + +static +int +udsopen(const char *uds) +{ + struct sockaddr_un sun; + size_t len; + int fd; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno)); + exit(1); + } + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + fprintf(stderr, "%s: UNIX domain socket path truncation\n", argv0); + exit(1); + } + len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family); + if (connect(fd, (struct sockaddr *)&sun, len) == -1) { + fprintf(stderr, "%s: connect: %s\n", argv0, strerror(errno)); + exit(1); + } + return fd; +} + +static +int +tcpopen(const char *host, const char *service) +{ + struct addrinfo hints, *res = nil, *rp; + int fd = -1, e; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */ + hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */ + hints.ai_socktype = SOCK_STREAM; + + if ((e = getaddrinfo(host, service, &hints, &res))) { + fprintf(stderr, "%s: getaddrinfo: %s\n", argv0, gai_strerror(e)); + exit(1); + } + + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) + continue; + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + close(fd); + fd = -1; + continue; + } + break; /* success */ + } + if (fd == -1) { + fprintf(stderr, "%s: could not connect to %s:%s: %s\n", + argv0, host, service, strerror(errno)); + exit(1); + } + + freeaddrinfo(res); + return fd; +} + +static +int +isnumeric(const char *s) +{ + errno = 0; + strtol(s, nil, 10); + return errno == 0; +} + +static +size_t +tokenize(char **result, size_t reslen, char *str, int delim) +{ + char *p = nil, *n = nil; + size_t i = 0; + + for (n = str; *n == ' '; n++) + ; + p = n; + while (*n != '\0') { + if (i >= reslen) + return 0; + if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0])) + delim = ':'; /* workaround non-RFC compliant messages */ + if (*n == delim) { + *n = '\0'; + result[i++] = p; + p = ++n; + } else { + n++; + } + } + /* add last entry */ + if (i < reslen && p < n && p && *p) + result[i++] = p; + return i; /* number of tokens */ +} + +static +void +channel_print(Channel *c, const char *buf) +{ + FILE *fp = nil; + time_t t = time(nil); + + if (!(fp = fopen(c->outpath, "a"))) + return; + fprintf(fp, "%lu %s\n", (unsigned long)t, buf); + fclose(fp); +} + +static +void +proc_channels_privmsg(int ircfd, Channel *c, char *buf) +{ + snprintf(msg, sizeof(msg), "<%s> %s", nick, buf); + channel_print(c, msg); + snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf); + ewritestr(ircfd, msg); +} + +static +void +proc_channels_input(int ircfd, Channel *c, char *buf) +{ + char *p = nil; + size_t buflen; + + if (buf[0] == '\0') + return; + if (buf[0] != '/') { + proc_channels_privmsg(ircfd, c, buf); + return; + } + + msg[0] = '\0'; + if ((buflen = strlen(buf)) < 2) + return; + if (buf[2] == ' ' || buf[2] == '\0') { + switch (buf[1]) { + case 'j': /* join */ + if (buflen < 3) + return; + if ((p = strchr(&buf[3], ' '))) /* password parameter */ + *p = '\0'; + if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '+') || + (buf[3] == '!')) + { + /* password protected channel */ + if (p) + snprintf(msg, sizeof(msg), "JOIN %s %s\r\n", &buf[3], p + 1); + else + snprintf(msg, sizeof(msg), "JOIN %s\r\n", &buf[3]); + channel_join(&buf[3]); + } else if (p) { + if ((c = channel_join(&buf[3]))) + proc_channels_privmsg(ircfd, c, p + 1); + return; + } + break; + case 't': /* topic */ + if (buflen >= 3) + snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n", c->name, &buf[3]); + break; + case 'a': /* away */ + if (buflen >= 3) { + snprintf(msg, sizeof(msg), "-!- %s is away \"%s\"", nick, &buf[3]); + channel_print(c, msg); + } + if (buflen >= 3) + snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &buf[3]); + else + snprintf(msg, sizeof(msg), "AWAY\r\n"); + break; + case 'n': /* change nick */ + if (buflen >= 3) { + strlcpy(_nick, &buf[3], sizeof(_nick)); + snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf[3]); + } + break; + case 'l': /* leave */ + if (c == channelmaster) + return; + if (buflen >= 3) + snprintf(msg, sizeof(msg), "PART %s :%s\r\n", c->name, &buf[3]); + else + snprintf(msg, sizeof(msg), + "PART %s :leaving\r\n", c->name); + ewritestr(ircfd, msg); + channel_leave(c); + return; + break; + case 'q': /* quit */ + if (buflen >= 3) + snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &buf[3]); + else + snprintf(msg, sizeof(msg), + "QUIT %s\r\n", "bye"); + ewritestr(ircfd, msg); + isrunning = 0; + return; + break; + default: /* raw IRC command */ + snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]); + break; + } + } else { + /* raw IRC command */ + snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]); + } + if (msg[0] != '\0') + ewritestr(ircfd, msg); +} + +static +void +proc_server_cmd(int fd, char *buf) +{ + Channel *c; + const char *channel; + char *argv[TOK_LAST], *cmd = nil, *p = nil; + unsigned int i; + + if (!buf || buf[0] == '\0') + return; + + /* clear tokens */ + for (i = 0; i < TOK_LAST; i++) + argv[i] = nil; + + /* check prefix */ + if (buf[0] == ':') { + if (!(p = strchr(buf, ' '))) + return; + *p = '\0'; + for (++p; *p == ' '; p++) + ; + cmd = p; + argv[TOK_NICKSRV] = &buf[1]; + if ((p = strchr(buf, '!'))) { + *p = '\0'; + argv[TOK_USER] = ++p; + } + } else { + cmd = buf; + } + + /* remove CRLFs */ + for (p = cmd; p && *p != '\0'; p++) { + if (*p == '\r' || *p == '\n') + *p = '\0'; + } + + if ((p = strchr(cmd, ':'))) { + *p = '\0'; + argv[TOK_TEXT] = ++p; + } + + tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' '); + + if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) { + return; + } else if (!strcmp("PING", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]); + ewritestr(fd, msg); + return; + } else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) { + /* server command */ + snprintf(msg, sizeof(msg), "%s%s", + argv[TOK_ARG] ? argv[TOK_ARG] : "", + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + channel_print(channelmaster, msg); + return; /* don't process further */ + } else if (!strcmp("ERROR", argv[TOK_CMD])) + snprintf(msg, sizeof(msg), "-!- error %s", + argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown"); + else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK_TEXT])) { + if (argv[TOK_TEXT]) + argv[TOK_CHAN] = argv[TOK_TEXT]; + snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s", + argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); + } else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) { + snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s", + argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); + /* if user itself leaves, don't write to channel (don't reopen channel). */ + if (!strcmp(argv[TOK_NICKSRV], nick)) + return; + } else if (!strcmp("MODE", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s", + argv[TOK_NICKSRV], + argv[TOK_CHAN] ? argv[TOK_CHAN] : "", + argv[TOK_ARG] ? argv[TOK_ARG] : "", + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("QUIT", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- %s(%s) has quit \"%s\"", + argv[TOK_NICKSRV], argv[TOK_USER], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] && + !strcmp(_nick, argv[TOK_TEXT])) { + strlcpy(nick, _nick, sizeof(nick)); + snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick); + channel_print(channelmaster, msg); + } else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) { + snprintf(msg, sizeof(msg), "-!- %s changed nick to %s", + argv[TOK_NICKSRV], argv[TOK_TEXT]); + } else if (!strcmp("TOPIC", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"", + argv[TOK_NICKSRV], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) { + snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")", + argv[TOK_NICKSRV], argv[TOK_ARG], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("NOTICE", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- \"%s\"", + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("PRIVMSG", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else { + return; /* can't read this message */ + } + if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick)) + channel = argv[TOK_NICKSRV]; + else + channel = argv[TOK_CHAN]; + + if (!channel || channel[0] == '\0') + c = channelmaster; + else + c = channel_join(channel); + if (c) + channel_print(c, msg); +} + +static +int +read_line(int fd, char *buf, size_t bufsiz) +{ + size_t i = 0; + char c = '\0'; + + do { + if (read(fd, &c, sizeof(char)) != sizeof(char)) + return -1; + buf[i++] = c; + } while (c != '\n' && i < bufsiz); + buf[i - 1] = '\0'; /* eliminates '\n' */ + return 0; +} + +static +void +handle_channels_input(int ircfd, Channel *c) +{ + char buf[IRC_MSG_MAX]; + + if (read_line(c->fdin, buf, sizeof(buf)) == -1) { + if (channel_reopen(c) == -1) + channel_rm(c); + return; + } + proc_channels_input(ircfd, c, buf); +} + +static +void +handle_server_output(int ircfd) +{ + char buf[IRC_MSG_MAX]; + + if (read_line(ircfd, buf, sizeof(buf)) == -1) { + fprintf(stderr, "%s: remote host closed connection: %s\n", + argv0, strerror(errno)); + exit(1); + } + fprintf(stdout, "%lu %s\n", (unsigned long)time(nil), buf); + fflush(stdout); + proc_server_cmd(ircfd, buf); +} + +static +void +sighandler(int sig) +{ + if (sig == SIGTERM || sig == SIGINT) + isrunning = 0; +} + +static +void +setup(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + sigaction(SIGTERM, &sa, nil); + sigaction(SIGINT, &sa, nil); +} + +static +void +run(int ircfd, const char *host) +{ + Channel *c, *tmp; + fd_set rdset; + struct timeval tv; + char ping_msg[IRC_MSG_MAX]; + int r, maxfd; + + snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host); + while (isrunning) { + maxfd = ircfd; + FD_ZERO(&rdset); + FD_SET(ircfd, &rdset); + for (c = channels; c; c = c->next) { + if (c->fdin > maxfd) + maxfd = c->fdin; + FD_SET(c->fdin, &rdset); + } + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 120; + r = select(maxfd + 1, &rdset, 0, 0, &tv); + if (r < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "%s: select: %s\n", argv0, strerror(errno)); + exit(1); + } else if (r == 0) { + if (time(nil) - last_response >= PING_TIMEOUT) { + channel_print(channelmaster, "-!- ii shutting down: ping timeout"); + exit(2); /* status code 2 for timeout */ + } + ewritestr(ircfd, ping_msg); + continue; + } + if (FD_ISSET(ircfd, &rdset)) { + handle_server_output(ircfd); + last_response = time(nil); + } + for (c = channels; c; c = tmp) { + tmp = c->next; + if (FD_ISSET(c->fdin, &rdset)) + handle_channels_input(ircfd, c); + } + } +} + +int +main(int argc, char *argv[]) +{ + Channel *c, *tmp; + struct passwd *spw; + const char *key = nil, *fullname = nil, *host = ""; + const char *uds = nil, *service = "6667"; + char prefix[PATH_MAX]; + int ircfd, r; + + /* use nickname and home dir of user by default */ + if (!(spw = getpwuid(getuid()))) { + fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno)); + exit(1); + } + strlcpy(nick, spw->pw_name, sizeof(nick)); + snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir); + + ARGBEGIN { + case 'f': + fullname = EARGF(usage()); + break; + case 'i': + strlcpy(prefix, EARGF(usage()), sizeof(prefix)); + break; + case 'k': + key = getenv(EARGF(usage())); + break; + case 'n': + strlcpy(nick, EARGF(usage()), sizeof(nick)); + break; + case 'p': + service = EARGF(usage()); + break; + case 's': + host = EARGF(usage()); + break; + case 'u': + uds = EARGF(usage()); + break; + default: + usage(); + break; + } ARGEND + + if (!*host) + usage(); + + if (uds) + ircfd = udsopen(uds); + else + ircfd = tcpopen(host, service); + +#ifdef __OpenBSD__ + /* OpenBSD pledge(2) support */ + if (pledge("stdio rpath wpath cpath dpath", nil) == -1) { + fprintf(stderr, "%s: pledge: %s\n", argv0, strerror(errno)); + exit(1); + } +#endif + + r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host); + if (r < 0 || (size_t)r >= sizeof(ircpath)) { + fprintf(stderr, "%s: path to irc directory too long\n", argv0); + exit(1); + } + create_dirtree(ircpath); + + channelmaster = channel_add(""); /* master channel */ + if (key) + loginkey(ircfd, key); + loginuser(ircfd, host, fullname && *fullname ? fullname : nick); + setup(); + run(ircfd, host); + if (channelmaster) + channel_leave(channelmaster); + + for (c = channels; c; c = tmp) { + tmp = c->next; + channel_leave(c); + } + + return 0; +} diff --git a/sys/cmd/ic/rules.mk b/sys/cmd/ic/rules.mk new file mode 100644 index 0000000..9a52e42 --- /dev/null +++ b/sys/cmd/ic/rules.mk @@ -0,0 +1,14 @@ +include share/push.mk +# Iterate through subdirectory tree + +# Local sources +SRCS_$(d) := $(d)/ic.c +BINS_$(d) := $(d)/ic + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/sys/cmd/ic/strlcpy.c b/sys/cmd/ic/strlcpy.c new file mode 100644 index 0000000..db0e6f0 --- /dev/null +++ b/sys/cmd/ic/strlcpy.c @@ -0,0 +1,32 @@ +/* Taken from OpenBSD */ +#include +#include + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + return(s - src - 1); /* count does not include NUL */ +} diff --git a/sys/cmd/ls/ls.c b/sys/cmd/ls/ls.c new file mode 100644 index 0000000..86aaa48 --- /dev/null +++ b/sys/cmd/ls/ls.c @@ -0,0 +1,36 @@ +#include +#include + +static struct Flags { + uchar a : 1; /* show all files */ + uchar r : 1; /* reverse sort */ + uchar v : 1; /* print detailed information */ + uchar h : 1; /* human readable */ + uchar s : 1; /* sort by size */ +} flag; + +static +void +usage(void) +{ + fputs("usage: ls [-ahsrv] [file ...]\n", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int i; + ARGBEGIN{ + case 'a': flag.a++; break; + default: + usage(); + }ARGEND; + + switch(argc) { + case 0: + *--argv = ".", ++argc; + /* fallthrough */ + case 1: + } +} diff --git a/sys/cmd/ls/rules.mk b/sys/cmd/ls/rules.mk new file mode 100644 index 0000000..d2990f0 --- /dev/null +++ b/sys/cmd/ls/rules.mk @@ -0,0 +1,13 @@ +include share/push.mk + +# Local sources +SRCS_$(d) := $(d)/ls.c +BINS_$(d) := $(d)/ls + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/sys/cmd/mv/mv.c b/sys/cmd/mv/mv.c new file mode 100644 index 0000000..0d19248 --- /dev/null +++ b/sys/cmd/mv/mv.c @@ -0,0 +1,130 @@ +#include +#include + +static struct Flags +{ + uchar f : 1; + uchar i : 1; + uchar v : 1; +} flag; + +static +char * +strcpyn(char *dst, char *src, int n) +{ + while(*src && n-->0) + *dst++ = *src++; + + *dst = 0; + return dst; +} + +static +int +mv(char *from, char *to) +{ + io·Stat src, dst; + + if(lstat(from, &src)) { + errorf("%s", from); + return 1; + } + + if(!flag.f && !access(to, F_OK)) { + int ask = 1; + int ch, first; + + if(flag.i && !access(from, F_OK)) + fprintf(stderr, "overwrite %s? ", to); + else + ask = 0; + + if (ask) { + first = ch = getchar(); + while (ch != '\n' && ch != EOF) + ch = getchar(); + + if (first != 'y' || first != 'Y') + return 0; + } + } + + if(!rename(from, to)) { + if (flag.v) + fprintf(stdout, "%s -> %s\n", from, to); + return 0; + } + + /* TODO: errno = EXDEV */ + errorf("rename: %s to %s: %s", from, to, strerror(errno)); + return 1; +} + +static +void +usage(void) +{ + fputs("usage: mv [-fiv] source ... dest\n", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int err, len; + io·Stat info; + char *arg, *nm, *e, path[4096]; + + ARGBEGIN{ + case 'f': + flag.f++; + break; + case 'i': + flag.i++; + break; + case 'v': + flag.v++; + break; + default: + usage(); + }ARGEND; + + if(argc < 2) + usage(); + + if(stat(argv[argc-1],&info) || !S_ISDIR(info.st_mode)) { + if (argc > 2) + usage(); + exit(mv(argv[0], argv[1])); + } + + /* we know the last argument is a directory */ + e = strcpyn(path, argv[argc-1], sizeof path-1); + *e++ = '/'; + + for(err=0; --argc; ++argv) { + arg = argv[0]; + + if ((nm = strrchr(arg, '/')) == nil) + nm = arg; + else { + /* case: name/ */ + if (!nm[1]) { + while(nm >= arg && nm[0] == '/') + nm--; + while(nm >= arg && nm[0] != '/') + nm--; + } + nm++; + } + + if ((len = strlen(nm)) >= arrend(path)-e) { + errorf("%s: path name overflow\n", arg); + err++; + continue; + } + memmove(e, nm, len+1); + err += mv(arg, path); + } + exit(err); +} diff --git a/sys/cmd/mv/rules.mk b/sys/cmd/mv/rules.mk new file mode 100644 index 0000000..f282ed8 --- /dev/null +++ b/sys/cmd/mv/rules.mk @@ -0,0 +1,13 @@ +include share/push.mk + +# Local sources +SRCS_$(d) := $(d)/mv.c +BINS_$(d) := $(d)/mv + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/sys/cmd/rm/rm.c b/sys/cmd/rm/rm.c new file mode 100644 index 0000000..7e83f5f --- /dev/null +++ b/sys/cmd/rm/rm.c @@ -0,0 +1,119 @@ +#include +#include +#include + +/* + * globals + */ + +static char errbuf[1024]; +static int numerrs; +static struct +{ + uchar i : 1; + uchar f : 1; + uchar r : 1; +} flag; + +/* + * utilities + */ + +static +void +errors(char *fmt, ...) +{ + va_list args; + + if(flag.f) + return; + + va_start(args, fmt); + vsnprintf(errbuf, arrlen(errbuf), fmt, args); + va_end(args); + + fprintf(stderr, "rm: %s\n", errbuf); + numerrs++; +} + +static +int +yes(void) +{ + int i, b; + i = b = getchar(); + while(b != '\n' && b != EOF) + b = getchar(); + + return(i== 'y'); +} + +static +int +rm(char *path, int level) +{ + int d; + struct stat buf; + if(stat(path, &buf)) { + errors("%s: non-existent", path); + return 1; + } + + if((buf.st_mode&S_IFMT) == S_IFDIR) { + if(!flag.r) { + errors("%s: is directory", path); + return 1; + } + if(access(path, 02) < 0) { + errors("%s: not permitted", path); + return 1; + } + if(flag.i && level != 0) { + printf("directory %s: ", path); + if(!yes()) + return 0; + } + if((d= + } +} + +/* + * main point of entry + */ + +static +void +usage(void) +{ + fprintf(stderr, "usage: rm [-ifr] file ...\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int i; + char *s; + + ARGBEGIN{ + case 'i': + flag.i = 1; + break; + case 'r': + flag.r = 1; + break; + case 'f': + flag.f = 1; + break; + default: + usage(); + }ARGEND; + + for(i = 0; i < argc; i++){ + s = argv[i]; + if (rm(s, 0) != -1) + continue; + } + + exit(numerrs); +} diff --git a/sys/cmd/rm/rules.mk b/sys/cmd/rm/rules.mk new file mode 100644 index 0000000..9cb4e92 --- /dev/null +++ b/sys/cmd/rm/rules.mk @@ -0,0 +1,13 @@ +include share/push.mk + +# Local sources +SRCS_$(d) := $(d)/rm.c +BINS_$(d) := $(d)/rm + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk -- cgit v1.2.1