Commit e361a6fd authored by Andrew Price's avatar Andrew Price
Browse files

Add a mwnci to the tree

This adds a new util named mwnci (Milliways Nifty Commandline Interface,
or something) which can be used to list and generate random entries in
users.bb and folders.bb. This should be useful for populating test files
(e.g. when testing a migration to a new database format) and examining
broken ones. Some more work needs to be done on choosing values for more
of the user fields but it should be basically useful already. It has some
built-in help so 'cd src; make test; utils/mwnci' will get you started.

Fixes #4
parent 7ae988f2
......@@ -19,6 +19,7 @@ src/utils/del_user
src/utils/fixuser
src/utils/listuser
src/utils/sizes
src/utils/mwnci
src/webclient/mwpoll
mozjs/build
mozjs/installroot
......@@ -46,6 +46,7 @@ test:
cp -a ../$$d $(TESTDIR)/mw/ ; \
done
$(MAKE) libdir="$(TESTDIR)" localstatedir="$(TESTDIR)" all
$(MAKE) -C utils libdir="$(TESTDIR)" localstatedir="$(TESTDIR)" all
testclean: clean
rm -rf "$(TESTDIR)"
......
......@@ -3,9 +3,12 @@ DEPTH=../../
include ../../Makefile.common
LDFLAGS+= -L..
CFLAGS+= -I..
CFLAGS+= -I.. -Wno-error
all: listuser fixuser del_user sizes
all: mwnci listuser fixuser del_user sizes
mwnci: mwnci.o ../libmw.a
$(CC) $(LDFLAGS) $(LDLIBS) -o $@ $^
listuser: listuser.o
$(CC) $(LDFLAGS) $(LDLIBS) -o $@ $^
......
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <errno.h>
#include <sys/time.h>
#include <ctype.h>
/* MW headers */
#include <files.h>
#include <folders.h>
#include <user.h>
struct prog;
struct subcmd {
const char *name;
const char *argfmt;
const char *help;
const char *desc;
int (*execute)(const struct prog *prog, int argc, char **argv);
};
static const struct subcmd * const subcommands;
static const struct subcmd *lookup_subcmd(const char *cmdname)
{
const struct subcmd *subcmd = subcommands;
while (subcmd->name != NULL) {
if (strcmp(cmdname, subcmd->name) == 0)
return subcmd;
subcmd++;
}
return NULL;
}
struct prog {
const char *name;
int subargc;
char **subargv;
const struct subcmd *subcmd;
};
static void usage(const char *name)
{
const struct subcmd *subcmd = subcommands;
printf("Usage:\n %s "
"[--help|-h] "
"<command> [<args>]"
"\n", name);
printf("\n");
printf("Commands:\n");
while (subcmd->name != NULL) {
printf(" %-10s %s\n", subcmd->name, subcmd->help);
subcmd++;
}
}
static int cmd_usage(const struct prog *prog, const char *name)
{
const struct subcmd *cmd;
cmd = lookup_subcmd(name);
if (cmd == NULL) {
fprintf(stderr, "No such command: %s\n", name);
return 1;
}
printf("Usage:\n");
printf(" %s %s %s\n", prog->name, cmd->name, cmd->argfmt);
printf("\n");
printf("%s\n", cmd->desc);
return 0;
}
#define DESC_HELP \
"Prints a help string for the given command, " \
"or for the program if no command is given."
static int help_main(const struct prog *prog, int argc, char **argv)
{
if (argc > 2) {
fprintf(stderr, "Unexpected argument: %s\n", argv[2]);
return 1;
}
if (argc == 1) {
usage(prog->name);
return 0;
}
return cmd_usage(prog, argv[1]);
}
static void print_user_entry(off_t off, struct person *rec)
{
printf("User@%04lx\n"
"Name: %s\n"
"Passwd: %s\n"
"Lastlogout: %"PRId32"\n"
"Folders: {0x%"PRIx32",0x%"PRIx32"}\n"
"Status: 0x%02hhx\n"
"Special: 0x%"PRIx16"\n"
"Lastread: (Not included)\n"
"Realname: %s\n"
"Contact: %s\n"
"Timeused: %"PRId32"\n"
"Idletime: %"PRId32"\n"
"Groups: 0x%02hhx\n"
"Doing: %s\n"
"Dowhen: %"PRId32"\n"
"Timeout: %"PRId32"\n"
"Spare: 0x%02hhx\n"
"Colour: 0x%02hhx\n"
"Room: %"PRIu16"\n"
"Chatprivs: %"PRIx32"\n"
"Chatmode: %"PRIx32"\n",
off, rec->name, rec->passwd, rec->lastlogout, rec->folders[0],
rec->folders[1], rec->status, rec->special, rec->realname,
rec->contact, rec->timeused, rec->idletime, rec->groups,
rec->doing, rec->dowhen, rec->timeout, rec->spare, rec->colour,
rec->room, rec->chatprivs, rec->chatmode);
}
#define DESC_USERLS \
"Lists the entries in a users.bb file. If no path is given, the built-in path is used."
static int userls_main(const struct prog *prog, int argc, char **argv)
{
struct person rec;
const char *fn;
off_t off = 0;
int fd;
if (argc > 2) {
fprintf(stderr, "Unexpected argument: %s\n", argv[2]);
return 1;
}
if (argc == 2)
fn = argv[1];
else
fn = STATEDIR"/users.bb";
fd = open(fn, O_RDONLY);
if (fd < 0 || (lseek(fd, 0, SEEK_SET) == -1)) {
perror(fn);
return 1;
}
printf("Listing entries from '%s'\n", fn);
while (read(fd, &rec, sizeof(rec)) == sizeof(rec)) {
print_user_entry(off, &rec);
off += sizeof(rec);
}
return 0;
}
static int is_unique_user_name(int fd, const char *name, int *err)
{
struct user user;
*err = 0;
for_each_user(&user, fd, *err) {
if (strcmp(user.record.name, name) == 0)
return 0;
}
if (*err < 0)
return 0;
return 1;
}
static int check_template(const char *optarg, size_t maxlen)
{
const char *i;
size_t size;
int count;
size = strlen(optarg);
if (size == 0) {
fprintf(stderr, "Empty template\n");
return 1;
}
if (size > maxlen) {
fprintf(stderr, "Template is too long: '%s'\n", optarg);
return 1;
}
for (count = 0, i = optarg; *i != '\0'; i++) {
if (*i == '%') {
count++;
} else if (!isprint(*i)) {
fprintf(stderr, "Template '%s' contains non-printable characters\n",
optarg);
return 1;
}
}
if (count < 3) {
fprintf(stderr, "Template '%s' contains too few '%%' characters.\n", optarg);
return 1;
}
return 0;
}
/* len should be the length of the string, which should be at least 1 less than
the capacity of the str array, i.e. len < strlen(str) + 1 */
static void template_subst(const char *template, char *str, size_t len)
{
char validchars[26+26+10];
size_t tsz = 0;
if (template != NULL)
tsz = strlen(template);
if (tsz != 0)
len = tsz;
else
len = (rand() % len - 2) + 3;
for (unsigned i = 0; i < 26; i++)
validchars[i] = 'A' + i;
for (unsigned i = 0; i < 26; i++)
validchars[26+i] = 'a' + i;
for (unsigned i = 0; i < 10; i++)
validchars[26+26+i] = '0' + i;
memset(str, '\0', len+1);
for (unsigned i = 0; i < len; i++) {
char *c = &str[i];
if (tsz > 0 && template[i] != '%') {
*c = template[i];
continue;
}
*c = validchars[rand() % (26*2+10)];
}
}
#define DESC_USERGEN \
"Generate a number of random entries in a users file (users.bb). " \
"If no path is specified the built-in path is used. The number of entries " \
"is required. The template option allows some of the characters in the user " \
"names to be generated from a pattern in which the '%' characters " \
"will be replaced with unique random characters."
static int usergen_main(const struct prog *prog, int argc, char **argv)
{
int c;
int fd;
unsigned entries = 0;
const char *path = NULL;
const char *tmplt_name = NULL;
struct option loptspec[] = {
{"entries", required_argument, NULL, 'n'},
{"name-template", required_argument, NULL, 'N'},
{NULL, 0, NULL, 0}
};
optind = 1;
while (1) {
c = getopt_long(argc, argv, "-n:N:", loptspec, NULL);
if (c == -1)
break;
switch (c) {
long num;
case 1:
if (path != NULL) {
fprintf(stderr, "Unexpected argument: '%s'\n", optarg);
return 1;
}
path = optarg;
break;
case 'n':
errno = 0;
num = strtol(optarg, NULL, 0);
if (errno != 0 || num < 1 || num > 64) {
fprintf(stderr, "Invalid entries argument: '%s'\n", optarg);
return 1;
}
entries = num;
break;
case 'N':
if (check_template(optarg, NAMESIZE))
return 1;
tmplt_name = optarg;
break;
case 'h':
cmd_usage(prog, prog->subcmd->name);
return 0;
case '?':
default:
cmd_usage(prog, prog->subcmd->name);
return 1;
}
}
if (optind < argc) {
fprintf(stderr, "Unrecognised or extraneous arguments: ");
while (optind < argc)
fprintf(stderr, "%s ", argv[optind++]);
fprintf(stderr, "\n");
return 1;
}
if (entries == 0) {
fprintf(stderr, "No --entries (-n) option given.\n");
return 1;
}
if (path == NULL)
path = STATEDIR"/users.bb";
fd = open(path, O_RDWR);
if (fd < 0) {
perror(path);
return 1;
}
Lock_File(fd);
printf("Adding up to %u entries to '%s'...\n", entries, path);
for (unsigned i = 0; i < entries; i++) {
char name[NAMESIZE+1];
struct user user;
ssize_t bytes;
memset(&user, 0, sizeof(user));
while (1) {
int err;
template_subst(tmplt_name, name, NAMESIZE);
if (is_unique_user_name(fd, name, &err))
break;
if (err < 0)
goto out_err;
}
memcpy(user.record.name, name, NAMESIZE+1);
/* TODO: set the rest of the user record fields to sensible random values */
user.record.status &= ~(1 << MWUSR_BANNED);
user.record.status &= ~(1 << MWUSR_DELETED);
user.posn = lseek(fd, 0, SEEK_END);
if (user.posn == (off_t)-1)
goto out_err;
bytes = write(fd, &user.record, sizeof(user.record));
if (bytes != sizeof(user.record))
goto out_err;
print_user_entry(user.posn, &user.record);
}
fsync(fd);
Unlock_File(fd);
close(fd);
return 0;
out_err:
perror(path);
Unlock_File(fd);
close(fd);
return 1;
}
static void print_folder_columns(void)
{
printf("Offset "
"Stat "
"Name "
"Topic "
"Fst "
"Lst "
"GpSt "
"Grps "
"Spare\n");
}
static void print_folder_entry(off_t off, struct folder *fol)
{
printf("0x%03lx: 0x%02x %-10s %-30s %04"PRId32" %04"PRId32" 0x%02hhx 0x%02hhx ",
off, fol->status, fol->name, fol->topic, fol->first,
fol->last, fol->g_status, fol->groups);
printf("{");
for (int i = 0; i < 9; i++)
printf("%02x,", fol->spare[i]);
printf("%02x", fol->spare[9]);
printf("}\n");
}
#define DESC_FOLDERLS \
"Lists the entries in a folders.bb file. If no path is given, the built-in path is used."
static int folderls_main(const struct prog *prog, int argc, char **argv)
{
struct folder fol;
const char *fn;
off_t off = 0;
int fd;
if (argc > 2) {
fprintf(stderr, "Unexpected argument: %s\n", argv[2]);
return 1;
}
if (argc == 2) {
fn = argv[1];
fd = open(fn, O_RDONLY);
} else {
fn = STATEDIR"/folders.bb";
fd = openfolderfile(O_RDONLY);
}
if (fd < 0 || (lseek(fd, 0, SEEK_SET) == -1)) {
perror(fn);
return 1;
}
printf("Listing entries from '%s'\n", fn);
print_folder_columns();
while (read(fd, &fol, sizeof(fol)) == sizeof(fol)) {
if (fol.status != 0)
print_folder_entry(off, &fol);
off += sizeof(fol);
}
return 0;
}
static int is_unique_folder_name(struct folder *folders, const char *name)
{
for (struct folder *fol = folders; fol - folders < 64; fol++)
if (strcmp(fol->name, name) == 0)
return 0;
return 1;
}
static void folder_gen(struct folder *folders, struct folder *fol, unsigned active,
const char *tmplt_name, const char *tmplt_topic)
{
char name[FOLNAMESIZE+1];
fol->status = rand();
fol->first = 0;
fol->last = 0;
fol->g_status = rand() | (1 << MWFOLDR_ACTIVE);
fol->groups = rand();
if (active) {
fol->status |= (1 << MWFOLDR_ACTIVE);
fol->g_status |= (1 << MWFOLDR_ACTIVE);
}
template_subst(tmplt_topic, fol->topic, TOPICSIZE);
do {
template_subst(tmplt_name, name, FOLNAMESIZE);
} while (!is_unique_folder_name(folders, name));
memcpy(fol->name, name, FOLNAMESIZE+1);
}
#define DESC_FOLDERGEN \
"Generate a number of random entries in a folder index file (folders.bb). " \
"If no path is specified the built-in path is used. The number of entries " \
"is required. The template options allow some of the characters in the folder " \
"names and topics to be generated from a pattern in which the '%' characters " \
"will be replaced with unique random characters. By default this command is " \
"safe and does not overwrite existing entries but this can be changed using " \
"the --destroy argument. If the --active flag is used, all folders will have " \
"their active status bits set instead of randomly set."
static int foldergen_main(const struct prog *prog, int argc, char **argv)
{
int c;
int fd;
unsigned count;
int destroy = 0;
unsigned active = 0;
unsigned entries = 0;
const char *path = NULL;
const char *tmplt_name = NULL;
const char *tmplt_topic = NULL;
struct folder folders[64];
struct option loptspec[] = {
{"active", no_argument, NULL, 'a'},
{"destroy", no_argument, NULL, 'd'},
{"entries", required_argument, NULL, 'n'},
{"name-template", required_argument, NULL, 'N'},
{"topic-template", required_argument, NULL, 'T'},
{NULL, 0, NULL, 0}
};
optind = 1;
while (1) {
c = getopt_long(argc, argv, "-adn:N:T:", loptspec, NULL);
if (c == -1) /* End of args with no subcommand */
break;
switch (c) {
long num;
case 1:
if (path != NULL) {
fprintf(stderr, "Unexpected argument: '%s'\n", optarg);
return 1;
}
path = optarg;
break;
case 'a':
active = 1;
break;
case 'd':
destroy = 1;
break;
case 'n':
errno = 0;
num = strtol(optarg, NULL, 0);
if (errno != 0 || num < 1 || num > 64) {
fprintf(stderr, "Invalid entries argument: '%s'\n", optarg);
return 1;
}
entries = num;
break;
case 'N':
if (check_template(optarg, FOLNAMESIZE))
return 1;
tmplt_name = optarg;
break;
case 'T':
if (check_template(optarg, TOPICSIZE))
return 1;
tmplt_topic = optarg;
break;
case 'h':
cmd_usage(prog, prog->subcmd->name);
return 0;
case '?':
default:
cmd_usage(prog, prog->subcmd->name);
return 1;
}
}
if (optind < argc) {
fprintf(stderr, "Unrecognised or extraneous arguments: ");
while (optind < argc)
fprintf(stderr, "%s ", argv[optind++]);
fprintf(stderr, "\n");
return 1;
}
if (entries == 0) {
fprintf(stderr, "No --entries (-n) option given.\n");
return 1;
}
if (path == NULL)
path = STATEDIR"/folders.bb";
fd = open(path, O_RDWR);
if (fd < 0) {
perror(path);
return 1;
}
Lock_File(fd);
if (pread(fd, folders, sizeof(struct folder) * 64, 0) != sizeof(struct folder) * 64)
goto out_err;
printf("Adding up to %u entries to '%s'...\n", entries, path);
print_folder_columns();
count = 0;
for (unsigned i = 0; count < entries && i < 64; i++) {
struct folder *fol = &folders[i];
ssize_t bytes;
if (fol->status != 0 && !destroy)
continue;
folder_gen(folders, fol, active, tmplt_name, tmplt_topic);
bytes = pwrite(fd, fol, sizeof(*fol), i * sizeof(*fol));
if (bytes != sizeof(*fol))
goto out_err;
print_folder_entry(i * sizeof(*fol), fol);
count++;
}
if (count > 0)
fsync(fd);
Unlock_File(fd);
close(fd);
return 0;
out_err:
perror(path);
Unlock_File(fd);
close(fd);
return 1;
}
static const struct subcmd _subcommands[] = {
{ "help",
"[command]",
"Display command help",