nisrestore.cc revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*
* Copyright (c) 1988-1998 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
*
* A NIS+ utility for restoring archived databases, as well as an
* out-of-band resync mechanism.
*/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include "nis_bkrst.h"
#include "../rpc.nisd/nis_proc.h"
/*
* Global state variables.
*/
static char *syncfile = "/var/nis/nisrestore.pid";
static int fd_sync;
static void abort_handler(int);
/*
* Extern variables related to the trans.log header.
*/
extern int __nis_logfd;
extern "C" char *relative_name(char *);
/*
* We are masquerading as the master server, so we can create and write
* to a temporary transaction file. This will get us around the
* CHILDPROC check.
*/
extern pid_t master_pid;
/*
* This restoration utility assumes the backup was performed by the nisbackup
* utility and not via some other tools. Using nisbackup will validate the
* following assumptions being made during the restoration sequence:
*
* - the backup directory is organized on a per-directory basis.
* - there are no log files. No table logs and no dictionary log.
* (the backup utility _simulates_ a checkpoint)
* - the dictionary file has entries only for the table files relevant
* to the directory being restored. In other words, the complete
* dictionary file is merged without validating each entry.
* - the transaction log has only entry and that is the UPD_TIME_STAMP
* entry for the directory being restored.
*
*/
/*
* Needs to be cleaned up in nislog as well as here. nis_log_common.o
* is shared by rpc.nisd as well as nislog and nisbackup and rpc.nisd
* source already defines this.
*/
int
abort_transaction(int xid)
{
xid = 0;
return (0);
}
static void
{
}
static void
abort_handler(int sig)
{
sig = 0;
exit(1);
}
/*
* clean_up() called atexit()
*/
static void
clean_up()
{
}
void
usage()
{
exit(1);
}
/*
* This routine vforks "rm -r" to remove a non-empty directory, for cleanup.
*/
int
{
struct stat s;
"nisbackup: directory %s does not exist.", name);
return (1);
}
case -1: /* error */
return (1);
case 0: /* child */
"-r",
name,
NULL) < 0)
_exit(1);
_exit(0); /* Since this is the child proc, we'll exit */
default: /* parent, we'll wait for the rm to complete */
return (1);
} else {
"Can't remove files: error = %d",
return (1);
}
}
}
return (0);
}
/*
* This routine takes the old name of the data file, a token to search for,
* and the replacement string to replace the token with. This will change
* names like org_dir.foo.bar to org_dir.foo (tok=.foo.bar, repl=.foo).
*/
char *
{
char *newname;
char *s;
if (!newname) {
return (NULL);
}
} else {
} else {
return (NULL);
}
}
return (newname);
}
/*
* This routine checks the data file names in the backup with their relative
* name on this server. If the master server that generated this backup lived
* in a different domain than this (replica) server, the files will need to
* be renamed and their names changed in the data dictionary.
*/
{
char *domainof;
char *p;
/*
* If we're restoring a root domain server, no renaming.
*/
return (FALSE);
}
/*
* The only way the localized name of the object is NULL is by
* using the -f(orce) option on a machine that's in the wrong
* domain for this object. All bets are off with the -f option. Punt.
*/
return (FALSE);
/*
* If localized name of directory exist in backup dir, no renaming.
*/
p = relative_name(full_obj_name);
free((void *)p);
return (FALSE);
}
exit(1);
}
continue; /* Ignore */
continue; /* Ignore */
/*
* Extrace the domain part of the entry, which will be the old suffix.
* Then get the domain part of the entry as it would be stored on this
* server (new suffix).
*/
} else {
}
} else {
}
break;
}
return (TRUE);
}
{
char *newtablename;
return (FALSE);
}
continue; /* Ignore */
continue; /* Ignore */
/*
* Take the leaf name of the table, append the new suffix, then rename
* the file. Attempt to unlink any .log file that may still be present.
*/
== FALSE) {
return (FALSE);
}
}
/* The temporary directory should be empty now, so we can remove it. */
return (FALSE);
}
return (TRUE);
}
/*
* bool_t is_server_of()
*
* Boolean function to check if local host is a server for the object
* passed in nis_obj. If the passed in object is not fully qualified, then
* nis_getnames() is called to resolve the object's full name. The parameter,
* full_name, is used to return the fully qualified name (ignored if set to
* NULL). The nis_server struct is returned in *srv.
*/
{
char myname[NIS_MAXNAMELEN];
int i;
/*
* We will use __nis_CacheBind instead of nis_lookup() so that we
* can take advantage of the NIS_SHARED_DIRCACHE.
*/
for (i = 0; namelist[i]; i++) {
#ifdef DEBUG
#endif
if (err == NIS_SUCCESS) {
if (full_name)
break;
}
}
} else { /* Fully qualified */
if (err == NIS_SUCCESS)
if (full_name)
}
if (err != NIS_SUCCESS) {
#ifdef DEBUG
#endif
return (0);
}
/*
* If we can get a directory object, we check for membership.
*/
if (err == NIS_SUCCESS) {
if (strcasecmp(myname,
== 0) {
return (1);
}
}
}
if (!force)
return (0);
}
/*
* This is a link list of the directories being restored on this server.
*/
struct nis_dir_list {
char *name;
struct nis_dir_list *next;
};
/*
* dirobj_list() maintains list of directory objects that are backed up
* with the -a(ll) option of the nisbackup utility.
*/
struct nis_dir_list *
{
int ss;
switch (op) {
case CACHE_INIT:
/* Initialize cache with the file name passed in argp */
return ((struct nis_dir_list *) NULL);
}
name++;
end++;
tmp = (struct nis_dir_list *)
XMALLOC(sizeof (struct nis_dir_list));
return ((struct nis_dir_list *) NULL);
}
return ((struct nis_dir_list *) NULL);
}
dirlisthead = tmp;
}
return (dirlisthead);
case CACHE_ADD:
return ((struct nis_dir_list *) NULL);
/* Check whether already in cache */
return (tmp);
/* Add it */
tmp = (struct nis_dir_list *)
malloc(sizeof (struct nis_dir_list));
return ((struct nis_dir_list *) NULL);
}
return ((struct nis_dir_list *) NULL);
}
dirlisthead = tmp;
#ifdef DEBUG
#endif
return (tmp);
case CACHE_SAVE: /* Save back'd up objects to a file */
return (NULL);
}
"Could not open file %s for updating\n",
argp);
return ((struct nis_dir_list *) NULL);
}
}
return (NULL);
return (dirlisthead);
case CACHE_FIND: /* Search for object in cache */
return (NULL);
return (tmp);
return (NULL);
case CACHE_DUMP: /* Show 'em what we've got */
return ((struct nis_dir_list *) NULL);
default:
return ((struct nis_dir_list *) NULL);
}
}
int
{
int error;
int ret;
char backup[1024];
char buf[NIS_MAXNAMELEN];
if (verbiage)
/*
* Map in transaction log file.
*/
"Unable to map transaction log : %s\n", buf);
return (0);
}
"Unable to merge transaction log, log unstable.\n");
return (0);
}
if (fd == -1) {
"Unable to open backup log (%s).\n", backup);
return (0);
}
/*
* Make a backup of the log in two steps, write the size of the log
* and then a copy of the log.
*/
sizeof (log_hdr);
"Unable to merge transaction log, disk full.\n");
return (0);
}
/*
* Now set the state to RESYNC so if we have to recover and read
* this back in, and we get screwed while reading it, we won't
* get into major trouble. This still leaves open one window :
* a) Start checkpoint
* b) Successfully backup to BACKUP
* c) Set log state to CHECKPOINT
* c) crash.
* d) Reboot and start reading in the backup log
* e) crash.
* f) reboot and now the log appears to be resync'ing without
* all of its data.
*/
"Unable to merge transaction log, disk full.\n");
sync_header();
return (0);
}
/* If we crash here we're ok since the log hasn't changed. */
sync_header();
break;
}
num++;
}
}
if (num == 0) {
/* Deleted all of the entries. */
#ifdef DEBUG
"merge_trans_log: all entries removed.\n");
#endif
sync_header();
if (ret == -1) {
"Cannot truncate transaction log file\n");
return (0);
}
if (ret == -1) {
"merge_trans_log: cannot increase transaction log file size\n");
return (0);
}
if (ret != 1) {
"cannot write one character to transaction log file\n");
return (0);
}
__nis_logsize = sizeof (log_hdr);
perror("msync:");
return (0);
}
} else {
#ifdef DEBUG
"merge_trans_log: some entries removed.\n");
#endif
sync_header();
if (error) {
"Transaction log merge failed, unable to resync.\n");
return (0);
}
}
/*
* Write back "last update" stamps for all of the directories
* we're restoring.
*/
sync_header();
return (1);
}
int
{
char dest[1024];
struct stat s;
exit(1);
}
if (rename_files) {
/*
* We need to rename each file! We'll copy them into the temporary
* rename_data_files().
*/
"Unable to create NIS+ directory %s\n",
dest);
exit(1);
}
} else {
"Unable to stat NIS+ directory %s.\n", dest);
exit(1);
}
} else {
"Unable to create NIS+ directory %s\n",
dest);
exit(1);
}
}
} else {
}
case -1: /* error */
exit(1);
case 0: /* child */
_exit(1);
_exit(0);
default: /* parent, we'll wait for the cp to complete */
exit(1);
} else {
"Can't copy backed up files: error = %d\n",
exit(1);
}
}
}
return (1);
}
void
sanity_checks(char *backupdir)
{
struct stat s;
char buf[NIS_MAXNAMELEN];
char backup_path[NIS_MAXNAMELEN];
char trans_log[NIS_MAXNAMELEN];
unsigned char u_nl[16];
char pid[16];
int fd_s;
#ifdef DEBUG
int elapse;
#endif
/*
* Create a synchronization file, to prevent multiple invocations
* of nisrestore from running simultaneously.
*/
"Unable to create pid file: %s\n", syncfile);
exit(1);
}
/*
* Lock without blocking. If we can't get it, another nisrestore
* must have it.
*/
exit(1);
}
/*
* We'll write our pid to the file, for good measure. Leave the
* fd_sync open for the duration of the restore.
*/
exit(1);
}
/*
* Catch signals and clean up files before exit.
*/
init_signals();
/*
* Clean up any files before we exit.
*/
if (*backupdir != '/') {
"Backup directory %s is not a absolute path name.\n",
exit(1);
}
/*
* Check if backup directory exists.
*/
"Backup directory %s does not exist.\n",
exit(1);
}
if (aflg) {
"Unable to access backup list : %s\n", buf);
exit(1);
}
/*
* Sanity check: make sure that we serve the directory object(s)
* listed in the backup_list file. Note: backup_list is created by
* nisbackup(1), so we know the objects are fully qualified.
*/
!force) {
exit(1);
}
}
}
/*
* Check if the NIS+ server is running. Report an error if it is.
* If srv_ptr is not set, they must be using the -f(orce), so all
* bets are off. Punt
*/
if (srv_ptr) {
#ifdef DEBUG
gettimeofday(&ctime, 0);
#endif
if (verbiage)
"Verifying rpc.nisd process is not running\n");
if (error == NIS_SUCCESS) {
"rpc.nisd is active, kill server before running nisrestore\n");
exit(1);
}
#ifdef DEBUG
gettimeofday(&ctime, 0);
#endif
}
#ifdef DEBUG
else
#endif
/*
* Check if backup directory(ies) exist and are valid.
*/
/*
* Check for the "data" subdirectory in the backup.
*/
"%s not found.\n", backup_path);
exit(1);
}
/*
* Check for the existence of a dictionary file in the backup-dir.
*/
"Dictionary file %s not found.\n", buf);
exit(1);
}
/*
* Check for the last update transaction file in the backup-dir.
*/
exit(1);
}
/*
* Read in the last update file, and cache the time stamp.
*/
exit(1);
}
/*
* Read in the backup revision number, and verify.
*/
"Invalid backup version #%X, should be #%X\n",
exit(1);
}
#ifdef DEBUG
#endif
exit(1);
}
}
/*
* Check for the local "data" directory existing. If not, create it.
*/
"Unable to create NIS+ directory %s\n", buf);
exit(1);
}
} else {
"Unable to stat NIS+ directory %s.\n", buf);
exit(1);
}
}
if (!db_initialize(buf)) {
"Unable to initialize data dictionary %s.", buf);
exit(1);
}
}
{
int ss;
/*
* If it doesn't exist, CACHE_SAVE will create it.
*/
} else {
return (FALSE);
name++;
end++;
return (FALSE);
}
}
}
}
/*
* rm_logs()
*
* This routine checks for each database file restored, if a .log file
* as they will be invalid after the restore is performed. As an optimization,
* directory has been created (new install) and therefore this routine is
* unnecessary.
*/
{
char localdir[NIS_MAXNAMELEN];
char buf[NIS_MAXNAMELEN];
char full_name[NIS_MAXNAMELEN];
char *domainof;
#ifdef DEBUG
printf("rm_logs: entry point\n");
#endif
return (FALSE);
}
continue; /* Ignore */
continue; /* Ignore */
continue; /* root.object doesn't have log file */
#ifdef DEBUG
#endif
continue;
/*
* Determine if this .log file belongs to a directory object that has been
* restored.
*/
continue;
}
/*
* It's a match, delete it!
*/
return (FALSE);
}
} /* while (readdir) */
return (TRUE);
}
print_backuplist(char *backupdir)
{
struct stat s;
char buf[NIS_MAXNAMELEN];
if (*backupdir != '/') {
"Backup directory %s is not a absolute path name.\n",
return (FALSE);
}
/*
* Check if backup directory exists.
*/
return (FALSE);
}
return (FALSE);
}
return (TRUE);
}
int
{
char *backupdir;
char nisdomain[NIS_MAXNAMELEN];
char newdict[NIS_MAXNAMELEN];
char buf[NIS_MAXNAMELEN];
int c;
char *tmp;
if (geteuid() != 0) {
exit(1);
}
if (argc < 3)
usage();
switch (c) {
case 'a' :
break;
case 'f' :
break;
case 't' :
break;
case 'v' :
break;
default:
usage();
}
}
usage();
} else {
usage();
/*
* Yes, I really mean "=", not "==". tmp is assigned in while.
*/
!force) { /* Not server for object specd */
exit(1);
} else {
if (dirobj_list(CACHE_ADD,
== NULL) {
"Internal error, unable to add to cache\n");
exit(1);
}
}
}
}
if (tocflg) {
if (!print_backuplist(backupdir)) {
"Could not list objects in backup directory %s\n",
exit(1);
}
exit(0);
}
master_pid = getpid();
/*
* Loop through dirobj_list, merge in dictionary entries. After looping
* through, close out the data dictionary, merge_trans_log(), update
* the serving_list and were done!
*/
/*
* Now, copy the contents of the backup directory to our local
* directory. Overwrite any local files if they exist. First we
* check to see if the files need to be renamed. If the master
* lives in a different domain than the server being restored,
* the database files have different names.
*/
if (verbiage)
&newsuf);
"Unable to copy file from backup directory %s.\n", buf);
exit(1);
}
== FALSE)) {
"Unable to move data files into place.\n");
exit(1);
}
if (verbiage)
"Adding the %s objects into the data dictionary.\n",
!= DB_SUCCESS) {
exit(1);
}
/*
* Since the backup doesn't have any .log files, any reminant
* .log's need to be removed. This is taken care of by
* rename_data_files() if rename_files is TRUE.
*/
exit(1);
}
}
/*
* Now closeout the data dictionary.
*/
dbstat);
exit(1);
}
if (verbiage)
if (!merge_trans_log()) {
"Unable to merge backed up transaction log.\n");
exit(1);
}
if (!merge_serving_list()) {
exit(1);
}
exit(0);
}