1998-09-16 05:57:36 +00:00
|
|
|
|
/* vinum.c: vinum interface program */
|
|
|
|
|
/*-
|
|
|
|
|
* Copyright (c) 1997, 1998
|
|
|
|
|
* Nan Yang Computer Services Limited. All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* This software is distributed under the so-called ``Berkeley
|
|
|
|
|
* License'':
|
|
|
|
|
*
|
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
|
* are met:
|
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
|
|
|
* must display the following acknowledgement:
|
|
|
|
|
* This product includes software developed by Nan Yang Computer
|
|
|
|
|
* Services Limited.
|
|
|
|
|
* 4. Neither the name of the Company nor the names of its contributors
|
|
|
|
|
* may be used to endorse or promote products derived from this software
|
|
|
|
|
* without specific prior written permission.
|
|
|
|
|
*
|
|
|
|
|
* This software is provided ``as is'', and any express or implied
|
|
|
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
|
|
|
* merchantability and fitness for a particular purpose are disclaimed.
|
|
|
|
|
* In no event shall the company or contributors be liable for any
|
|
|
|
|
* direct, indirect, incidental, special, exemplary, or consequential
|
|
|
|
|
* damages (including, but not limited to, procurement of substitute
|
|
|
|
|
* goods or services; loss of use, data, or profits; or business
|
|
|
|
|
* interruption) however caused and on any theory of liability, whether
|
|
|
|
|
* in contract, strict liability, or tort (including negligence or
|
|
|
|
|
* otherwise) arising in any way out of the use of this software, even if
|
|
|
|
|
* advised of the possibility of such damage.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
1999-03-28 08:51:29 +00:00
|
|
|
|
/* $Id: v.c,v 1.25 1999/03/21 01:18:23 grog Exp grog $ */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#include <libutil.h>
|
|
|
|
|
#include <netdb.h>
|
|
|
|
|
#include <setjmp.h>
|
1999-03-02 07:01:51 +00:00
|
|
|
|
#include <signal.h>
|
1998-09-16 05:57:36 +00:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <syslog.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/ioctl.h>
|
1998-12-28 16:32:39 +00:00
|
|
|
|
#include <dev/vinum/vinumhdr.h>
|
1998-09-16 05:57:36 +00:00
|
|
|
|
#include "vext.h"
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <readline/history.h>
|
|
|
|
|
#include <readline/readline.h>
|
1999-01-21 00:45:11 +00:00
|
|
|
|
#include <sys/linker.h>
|
|
|
|
|
#include <sys/module.h>
|
|
|
|
|
#include <sys/resource.h>
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
|
|
|
|
FILE *cf; /* config file handle */
|
1999-05-02 08:00:30 +00:00
|
|
|
|
FILE *history; /* history file */
|
|
|
|
|
char *historyfile; /* and its name */
|
|
|
|
|
|
|
|
|
|
char *dateformat; /* format in which to store date */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
|
|
|
|
char buffer[BUFSIZE]; /* buffer to read in to */
|
|
|
|
|
|
|
|
|
|
int line = 0; /* stdin line number for error messages */
|
|
|
|
|
int file_line = 0; /* and line in input file (yes, this is tacky) */
|
|
|
|
|
int inerror; /* set to 1 to exit after end of config file */
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
|
1999-01-06 04:09:01 +00:00
|
|
|
|
#if VINUMDEBUG
|
1998-09-16 05:57:36 +00:00
|
|
|
|
int debug = 0; /* debug flag, usage varies */
|
|
|
|
|
#endif
|
|
|
|
|
int force = 0; /* set to 1 to force some dangerous ops */
|
|
|
|
|
int verbose = 0; /* set verbose operation */
|
|
|
|
|
int Verbose = 0; /* set very verbose operation */
|
|
|
|
|
int recurse = 0; /* set recursion */
|
|
|
|
|
int stats = 0; /* show statistics */
|
1999-04-10 04:00:49 +00:00
|
|
|
|
int dowait = 0; /* wait for completion */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
|
|
|
|
/* Structures to read kernel data into */
|
|
|
|
|
struct _vinum_conf vinum_conf; /* configuration information */
|
|
|
|
|
|
|
|
|
|
struct volume vol;
|
|
|
|
|
struct plex plex;
|
|
|
|
|
struct sd sd;
|
|
|
|
|
struct drive drive;
|
|
|
|
|
|
|
|
|
|
jmp_buf command_fail; /* return on a failed command */
|
|
|
|
|
int superdev; /* vinum super device */
|
|
|
|
|
|
1999-01-21 00:45:11 +00:00
|
|
|
|
void start_daemon(void);
|
|
|
|
|
|
1998-09-16 05:57:36 +00:00
|
|
|
|
#define ofs(x) ((void *) (& ((struct confdata *) 0)->x)) /* offset of x in struct confdata */
|
|
|
|
|
|
|
|
|
|
/* create description-file
|
|
|
|
|
Create a volume as described in description-file
|
|
|
|
|
modify description-file
|
|
|
|
|
Modify the objects as described in description-file
|
|
|
|
|
list [-r] [volume | plex | subdisk]
|
|
|
|
|
List information about specified objects
|
|
|
|
|
set [-f] state volume | plex | subdisk | disk
|
|
|
|
|
Set the state of the object to state
|
|
|
|
|
rm [-f] [-r] volume | plex | subdisk
|
|
|
|
|
Remove an object
|
|
|
|
|
start [volume | plex | subdisk]
|
|
|
|
|
Allow the system to access the objects
|
|
|
|
|
stop [-f] [volume | plex | subdisk]
|
|
|
|
|
Terminate access the objects
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
char *token[MAXARGS]; /* pointers to individual tokens */
|
|
|
|
|
int tokens; /* number of tokens */
|
|
|
|
|
|
|
|
|
|
int
|
1999-04-09 01:14:36 +00:00
|
|
|
|
main(int argc, char *argv[], char *envp[])
|
1998-09-16 05:57:36 +00:00
|
|
|
|
{
|
1999-02-11 06:13:44 +00:00
|
|
|
|
#if __FreeBSD__ >= 3
|
|
|
|
|
if (modfind(WRONGMOD) >= 0) { /* wrong module loaded, */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
fprintf(stderr, "Wrong module loaded: %s. Starting %s(8).\n", WRONGMOD, WRONGMOD);
|
1999-04-09 01:14:36 +00:00
|
|
|
|
argv[0] = "/sbin/" WRONGMOD;
|
|
|
|
|
execve(argv[0], argv, envp);
|
1999-02-11 06:13:44 +00:00
|
|
|
|
exit(1);
|
|
|
|
|
}
|
1999-01-21 00:45:11 +00:00
|
|
|
|
if (modfind(VINUMMOD) < 0) {
|
|
|
|
|
/* need to load the vinum module */
|
|
|
|
|
if (kldload(VINUMMOD) < 0 || modfind(VINUMMOD) < 0) {
|
1999-03-02 07:01:51 +00:00
|
|
|
|
perror(VINUMMOD ": Kernel module not available");
|
1999-01-21 00:45:11 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
1999-02-11 06:13:44 +00:00
|
|
|
|
#endif
|
1999-01-21 00:45:11 +00:00
|
|
|
|
|
1999-05-02 08:00:30 +00:00
|
|
|
|
dateformat = getenv("VINUM_DATEFORMAT");
|
|
|
|
|
if (dateformat == NULL)
|
|
|
|
|
dateformat = "%e %b %Y %H:%M:%S";
|
|
|
|
|
historyfile = getenv("VINUM_HISTORY");
|
|
|
|
|
if (historyfile == NULL)
|
|
|
|
|
historyfile = DEFAULT_HISTORYFILE;
|
|
|
|
|
history = fopen(historyfile, "a+");
|
|
|
|
|
if (history != NULL) {
|
|
|
|
|
timestamp();
|
|
|
|
|
fprintf(history, "*** " VINUMMOD " started ***\n");
|
|
|
|
|
fflush(history); /* before we start the daemon */
|
|
|
|
|
} else
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"Can't open history file %s: %s (%d)\n",
|
|
|
|
|
historyfile,
|
|
|
|
|
strerror(errno),
|
|
|
|
|
errno);
|
1999-03-02 07:01:51 +00:00
|
|
|
|
superdev = open(VINUM_SUPERDEV_NAME, O_RDWR); /* open vinum superdevice */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if (superdev < 0) { /* no go */
|
1999-03-19 07:21:27 +00:00
|
|
|
|
if (errno == ENODEV) { /* not configured, */
|
|
|
|
|
superdev = open(VINUM_WRONGSUPERDEV_NAME, O_RDWR); /* do we have a debug mismatch? */
|
|
|
|
|
if (superdev >= 0) { /* yup! */
|
|
|
|
|
#if VINUMDEBUG
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"This program is compiled with debug support, but the kernel module does\n"
|
|
|
|
|
"not have debug support. This program must be matched with the kernel\n"
|
|
|
|
|
"module. Please alter /usr/src/sbin/" VINUMMOD "/Makefile and remove\n"
|
|
|
|
|
"the option -DVINUMDEBUG from the CFLAGS definition, or alternatively\n"
|
|
|
|
|
"edit /usr/src/sys/modules/" VINUMMOD "/Makefile and add the option\n"
|
|
|
|
|
"-DVINUMDEBUG to the CFLAGS definition. Then rebuild the component\n"
|
|
|
|
|
"of your choice with 'make clean all install'. If you rebuild the kernel\n"
|
|
|
|
|
"module, you must stop " VINUMMOD " and restart it\n");
|
|
|
|
|
#else
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"This program is compiled without debug support, but the kernel module\n"
|
|
|
|
|
"includes debug support. This program must be matched with the kernel\n"
|
|
|
|
|
"module. Please alter /usr/src/sbin/" VINUMMOD "/Makefile and add\n"
|
|
|
|
|
"the option -DVINUMDEBUG to the CFLAGS definition, or alternatively\n"
|
|
|
|
|
"edit /usr/src/sys/modules/" VINUMMOD "/Makefile and remove the option\n"
|
|
|
|
|
"-DVINUMDEBUG from the CFLAGS definition. Then rebuild the component\n"
|
|
|
|
|
"of your choice with 'make clean all install'. If you rebuild the kernel\n"
|
|
|
|
|
"module, you must stop " VINUMMOD " and restart it\n");
|
|
|
|
|
#endif
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
} else if (errno == ENOENT) /* we don't have our node, */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
make_devices(); /* create them first */
|
|
|
|
|
if (superdev < 0) {
|
|
|
|
|
perror("Can't open " VINUM_SUPERDEV_NAME);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
1999-01-21 00:45:11 +00:00
|
|
|
|
/* Check if the d<>mon is running. If not, start it in the
|
|
|
|
|
* background */
|
|
|
|
|
start_daemon();
|
|
|
|
|
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if (argc > 1) { /* we have a command on the line */
|
|
|
|
|
if (setjmp(command_fail) != 0) /* long jumped out */
|
|
|
|
|
return -1;
|
|
|
|
|
parseline(argc - 1, &argv[1]); /* do it */
|
|
|
|
|
} else {
|
|
|
|
|
for (;;) { /* ugh */
|
|
|
|
|
char *c;
|
1999-03-02 07:01:51 +00:00
|
|
|
|
int childstatus; /* from wait4 */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
|
|
|
|
setjmp(command_fail); /* come back here on catastrophic failure */
|
|
|
|
|
|
1999-03-28 08:51:29 +00:00
|
|
|
|
while (wait4(-1, &childstatus, WNOHANG, NULL) > 0); /* wait for all dead children */
|
1999-03-02 07:01:51 +00:00
|
|
|
|
c = readline(VINUMMOD " -> "); /* get an input */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if (c == NULL) { /* EOF or error */
|
|
|
|
|
if (ferror(stdin)) {
|
|
|
|
|
fprintf(stderr, "Can't read input: %s (%d)\n", strerror(errno), errno);
|
|
|
|
|
return 1;
|
|
|
|
|
} else { /* EOF */
|
|
|
|
|
printf("\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
} else if (*c) { /* got something there */
|
|
|
|
|
add_history(c); /* save it in the history */
|
|
|
|
|
strcpy(buffer, c); /* put it where we can munge it */
|
|
|
|
|
free(c);
|
|
|
|
|
line++; /* count the lines */
|
|
|
|
|
tokens = tokenize(buffer, token);
|
|
|
|
|
/* got something potentially worth parsing */
|
|
|
|
|
if (tokens)
|
|
|
|
|
parseline(tokens, token); /* and do what he says */
|
|
|
|
|
}
|
1999-05-02 08:00:30 +00:00
|
|
|
|
if (history)
|
|
|
|
|
fflush(history);
|
1998-09-16 05:57:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0; /* normal completion */
|
|
|
|
|
}
|
|
|
|
|
|
1999-01-21 00:45:11 +00:00
|
|
|
|
/* stop the hard way */
|
|
|
|
|
void
|
|
|
|
|
vinum_quit(int argc, char *argv[], char *argv0[])
|
|
|
|
|
{
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
1998-09-16 05:57:36 +00:00
|
|
|
|
#define FUNKEY(x) { kw_##x, &vinum_##x } /* create pair "kw_foo", vinum_foo */
|
|
|
|
|
|
|
|
|
|
struct funkey {
|
|
|
|
|
enum keyword kw;
|
|
|
|
|
void (*fun) (int argc, char *argv[], char *arg0[]);
|
|
|
|
|
} funkeys[] = {
|
|
|
|
|
|
|
|
|
|
FUNKEY(create),
|
|
|
|
|
FUNKEY(read),
|
1999-01-06 04:09:01 +00:00
|
|
|
|
#ifdef VINUMDEBUG
|
1998-09-16 05:57:36 +00:00
|
|
|
|
FUNKEY(debug),
|
|
|
|
|
#endif
|
|
|
|
|
FUNKEY(volume),
|
|
|
|
|
FUNKEY(plex),
|
|
|
|
|
FUNKEY(sd),
|
|
|
|
|
FUNKEY(drive),
|
|
|
|
|
FUNKEY(modify),
|
|
|
|
|
FUNKEY(list),
|
|
|
|
|
FUNKEY(ld),
|
|
|
|
|
FUNKEY(ls),
|
|
|
|
|
FUNKEY(lp),
|
|
|
|
|
FUNKEY(lv),
|
|
|
|
|
FUNKEY(info),
|
|
|
|
|
FUNKEY(set),
|
|
|
|
|
FUNKEY(init),
|
|
|
|
|
FUNKEY(label),
|
|
|
|
|
FUNKEY(resetconfig),
|
|
|
|
|
FUNKEY(rm),
|
|
|
|
|
FUNKEY(attach),
|
|
|
|
|
FUNKEY(detach),
|
|
|
|
|
FUNKEY(rename),
|
|
|
|
|
FUNKEY(replace),
|
|
|
|
|
FUNKEY(printconfig),
|
1999-03-02 07:01:51 +00:00
|
|
|
|
FUNKEY(saveconfig),
|
1998-09-16 05:57:36 +00:00
|
|
|
|
FUNKEY(start),
|
|
|
|
|
FUNKEY(stop),
|
1999-01-21 00:45:11 +00:00
|
|
|
|
FUNKEY(makedev),
|
|
|
|
|
FUNKEY(help),
|
|
|
|
|
FUNKEY(quit),
|
|
|
|
|
FUNKEY(setdaemon),
|
1998-09-16 05:57:36 +00:00
|
|
|
|
FUNKEY(resetstats)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Take args arguments at argv and attempt to perform the operation specified */
|
|
|
|
|
void
|
|
|
|
|
parseline(int args, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
int j;
|
|
|
|
|
enum keyword command; /* command to execute */
|
|
|
|
|
|
1999-05-02 08:00:30 +00:00
|
|
|
|
if (history != NULL) { /* save the command to history file */
|
|
|
|
|
timestamp();
|
|
|
|
|
for (i = 0; i < args; i++) /* all args */
|
|
|
|
|
fprintf(history, "%s ", argv[i]);
|
|
|
|
|
fputs("\n", history);
|
|
|
|
|
}
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if ((args == 0) /* empty line */
|
|
|
|
|
||(*argv[0] == '#')) /* or a comment, */
|
|
|
|
|
return;
|
|
|
|
|
if (args == MAXARGS) { /* too many arguments, */
|
|
|
|
|
fprintf(stderr, "Too many arguments to %s, this can't be right\n", argv[0]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
command = get_keyword(argv[0], &keyword_set);
|
|
|
|
|
force = 0; /* initialize flags */
|
|
|
|
|
verbose = 0; /* initialize flags */
|
|
|
|
|
Verbose = 0; /* initialize flags */
|
|
|
|
|
recurse = 0; /* initialize flags */
|
|
|
|
|
stats = 0; /* initialize flags */
|
|
|
|
|
/* First handle generic options */
|
|
|
|
|
for (i = 1; (i < args) && (argv[i][0] == '-'); i++) { /* while we have flags */
|
|
|
|
|
for (j = 1; j < strlen(argv[i]); j++)
|
|
|
|
|
switch (argv[i][j]) {
|
1999-01-06 04:09:01 +00:00
|
|
|
|
#if VINUMDEBUG
|
1998-09-16 05:57:36 +00:00
|
|
|
|
case 'd': /* -d: debug */
|
|
|
|
|
debug = 1;
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case 'f': /* -f: force */
|
|
|
|
|
force = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'v': /* -v: verbose */
|
|
|
|
|
verbose++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'V': /* -V: Very verbose */
|
|
|
|
|
verbose++;
|
|
|
|
|
Verbose++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'r': /* -r: recurse */
|
|
|
|
|
recurse = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 's': /* -s: show statistics */
|
|
|
|
|
stats = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
1999-04-10 04:00:49 +00:00
|
|
|
|
case 'w': /* -w: wait for completion */
|
|
|
|
|
dowait = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
1998-09-16 05:57:36 +00:00
|
|
|
|
default:
|
|
|
|
|
fprintf(stderr, "Invalid flag: %s\n", argv[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Pass what we have left to the command to handle it */
|
|
|
|
|
for (j = 0; j < (sizeof(funkeys) / sizeof(struct funkey)); j++) {
|
|
|
|
|
if (funkeys[j].kw == command) { /* found the command */
|
|
|
|
|
funkeys[j].fun(args - i, &argv[i], &argv[0]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "Unknown command: %s\n", argv[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
get_drive_info(struct drive *drive, int index)
|
|
|
|
|
{
|
|
|
|
|
*(int *) drive = index; /* put in drive to hand to driver */
|
|
|
|
|
if (ioctl(superdev, VINUM_DRIVECONFIG, drive) < 0) {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"Can't get config for drive %d: %s\n",
|
|
|
|
|
index,
|
|
|
|
|
strerror(errno));
|
|
|
|
|
longjmp(command_fail, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
get_sd_info(struct sd *sd, int index)
|
|
|
|
|
{
|
|
|
|
|
*(int *) sd = index; /* put in sd to hand to driver */
|
|
|
|
|
if (ioctl(superdev, VINUM_SDCONFIG, sd) < 0) {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"Can't get config for subdisk %d: %s\n",
|
|
|
|
|
index,
|
|
|
|
|
strerror(errno));
|
|
|
|
|
longjmp(command_fail, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the contents of the sd entry for subdisk <sdno>
|
|
|
|
|
* of the specified plex. */
|
|
|
|
|
void
|
|
|
|
|
get_plex_sd_info(struct sd *sd, int plexno, int sdno)
|
|
|
|
|
{
|
|
|
|
|
((int *) sd)[0] = plexno;
|
|
|
|
|
((int *) sd)[1] = sdno; /* pass parameters */
|
|
|
|
|
if (ioctl(superdev, VINUM_PLEXSDCONFIG, sd) < 0) {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"Can't get config for subdisk %d (part of plex %d): %s\n",
|
|
|
|
|
sdno,
|
|
|
|
|
plexno,
|
|
|
|
|
strerror(errno));
|
|
|
|
|
longjmp(command_fail, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
get_plex_info(struct plex *plex, int index)
|
|
|
|
|
{
|
|
|
|
|
*(int *) plex = index; /* put in plex to hand to driver */
|
|
|
|
|
if (ioctl(superdev, VINUM_PLEXCONFIG, plex) < 0) {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"Can't get config for plex %d: %s\n",
|
|
|
|
|
index,
|
|
|
|
|
strerror(errno));
|
|
|
|
|
longjmp(command_fail, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
get_volume_info(struct volume *volume, int index)
|
|
|
|
|
{
|
|
|
|
|
*(int *) volume = index; /* put in volume to hand to driver */
|
|
|
|
|
if (ioctl(superdev, VINUM_VOLCONFIG, volume) < 0) {
|
|
|
|
|
fprintf(stderr,
|
|
|
|
|
"Can't get config for volume %d: %s\n",
|
|
|
|
|
index,
|
|
|
|
|
strerror(errno));
|
|
|
|
|
longjmp(command_fail, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create the device nodes for vinum objects */
|
|
|
|
|
void
|
|
|
|
|
make_devices(void)
|
|
|
|
|
{
|
|
|
|
|
int volno;
|
|
|
|
|
int plexno;
|
|
|
|
|
int sdno;
|
|
|
|
|
int driveno;
|
|
|
|
|
|
|
|
|
|
char filename[PATH_MAX]; /* for forming file names */
|
|
|
|
|
|
1999-03-02 07:01:51 +00:00
|
|
|
|
if (access("/dev", W_OK) < 0) { /* can't access /dev to write? */
|
|
|
|
|
if (errno == EROFS) /* because it's read-only, */
|
1999-04-09 01:14:36 +00:00
|
|
|
|
fprintf(stderr, VINUMMOD ": /dev is mounted read-only, not rebuilding " VINUM_DIR "\n");
|
1999-03-02 07:01:51 +00:00
|
|
|
|
else
|
|
|
|
|
perror(VINUMMOD ": Can't write to /dev");
|
|
|
|
|
return;
|
|
|
|
|
}
|
1999-05-02 08:00:30 +00:00
|
|
|
|
if (history) {
|
|
|
|
|
timestamp();
|
|
|
|
|
fprintf(history, "*** Created devices ***\n");
|
|
|
|
|
}
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if (superdev >= 0) /* super device open */
|
|
|
|
|
close(superdev);
|
|
|
|
|
|
|
|
|
|
system("rm -rf " VINUM_DIR " " VINUM_RDIR); /* remove the old directories */
|
|
|
|
|
system("mkdir -p " VINUM_DIR "/drive " /* and make them again */
|
|
|
|
|
VINUM_DIR "/plex "
|
|
|
|
|
VINUM_DIR "/sd "
|
1999-01-21 00:45:11 +00:00
|
|
|
|
VINUM_DIR "/rsd "
|
1998-09-16 05:57:36 +00:00
|
|
|
|
VINUM_DIR "/vol "
|
|
|
|
|
VINUM_DIR "/rvol "
|
|
|
|
|
VINUM_RDIR);
|
|
|
|
|
|
|
|
|
|
if (mknod(VINUM_SUPERDEV_NAME,
|
|
|
|
|
S_IRWXU | S_IFBLK, /* block device, user only */
|
|
|
|
|
VINUM_SUPERDEV) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", VINUM_SUPERDEV_NAME, strerror(errno));
|
|
|
|
|
|
1999-03-19 07:21:27 +00:00
|
|
|
|
if (mknod(VINUM_WRONGSUPERDEV_NAME,
|
|
|
|
|
S_IRWXU | S_IFBLK, /* block device, user only */
|
|
|
|
|
VINUM_WRONGSUPERDEV) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", VINUM_WRONGSUPERDEV_NAME, strerror(errno));
|
|
|
|
|
|
1998-09-16 05:57:36 +00:00
|
|
|
|
superdev = open(VINUM_SUPERDEV_NAME, O_RDWR); /* open the super device */
|
|
|
|
|
|
1999-03-02 07:01:51 +00:00
|
|
|
|
if (mknod(VINUM_DAEMON_DEV_NAME, /* daemon super device */
|
|
|
|
|
S_IRWXU | S_IFBLK, /* block device, user only */
|
|
|
|
|
VINUM_DAEMON_DEV) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", VINUM_DAEMON_DEV_NAME, strerror(errno));
|
|
|
|
|
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
|
|
|
|
|
perror("Can't get vinum config");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* First, create directories for the volumes */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
for (volno = 0; volno < vinum_conf.volumes_allocated; volno++) {
|
1998-09-16 05:57:36 +00:00
|
|
|
|
dev_t voldev;
|
|
|
|
|
dev_t rvoldev;
|
|
|
|
|
|
|
|
|
|
get_volume_info(&vol, volno);
|
|
|
|
|
if (vol.state != volume_unallocated) { /* we could have holes in our lists */
|
|
|
|
|
voldev = VINUMBDEV(volno, 0, 0, VINUM_VOLUME_TYPE); /* create a block device number */
|
|
|
|
|
rvoldev = VINUMCDEV(volno, 0, 0, VINUM_VOLUME_TYPE); /* and a character device */
|
|
|
|
|
|
|
|
|
|
/* Create /dev/vinum/<myvol> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/%s", vol.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFBLK, voldev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create /dev/rvinum/<myvol> */
|
|
|
|
|
sprintf(filename, VINUM_RDIR "/%s", vol.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFCHR, rvoldev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create /dev/vinum/r<myvol> XXX until we fix fsck and friends */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/r%s", vol.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFCHR, rvoldev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create /dev/vinum/vol/<myvol> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/vol/%s", vol.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFBLK, voldev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create /dev/vinum/rvol/<myvol> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/rvol/%s", vol.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFCHR, rvoldev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create /dev/vinum/vol/<myvol>.plex/ */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/vol/%s.plex", vol.name);
|
|
|
|
|
if (mkdir(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Now create device entries for the plexes in
|
|
|
|
|
* /dev/vinum/<vol>.plex/ and /dev/vinum/plex */
|
|
|
|
|
for (plexno = 0; plexno < vol.plexes; plexno++) {
|
|
|
|
|
dev_t plexdev;
|
|
|
|
|
|
|
|
|
|
get_plex_info(&plex, vol.plex[plexno]);
|
|
|
|
|
if (plex.state != plex_unallocated) {
|
|
|
|
|
plexdev = VINUMBDEV(volno, plexno, 0, VINUM_PLEX_TYPE);
|
|
|
|
|
|
|
|
|
|
/* Create device /dev/vinum/vol/<vol>.plex/<plex> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/vol/%s.plex/%s", vol.name, plex.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFBLK, plexdev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* And /dev/vinum/plex/<plex> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/plex/%s", plex.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFBLK, plexdev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create directory /dev/vinum/vol/<vol>.plex/<plex>.sd */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/vol/%s.plex/%s.sd", vol.name, plex.name);
|
|
|
|
|
if (mkdir(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* Create the contents of /dev/vinum/<vol>.plex/<plex>.sd */
|
|
|
|
|
for (sdno = 0; sdno < plex.subdisks; sdno++) {
|
|
|
|
|
dev_t sddev;
|
|
|
|
|
|
|
|
|
|
get_plex_sd_info(&sd, vol.plex[plexno], sdno);
|
|
|
|
|
if (sd.state != sd_unallocated) {
|
|
|
|
|
sddev = VINUMBDEV(volno, plexno, sdno, VINUM_SD_TYPE);
|
|
|
|
|
|
|
|
|
|
/* Create /dev/vinum/vol/<vol>.plex/<plex>.sd/<sd> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/vol/%s.plex/%s.sd/%s", vol.name, plex.name, sd.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFBLK, sddev) < 0)
|
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
1999-01-21 00:45:11 +00:00
|
|
|
|
/* /dev/vinum/sd/<sd> */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
sprintf(filename, VINUM_DIR "/sd/%s", sd.name);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFBLK, sddev) < 0)
|
1999-01-21 00:45:11 +00:00
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
/* And /dev/vinum/rsd/<sd> */
|
|
|
|
|
sprintf(filename, VINUM_DIR "/rsd/%s", sd.name);
|
|
|
|
|
sddev = VINUMCDEV(volno, plexno, sdno, VINUM_SD_TYPE);
|
|
|
|
|
if (mknod(filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IFCHR, sddev) < 0)
|
1998-09-16 05:57:36 +00:00
|
|
|
|
fprintf(stderr, "Can't create %s: %s\n", filename, strerror(errno));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Drives. Do this later (both logical and physical names) XXX */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
for (driveno = 0; driveno < vinum_conf.drives_allocated; driveno++) {
|
1998-09-16 05:57:36 +00:00
|
|
|
|
get_drive_info(&drive, driveno);
|
|
|
|
|
if (drive.state != drive_unallocated) {
|
|
|
|
|
sprintf(filename, "ln -s %s " VINUM_DIR "/drive/%s", drive.devicename, drive.label.name);
|
|
|
|
|
system(filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
1999-01-21 00:45:11 +00:00
|
|
|
|
/* command line interface for the 'makedev' command */
|
|
|
|
|
void
|
|
|
|
|
vinum_makedev(int argc, char *argv[], char *arg0[])
|
|
|
|
|
{
|
|
|
|
|
make_devices();
|
|
|
|
|
}
|
|
|
|
|
|
1999-03-02 07:01:51 +00:00
|
|
|
|
/*
|
|
|
|
|
* Find the object "name". Return object type at type,
|
1998-09-16 05:57:36 +00:00
|
|
|
|
* and the index as the return value.
|
|
|
|
|
* If not found, return -1 and invalid_object.
|
|
|
|
|
*/
|
|
|
|
|
int
|
|
|
|
|
find_object(const char *name, enum objecttype *type)
|
|
|
|
|
{
|
|
|
|
|
int object;
|
|
|
|
|
|
|
|
|
|
if (ioctl(superdev, VINUM_GETCONFIG, &vinum_conf) < 0) {
|
|
|
|
|
perror("Can't get vinum config");
|
|
|
|
|
*type = invalid_object;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
/* Search the drive table */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
for (object = 0; object < vinum_conf.drives_allocated; object++) {
|
1998-09-16 05:57:36 +00:00
|
|
|
|
get_drive_info(&drive, object);
|
|
|
|
|
if (strcmp(name, drive.label.name) == 0) {
|
|
|
|
|
*type = drive_object;
|
|
|
|
|
return object;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Search the subdisk table */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
for (object = 0; object < vinum_conf.subdisks_allocated; object++) {
|
1998-09-16 05:57:36 +00:00
|
|
|
|
get_sd_info(&sd, object);
|
|
|
|
|
if (strcmp(name, sd.name) == 0) {
|
|
|
|
|
*type = sd_object;
|
|
|
|
|
return object;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Search the plex table */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
for (object = 0; object < vinum_conf.plexes_allocated; object++) {
|
1998-09-16 05:57:36 +00:00
|
|
|
|
get_plex_info(&plex, object);
|
|
|
|
|
if (strcmp(name, plex.name) == 0) {
|
|
|
|
|
*type = plex_object;
|
|
|
|
|
return object;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Search the volume table */
|
1999-04-17 04:13:04 +00:00
|
|
|
|
for (object = 0; object < vinum_conf.volumes_allocated; object++) {
|
1998-09-16 05:57:36 +00:00
|
|
|
|
get_volume_info(&vol, object);
|
|
|
|
|
if (strcmp(name, vol.name) == 0) {
|
|
|
|
|
*type = volume_object;
|
|
|
|
|
return object;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Didn't find the name: invalid */
|
|
|
|
|
*type = invalid_object;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
1999-01-21 00:45:11 +00:00
|
|
|
|
/* Continue reviving a subdisk in the background */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
void
|
1999-01-21 00:45:11 +00:00
|
|
|
|
continue_revive(int sdno)
|
1998-09-16 05:57:36 +00:00
|
|
|
|
{
|
1999-01-21 00:45:11 +00:00
|
|
|
|
struct sd sd;
|
1998-09-16 05:57:36 +00:00
|
|
|
|
pid_t pid;
|
1999-01-21 00:45:11 +00:00
|
|
|
|
get_sd_info(&sd, sdno);
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
1999-01-06 04:09:01 +00:00
|
|
|
|
#if VINUMDEBUG
|
1998-09-16 05:57:36 +00:00
|
|
|
|
if (debug)
|
|
|
|
|
pid = 0; /* wander through into the "child" process */
|
|
|
|
|
else
|
|
|
|
|
pid = fork(); /* do this in the background */
|
1999-01-06 04:09:01 +00:00
|
|
|
|
#else
|
|
|
|
|
pid = fork(); /* do this in the background */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
#endif
|
|
|
|
|
if (pid == 0) { /* we're the child */
|
|
|
|
|
struct _ioctl_reply reply;
|
|
|
|
|
struct vinum_ioctl_msg *message = (struct vinum_ioctl_msg *) &reply;
|
|
|
|
|
|
1999-03-02 07:01:51 +00:00
|
|
|
|
openlog(VINUMMOD, LOG_CONS | LOG_PERROR | LOG_PID, LOG_KERN);
|
1999-01-21 00:45:11 +00:00
|
|
|
|
syslog(LOG_INFO | LOG_KERN, "reviving %s", sd.name);
|
1998-09-16 05:57:36 +00:00
|
|
|
|
|
|
|
|
|
for (reply.error = EAGAIN; reply.error == EAGAIN;) {
|
1999-01-21 00:45:11 +00:00
|
|
|
|
message->index = sdno; /* pass sd number */
|
|
|
|
|
message->type = sd_object; /* and type of object */
|
1998-09-16 05:57:36 +00:00
|
|
|
|
message->state = object_up;
|
|
|
|
|
ioctl(superdev, VINUM_SETSTATE, message);
|
|
|
|
|
}
|
|
|
|
|
if (reply.error) {
|
|
|
|
|
syslog(LOG_ERR | LOG_KERN,
|
1999-01-21 00:45:11 +00:00
|
|
|
|
"can't revive %s: %s",
|
|
|
|
|
sd.name,
|
1998-09-16 05:57:36 +00:00
|
|
|
|
reply.msg[0] ? reply.msg : strerror(reply.error));
|
|
|
|
|
exit(1);
|
|
|
|
|
} else {
|
1999-01-21 00:45:11 +00:00
|
|
|
|
get_sd_info(&sd, sdno); /* update the info */
|
|
|
|
|
syslog(LOG_INFO | LOG_KERN, "%s is %s", sd.name, sd_state(sd.state));
|
1998-09-16 05:57:36 +00:00
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
} else if (pid < 0) /* couldn't fork? */
|
1999-01-21 00:45:11 +00:00
|
|
|
|
fprintf(stderr, "Can't continue reviving %s: %s\n", sd.name, strerror(errno));
|
1998-09-16 05:57:36 +00:00
|
|
|
|
else
|
1999-01-21 00:45:11 +00:00
|
|
|
|
printf("Reviving %s in the background\n", sd.name);
|
|
|
|
|
}
|
|
|
|
|
|
1999-03-02 07:01:51 +00:00
|
|
|
|
/*
|
|
|
|
|
* Check if the daemon is running,
|
1999-01-21 00:45:11 +00:00
|
|
|
|
* start it if it isn't. The check itself
|
|
|
|
|
* could take a while, so we do it as a separate
|
|
|
|
|
* process, which will become the daemon if one isn't
|
1999-03-02 07:01:51 +00:00
|
|
|
|
* running already
|
|
|
|
|
*/
|
1999-01-21 00:45:11 +00:00
|
|
|
|
void
|
|
|
|
|
start_daemon(void)
|
|
|
|
|
{
|
|
|
|
|
int pid;
|
|
|
|
|
int status;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
pid = (int) fork();
|
|
|
|
|
|
|
|
|
|
if (pid == 0) { /* We're the child, do the work */
|
1999-03-02 07:01:51 +00:00
|
|
|
|
/*
|
|
|
|
|
* We have a problem when stopping the subsystem:
|
|
|
|
|
* The only way to know that we're idle is when
|
|
|
|
|
* all open superdevs close. But we want the
|
|
|
|
|
* daemon to clean up for us, and since we can't
|
|
|
|
|
* count the opens, we need to have the main device
|
|
|
|
|
* closed when we stop. We solve this conundrum
|
|
|
|
|
* by getting the daemon to open a separate device.
|
|
|
|
|
*/
|
|
|
|
|
close(superdev); /* this is the wrong device */
|
|
|
|
|
superdev = open(VINUM_DAEMON_DEV_NAME, O_RDWR); /* open deamon superdevice */
|
|
|
|
|
if (superdev < 0) {
|
|
|
|
|
perror("Can't open " VINUM_DAEMON_DEV_NAME);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
1999-01-21 00:45:11 +00:00
|
|
|
|
error = daemon(0, 0); /* this will fork again, but who's counting? */
|
|
|
|
|
if (error != 0) {
|
|
|
|
|
fprintf(stderr, "Can't start daemon: %s (%d)\n", strerror(errno), errno);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
1999-03-02 07:01:51 +00:00
|
|
|
|
setproctitle(VINUMMOD " daemon"); /* show what we're doing */
|
1999-01-21 00:45:11 +00:00
|
|
|
|
status = ioctl(superdev, VINUM_FINDDAEMON, NULL);
|
|
|
|
|
if (status != 0) { /* no daemon, */
|
|
|
|
|
ioctl(superdev, VINUM_DAEMON, &verbose); /* we should hang here */
|
|
|
|
|
syslog(LOG_ERR | LOG_KERN, "%s", strerror(errno));
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
exit(0); /* when told to die */
|
|
|
|
|
} else if (pid < 0) /* couldn't fork */
|
|
|
|
|
printf("Can't fork to check daemon\n");
|
1998-09-16 05:57:36 +00:00
|
|
|
|
}
|
1999-05-02 08:00:30 +00:00
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
timestamp()
|
|
|
|
|
{
|
|
|
|
|
struct timeval now;
|
|
|
|
|
struct tm *date;
|
|
|
|
|
char datetext[MAXDATETEXT];
|
|
|
|
|
|
|
|
|
|
if (history != NULL) {
|
|
|
|
|
if (gettimeofday(&now, NULL) != 0) {
|
|
|
|
|
fprintf(stderr, "Can't get time: %s\n", strerror(errno));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
date = localtime(&(time_t) now.tv_sec);
|
|
|
|
|
strftime(datetext, MAXDATETEXT, dateformat, date),
|
|
|
|
|
fprintf(history,
|
|
|
|
|
"%s.%06ld ",
|
|
|
|
|
datetext,
|
|
|
|
|
now.tv_usec);
|
|
|
|
|
}
|
|
|
|
|
}
|