/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 * or http://www.opensolaris.org/os/licensing. * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rdcadm.h" /* * Special re-use of sndrboot to fix SNDR set IDs during post-patch processing */ #define RDC_CMD_FIXSETIDS 0xFEEDFACE /* * config file user level Dual copy pair structure */ typedef struct _sd_dual_pair { char fhost[MAX_RDC_HOST_SIZE]; /* Hostname for primary device */ char fnetaddr[RDC_MAXADDR]; /* Host netaddr for primary device */ char ffile[NSC_MAXPATH]; /* Primary device */ char fbitmap[NSC_MAXPATH]; /* Primary bitmap device */ char thost[MAX_RDC_HOST_SIZE]; /* Hostname for secondary device */ char tnetaddr[RDC_MAXADDR]; /* Host netaddr for secondary device */ char tfile[NSC_MAXPATH]; /* Secondary device */ char tbitmap[NSC_MAXPATH]; /* Secondary bitmap device */ char directfile[NSC_MAXPATH]; /* Local FCAL direct IO volume */ char diskqueue[NSC_MAXPATH]; /* Disk Queue volume */ char group[NSC_MAXPATH]; /* Group name */ char lhost[MAX_RDC_HOST_SIZE]; /* Logical hostname for cluster */ int doasync; /* Device is in sync/async mode */ int setid; /* unique setid of this set */ } _sd_dual_pair_t; #include #include #include #include #include #include #include char *ctag = NULL; int parseopts(int, char **, int *); static int rdc_operation(char *, char *, char *, char *, char *, char *, int, char *, char *, char *, int, char *, int setid); static int read_libcfg(int); static void usage(void); extern char *basename(char *); int rdc_maxsets; static _sd_dual_pair_t *pair_list; char *program; struct netbuf svaddr; struct netbuf *svp; struct netconfig nconf; struct netconfig *conf; struct knetconfig knconf; static int clustered = 0; static int proto_test = 0; #ifdef lint int sndrboot_lintmain(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { char fromhost[MAX_RDC_HOST_SIZE]; char tohost[MAX_RDC_HOST_SIZE]; char fromfile[NSC_MAXPATH]; char tofile[NSC_MAXPATH]; char frombitmap[NSC_MAXPATH]; char tobitmap[NSC_MAXPATH]; char directfile[NSC_MAXPATH]; char diskqueue[NSC_MAXPATH]; char group[NSC_MAXPATH]; char lhost[MAX_RDC_HOST_SIZE]; int pairs; int pid; int flag = 0; int doasync; int rc; char *required; int setid; (void) setlocale(LC_ALL, ""); (void) textdomain("rdc"); program = basename(argv[0]); rc = rdc_check_release(&required); if (rc < 0) { rdc_err(NULL, gettext("unable to determine the current " "Solaris release: %s\n"), strerror(errno)); } else if (rc == FALSE) { rdc_err(NULL, gettext("incorrect Solaris release (requires %s)\n"), required); } rdc_maxsets = rdc_get_maxsets(); if (rdc_maxsets == -1) { spcs_log("sndr", NULL, gettext("%s unable to get maxsets value from kernel"), program); rdc_err(NULL, gettext("unable to get maxsets value from kernel")); } pair_list = calloc(rdc_maxsets, sizeof (*pair_list)); if (pair_list == NULL) { rdc_err(NULL, gettext( "unable to allocate pair_list" " array for %d sets"), rdc_maxsets); } if (parseopts(argc, argv, &flag)) return (1); pairs = read_libcfg(flag); if (flag == RDC_CMD_FIXSETIDS) { if (pairs) { spcs_log("sndr", NULL, gettext("Fixed %d Remote Mirror" " set IDs"), pairs); #ifdef DEBUG rdc_warn(NULL, gettext("Fixed %d Remote Mirror set " "IDs"), pairs); #endif } return (0); } if (pairs == 0) { #ifdef DEBUG rdc_err(NULL, gettext("Config contains no dual copy sets")); #else return (0); #endif } while (pairs--) { pid = fork(); if (pid == -1) { /* error forking */ perror("fork"); continue; } if (pid > 0) /* this is parent process */ continue; /* * At this point, this is the child process. Do the operation */ (void) strncpy(fromfile, pair_list[pairs].ffile, NSC_MAXPATH); (void) strncpy(tofile, pair_list[pairs].tfile, NSC_MAXPATH); (void) strncpy(frombitmap, pair_list[pairs].fbitmap, NSC_MAXPATH); (void) strncpy(fromhost, pair_list[pairs].fhost, MAX_RDC_HOST_SIZE); (void) strncpy(tohost, pair_list[pairs].thost, MAX_RDC_HOST_SIZE); (void) strncpy(tobitmap, pair_list[pairs].tbitmap, NSC_MAXPATH); (void) strncpy(directfile, pair_list[pairs].directfile, NSC_MAXPATH); (void) strncpy(diskqueue, pair_list[pairs].diskqueue, NSC_MAXPATH); (void) strncpy(group, pair_list[pairs].group, NSC_MAXPATH); (void) strncpy(lhost, pair_list[pairs].lhost, MAX_RDC_HOST_SIZE); doasync = pair_list[pairs].doasync; setid = pair_list[pairs].setid; if (rdc_operation(fromhost, fromfile, frombitmap, tohost, tofile, tobitmap, flag, directfile, group, diskqueue, doasync, lhost, setid) < 0) { exit(255); } exit(0); } while ((wait((int *)0) > 0)) ; return (0); } static int rdc_operation(fromhost, fromfile, frombitmap, tohost, tofile, tobitmap, flag, directfile, group, diskqueue, doasync, lhost, setid) char *fromhost, *fromfile, *frombitmap; char *tohost, *tofile, *tobitmap; int flag, doasync; char *directfile; char *group, *diskqueue; int setid; char *lhost; { const int getaddr = (flag == RDC_CMD_RESUME); const int rpcbind = !getaddr; rdc_config_t parms; int ret; spcs_s_info_t ustatus; struct hostent *hp; char fromname[MAXHOSTNAMELEN], toname[MAXHOSTNAMELEN]; struct t_info tinfo; int i; conf = &nconf; bzero(&fromname, MAXHOSTNAMELEN); bzero(&toname, MAXHOSTNAMELEN); hp = gethost_byname(fromhost); if (hp == NULL) { spcs_log("sndr", NULL, gettext("%s gethost_byname failed for %s"), program, fromhost); } if (strcmp(hp->h_name, fromhost) == 0) (void) strncpy(fromname, hp->h_name, MAXHOSTNAMELEN); else { for (i = 0; hp->h_aliases[i] != NULL; i++) { if (strcmp(hp->h_aliases[i], fromhost) == 0) (void) strncpy(fromname, hp->h_aliases[i], MAXHOSTNAMELEN); } } if (fromname[0] == '\0') { spcs_log("sndr", NULL, gettext("%s host %s is not local"), program, fromhost); rdc_err(NULL, gettext("Host %s is not local"), fromhost); } hp = gethost_byname(tohost); if (hp == NULL) { spcs_log("sndr", NULL, gettext("%s gethost_byname failed for %s"), program, tohost); } if (strcmp(hp->h_name, tohost) == 0) (void) strncpy(toname, hp->h_name, MAXHOSTNAMELEN); else { for (i = 0; hp->h_aliases[i] != NULL; i++) { if (strcmp(hp->h_aliases[i], tohost) == 0) (void) strncpy(toname, hp->h_aliases[i], MAXHOSTNAMELEN); } } if (toname[0] == '\0') { spcs_log("sndr", NULL, gettext("%s host %s is not local"), program, tohost); rdc_err(NULL, gettext("Host %s is not local"), tohost); } if (self_check(fromname) && self_check(toname)) { spcs_log("sndr", NULL, gettext("%s Both %s and %s are local"), program, fromhost, tohost); rdc_err(NULL, gettext("Both %s and %s are local"), fromhost, tohost); } /* * Now build up the address for each host including port and transport */ if (getaddr) { svp = get_addr(toname, RDC_PROGRAM, RDC_VERS_MIN, &conf, proto_test?NC_UDP: NULL, "rdc", &tinfo, rpcbind); if (svp == NULL) { #ifdef DEBUG (void) printf("get_addr failed for Ver 4 %s\n", toname); #endif spcs_log("sndr", NULL, gettext("%s get_addr failed for Ver 4"), program); return (-1); } svaddr = *svp; } else { bzero(&svaddr, sizeof (svaddr)); } parms.rdc_set->secondary.addr.len = svaddr.len; parms.rdc_set->secondary.addr.maxlen = svaddr.maxlen; parms.rdc_set->secondary.addr.buf = (void *)svaddr.buf; #ifdef DEBUG_ADDR (void) fprintf(stderr, "secondary buf %x len %d\n", svaddr.buf, svaddr.len); for (i = 0; i < svaddr.len; i++) (void) printf("%u ", svaddr.buf[i]); (void) printf("\n"); #endif if (getaddr) { svp = get_addr(fromname, RDC_PROGRAM, RDC_VERS_MIN, &conf, proto_test?NC_UDP: NULL, "rdc", &tinfo, rpcbind); if (svp == NULL) { #ifdef DEBUG (void) printf("get_addr failed for Ver 4 %s\n", fromname); #endif return (-1); } svaddr = *svp; } else { ; /*EMPTY*/ } parms.rdc_set->primary.addr.len = svaddr.len; parms.rdc_set->primary.addr.maxlen = svaddr.maxlen; parms.rdc_set->primary.addr.buf = (void *)svaddr.buf; #ifdef DEBUG_ADDR (void) fprintf(stderr, "primary buf %x len %d\n", svaddr.buf, svaddr.len); for (i = 0; i < svaddr.len; i++) (void) printf("%u ", svaddr.buf[i]); (void) printf("\n"); #endif if (getaddr) { (void) convert_nconf_to_knconf(conf, &knconf); #ifdef DEBUG_ADDR (void) printf("knconf %x %s %s %x\n", knconf.knc_semantics, knconf.knc_protofmly, knconf.knc_proto, knconf.knc_rdev); #endif parms.rdc_set->netconfig = &knconf; } else { parms.rdc_set->netconfig = NULL; } if (!clustered && !self_check(fromname) && !self_check(toname)) { spcs_log("sndr", NULL, gettext("%s Neither %s nor %s is local"), program, fromhost, tohost); rdc_err(NULL, gettext("Neither %s nor %s is local"), fromhost, tohost); } (void) strncpy(parms.rdc_set->primary.intf, fromhost, MAX_RDC_HOST_SIZE); (void) strncpy(parms.rdc_set->primary.file, fromfile, NSC_MAXPATH); (void) strncpy(parms.rdc_set->primary.bitmap, frombitmap, NSC_MAXPATH); (void) strncpy(parms.rdc_set->secondary.intf, tohost, MAX_RDC_HOST_SIZE); (void) strncpy(parms.rdc_set->secondary.file, tofile, NSC_MAXPATH); (void) strncpy(parms.rdc_set->secondary.bitmap, tobitmap, NSC_MAXPATH); (void) strncpy(parms.rdc_set->group_name, group, NSC_MAXPATH); (void) strncpy(parms.rdc_set->disk_queue, diskqueue, NSC_MAXPATH); parms.rdc_set->maxqfbas = maxqfbas; parms.rdc_set->maxqitems = maxqitems; parms.rdc_set->autosync = autosync; parms.rdc_set->asyncthr = asyncthr; parms.rdc_set->setid = setid; /* gethostid(3c) is defined to return a 32bit value */ parms.rdc_set->syshostid = (int32_t)gethostid(); parms.command = 0; parms.options = 0; parms.command = flag; if (flag == RDC_CMD_RESUME) { if (doasync) parms.options |= RDC_OPT_ASYNC; else parms.options |= RDC_OPT_SYNC; } if (clustered) { if (!ctag) goto noconfig; if (strcmp(ctag, "-") == 0) goto noconfig; #ifdef DEBUG (void) fprintf(stderr, "logical hostname: %s\n", lhost); #endif if (strcmp(lhost, fromname) == 0) { parms.options |= RDC_OPT_PRIMARY; (void) strncpy(parms.rdc_set->direct_file, directfile, NSC_MAXPATH); } else { parms.options |= RDC_OPT_SECONDARY; parms.rdc_set->direct_file[0] = 0; /* no fcal direct */ } } else { noconfig: /* * If not clustered, don't resume sndr sets with lhost */ if ((flag == RDC_CMD_RESUME) && lhost && strlen(lhost)) return (0); if (self_check(fromname)) { parms.options |= RDC_OPT_PRIMARY; (void) strncpy(parms.rdc_set->direct_file, directfile, NSC_MAXPATH); } else { parms.options |= RDC_OPT_SECONDARY; parms.rdc_set->direct_file[0] = 0; /* no fcal direct */ } } ustatus = spcs_s_ucreate(); errno = 0; ret = RDC_IOCTL(RDC_CONFIG, &parms, NULL, 0, 0, 0, ustatus); if (ret != SPCS_S_OK) { /* Surpress error messages for suspend on cluster elements */ if ((flag == RDC_CMD_SUSPEND) && (errno == RDC_EALREADY) && !clustered && lhost && strlen(lhost)) { spcs_s_ufree(&ustatus); return (0); } (void) fprintf(stderr, gettext("Remote Mirror: %s %s %s %s %s %s\n"), fromhost, fromfile, frombitmap, tohost, tofile, tobitmap); if (errno == RDC_EEINVAL) { spcs_log("sndr", NULL, gettext("%s %s %s %s %s %s %s %s\n%s"), program, rdc_decode_flag(flag, parms.options), fromhost, fromfile, frombitmap, tohost, tofile, tobitmap, gettext("invalid command option")); rdc_err(&ustatus, gettext("Remote Mirror: invalid command option " "'%s'"), rdc_decode_flag(flag, parms.options)); } else { spcs_log("sndr", &ustatus, gettext("%s %s %s %s %s %s %s %s"), program, rdc_decode_flag(flag, parms.options), fromhost, fromfile, frombitmap, tohost, tofile, tobitmap); rdc_err(&ustatus, 0); } } spcs_log("sndr", NULL, gettext("%s %s %s %s %s %s %s %s\nSuccessful"), program, rdc_decode_flag(flag, parms.options), fromhost, fromfile, frombitmap, tohost, tofile, tobitmap); spcs_s_ufree(&ustatus); return (0); } /* * assign setid's to any existing * sets without setids, making sure of course NOT to re-use a setid */ int update_setids(CFGFILE *cfg, int *no_id, int highest) { int setid; char buf[CFG_MAX_BUF]; char key[CFG_MAX_KEY]; char *ctag; /* If in a Sun Cluster, SetIDs need to have a ctag */ if ((ctag = cfg_get_resource(cfg)) != NULL) { ctag = strdup(ctag); cfg_resource(cfg, "setid-ctag"); } /* * Paranoia. IF there are any sets with setids, we don't * want to re-use their number. */ if (highest > get_new_cfg_setid(cfg)) { bzero(&buf, sizeof (buf)); (void) sprintf(buf, "%d", highest); if (cfg_put_cstring(cfg, "setid.set1.value", buf, sizeof (buf)) < 0) rdc_warn(NULL, gettext("sndrboot: Unable to store " "new setid")); } for (setid = 0; no_id[setid]; setid++) { bzero(&buf, sizeof (buf)); bzero(&key, sizeof (key)); (void) sprintf(buf, "%d", get_new_cfg_setid(cfg)); (void) sprintf(key, "sndr.set%d.options", no_id[setid]); if (cfg_put_options(cfg, CFG_SEC_CONF, key, "setid", buf) < 0) rdc_warn(NULL, gettext("sndrboot: Unable to store " "unique setid")); pair_list[no_id[setid] - 1].setid = atoi(buf); } /* Restore old ctag if in a Sun Cluster */ if (ctag) { cfg_resource(cfg, ctag); free(ctag); } if (cfg_commit(cfg) < 0) rdc_err(NULL, gettext("sndrboot: Failed to commit setids")); return (setid); } /* * this is called when the option lghn is no available in libdscfg * that should only happen on an upgrade. * cfg write lock must be held across this function */ char * get_lghn(CFGFILE *cfg, char *ctag, int setnum, int flag) { FILE *pipe; char rsgrp[SCCONF_MAXSTRINGLEN]; char cmd[SCCONF_MAXSTRINGLEN]; static char lhostname[MAX_RDC_HOST_SIZE]; char key[CFG_MAX_KEY]; int rc; if (ctag == NULL) goto fail; bzero(&lhostname, sizeof (lhostname)); (void) sprintf(rsgrp, "%s-stor-rg", ctag); /* BEGIN CSTYLED */ rc = snprintf(cmd, SCCONF_MAXSTRINGLEN, "/usr/cluster/bin/scrgadm -pvv | fgrep HostnameList \ | fgrep %s | fgrep value | awk -F: '{ print $4 }'", rsgrp); /* END CSTYLED */ if (rc < 0) { rdc_err(NULL, gettext("Error getting scrgadm output")); } pipe = popen(cmd, "r"); if (pipe == NULL) { rdc_err(NULL, gettext("Error opening pipe")); } rc = fscanf(pipe, "%s", lhostname); (void) pclose(pipe); if (rc != 1) { rdc_err(NULL, gettext("Unable to get logical host")); } /* not really failing, but suspend does not have the config lock */ if (flag == RDC_CMD_SUSPEND) goto fail; bzero(&key, sizeof (key)); (void) snprintf(key, sizeof (key), "sndr.set%d.options", setnum); if (cfg_put_options(cfg, CFG_SEC_CONF, key, "lghn", lhostname) < 0) rdc_warn(NULL, gettext("sndrboot: Unable to store logical " "host name in configuration database")); if (cfg_commit(cfg) < 0) rdc_err(NULL, gettext("sndrboot: Failed to commit logical host name")); fail: return (lhostname); } /* * read_libcfg() * * DESCRIPTION: Read the relevant config info via libcfg * * Outputs: * int i Number of pairs of devices * * Side Effects: The 0 to i-1 entries in the pair_list are filled. * */ static int read_libcfg(int flag) { char fromhost[MAX_RDC_HOST_SIZE]; char fromfile[NSC_MAXPATH]; char frombitmap[NSC_MAXPATH]; char tohost[MAX_RDC_HOST_SIZE]; char tofile[NSC_MAXPATH]; char tobitmap[NSC_MAXPATH]; char directfile[NSC_MAXPATH]; char diskqueue[NSC_MAXPATH]; char group[NSC_MAXPATH]; char lhost[MAX_RDC_HOST_SIZE]; char sync[16]; char setid[64]; int doasync; CFGFILE *cfg; int i, j = 0; int rc; char buf[CFG_MAX_BUF]; char key[CFG_MAX_KEY]; char dummy[NSC_MAXPATH]; int setnumber; int numsets; int highest = 0; char lghn[5]; int *no_id; if ((cfg = cfg_open("")) == NULL) rdc_err(NULL, gettext("Error opening config")); /* * If RDC_CMD_FIXSETIDS, we were called during post-patch install * Acquire a write-lock on the cfg_lock(), so the code can attempt * to fix setIDs */ if (flag == RDC_CMD_FIXSETIDS) { if (!cfg_lock(cfg, CFG_WRLOCK)) rdc_err(NULL, gettext("Error write locking config")); cfg_resource(cfg, NULL); } else { if (!cfg_lock(cfg, CFG_RDLOCK)) rdc_err(NULL, gettext("Error locking config")); cfg_resource(cfg, ctag); } if ((numsets = cfg_get_num_entries(cfg, "sndr")) < 0) rdc_err(NULL, gettext("Unable to get set info from config")); no_id = (int *)calloc(numsets + 1, sizeof (int)); if (!no_id) rdc_err(NULL, gettext("No memory")); (void) snprintf(lghn, sizeof (lghn), "lghn"); for (i = 0; i < rdc_maxsets; i++) { setnumber = i + 1; bzero(buf, CFG_MAX_BUF); (void) snprintf(key, sizeof (key), "sndr.set%d", setnumber); if (cfg_get_cstring(cfg, key, buf, CFG_MAX_BUF) < 0) break; rc = sscanf(buf, "%s %s %s %s %s %s %s %s %s %s %s %s", fromhost, fromfile, frombitmap, tohost, tofile, tobitmap, directfile, sync, group, dummy, dummy, diskqueue); if (rc != 12) rdc_err(NULL, gettext("cfg input error (%d)"), rc); if (strcmp(directfile, "ip") == 0) (void) strcpy(directfile, ""); if (strcmp(group, "-") == 0) (void) strcpy(group, ""); if (strcmp(diskqueue, "-") == 0) (void) strcpy(diskqueue, ""); (void) snprintf(key, sizeof (key), "sndr.set%d.options", setnumber); if (cfg_get_single_option(cfg, CFG_SEC_CONF, key, lghn, lhost, MAX_RDC_HOST_SIZE) < 0) (void) strcpy(lhost, get_lghn(cfg, ctag, setnumber, flag)); if (strcmp(sync, "sync") == 0) doasync = 0; else if (strcmp(sync, "async") == 0) doasync = 1; else { cfg_close(cfg); rdc_err(NULL, gettext("Set %s:%s neither sync nor async"), tohost, tofile); } (void) strncpy(pair_list[i].fhost, fromhost, MAX_RDC_HOST_SIZE); (void) strncpy(pair_list[i].ffile, fromfile, NSC_MAXPATH); (void) strncpy(pair_list[i].fbitmap, frombitmap, NSC_MAXPATH); (void) strncpy(pair_list[i].thost, tohost, MAX_RDC_HOST_SIZE); (void) strncpy(pair_list[i].tfile, tofile, NSC_MAXPATH); (void) strncpy(pair_list[i].tbitmap, tobitmap, NSC_MAXPATH); (void) strncpy(pair_list[i].directfile, directfile, NSC_MAXPATH); (void) strncpy(pair_list[i].diskqueue, diskqueue, NSC_MAXPATH); (void) strncpy(pair_list[i].group, group, NSC_MAXPATH); (void) strncpy(pair_list[i].lhost, lhost, MAX_RDC_HOST_SIZE); pair_list[i].doasync = doasync; if (cfg_get_single_option(cfg, CFG_SEC_CONF, key, "setid", setid, sizeof (setid)) < 0) { no_id[j++] = setnumber; } pair_list[i].setid = atoi(setid); if (pair_list[i].setid > highest) highest = pair_list[i].setid; if (gethost_netaddrs(fromhost, tohost, (char *)pair_list[i].fnetaddr, (char *)pair_list[i].tnetaddr) < 0) { cfg_close(cfg); spcs_log("sndr", NULL, gettext("%s unable to determine IP addresses " "for hosts %s %s"), program, fromhost, tohost); rdc_err(NULL, gettext("unable to determine IP " "addresses for hosts %s, %s"), fromhost, tohost); } } /* * fix any lost set ids if possible, also deal with upgrade */ if (j > 0 && flag == RDC_CMD_FIXSETIDS) { (void) update_setids(cfg, no_id, highest); i = j; /* Set number of fixups */ } free(no_id); cfg_close(cfg); return (i); } int parseopts(argc, argv, flag) int argc; char **argv; int *flag; { int errflag = 0; char c; char inval = 0; #ifdef DEBUG while ((c = getopt(argc, argv, "C:Urs")) != -1) { #else while ((c = getopt(argc, argv, "C:rs")) != -1) { #endif switch (c) { case 'C': clustered = TRUE; ctag = optarg; break; #ifdef DEBUG case 'U': proto_test = 1; break; #endif case 'r': if (*flag) inval = 1; *flag = RDC_CMD_RESUME; break; case 's': if (*flag) inval = 1; *flag = RDC_CMD_SUSPEND; break; case '?': errflag++; } } /* * Special fix to address no SetIds in AVS 3.1 to 3.2 install + patch * Adjust set IDs, if someone invokes the following invalid command * * /use/sbin/sndrboot -C post-patch-setids -r -s * * Command will be called in post-install of the patch containing fix * */ if (clustered && (strcmp(ctag, "post-patch-setids") == 0) && *flag && inval) { *flag = RDC_CMD_FIXSETIDS; return (0); } if (inval) { rdc_warn(NULL, gettext("Invalid argument combination")); errflag = 1; } if (!*flag || errflag) { usage(); return (-1); } return (0); } static void usage() { (void) fprintf(stderr, gettext("usage:\n")); (void) fprintf(stderr, gettext("\t%s -r [-C tag]\t\t" "resume\n"), program); (void) fprintf(stderr, gettext("\t%s -s [-C tag]\t\t" "suspend\n"), program); }