# # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. DAEMON_SRCS = powerd.c sysstat.c DAEMON_OBJS = $(DAEMON_SRCS:%.c=%.o) DAEMON = powerd PMCFG_SRCS = conf.c parse.c handlers.c PMCFG_OBJS = $(PMCFG_SRCS:%.c=%.o) PMCFG = pmconfig SUSPEND_SRCS = sys-suspend.c pm_pam_conv.c SUSPEND_OBJS = $(SUSPEND_SRCS:%.c=%.o) SUSPEND = sys-suspend SRCS = $(DAEMON_SRCS) $(PMCFG_SRCS) $(SUSPEND_SRCS) OBJS = $(SRCS:%.c=%.o) PROG = $(DAEMON) $(PMCFG) $(SUSPEND) POWERCONF= power.conf ETCFILES = $(POWERCONF) POWERPERM = power DEFAULTFILES = power.dfl MANIFEST= power.xml SVCMETHOD= svc-power include ../Makefile.cmd ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) TEXT_DOMAIN= SUNW_OST_OSCMD XGETFLAGS += -a -x power_all.xcl POFILE= power_all.po POFILES= $(PMCFG_SRCS:%.c=%.po) $(SUSPEND_SRCS:%.c=%.po) # pmconfig only needs libdevinfo on sparc sparc_LDEVINFO= -ldevinfo -lefi -ladm -lzfs -lnvpair i386_LDEVINFO= LDEVINFO= -ldevinfo DAEMON_LDLIBS = $(LDLIBS.cmd) -lkstat $(LDEVINFO) PMCFG_LDLIBS = $(LDLIBS.cmd) -lsmbios -lkstat $($(MACH)_LDEVINFO) SUSPEND_LDLIBS = $(LDLIBS.cmd) -lbsm -lpam -lsecdb ROOTUSRSBINPMCFG= $(PMCFG:%=$(ROOTUSRSBIN)/%) $(ROOTUSRSBINPMCFG) : FILEMODE= 4555 ROOTUSRBINSUSPEND= $(SUSPEND:%=$(ROOTBIN)/%) $(ROOTUSRBINSUSPEND) : FILEMODE= 4555 ROOTLIBPOWER= $(ROOTLIB)/power ROOTLIBPOWERDAEMON= $(DAEMON:%=$(ROOTLIBPOWER)/%) $(ROOTLIBPOWER) : FILEMODE= 755 $(ROOTLIBPOWERDAEMON) : FILEMODE= 555 ROOTETCFILES= $(ETCFILES:%=$(ROOTETC)/%) $(ROOTETCFILES) : FILEMODE= 644 # # lint pass one enforcement # CFLAGS += $(CCVERBOSE) SMOFF += signed .PARALLEL: $(OBJS) .KEEP_STATE: all: $(PROG) $(POWERPERM).dfl $(ETCFILES) install clean: $(POWERCONF): $(POWERCONF).$(MACH) # Hammerhead: GNU Make fix - original rule used $< which always picks first # source file (powerd.c) for all targets. Use implicit rule with target-specific # CPPFLAGS instead. $(DAEMON_OBJS): CPPFLAGS += -D_REENTRANT $(DAEMON): $(DAEMON_OBJS) $(LINK.c) -o $@ $(DAEMON_OBJS) $(DAEMON_LDLIBS) $(POST_PROCESS) $(PMCFG_OBJS): pmconfig.h $(PMCFG): $(PMCFG_OBJS) $(LINK.c) -o $@ $(PMCFG_OBJS) $(PMCFG_LDLIBS) $(POST_PROCESS) $(SUSPEND): $(SUSPEND_OBJS) $(LINK.c) -o $@ $(SUSPEND_OBJS) $(SUSPEND_LDLIBS) $(POST_PROCESS) install: all $(ROOTUSRSBINPMCFG) $(ROOTLIBPOWERDAEMON) $(ROOTUSRBINSUSPEND) \ $(ROOTETCFILES) $(ROOTETCDEFAULTFILES) \ $(ROOTMANIFEST) $(ROOTSVCMETHOD) $(ROOTLIBPOWER): $(INS.dir) $(ROOTLIBPOWER)/%: % $(INS.file) $(ROOTLIBPOWERDAEMON): $(ROOTLIBPOWER) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ check: $(CHKMANIFEST) clean: $(RM) $(OBJS) $(POWERCONF) $(RM) $(POFILE) $(POFILES) lint : LINTFLAGS=-auxn lint: $(LINT.c) $(DAEMON_SRCS) $(LINT.c) $(PMCFG_SRCS) $(LINT.c) $(SUSPEND_SRCS) cstyle: $(CSTYLE) $(SRCS) %: %.$(MACH) $(RM) $@ cat $< > $@ include ../Makefile.targ /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * "pmconfig" performs a mixture of Energy-Star configuration tasks * for both CheckPoint-Resume and Power-Management services. * Tasks include parsing a config file (usually "/etc/power.conf"), * updating CPR and PM config files, and setting various PM options * via ioctl requests. From the mix, pmconfig should have a more * generalized name similar to "estarconfig". * * OPTIONS: * "-r" reset CPR and PM options to default and exit. * "-f file" specify an alternate config file; this is a * private/non-advertised option used by "dtpower". */ #include "pmconfig.h" #include #include #include #include #include "powerd.h" #define MCCPY_FIELD(dst, src, field) \ (void) memccpy(&dst.field, &src.field, 0, sizeof (dst.field) - 1) static char conf_header[] = "#\n" "# Copyright 1996-2002 Sun Microsystems, Inc. All rights reserved.\n" "# Use is subject to license terms.\n" "#\n" "# Power Management Configuration File\n" "#\n\n"; static char *prog; static char *cpr_conf = CPR_CONFIG; static char tmp_conf[] = "/etc/.tmp.conf.XXXXXX"; static char orig_conf[] = "/etc/power.conf-Orig"; static char default_conf[] = "/etc/power.conf"; static char *power_conf = default_conf; static pid_t powerd_pid; static prmup_t *checkup; static int tmp_fd; char estar_vers = ESTAR_VNONE; int ua_err = 0; int debug = 0; static struct cprconfig disk_cc; struct cprconfig new_cc; struct stat def_info; static int fflag, rflag; int pm_fd; uid_t ruid; int def_src; /* * Until we get more graphics driver support, we only enable autopm, * S3 support and autoS3 by default on X86 systems that are on our whitelist. */ int whitelist_only = 1; int verify = 0; static void cleanup(void) { free(line_args); if (access(tmp_conf, F_OK) == 0) (void) unlink(tmp_conf); } /* * Multi-purpose message output routine; also exits when * (status == MEXIT), other status is non-fatal. * VARARGS2 */ void mesg(int code, char *fmt, ...) { va_list vargs; /* * debug is checked once here, avoiding N duplicate checks * before each MDEBUG caller and unnecessary text dupduplication. */ if (debug == 0) { /* * If debug is not enabled, skip a debug message; * lead with the program name for an error message, * and follow with a filename and line number if an * error occurs while parsing a conf file. */ if (code == MDEBUG) return; (void) fprintf(stderr, "%s: ", prog); if (lineno) (void) fprintf(stderr, "\"%s\" line %d, ", power_conf, lineno); } va_start(vargs, fmt); (void) vfprintf(stderr, gettext(fmt), vargs); va_end(vargs); if (code == MEXIT) { cleanup(); exit(MEXIT); } } static void usage(void) { (void) fprintf(stderr, gettext("Usage: %s [-r]\n"), prog); exit(1); } /* * Lookup estar version, check if uadmin() service is supported, * and read cpr_config info from disk. */ static void get_cpr_info(void) { ssize_t nread; char *err_fmt; int fd; #ifdef sparc lookup_estar_vers(); if (estar_vers == ESTAR_V2) new_cc.is_cpr_default = 1; else if (estar_vers == ESTAR_V3) new_cc.is_autopm_default = 1; if (uadmin(A_FREEZE, AD_CHECK, 0) == 0) new_cc.is_cpr_capable = 1; else ua_err = errno; if ((fd = open("/dev/tod", O_RDONLY)) != -1) { new_cc.is_autowakeup_capable = 1; (void) close(fd); } #endif /* sparc */ /* * Read in the cpr conf file. If any open or read error occurs, * display an error message only for a non-root user. The file * may not exist on a newly installed system. */ err_fmt = "%s %s; please rerun %s as root\n"; if ((fd = open(cpr_conf, O_RDONLY)) == -1) { if (ruid) mesg(MEXIT, err_fmt, gettext("cannot open"), cpr_conf, prog); } else { nread = read(fd, &disk_cc, sizeof (disk_cc)); (void) close(fd); if (nread != (ssize_t)sizeof (disk_cc)) { if (ruid) mesg(MEXIT, err_fmt, cpr_conf, gettext("file corrupted"), prog); else { (void) unlink(cpr_conf); bzero(&disk_cc, sizeof (disk_cc)); } } } } /* * Unconfigure and reset PM, device is left open for later use. */ static void pm_rem_reset(void) { char *err_fmt = NULL; if ((pm_fd = open("/dev/pm", O_RDWR)) == -1) err_fmt = "cannot open \"/dev/pm\": %s\n"; else if (ioctl(pm_fd, PM_RESET_PM, 0) == -1) err_fmt = "cannot reset pm state: %s\n"; if (err_fmt) mesg(MEXIT, err_fmt, strerror(errno)); } static void get_powerd_pid(void) { char pidstr[16]; int fd; if ((fd = open(PIDPATH, O_RDONLY)) == -1) return; bzero(pidstr, sizeof (pidstr)); if (read(fd, pidstr, sizeof (pidstr)) > 0) { powerd_pid = atoi(pidstr); mesg(MDEBUG, "got powerd pid %ld\n", powerd_pid); } (void) close(fd); } /* * Write revised cprconfig struct to disk based on perms; * returns 1 if any error, otherwise 0. */ static int update_cprconfig(void) { struct cprconfig *wrt_cc = &new_cc; char *err_fmt = NULL; int fd; if (rflag) { /* For "pmconfig -r" case, copy select cpr-related fields. */ new_cc.cf_magic = disk_cc.cf_magic; new_cc.cf_type = disk_cc.cf_type; MCCPY_FIELD(new_cc, disk_cc, cf_path); MCCPY_FIELD(new_cc, disk_cc, cf_fs); MCCPY_FIELD(new_cc, disk_cc, cf_devfs); MCCPY_FIELD(new_cc, disk_cc, cf_dev_prom); } if (!pm_status.perm) { if (cpr_status.update == NOUP) return (1); /* save new struct data with old autopm setting */ MCCPY_FIELD(new_cc, disk_cc, apm_behavior); } else if (!cpr_status.perm) { if (pm_status.update == NOUP) return (1); /* save original struct with new autopm setting */ MCCPY_FIELD(disk_cc, new_cc, apm_behavior); wrt_cc = &disk_cc; } else if (cpr_status.update == NOUP || pm_status.update == NOUP) return (1); if ((fd = open(cpr_conf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) err_fmt = "cannot open/create \"%s\", %s\n"; else if (write(fd, wrt_cc, sizeof (*wrt_cc)) != sizeof (*wrt_cc)) err_fmt = "error writing \"%s\", %s\n"; if (err_fmt) mesg(MERR, err_fmt, cpr_conf, strerror(errno)); if (fd != -1) (void) close(fd); return (err_fmt != NULL); } /* * Signal old powerd when there's a valid pid, or start a new one; * returns 1 if any error, otherwise 0. */ static int restart_powerd(void) { char *powerd = "/usr/lib/power/powerd"; int status = 0; pid_t pid, wp; if (powerd_pid > 0) { if (sigsend(P_PID, powerd_pid, SIGHUP) == 0) return (0); else if (errno != ESRCH) { mesg(MERR, "cannot deliver hangup to powerd\n"); return (1); } } if ((pid = fork()) == NOPID) wp = -1; else if (pid == P_MYPID) { (void) setreuid(0, 0); (void) setregid(0, 0); (void) setgroups(0, NULL); if (debug) (void) execle(powerd, powerd, "-d", NULL, NULL); else (void) execle(powerd, powerd, NULL, NULL); exit(1); } else { do { wp = waitpid(pid, &status, 0); } while (wp == -1 && errno == EINTR); } if (wp == -1) mesg(MERR, "could not start %s\n", powerd); return (wp == -1 || status != 0); } static void save_orig(void) { static char *args[] = { "/bin/cp", default_conf, orig_conf, NULL }; struct stat stbuf; int pid; if (stat(orig_conf, &stbuf) == 0 && stbuf.st_size) return; pid = fork(); if (pid == NOPID) return; else if (pid == P_MYPID) { (void) execve(args[0], args, NULL); exit(1); } else (void) waitpid(pid, NULL, 0); } static void tmp_write(void *buf, size_t len) { if (write(tmp_fd, buf, len) != (ssize_t)len) mesg(MEXIT, "error writing tmp file, %s\n", strerror(errno)); } static void tmp_save_line(char *line, size_t len, cinfo_t *cip) { if (cip && cip->cmt) tmp_write(cip->cmt, strlen(cip->cmt)); tmp_write(line, len); } /* * Filter conf lines and write them to the tmp file. */ static void filter(char *line, size_t len, cinfo_t *cip) { int selected; /* * Lines from an alt conf file are selected when either: * cip is NULL (keyword not matched, probably an old-style device), * OR: it's both OK to accept the conf line (alt) AND either: * preference is not set (NULL checkup) OR the cpr/pm preference * (checkup) matches conftab status. */ selected = (cip == NULL || (cip->alt && (checkup == NULL || checkup == cip->status))); mesg(MDEBUG, "filter: set \"%s\", selected %d\n", cip ? cip->status->set : "none", selected); if (selected) tmp_save_line(line, len, cip); } /* * Set checkup for conf line selection and parse a conf file with filtering. * When pref is NULL, filter selects all conf lines from the new conf file; * otherwise filter selects only cpr or pm related lines from the new or * default conf file based on cpr or pm perm. */ static void conf_scanner(prmup_t *pref) { mesg(MDEBUG, "\nscanning set is %s\n", pref ? pref->set : "both"); checkup = pref; parse_conf_file((pref == NULL || pref->perm) ? power_conf : default_conf, filter, B_FALSE); } /* * Search for any non-alt entries, call the handler routine, * and write entries to the tmp file. */ static void search(char *line, size_t len, cinfo_t *cip) { int skip; skip = (cip == NULL || cip->alt); mesg(MDEBUG, "search: %s\n", skip ? "skipped" : "retained"); if (skip) return; if (cip->status->perm) (void) (*cip->handler)(); tmp_save_line(line, len, cip); } /* * When perm and update status are OK, write a new conf file * and rename to default_conf with the original attributes; * returns 1 if any error, otherwise 0. */ static int write_conf(void) { char *name, *err_str = NULL; struct stat stbuf; if ((cpr_status.perm && cpr_status.update != OKUP) || (pm_status.perm && pm_status.update != OKUP)) { mesg(MDEBUG, "\nconf not written, " "(cpr perm %d update %d), (pm perm %d update %d)\n", cpr_status.perm, cpr_status.update, pm_status.perm, pm_status.update); return (1); } save_orig(); if ((tmp_fd = mkstemp(tmp_conf)) == -1) { mesg(MERR, "cannot open/create tmp file \"%s\"\n", tmp_conf); return (1); } tmp_write(conf_header, sizeof (conf_header) - 1); /* * When both perms are set, save selected lines from the new file; * otherwise save selected subsets from the new and default files. */ if (cpr_status.perm && pm_status.perm) conf_scanner(NULL); else { conf_scanner(&cpr_status); conf_scanner(&pm_status); } /* * "dtpower" will craft an alt conf file with modified content from * /etc/power.conf, but any alt conf file is not a trusted source; * since some alt conf lines may be skipped, the trusted source is * searched for those lines to retain their functionality. */ parse_conf_file(default_conf, search, B_FALSE); (void) close(tmp_fd); if (stat(name = default_conf, &stbuf) == -1) err_str = "stat"; else if (chmod(name = tmp_conf, stbuf.st_mode) == -1) err_str = "chmod"; else if (chown(tmp_conf, stbuf.st_uid, stbuf.st_gid) == -1) err_str = "chown"; else if (rename(tmp_conf, default_conf) == -1) err_str = "rename"; else mesg(MDEBUG, "\n\"%s\" renamed to \"%s\"\n", tmp_conf, default_conf); if (err_str) mesg(MERR, "cannot %s \"%s\", %s\n", err_str, name, strerror(errno)); return (err_str != NULL); } /* ARGSUSED */ int main(int cnt, char **vec) { int rval = 0; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); for (prog = *vec++; *vec && **vec == '-'; vec++) { if (strlen(*vec) > 2) usage(); switch (*(*vec + 1)) { case 'd': debug = 1; break; case 'f': fflag = 1; if ((power_conf = *++vec) == NULL) usage(); break; case 'r': rflag = 1; break; case 'W': whitelist_only = 0; break; case 'v': verify = 1; break; default: usage(); break; } } if (rflag && fflag) usage(); lookup_perms(); mesg(MDEBUG, "ruid %d, perms: cpr %d, pm %d\n", ruid, cpr_status.perm, pm_status.perm); if ((!cpr_status.perm && !pm_status.perm) || (rflag && !(cpr_status.perm && pm_status.perm))) mesg(MEXIT, "%s\n", strerror(EACCES)); if (rflag == 0 && access(power_conf, R_OK)) mesg(MEXIT, "\"%s\" is not readable\n", power_conf); get_cpr_info(); if (pm_status.perm) pm_rem_reset(); get_powerd_pid(); (void) umask(022); if (rflag) return (update_cprconfig() || restart_powerd()); if (stat(default_conf, &def_info) == -1) mesg(MEXIT, "cannot stat %s, %s\n", default_conf, strerror(errno)); new_cc.loadaverage_thold = DFLT_THOLD; parse_conf_file(power_conf, NULL, B_TRUE); if (fflag) rval = write_conf(); cleanup(); if (pm_status.perm) (void) close(pm_fd); if (rval == 0) rval = (update_cprconfig() || restart_powerd()); return (rval); } /* * 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 2015 Gary Mills * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "pmconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #define STRCPYLIM(dst, src, str) strcpy_limit(dst, src, sizeof (dst), str) #define LASTBYTE(str) (str + strlen(str) - 1) static char nerr_fmt[] = "number is out of range (%s)\n"; static char alloc_fmt[] = "cannot allocate space for \"%s\", %s\n"; static char set_thresh_fmt[] = "error setting threshold(s) for \"%s\", %s\n"; static char bad_thresh_fmt[] = "bad threshold(s)\n"; static char stat_fmt[] = "cannot stat \"%s\", %s\n"; static char always_on[] = "always-on"; #define PM_DEFAULT_ALGORITHM -1 /* * When lines in a config file (usually "/etc/power.conf") start with * a recognized keyword, a "handler" routine is called for specific * CPR or PM -related action(s). Each routine returns a status code * indicating whether all tasks were successful; if any errors occured, * future CPR or PM updates are skipped. Following are the handler * routines for all keywords: */ static char pm_cmd_string[32]; static char * pm_map(int cmd) { pm_req_t req; req.value = cmd; req.data = (void *)pm_cmd_string; req.datasize = sizeof (pm_cmd_string); if (ioctl(pm_fd, PM_GET_CMD_NAME, &req) < 0) { perror(gettext("PM_GET_CMD_NAME failed:")); return ("??"); } return (pm_cmd_string); } static int isonlist(char *listname, const char *man, const char *prod) { pm_searchargs_t sl; int ret; sl.pms_listname = listname; sl.pms_manufacturer = (char *)man; sl.pms_product = (char *)prod; ret = ioctl(pm_fd, PM_SEARCH_LIST, &sl); mesg(MDEBUG, "PM_SEARCH_LIST %s for %s,%s returns %d\n", listname, man, prod, ret); return (ret == 0); } static int do_ioctl(int ioctl_cmd, char *keyword, char *behavior, int suppress) { mesg(MDEBUG, "doing ioctl %s for %s ", pm_map(ioctl_cmd), keyword); if (ioctl(pm_fd, ioctl_cmd, NULL) == -1) { int suppressed = suppress == -1 || suppress == errno; if (!suppressed) { mesg(MERR, "%s %s failed, %s\n", keyword, behavior, strerror(errno)); return (NOUP); } else { mesg(MDEBUG, "%s %s failed, %s\n", keyword, behavior, strerror(errno)); return (OKUP); } } mesg(MDEBUG, "succeeded\n"); return (OKUP); } /* * Check for valid cpupm behavior and communicate it to the kernel. */ int cpupm(void) { struct bmtoc { char *behavior; char *mode; int cmd; int Errno; }; static struct bmtoc bmlist[] = { "disable", "\0", PM_STOP_CPUPM, EINVAL, "enable", "poll-mode", PM_START_CPUPM_POLL, EBUSY, "enable", "event-mode", PM_START_CPUPM_EV, EBUSY, "enable", "\0", PM_START_CPUPM, EBUSY, NULL, 0, 0, 0 }; struct bmtoc *bp; char *behavior; char *mode; behavior = LINEARG(1); if ((mode = LINEARG(2)) == NULL) mode = "\0"; for (bp = bmlist; bp->cmd; bp++) { if (strcmp(behavior, bp->behavior) == 0 && strcmp(mode, bp->mode) == 0) { break; } } if (bp->cmd == 0) { if (LINEARG(2) == NULL) { mesg(MERR, "invalid cpupm behavior \"%s\"\n", behavior); } else { mesg(MERR, "invalid cpupm behavior \"%s %s\"\n", behavior, mode); } return (NOUP); } if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) { mesg(MERR, "cpupm %s failed, %s\n", behavior, strerror(errno)); return (NOUP); } return (OKUP); } /* * Check for valid cpu_deep_idle option and communicate it to the kernel. */ int cpuidle(void) { struct btoc { char *behavior; int cmd; int Errno; }; static struct btoc blist[] = { "disable", PM_DISABLE_CPU_DEEP_IDLE, EINVAL, "enable", PM_ENABLE_CPU_DEEP_IDLE, EBUSY, "default", PM_DEFAULT_CPU_DEEP_IDLE, EBUSY, NULL, 0, 0 }; struct btoc *bp; char *behavior; for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) { if (strcmp(behavior, bp->behavior) == 0) break; } if (bp->cmd == 0) { mesg(MERR, "invalid cpu_deep_idle behavior \"%s\"\n", behavior); return (NOUP); } if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) { mesg(MERR, "cpu_deep_idle %s failed, %s\n", behavior, strerror(errno)); return (NOUP); } return (OKUP); } /* * Two decisions are identical except for the list names and ioctl commands * inputs: whitelist, blacklist, yes, no * if (! ("S3" kstat exists)) * return (no) * if (SystemInformation.Manufacturer == "Sun Microsystems" && * (Pref_PM_Profile == Workstation || Pref_PM_Profile == Desktop)) { * if (platform on blacklist) * return (no) * return (yes) * } else { * if (platform on whitelist) * return (yes) * return (no) * } */ int S3_helper(char *whitelist, char *blacklist, int yes, int no, char *keyword, char *behavior, int *didyes, int suppress) { int oflags = SMB_O_NOCKSUM | SMB_O_NOVERS; smbios_hdl_t *shp; smbios_system_t sys; id_t id; int ret; kstat_ctl_t *kc; kstat_t *ksp; kstat_named_t *dp; smbios_info_t info; int preferred_pm_profile = 0; char yesstr[32], nostr[32]; /* DEBUG */ *didyes = 0; (void) strncpy(yesstr, pm_map(yes), sizeof (yesstr)); (void) strncpy(nostr, pm_map(no), sizeof (nostr)); mesg(MDEBUG, "S3_helper(%s, %s, %s, %s, %s, %s)\n", whitelist, blacklist, yesstr, nostr, keyword, behavior); if ((kc = kstat_open()) == NULL) { mesg(MDEBUG, "kstat_open failed\n"); return (OKUP); } ksp = kstat_lookup(kc, "acpi", -1, "acpi"); if (ksp == NULL) { mesg(MDEBUG, "kstat_lookup 'acpi', -1, 'acpi' failed\n"); (void) kstat_close(kc); return (OKUP); } (void) kstat_read(kc, ksp, NULL); dp = kstat_data_lookup(ksp, "S3"); if (dp == NULL || dp->value.l == 0) { mesg(MDEBUG, "kstat_data_lookup 'S3' fails\n"); if (dp != NULL) mesg(MDEBUG, "value.l %lx\n", dp->value.l); (void) kstat_close(kc); return (do_ioctl(no, keyword, behavior, suppress)); } mesg(MDEBUG, "kstat indicates S3 support (%lx)\n", dp->value.l); if (!whitelist_only) { /* * We still have an ACPI ksp, search it again for * 'preferred_pm_profile' (needs to be valid if we don't * aren't only using a whitelist). */ dp = kstat_data_lookup(ksp, "preferred_pm_profile"); if (dp == NULL) { mesg(MDEBUG, "kstat_data_lookup 'ppmp fails\n"); (void) kstat_close(kc); return (do_ioctl(no, keyword, behavior, suppress)); } mesg(MDEBUG, "kstat indicates preferred_pm_profile is %lx\n", dp->value.l); preferred_pm_profile = dp->value.l; } (void) kstat_close(kc); if ((shp = smbios_open(NULL, SMB_VERSION, oflags, &ret)) == NULL) { /* we promised not to complain */ /* we bail leaving it to the kernel default */ mesg(MDEBUG, "smbios_open failed %d\n", errno); return (OKUP); } if ((id = smbios_info_system(shp, &sys)) == SMB_ERR) { mesg(MDEBUG, "smbios_info_system failed %d\n", errno); smbios_close(shp); return (OKUP); } if (smbios_info_common(shp, id, &info) == SMB_ERR) { mesg(MDEBUG, "smbios_info_common failed %d\n", errno); smbios_close(shp); return (OKUP); } mesg(MDEBUG, "Manufacturer: %s\n", info.smbi_manufacturer); mesg(MDEBUG, "Product: %s\n", info.smbi_product); smbios_close(shp); if (!whitelist_only) { #define PPP_DESKTOP 1 #define PPP_WORKSTATION 3 if (strcmp(info.smbi_manufacturer, "Sun Microsystems") == 0 && (preferred_pm_profile == PPP_DESKTOP || preferred_pm_profile == PPP_WORKSTATION)) { if (isonlist(blacklist, info.smbi_manufacturer, info.smbi_product)) { return (do_ioctl(no, keyword, behavior, suppress)); } else { ret = do_ioctl(yes, keyword, behavior, suppress); *didyes = (ret == OKUP); return (ret); } } } if (isonlist(whitelist, info.smbi_manufacturer, info.smbi_product)) { ret = do_ioctl(yes, keyword, behavior, suppress); *didyes = (ret == OKUP); return (ret); } else { return (do_ioctl(no, keyword, behavior, suppress)); } } int S3sup(void) /* S3-support keyword handler */ { struct btoc { char *behavior; int cmd; }; static struct btoc blist[] = { "default", PM_DEFAULT_ALGORITHM, "enable", PM_ENABLE_S3, "disable", PM_DISABLE_S3, NULL, 0 }; struct btoc *bp; char *behavior; int dontcare; for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) { if (strcmp(behavior, bp->behavior) == 0) break; } if (bp->cmd == 0) { mesg(MERR, "invalid S3-support behavior \"%s\"\n", behavior); return (NOUP); } switch (bp->cmd) { case PM_ENABLE_S3: case PM_DISABLE_S3: return (do_ioctl(bp->cmd, "S3-support", behavior, EBUSY)); case PM_DEFAULT_ALGORITHM: /* * we suppress errors in the "default" case because we * already did an invisible default call, so we know we'll * get EBUSY */ return (S3_helper("S3-support-enable", "S3-support-disable", PM_ENABLE_S3, PM_DISABLE_S3, "S3-support", behavior, &dontcare, EBUSY)); default: mesg(MERR, "S3-support %s failed, %s\n", behavior, strerror(errno)); return (NOUP); } } /* * Check for valid autoS3 behavior and save after ioctl success. */ int autoS3(void) { struct btoc { char *behavior; int cmd; }; static struct btoc blist[] = { "default", PM_DEFAULT_ALGORITHM, "disable", PM_STOP_AUTOS3, "enable", PM_START_AUTOS3, NULL, 0 }; struct btoc *bp; char *behavior; int dontcare; for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) { if (strcmp(behavior, bp->behavior) == 0) break; } if (bp->cmd == 0) { mesg(MERR, "invalid autoS3 behavior \"%s\"\n", behavior); return (NOUP); } switch (bp->cmd) { default: mesg(MERR, "autoS3 %s failed, %s\n", behavior, strerror(errno)); mesg(MDEBUG, "unknown command\n", bp->cmd); return (OKUP); case PM_STOP_AUTOS3: case PM_START_AUTOS3: return (do_ioctl(bp->cmd, "autoS3", behavior, EBUSY)); case PM_DEFAULT_ALGORITHM: return (S3_helper("S3-autoenable", "S3-autodisable", PM_START_AUTOS3, PM_STOP_AUTOS3, "autoS3", behavior, &dontcare, EBUSY)); } } /* * Check for valid autopm behavior and save after ioctl success. */ int autopm(void) { struct btoc { char *behavior; int cmd, Errno, isdef; }; static struct btoc blist[] = { "default", PM_START_PM, -1, 1, "disable", PM_STOP_PM, EINVAL, 0, "enable", PM_START_PM, EBUSY, 0, NULL, 0, 0, 0, }; struct btoc *bp; char *behavior; for (behavior = LINEARG(1), bp = blist; bp->cmd; bp++) { if (strcmp(behavior, bp->behavior) == 0) break; } if (bp->cmd == 0) { mesg(MERR, "invalid autopm behavior \"%s\"\n", behavior); return (NOUP); } /* * for "default" behavior, do not enable autopm if not ESTAR_V3 */ #if defined(__sparc) if (!bp->isdef || (estar_vers == ESTAR_V3)) { if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) { mesg(MERR, "autopm %s failed, %s\n", behavior, strerror(errno)); return (NOUP); } } (void) strcpy(new_cc.apm_behavior, behavior); return (OKUP); #endif #if defined(__x86) if (!bp->isdef) { if (ioctl(pm_fd, bp->cmd, NULL) == -1 && errno != bp->Errno) { mesg(MERR, "autopm %s failed, %s\n", behavior, strerror(errno)); return (NOUP); } mesg(MDEBUG, "autopm %s succeeded\n", behavior); return (OKUP); } else { int didenable; int ret = S3_helper("autopm-enable", "autopm-disable", PM_START_PM, PM_STOP_PM, "autopm", behavior, &didenable, bp->Errno); if (didenable) { /* tell powerd to attach all devices */ new_cc.is_autopm_default = 1; (void) strcpy(new_cc.apm_behavior, behavior); } return (ret); } #endif } static int gethm(char *src, int *hour, int *min) { if (sscanf(src, "%d:%d", hour, min) != 2) { mesg(MERR, "bad time format (%s)\n", src); return (-1); } return (0); } static void strcpy_limit(char *dst, char *src, size_t limit, char *info) { if (strlcpy(dst, src, limit) >= limit) mesg(MEXIT, "%s is too long (%s)\n", info, src); } /* * Convert autoshutdown idle and start/finish times; * check and record autoshutdown behavior. */ int autosd(void) { char **bp, *behavior; char *unrec = gettext("unrecognized autoshutdown behavior"); static char *blist[] = { "autowakeup", "default", "noshutdown", "shutdown", "unconfigured", NULL }; new_cc.as_idle = atoi(LINEARG(1)); if (gethm(LINEARG(2), &new_cc.as_sh, &new_cc.as_sm) || gethm(LINEARG(3), &new_cc.as_fh, &new_cc.as_fm)) return (NOUP); mesg(MDEBUG, "idle %d, start %d:%02d, finish %d:%02d\n", new_cc.as_idle, new_cc.as_sh, new_cc.as_sm, new_cc.as_fh, new_cc.as_fm); for (behavior = LINEARG(4), bp = blist; *bp; bp++) { if (strcmp(behavior, *bp) == 0) break; } if (*bp == NULL) { mesg(MERR, "%s: \"%s\"\n", unrec, behavior); return (NOUP); } STRCPYLIM(new_cc.as_behavior, *bp, unrec); return (OKUP); } /* * Check for a real device and try to resolve to a full path. * The orig/resolved path may be modified into a prom pathname, * and an allocated copy of the result is stored at *destp; * the caller will need to free that space. Returns 1 for any * error, otherwise 0; also sets *errp after an alloc error. */ static int devpath(char **destp, char *src, int *errp) { struct stat stbuf; char buf[PATH_MAX]; char *cp, *dstr; int devok, dcs = 0; size_t len; /* * When there's a real device, try to resolve the path * and trim the leading "/devices" component. */ if ((devok = (stat(src, &stbuf) == 0 && stbuf.st_rdev)) != 0) { if (realpath(src, buf) == NULL) { mesg(MERR, "realpath cannot resolve \"%s\"\n", src, strerror(errno)); return (1); } src = buf; dstr = "/devices"; len = strlen(dstr); dcs = (strncmp(src, dstr, len) == 0); if (dcs) src += len; } else mesg(MDEBUG, stat_fmt, src, strerror(errno)); /* * When the path has ":anything", display an error for * a non-device or truncate a resolved+modifed path. */ if ((cp = strchr(src, ':')) != NULL) { if (devok == 0) { mesg(MERR, "physical path may not contain " "a minor string (%s)\n", src); return (1); } else if (dcs) *cp = '\0'; } if ((*destp = strdup(src)) == NULL) { *errp = NOUP; mesg(MERR, alloc_fmt, src, strerror(errno)); } return (*destp == NULL); } /* * Call pm ioctl request(s) to set property/device dependencies. */ static int dev_dep_common(int isprop) { int cmd, argn, upval = OKUP; char *src, *first, **destp; pm_req_t pmreq; bzero(&pmreq, sizeof (pmreq)); src = LINEARG(1); if (isprop) { cmd = PM_ADD_DEPENDENT_PROPERTY; first = NULL; pmreq.pmreq_kept = src; } else { cmd = PM_ADD_DEPENDENT; if (devpath(&first, src, &upval)) return (upval); pmreq.pmreq_kept = first; } destp = &pmreq.pmreq_keeper; /* * Now loop through any dependents. */ for (argn = 2; (src = LINEARG(argn)) != NULL; argn++) { if (devpath(destp, src, &upval)) { if (upval != OKUP) return (upval); break; } if ((upval = ioctl(pm_fd, cmd, &pmreq)) == -1) { mesg(MDEBUG, "pm ioctl, cmd %d, errno %d\n" "kept \"%s\", keeper \"%s\"\n", cmd, errno, pmreq.pmreq_kept, pmreq.pmreq_keeper); mesg(MERR, "cannot set \"%s\" dependency " "for \"%s\", %s\n", pmreq.pmreq_keeper, pmreq.pmreq_kept, strerror(errno)); } free(*destp); *destp = NULL; if (upval != OKUP) break; } free(first); return (upval); } int ddprop(void) { return (dev_dep_common(1)); } int devdep(void) { return (dev_dep_common(0)); } /* * Convert a numeric string (with a possible trailing scaling byte) * into an integer. Returns a converted value and *nerrp unchanged, * or 0 with *nerrp set to 1 for a conversion error. */ static int get_scaled_value(char *str, int *nerrp) { longlong_t svalue = 0, factor = 1; char *sp; errno = 0; svalue = strtol(str, &sp, 0); if (errno || (*str != '-' && (*str < '0' || *str > '9'))) *nerrp = 1; else if (sp && *sp != '\0') { if (*sp == 'h') factor = 3600; else if (*sp == 'm') factor = 60; else if (*sp != 's') *nerrp = 1; } /* any bytes following sp are ignored */ if (*nerrp == 0) { svalue *= factor; if (svalue < INT_MIN || svalue > INT_MAX) *nerrp = 1; } if (*nerrp) mesg(MERR, nerr_fmt, str); mesg(MDEBUG, "got scaled value %d\n", (int)svalue); return ((int)svalue); } /* * Increment the count of threshold values, * reallocate *vlistp and append another element. * Returns 1 on error, otherwise 0. */ static int vlist_append(int **vlistp, int *vcntp, int value) { (*vcntp)++; if ((*vlistp = realloc(*vlistp, *vcntp * sizeof (**vlistp))) != NULL) *(*vlistp + *vcntp - 1) = value; else mesg(MERR, alloc_fmt, "threshold list", strerror(errno)); return (*vlistp == NULL); } /* * Convert a single threshold string or paren groups of thresh's as * described below. All thresh's are saved to an allocated list at * *vlistp; the caller will need to free that space. On return: * *vcntp is the count of the vlist array, and vlist is either * a single thresh or N groups of thresh's with a trailing zero: * (cnt_1 thr_1a thr_1b [...]) ... (cnt_N thr_Na thr_Nb [...]) 0. * Returns 0 when all conversions were OK, and 1 for any syntax, * conversion, or alloc error. */ static int get_thresh(int **vlistp, int *vcntp) { int argn, value, gci = 0, grp_cnt = 0, paren = 0, nerr = 0; char *rp, *src; for (argn = 2; (src = LINEARG(argn)) != NULL; argn++) { if (*src == LPAREN) { gci = *vcntp; if ((nerr = vlist_append(vlistp, vcntp, 0)) != 0) break; paren = 1; src++; } if (*(rp = LASTBYTE(src)) == RPAREN) { if (paren) { grp_cnt = *vcntp - gci; *(*vlistp + gci) = grp_cnt; paren = 0; *rp = '\0'; } else { nerr = 1; break; } } value = get_scaled_value(src, &nerr); if (nerr || (nerr = vlist_append(vlistp, vcntp, value))) break; } if (nerr == 0 && grp_cnt) nerr = vlist_append(vlistp, vcntp, 0); return (nerr); } /* * Set device thresholds from (3) formats: * path "always-on" * path time-spec: [0-9]+[{h,m,s}] * path (ts1 ts2 ...)+ */ int devthr(void) { int cmd, upval = OKUP, nthresh = 0, *vlist = NULL; pm_req_t pmreq; bzero(&pmreq, sizeof (pmreq)); if (devpath(&pmreq.physpath, LINEARG(1), &upval)) return (upval); if (strcmp(LINEARG(2), always_on) == 0) { cmd = PM_SET_DEVICE_THRESHOLD; pmreq.value = INT_MAX; } else if (get_thresh(&vlist, &nthresh)) { mesg(MERR, bad_thresh_fmt); upval = NOUP; } else if (nthresh == 1) { pmreq.value = *vlist; cmd = PM_SET_DEVICE_THRESHOLD; } else { pmreq.data = vlist; pmreq.datasize = (nthresh * sizeof (*vlist)); cmd = PM_SET_COMPONENT_THRESHOLDS; } if (upval != NOUP && (upval = ioctl(pm_fd, cmd, &pmreq)) == -1) mesg(MERR, set_thresh_fmt, pmreq.physpath, strerror(errno)); free(vlist); free(pmreq.physpath); return (upval); } static int scan_int(char *src, int *dst) { long lval; const char *errstr; errno = 0; lval = strtonumx(LINEARG(1), 0, INT_MAX, &errstr, 0); if (errstr != NULL) { mesg(MERR, nerr_fmt, src); return (NOUP); } *dst = (int)lval; return (OKUP); } static int scan_float(char *src, float *dst) { float fval; errno = 0; fval = strtof(src, NULL); if (errno || fval < 0.0) { mesg(MERR, nerr_fmt, src); return (NOUP); } *dst = fval; return (OKUP); } int dreads(void) { return (scan_int(LINEARG(1), &new_cc.diskreads_thold)); } /* * Set pathname for idlecheck; * an overflowed pathname is treated as a fatal error. */ int idlechk(void) { STRCPYLIM(new_cc.idlecheck_path, LINEARG(1), "idle path"); return (OKUP); } int loadavg(void) { return (scan_float(LINEARG(1), &new_cc.loadaverage_thold)); } int nfsreq(void) { return (scan_int(LINEARG(1), &new_cc.nfsreqs_thold)); } #ifdef sparc static char open_fmt[] = "cannot open \"%s\", %s\n"; /* * Verify the filesystem type for a regular statefile is "ufs" * or verify a block device is not in use as a mounted filesystem. * Returns 1 if any error, otherwise 0. */ static int check_mount(char *sfile, dev_t sfdev, int ufs) { char *src, *err_fmt = NULL, *mnttab = MNTTAB; int rgent, match = 0; struct mnttab zroot = { 0 }; struct mnttab entry; struct extmnttab ent; FILE *fp; if ((fp = fopen(mnttab, "r")) == NULL) { mesg(MERR, open_fmt, mnttab, strerror(errno)); return (1); } if (ufs) { zroot.mnt_mountp = "/"; zroot.mnt_fstype = "zfs"; if (getmntany(fp, &entry, &zroot) == 0) { err_fmt = "ufs statefile with zfs root is not" " supported\n"; mesg(MERR, err_fmt, sfile); (void) fclose(fp); return (1); } resetmnttab(fp); } /* * Search for a matching dev_t; * ignore non-ufs filesystems for a regular statefile. */ while ((rgent = getextmntent(fp, &ent, sizeof (ent))) != -1) { if (rgent > 0) { mesg(MERR, "error reading \"%s\"\n", mnttab); (void) fclose(fp); return (1); } else if (ufs && strcmp(ent.mnt_fstype, "ufs")) continue; else if (makedev(ent.mnt_major, ent.mnt_minor) == sfdev) { match = 1; break; } } /* * No match is needed for a block device statefile, * a match is needed for a regular statefile. */ if (match == 0) { if (new_cc.cf_type != CFT_UFS) STRCPYLIM(new_cc.cf_devfs, sfile, "block statefile"); else err_fmt = "cannot find ufs mount point for \"%s\"\n"; } else if (new_cc.cf_type == CFT_UFS) { STRCPYLIM(new_cc.cf_fs, ent.mnt_mountp, "mnt entry"); STRCPYLIM(new_cc.cf_devfs, ent.mnt_special, "mnt special"); while (*(sfile + 1) == '/') sfile++; src = sfile + strlen(ent.mnt_mountp); while (*src == '/') src++; STRCPYLIM(new_cc.cf_path, src, "statefile path"); } else err_fmt = "statefile device \"%s\" is a mounted filesystem\n"; (void) fclose(fp); if (err_fmt) mesg(MERR, err_fmt, sfile); return (err_fmt != NULL); } /* * Convert a Unix device to a prom device and save on success, * log any ioctl/conversion error. */ static int utop(char *fs_name, char *prom_name) { union obpbuf { char buf[OBP_MAXPATHLEN + sizeof (uint_t)]; struct openpromio oppio; }; union obpbuf oppbuf; struct openpromio *opp; char *promdev = "/dev/openprom"; int fd, upval; if ((fd = open(promdev, O_RDONLY)) == -1) { mesg(MERR, open_fmt, promdev, strerror(errno)); return (NOUP); } opp = &oppbuf.oppio; opp->oprom_size = OBP_MAXPATHLEN; strcpy_limit(opp->oprom_array, fs_name, OBP_MAXPATHLEN, "statefile device"); upval = ioctl(fd, OPROMDEV2PROMNAME, opp); (void) close(fd); if (upval == OKUP) { strcpy_limit(prom_name, opp->oprom_array, OBP_MAXPATHLEN, "prom device"); } else { openlog("pmconfig", 0, LOG_DAEMON); syslog(LOG_NOTICE, gettext("cannot convert \"%s\" to prom device"), fs_name); closelog(); } return (upval); } /* * given the path to a zvol, return the cXtYdZ name * returns < 0 on error, 0 if it isn't a zvol, > 1 on success */ static int ztop(char *arg, char *diskname) { zpool_handle_t *zpool_handle; nvlist_t *config, *nvroot; nvlist_t **child; uint_t children; libzfs_handle_t *lzfs; char *vname; char *p; char pool_name[MAXPATHLEN]; if (strncmp(arg, "/dev/zvol/dsk/", 14)) { return (0); } arg += 14; (void) strncpy(pool_name, arg, MAXPATHLEN); if ((p = strchr(pool_name, '/')) != NULL) *p = '\0'; STRCPYLIM(new_cc.cf_fs, p + 1, "statefile path"); if ((lzfs = libzfs_init()) == NULL) { mesg(MERR, "failed to initialize ZFS library\n"); return (-1); } if ((zpool_handle = zpool_open(lzfs, pool_name)) == NULL) { mesg(MERR, "couldn't open pool '%s'\n", pool_name); libzfs_fini(lzfs); return (-1); } config = zpool_get_config(zpool_handle, NULL); if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &nvroot) != 0) { zpool_close(zpool_handle); libzfs_fini(lzfs); return (-1); } verify(nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, &child, &children) == 0); if (children != 1) { mesg(MERR, "expected one vdev, got %d\n", children); zpool_close(zpool_handle); libzfs_fini(lzfs); return (-1); } vname = zpool_vdev_name(lzfs, zpool_handle, child[0], B_FALSE); if (vname == NULL) { mesg(MERR, "couldn't determine vdev name\n"); zpool_close(zpool_handle); libzfs_fini(lzfs); return (-1); } (void) strcpy(diskname, "/dev/dsk/"); (void) strcat(diskname, vname); free(vname); zpool_close(zpool_handle); libzfs_fini(lzfs); return (1); } /* * returns NULL if the slice is good (e.g. does not start at block * zero, or a string describing the error if it doesn't */ static boolean_t is_good_slice(char *sfile, char **err) { int fd, rc; struct vtoc vtoc; dk_gpt_t *gpt; char rdskname[MAXPATHLEN]; char *x, *y; *err = NULL; /* convert from dsk to rdsk */ STRCPYLIM(rdskname, sfile, "disk name"); x = strstr(rdskname, "dsk/"); y = strstr(sfile, "dsk/"); if (x != NULL) { *x++ = 'r'; (void) strcpy(x, y); } if ((fd = open(rdskname, O_RDONLY)) == -1) { *err = "could not open '%s'\n"; } else if ((rc = read_vtoc(fd, &vtoc)) >= 0) { /* * we got a slice number; now check the block * number where the slice starts */ if (vtoc.v_part[rc].p_start < 2) *err = "using '%s' would clobber the disk label\n"; (void) close(fd); return (*err ? B_FALSE : B_TRUE); } else if ((rc == VT_ENOTSUP) && (efi_alloc_and_read(fd, &gpt)) >= 0) { /* EFI slices don't clobber the disk label */ free(gpt); (void) close(fd); return (B_TRUE); } else *err = "could not read partition table from '%s'\n"; return (B_FALSE); } /* * Check for a valid statefile pathname, inode and mount status. */ int sfpath(void) { static int statefile; char *err_fmt = NULL; char *sfile, *sp, ch; char diskname[256]; struct stat stbuf; int dir = 0; dev_t dev = NODEV; if (statefile) { mesg(MERR, "ignored redundant statefile entry\n"); return (OKUP); } else if (ua_err) { if (ua_err != ENOTSUP) mesg(MERR, "uadmin(A_FREEZE, A_CHECK, 0): %s\n", strerror(ua_err)); return (NOUP); } /* * Check for an absolute path and trim any trailing '/'. */ sfile = LINEARG(1); if (*sfile != '/') { mesg(MERR, "statefile requires an absolute path\n"); return (NOUP); } for (sp = sfile + strlen(sfile) - 1; sp > sfile && *sp == '/'; sp--) *sp = '\0'; /* * If the statefile doesn't exist, the leading path must be a dir. */ if (stat(sfile, &stbuf) == -1) { if (errno == ENOENT) { dir = 1; if ((sp = strrchr(sfile, '/')) == sfile) sp++; ch = *sp; *sp = '\0'; if (stat(sfile, &stbuf) == -1) err_fmt = stat_fmt; *sp = ch; } else err_fmt = stat_fmt; if (err_fmt) { mesg(MERR, err_fmt, sfile, strerror(errno)); return (NOUP); } } /* * Check for regular/dir/block types, set cf_type and dev. */ if (S_ISREG(stbuf.st_mode) || (dir && S_ISDIR(stbuf.st_mode))) { new_cc.cf_type = CFT_UFS; dev = stbuf.st_dev; } else if (S_ISBLK(stbuf.st_mode)) { if (is_good_slice(sfile, &err_fmt)) { switch (ztop(sfile, diskname)) { case 1: new_cc.cf_type = CFT_ZVOL; break; case 0: new_cc.cf_type = CFT_SPEC; break; case -1: default: return (NOUP); } dev = stbuf.st_rdev; } } else err_fmt = "bad file type for \"%s\"\n" "statefile must be a regular file or block device\n"; if (err_fmt) { mesg(MERR, err_fmt, sfile); return (NOUP); } if (check_mount(sfile, dev, (new_cc.cf_type == CFT_UFS))) return (NOUP); if (new_cc.cf_type == CFT_ZVOL) { if (utop(diskname, new_cc.cf_dev_prom)) return (NOUP); } else if (utop(new_cc.cf_devfs, new_cc.cf_dev_prom)) { return (NOUP); } new_cc.cf_magic = CPR_CONFIG_MAGIC; statefile = 1; return (OKUP); } #endif /* sparc */ /* * Common function to set a system or cpu threshold. */ static int cmnthr(int req) { int value, nerr = 0, upval = OKUP; char *thresh = LINEARG(1); if (strcmp(thresh, always_on) == 0) value = INT_MAX; else if ((value = get_scaled_value(thresh, &nerr)) < 0 || nerr) { mesg(MERR, "%s must be a positive value\n", LINEARG(0)); upval = NOUP; } if (upval == OKUP) (void) ioctl(pm_fd, req, value); return (upval); } /* * Try setting system threshold. */ int systhr(void) { return (cmnthr(PM_SET_SYSTEM_THRESHOLD)); } /* * Try setting cpu threshold. */ int cputhr(void) { return (cmnthr(PM_SET_CPU_THRESHOLD)); } int tchars(void) { return (scan_int(LINEARG(1), &new_cc.ttychars_thold)); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "pmconfig.h" #include #include #ifdef sparc #include static char sf_cmt[] = "# Statefile\t\tPath\n"; #endif static char as_cmt[] = "# Auto-Shutdown\t\tIdle(min)\tStart/Finish(hh:mm)\tBehavior\n"; char **line_args; int lineno = 0; /* * cpr and pm combined permission/update status */ prmup_t cpr_status = { 0, OKUP, "cpr" }; prmup_t pm_status = { 0, OKUP, "pm" }; /* * For config file parsing to work correctly/efficiently, this table * needs to be sorted by .keyword and any longer string like "device" * must appear before a substring like "dev". */ static cinfo_t conftab[] = { "S3-support", S3sup, &pm_status, NULL, 2, 0, 1, "autoS3", autoS3, &pm_status, NULL, 2, 0, 1, "autopm", autopm, &pm_status, NULL, 2, 0, 1, "autoshutdown", autosd, &cpr_status, as_cmt, 5, 0, 1, "cpu-threshold", cputhr, &pm_status, NULL, 2, 0, 1, "cpu_deep_idle", cpuidle, &pm_status, NULL, 2, 0, 1, "cpupm", cpupm, &pm_status, NULL, 2, 1, 1, "device-dependency-property", ddprop, &pm_status, NULL, 3, 1, 1, "device-dependency", devdep, &pm_status, NULL, 3, 1, 1, "device-thresholds", devthr, &pm_status, NULL, 3, 1, 1, "diskreads", dreads, &cpr_status, NULL, 2, 0, 1, "idlecheck", idlechk, &cpr_status, NULL, 2, 0, 0, "loadaverage", loadavg, &cpr_status, NULL, 2, 0, 1, "nfsreqs", nfsreq, &cpr_status, NULL, 2, 0, 1, #ifdef sparc "statefile", sfpath, &cpr_status, sf_cmt, 2, 0, 0, #endif "system-threshold", systhr, &pm_status, NULL, 2, 0, 1, "ttychars", tchars, &cpr_status, NULL, 2, 0, 1, NULL, NULL, NULL, NULL, 0, 0, 0, }; /* * Set cpr/pm permission from default file info. */ static void set_perm(char *defstr, char *user, int *perm, int cons) { char *dinfo, *tk; /* * /etc/default/power entries are: * all (all users + root) * - (none + root) * (list users + root) * console-owner (console onwer + root) * Any error in reading/parsing the file limits the * access requirement to root. */ dinfo = defread(defstr); mesg(MDEBUG, "set_perm: \"%s\", value \"%s\"\n", defstr, dinfo ? dinfo : "NULL"); if (dinfo == NULL) return; else if (strcmp(dinfo, "all") == 0) *perm = 1; else if (strcmp(dinfo, "console-owner") == 0) *perm = cons; else if (user != NULL && (*dinfo == '<') && (tk = strrchr(++dinfo, '>'))) { /* Scan dinfo for a matching user. */ for (*tk = '\0'; (tk = strtok(dinfo, ", ")) != NULL; dinfo = NULL) { mesg(MDEBUG, "match_user: cmp (\"%s\", \"%s\")\n", tk, user); if (strcmp(tk, user) == 0) { *perm = 1; break; } } } } /* * Lookup cpr/pm user permissions in "/etc/default/power". */ void lookup_perms(void) { struct passwd *pent; struct stat stbuf; int cons_perm; char *user; if ((ruid = getuid()) == 0) { cpr_status.perm = pm_status.perm = 1; return; } else if ((pent = getpwuid(ruid)) != NULL) { user = pent->pw_name; } else { user = NULL; } if (defopen("/etc/default/power") == -1) return; if (stat("/dev/console", &stbuf) == -1) cons_perm = 0; else cons_perm = (ruid == stbuf.st_uid); set_perm("PMCHANGEPERM=", user, &pm_status.perm, cons_perm); set_perm("CPRCHANGEPERM=", user, &cpr_status.perm, cons_perm); (void) defopen(NULL); } #ifdef sparc /* * Lookup energystar-v[23] property and set estar_vers. */ void lookup_estar_vers(void) { char es_prop[] = "energystar-v?", *fmt = "%s init/access error\n"; di_prom_handle_t ph; di_node_t node; uchar_t *prop_data; int last; char ch; if ((node = di_init("/", DINFOPROP)) == DI_NODE_NIL) { mesg(MERR, fmt, "di_init"); return; } else if ((ph = di_prom_init()) == DI_PROM_HANDLE_NIL) { mesg(MERR, fmt, "di_prom_init"); di_fini(node); return; } last = strlen(es_prop) - 1; for (ch = ESTAR_V2; ch <= ESTAR_V3; ch++) { es_prop[last] = ch; if (di_prom_prop_lookup_bytes(ph, node, es_prop, &prop_data) == 0) { mesg(MDEBUG, "get_estar_vers: %s prop found\n", es_prop); estar_vers = ch; break; } } di_prom_fini(ph); di_fini(node); } #endif /* sparc */ /* * limit open() to the real user */ static int pmc_open(char *name, int oflag) { uid_t euid; int fd; euid = geteuid(); if (seteuid(ruid) == -1) mesg(MEXIT, "cannot reset euid to %d, %s\n", ruid, strerror(errno)); fd = open(name, oflag); (void) seteuid(euid); return (fd); } /* * Alloc space and read a config file; caller needs to free the space. */ static char * get_conf_data(char *name) { struct stat stbuf; ssize_t nread; size_t size; char *buf; int fd; if ((fd = pmc_open(name, O_RDONLY)) == -1) mesg(MEXIT, "cannot open %s\n", name); else if (fstat(fd, &stbuf) == -1) mesg(MEXIT, "cannot stat %s\n", name); size = (size_t)stbuf.st_size; def_src = (stbuf.st_ino == def_info.st_ino && stbuf.st_dev == def_info.st_dev); if ((buf = malloc(size + 1)) == NULL) mesg(MEXIT, "cannot allocate %u for \"%s\"\n", size + 1, name); nread = read(fd, buf, size); (void) close(fd); if (nread != (ssize_t)size) mesg(MEXIT, "read error, expect %u, got %d, file \"%s\"\n", size, nread, name); *(buf + size) = '\0'; return (buf); } /* * Add an arg to line_args, adding space if needed. */ static void newarg(char *arg, int index) { static int alcnt; size_t size; if ((index + 1) > alcnt) { alcnt += 4; size = alcnt * sizeof (*line_args); if ((line_args = realloc(line_args, size)) == NULL) mesg(MEXIT, "cannot alloc %u for line args\n", size); } *(line_args + index) = arg; } /* * Convert blank-delimited words into an arg vector and return * the arg count; character strings get null-terminated in place. */ static int build_args(char *cline, char *tail) { extern int debug; char **vec, *arg, *cp; int cnt = 0; /* * Search logic: look for "\\\n" as a continuation marker, * treat any other "\\*" as ordinary arg data, scan until a * white-space delimiter is found, and if the arg has length, * null-terminate and save arg to line_args. The scan includes * tail so the last arg is found without any special-case code. */ for (arg = cp = cline; cp <= tail; cp++) { if (*cp == '\\') { if (*(cp + 1) && *(cp + 1) != '\n') { cp++; continue; } } else if (strchr(" \t\n", *cp) == NULL) continue; if (cp - arg) { *cp = '\0'; newarg(arg, cnt++); } arg = cp + 1; } newarg(NULL, cnt); if (debug) { mesg(MDEBUG, "\nline %d, found %d args:\n", lineno, cnt); for (vec = line_args; *vec; vec++) mesg(MDEBUG, " \"%s\"\n", *vec); } return (cnt); } /* * Match leading keyword from a conf line and * return a reference to a config info struct. */ static cinfo_t * get_cinfo(void) { cinfo_t *cip, *info = NULL; char *keyword; int chr_diff; /* * Scan the config table for a matching keyword; since the table * is sorted by keyword strings, a few optimizations can be done: * first compare only the first byte of the keyword, skip any * table string that starts with a lower ASCII value, compare the * full string only when the first byte matches, and stop checking * if the table string starts with a higher ASCII value. */ keyword = LINEARG(0); for (cip = conftab; cip->keyword; cip++) { chr_diff = (int)(*cip->keyword - *keyword); #if 0 mesg(MDEBUG, "line %d, ('%c' - '%c') = %d\n", lineno, *cip->keyword, *line, chr_diff); #endif if (chr_diff < 0) continue; else if (chr_diff == 0) { if (strcmp(keyword, cip->keyword) == 0) { info = cip; break; } } else break; } return (info); } /* * Find the end of a [possibly continued] conf line * and record the real/lf-delimited line count at *lcnt. */ static char * find_line_end(char *line, int *lcnt) { char *next, *lf; *lcnt = 0; next = line; while ((lf = strchr(next, '\n')) != NULL) { (*lcnt)++; if (lf == line || (*(lf - 1) != '\\') || *(lf + 1) == '\0') break; next = lf + 1; } return (lf); } /* * Parse the named conf file and for each conf line * call the action routine or conftab handler routine. */ void parse_conf_file(char *name, vact_t action, boolean_t first_parse) { char *file_buf, *cline, *line, *lend; cinfo_t *cip; int linc, cnt; size_t llen; int dontcare; /* * Do the "implied default" for autoS3, but only before we * start parsing the first conf file. */ if (first_parse) { (void) S3_helper("S3-support-enable", "S3-support-disable", PM_ENABLE_S3, PM_DISABLE_S3, "S3-support", "default", &dontcare, -1); } file_buf = get_conf_data(name); mesg(MDEBUG, "\nnow parsing \"%s\"...\n", name); lineno = 1; line = file_buf; while ((lend = find_line_end(line, &linc)) != NULL) { /* * Each line should start with valid data * but leading white-space can be ignored */ while (line < lend) { if (*line != ' ' && *line != '\t') break; line++; } /* * Copy line into allocated space and null-terminate * without the trailing line-feed. */ if ((llen = (lend - line)) != 0) { if ((cline = malloc(llen + 1)) == NULL) mesg(MEXIT, "cannot alloc %u bytes " "for line copy\n", llen); (void) memcpy(cline, line, llen); *(cline + llen) = '\0'; } else cline = NULL; /* * For blank and comment lines: possibly show a debug * message and otherwise ignore them. For other lines: * parse into an arg vector and try to match the first * arg with conftab keywords. When a match is found, * check for exact or minimum arg count, and call the * action or handler routine; if handler does not return * OKUP, set the referenced update value to NOUP so that * later CPR or PM updates are skipped. */ if (llen == 0) mesg(MDEBUG, "\nline %d, blank...\n", lineno); else if (*line == '#') mesg(MDEBUG, "\nline %d, comment...\n", lineno); else if ((cnt = build_args(cline, cline + llen)) != 0) { if ((cip = get_cinfo()) == NULL) { mesg(MEXIT, "unrecognized keyword \"%s\"\n", LINEARG(0)); } else if (cnt != cip->argc && (cip->any == 0 || cnt < cip->argc)) { mesg(MEXIT, "found %d args, expect %d%s\n", cnt, cip->argc, cip->any ? "+" : ""); } else if (action) (*action)(line, llen + 1, cip); else if (cip->status->perm && (def_src || cip->alt)) { if ((*cip->handler)() != OKUP) cip->status->update = NOUP; } else { mesg(MDEBUG, "==> handler skipped: %s_perm %d, " "def_src %d, alt %d\n", cip->status->set, cip->status->perm, def_src, cip->alt); } } if (cline) free(cline); line = lend + 1; lineno += linc; } lineno = 0; free(file_buf); if (verify) { int ret = ioctl(pm_fd, PM_GET_PM_STATE, NULL); if (ret < 0) { mesg(MDEBUG, "Cannot get PM state: %s\n", strerror(errno)); } switch (ret) { case PM_SYSTEM_PM_ENABLED: mesg(MDEBUG, "Autopm Enabled\n"); break; case PM_SYSTEM_PM_DISABLED: mesg(MDEBUG, "Autopm Disabled\n"); break; } ret = ioctl(pm_fd, PM_GET_S3_SUPPORT_STATE, NULL); if (ret < 0) { mesg(MDEBUG, "Cannot get PM state: %s\n", strerror(errno)); } switch (ret) { case PM_S3_SUPPORT_ENABLED: mesg(MDEBUG, "S3 support Enabled\n"); break; case PM_S3_SUPPORT_DISABLED: mesg(MDEBUG, "S3 support Disabled\n"); break; } ret = ioctl(pm_fd, PM_GET_AUTOS3_STATE, NULL); if (ret < 0) { mesg(MDEBUG, "Cannot get PM state: %s\n", strerror(errno)); } switch (ret) { case PM_AUTOS3_ENABLED: mesg(MDEBUG, "AutoS3 Enabled\n"); break; case PM_AUTOS3_DISABLED: mesg(MDEBUG, "AutoS3 Disabled\n"); break; } } } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ /* * This file has all of the PAM related code for sys-suspend. It is * part of it's own file, as these could be part of some bigger item * that can handle generic PAM facilities (certainly the getinput() * function could be in a common library). However, as that does not * yet exist, we replicate it here so we can get the job done. */ #define __EXTENSIONS__ /* to expose flockfile and friends in stdio.h */ #include #include #include #include #include #include #include #include #include #include #include static int ctl_c; /* was the conversation interrupted? */ /* ARGSUSED 1 */ static void interrupt(int x) { ctl_c = 1; } /* * getinput -- read user input from stdin abort on ^C * * Entry noecho == TRUE, don't echo input. * * Exit User's input. * If interrupted, send SIGINT to caller for processing. */ static char * getinput(int noecho) { struct termio tty; unsigned short tty_flags = 0; char input[PAM_MAX_RESP_SIZE + 1]; int c; int i = 0; void (*sig)(int); ctl_c = 0; sig = signal(SIGINT, interrupt); if (noecho) { (void) ioctl(fileno(stdin), TCGETA, &tty); tty_flags = tty.c_lflag; tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); (void) ioctl(fileno(stdin), TCSETAF, &tty); } /* go to end, but don't overflow PAM_MAX_RESP_SIZE */ flockfile(stdin); while (ctl_c == 0 && (c = getchar_unlocked()) != '\n' && c != '\r' && c != EOF) { if (i < PAM_MAX_RESP_SIZE) { input[i++] = (char)c; } } funlockfile(stdin); input[i] = '\0'; if (noecho) { tty.c_lflag = tty_flags; (void) ioctl(fileno(stdin), TCSETAW, &tty); (void) fputc('\n', stdout); } (void) signal(SIGINT, sig); if (ctl_c == 1) (void) kill(getpid(), SIGINT); return (strdup(input)); } /* * Service modules don't clean up responses if an error is returned. * Free responses here. */ static void free_resp(int num_msg, struct pam_response *pr) { int i; struct pam_response *r = pr; if (pr == NULL) return; for (i = 0; i < num_msg; i++, r++) { if (r->resp) { /* clear before freeing -- may be a password */ bzero(r->resp, strlen(r->resp)); free(r->resp); r->resp = NULL; } } free(pr); } int pam_tty_conv(int num_msg, const struct pam_message **mess, struct pam_response **resp, void *my_data) { const struct pam_message *m = *mess; struct pam_response *r = calloc(num_msg, sizeof (struct pam_response)); int i; if (num_msg >= PAM_MAX_NUM_MSG) { (void) fprintf(stderr, "too many messages %d >= %d\n", num_msg, PAM_MAX_NUM_MSG); free(r); *resp = NULL; return (PAM_CONV_ERR); } /* Talk it out */ *resp = r; for (i = 0; i < num_msg; i++) { int echo_off; /* bad message from service module */ if (m->msg == NULL) { (void) fprintf(stderr, "message[%d]: %d/NULL\n", i, m->msg_style); goto err; } /* * fix up final newline: * removed for prompts * added back for messages */ if (m->msg[strlen(m->msg)] == '\n') m->msg[strlen(m->msg)] = '\0'; r->resp = NULL; r->resp_retcode = 0; echo_off = 0; switch (m->msg_style) { case PAM_PROMPT_ECHO_OFF: echo_off = 1; /*FALLTHROUGH*/ case PAM_PROMPT_ECHO_ON: (void) fputs(m->msg, stdout); r->resp = getinput(echo_off); break; case PAM_ERROR_MSG: (void) fputs(m->msg, stderr); (void) fputc('\n', stderr); break; case PAM_TEXT_INFO: (void) fputs(m->msg, stdout); (void) fputc('\n', stdout); break; default: (void) fprintf(stderr, "message[%d]: unknown type " "%d/val=\"%s\"\n", i, m->msg_style, m->msg); /* error, service module won't clean up */ goto err; } if (errno == EINTR) goto err; /* next message/response */ m++; r++; } return (PAM_SUCCESS); err: free_resp(i, r); *resp = NULL; return (PAM_CONV_ERR); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _PMCONFIG_H #define _PMCONFIG_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #include #define LINEARG(an) *(line_args + an) #define LINELEN 80 #define MOREARGS 4 #define ESTAR_VNONE 0 #define ESTAR_V2 '2' #define ESTAR_V3 '3' #define LPAREN '(' #define RPAREN ')' #define MDEBUG 0 #define MEXIT 1 #define MERR -1 /* * return values from handler routines; * chosen to match syscall return values */ #define NOUP -1 #define OKUP 0 #define DFLT_THOLD 0.04 struct perm_update { int perm; /* cpr or pm permission */ int update; /* flag updates from cpr/pm data */ char *set; /* "cpr" or "pm" */ }; typedef struct perm_update prmup_t; struct cinfo { char *keyword; /* keyword string */ int (*handler)(void); /* keyword handler routine */ prmup_t *status; /* permission and update status */ char *cmt; /* config file comment */ short argc; /* config line arg count */ uint8_t any; /* 0: match argc, 1: at least argc */ uint8_t alt; /* conf line OK from an alt source */ }; typedef struct cinfo cinfo_t; typedef void (*vact_t)(char *, size_t, cinfo_t *); /* Suspend/Resume flags */ extern int whitelist_only; extern int verify; /* * "conf.c" */ extern prmup_t cpr_status, pm_status; extern struct cprconfig new_cc; extern struct stat def_info; extern char estar_vers; extern int pm_fd, ua_err; extern uid_t ruid; extern int def_src; extern void mesg(int, char *, ...); /* * "parse.c" */ extern int lineno; extern char **line_args; extern void lookup_estar_vers(void); extern void lookup_perms(void); extern void parse_conf_file(char *, vact_t, boolean_t); /* * handlers.c */ extern int S3_helper(char *, char *, int, int, char *, char *, int *, int); extern int S3sup(void); extern int autoS3(void); extern int autopm(void); extern int autosd(void); extern int cpupm(void); extern int cpuidle(void); extern int cputhr(void); extern int ddprop(void); extern int devdep(void); extern int devthr(void); extern int dreads(void); extern int idlechk(void); extern int loadavg(void); extern int nfsreq(void); extern int sfpath(void); extern int systhr(void); extern int tchars(void); #ifdef __cplusplus } #endif #endif /* _PMCONFIG_H */ # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Power Management Configuration File # # This entry keeps removable media from being powered down unless the # console framebuffer and monitor are powered down # (See removable-media(9P)) device-dependency-property removable-media /dev/fb autopm default autoS3 default cpupm enable cpu-threshold 1s # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Power Management Configuration File # # This entry keeps removable media from being powered down unless the # console framebuffer and monitor are powered down # (See removable-media(9P)) device-dependency-property removable-media /dev/fb # Auto-Shutdown Idle(min) Start/Finish(hh:mm) Behavior autoshutdown 30 9:00 9:00 unconfigured autopm default # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # 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 # 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 # # # This file contains tunable parameters for Power Management (PM) and # Checkpoint-Resume (CPR). See power.conf(5), pmconfig(8), pm(4D), # and cpr(4) for details. # # # # PMCHANGEPERM and CPRCHANGEPERM variables control changing of PM # and CPR configurations respectively. Possible values are: # # all any user can change the configuration. # # - nobody except super-user can change # the configuration. # # a user in this user list or super-user # can change the configuration. The list of # user is a space and/or comma (,) seperated # list. It is required to enclose the list # in < and > characters. # # console-owner a user who owns the system console device # node or super-user can change the configuration. # # The default values are PMCHANGEPERM=console-owner and CPRCHANGEPERM= # console-owner. # PMCHANGEPERM=console-owner CPRCHANGEPERM=console-owner # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # msgid "#\n" "# Copyright 1996-2002 Sun Microsystems, Inc. All rights reserved.\n" "# Use is subject to license terms.\n" "#\n" "# Power Management Configuration File\n" "#\n\n" msgid "/etc/.cpr_config" msgid "/etc/.tmp.conf.XXXXXX" msgid "/etc/power.conf-Orig" msgid "/etc/power.conf" msgid "%s: " msgid "\"%s\" line %d, " msgid "/dev/pm" msgid "/var/run/powerd.pid" msgid "got powerd pid %ld\n" msgid "/usr/lib/power/powerd" msgid "/bin/cp" msgid "filter: set \"%s\", selected %d\n" msgid "none" msgid "\nscanning set is %s\n" msgid "both" msgid "search: %s\n" msgid "skipped" msgid "retained" msgid "\nconf not written, " "(cpr perm %d update %d), (pm perm %d update %d)\n" msgid "stat" msgid "chmod" msgid "chown" msgid "rename" msgid "\n\"%s\" renamed to \"%s\"\n" msgid "" msgid "ruid %d, perms: cpr %d, pm %d\n" msgid "%s\n" msgid "# Auto-Shutdown\t\tIdle(min)\tStart/Finish(hh:mm)\tBehavior\n" msgid "cpr" msgid "pm" msgid "autopm" msgid "autoshutdown" msgid "device-dependency-property" msgid "device-dependency" msgid "device-thresholds" msgid "diskreads" msgid "idlecheck" msgid "loadaverage" msgid "nfsreqs" msgid "system-threshold" msgid "ttychars" msgid "set_perm: \"%s\", value \"%s\"\n" msgid "NULL" msgid "all" msgid "console-owner" msgid ", " msgid "match_user: cmp (\"%s\", \"%s\")\n" msgid "/etc/default/power" msgid "/dev/console" msgid "PMCHANGEPERM=" msgid "CPRCHANGEPERM=" msgid " \t\n" msgid "\nline %d, found %d args:\n" msgid " \"%s\"\n" msgid "\nnow parsing \"%s\"...\n" msgid "\nline %d, blank...\n" msgid "\nline %d, comment...\n" msgid "+" msgid "==> handler skipped: %s_perm %d, " "def_src %d, alt %d\n" msgid "always-on" msgid "disable" msgid "enable" msgid "%d:%d" msgid "autowakeup" msgid "default" msgid "noshutdown" msgid "shutdown" msgid "unconfigured" msgid "idle %d, start %d:%02d, finish %d:%02d\n" msgid "%s: \"%s\"\n" msgid "/devices" msgid "pm ioctl, cmd %d, errno %d\n" "kept \"%s\", keeper \"%s\"\n" msgid "got scaled value %d\n" msgid "threshold list" msgid "idle path" msgid "cpu-threshold" msgid "cpupm" msgid "-d" msgid "S3-support" msgid "autoS3" msgid "S3-support-enable" msgid "S3-support-disable" msgid "??" msgid "S3_helper(%s, %s, %s, %s, %s, %s)\n" msgid "acpi" msgid "S3" msgid "value.l %lx\n" msgid "preferred_pm_profile" msgid "Manufacturer: %s\n" msgid "Product: %s\n" msgid "Sun Microsystems" msgid "S3-support" msgid "S3-support-enable" msgid "S3-support-disable" msgid "autoS3" msgid "S3-autoenable" msgid "S3-autodisable" msgid "autopm-enable" msgid "autopm-disable" /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ #include /* Standard */ #include #include #include #include #include #include #include #include #include #include #include /* Time-Of-Day chip */ #include #include #include /* IPC functions */ #include /* signal handling */ #include #include #include #include #include /* power management driver */ #include #include /* for prom access */ #include /* for MIN & MAX macros */ #include #include /* for INFTIM */ #include #include #include #include #include "powerd.h" /* External Functions */ extern struct tm *localtime_r(const time_t *, struct tm *); extern void sysstat_init(void); extern int check_tty(hrtime_t *, int); extern int check_disks(hrtime_t *, int); extern int check_load_ave(hrtime_t *, float); extern int check_nfs(hrtime_t *, int); extern int last_disk_activity(hrtime_t *, int); extern int last_tty_activity(hrtime_t *, int); extern int last_load_ave_activity(hrtime_t *); extern int last_nfs_activity(hrtime_t *, int); #define PM "/dev/pm" #define TOD "/dev/tod" #define PROM "/dev/openprom" #define PB "/dev/power_button" #define SRN "/dev/srn" #define LOGFILE "./powerd.log" #define PBM_THREAD 0 #define ATTACH_THREAD 1 #define NUM_THREADS 2 #define CHECK_INTERVAL 5 #define IDLECHK_INTERVAL 15 #define MINS_TO_SECS 60 #define HOURS_TO_SECS (60 * 60) #define DAYS_TO_SECS (24 * 60 * 60) #define HOURS_TO_MINS 60 #define DAYS_TO_MINS (24 * 60) #define LIFETIME_SECS (7 * 365 * DAYS_TO_SECS) #define DEFAULT_POWER_CYCLE_LIMIT 10000 #define DEFAULT_SYSTEM_BOARD_DATE 804582000 /* July 1, 1995 */ #define LLEN 80 typedef enum {root, options} prom_node_t; /* State Variables */ static struct cprconfig asinfo; static time_t shutdown_time; /* Time for next shutdown check */ static time_t checkidle_time; /* Time for next idleness check */ static time_t last_resume; pwr_info_t *info; /* private as config data buffer */ static int pb_fd; /* power button driver */ static int broadcast; /* Enables syslog messages */ static int start_calc; static int autoshutdown_en; static int do_idlecheck; static int got_sighup; static int estar_v2_prop; static int estar_v3_prop; static int log_power_cycles_error = 0; static int log_system_board_date_error = 0; static int log_no_autoshutdown_warning = 0; static mutex_t poweroff_mutex; static char *autoshutdown_cmd[] = { "/usr/bin/sys-suspend", "-n", "-d", ":0", NULL }; static char *power_button_cmd[] = { "/usr/bin/sys-suspend", "-h", "-d", ":0", NULL }; #ifdef __x86 static char *autoS3_cmd[] = { "/usr/bin/sys-suspend", "-n", "-d", ":0", NULL }; #endif static char pidpath[] = PIDPATH; static char scratch[PATH_MAX]; static char *prog; /* Local Functions */ static void alarm_handler(int); static void thaw_handler(int); static void kill_handler(int); static void work_handler(int); static void check_shutdown(time_t *, hrtime_t *); static void check_idleness(time_t *, hrtime_t *); static int last_system_activity(hrtime_t *); static int run_idlecheck(void); static void set_alarm(time_t); static int poweroff(const char *, char **); static int is_ok2shutdown(time_t *); static int get_prom(int, prom_node_t, char *, char *, size_t); static void *power_button_monitor(void *); static int open_pidfile(char *); static int write_pidfile(int, pid_t); static int read_cpr_config(void); static void *system_activity_monitor(void *); #ifdef __x86 static void *autos3_monitor(void *); #endif static void do_attach(void); static void *attach_devices(void *); static int powerd_debug; /* PRINTFLIKE1 */ static void logerror(const char *fmt, ...) { va_list args; va_start(args, fmt); if (broadcast) vsyslog(LOG_ERR, fmt, args); va_end(args); } static void estrcpy(char *dst, char *src, size_t dlen) { size_t slen; slen = strlcpy(dst, src, dlen); if (slen >= dlen) { logerror("%s: string too long \"%s ...\"\n" "(len %d, max %d)\n", prog, dst, slen, dlen - 1); exit(EXIT_FAILURE); } } int main(int argc, char *argv[]) { pid_t pid; int pm_fd; struct sigaction act; sigset_t sigmask; int c; char errmsg[PATH_MAX + 64]; int pid_fd; prog = argv[0]; if (geteuid() != 0) { (void) fprintf(stderr, "%s: Must be root\n", prog); exit(EXIT_FAILURE); } if ((pid_fd = open_pidfile(prog)) == -1) exit(EXIT_FAILURE); /* * Process options */ broadcast = 1; while ((c = getopt(argc, argv, "nd")) != EOF) { switch (c) { case 'd': powerd_debug = 1; break; case 'n': broadcast = 0; break; case '?': (void) fprintf(stderr, "Usage: %s [-n]\n", prog); exit(EXIT_FAILURE); } } pm_fd = open(PM, O_RDWR); if (pm_fd == -1) { (void) snprintf(errmsg, sizeof (errmsg), "%s: %s", prog, PM); perror(errmsg); exit(EXIT_FAILURE); } (void) close(pm_fd); /* * Initialize mutex lock used to insure only one command to * run at a time. */ if (mutex_init(&poweroff_mutex, USYNC_THREAD, NULL) != 0) { (void) fprintf(stderr, "%s: Unable to initialize mutex lock\n", prog); exit(EXIT_FAILURE); } if ((info = (pwr_info_t *)malloc(sizeof (pwr_info_t))) == NULL) { (void) snprintf(errmsg, sizeof (errmsg), "%s: malloc", prog); perror(errmsg); exit(EXIT_FAILURE); } /* * Daemon is set to go... */ if ((pid = fork()) < 0) exit(EXIT_FAILURE); else if (pid != 0) exit(EXIT_SUCCESS); pid = getpid(); openlog(prog, 0, LOG_DAEMON); if (write_pidfile(pid_fd, pid) == -1) /* logs errors on failure */ exit(EXIT_FAILURE); (void) close(pid_fd); /* * Close all the parent's file descriptors (Bug 1225843). */ closefrom(0); (void) setsid(); (void) chdir("/"); (void) umask(0); #ifdef DEBUG /* * Connect stdout to the console. */ if (dup2(open("/dev/console", O_WRONLY|O_NOCTTY), 1) == -1) { logerror("Unable to connect to the console."); } #endif info->pd_flags = PD_AC; info->pd_idle_time = -1; info->pd_start_time = 0; info->pd_finish_time = 0; /* * Allow SIGQUIT, SIGINT and SIGTERM signals to terminate us * any time */ act.sa_handler = kill_handler; (void) sigemptyset(&act.sa_mask); act.sa_flags = 0; (void) sigaction(SIGQUIT, &act, NULL); (void) sigaction(SIGINT, &act, NULL); (void) sigaction(SIGTERM, &act, NULL); (void) sigfillset(&sigmask); (void) sigdelset(&sigmask, SIGQUIT); (void) sigdelset(&sigmask, SIGINT); (void) sigdelset(&sigmask, SIGTERM); (void) thr_sigsetmask(SIG_SETMASK, &sigmask, NULL); /* * If "power_button" device node can be opened, create a new * thread to monitor the power button. */ if ((pb_fd = open(PB, O_RDONLY)) != -1) { if (powerd_debug) logerror("powerd starting power button monitor."); if (thr_create(NULL, 0, power_button_monitor, NULL, THR_DAEMON, NULL) != 0) { logerror("Unable to monitor system's power button."); } } do_attach(); /* * Create a new thread to monitor system activity and suspend * system if idle. */ if (powerd_debug) logerror("powerd starting system activity monitor."); if (thr_create(NULL, 0, system_activity_monitor, NULL, THR_DAEMON, NULL) != 0) { logerror("Unable to create thread to monitor system activity."); } #ifdef __x86 /* * Create a new thread to handle autos3 trigger */ if (powerd_debug) logerror("powerd starting autos3 monitor."); if (thr_create(NULL, 0, autos3_monitor, NULL, THR_DAEMON, NULL) != 0) { logerror("Unable to create thread to monitor autos3 activity."); } #endif /* * Block until we receive an explicit terminate signal */ (void) sigsuspend(&sigmask); return (1); } static void * system_activity_monitor(void *arg __unused) { struct sigaction act; sigset_t sigmask; /* * Setup for gathering system's statistic. */ sysstat_init(); /* * In addition to the SIGQUIT, SIGINT and SIGTERM signals already * being handled, this thread also needs to handle SIGHUP, SIGALRM * and SIGTHAW signals. */ (void) sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = alarm_handler; (void) sigaction(SIGALRM, &act, NULL); act.sa_handler = work_handler; (void) sigaction(SIGHUP, &act, NULL); act.sa_handler = thaw_handler; (void) sigaction(SIGTHAW, &act, NULL); /* * Invoke work_handler with a dummy SIGHUP signal to read * cpr config file, get autoshutdown properties and schedule * an alarm if needed. */ work_handler(SIGHUP); /* * Wait for signal to read file */ (void) thr_sigsetmask(0, 0, &sigmask); (void) sigdelset(&sigmask, SIGHUP); (void) sigdelset(&sigmask, SIGALRM); (void) sigdelset(&sigmask, SIGTHAW); (void) thr_sigsetmask(SIG_SETMASK, &sigmask, NULL); do { (void) sigsuspend(&sigmask); } while (errno == EINTR); return (NULL); } #ifdef __x86 static void * autos3_monitor(void *arg __unused) { struct pollfd poll_fd; srn_event_info_t srn_event; /* contains suspend type */ int fd, ret; fd = open(SRN, O_RDWR|O_EXCL|O_NDELAY); if (fd == -1) { logerror("Unable to open %s: %s", SRN, strerror(errno)); thr_exit((void *)(intptr_t)errno); } /* * Tell device we want the special sauce */ ret = ioctl(fd, SRN_IOC_AUTOSX, NULL); if (ret == -1) { logerror("Ioctl SRN_IOC_AUTOSX failed: %s", strerror(errno)); (void) close(fd); thr_exit((void *)(intptr_t)errno); } poll_fd.fd = fd; /*CONSTCOND*/ while (1) { poll_fd.revents = 0; poll_fd.events = POLLIN; if (poll(&poll_fd, 1, -1) < 0) { switch (errno) { case EINTR: case EAGAIN: continue; default: logerror("Poll error: %s", strerror(errno)); (void) close(fd); thr_exit((void *)(intptr_t)errno); } } ret = ioctl(fd, SRN_IOC_NEXTEVENT, &srn_event); if (ret == -1) { logerror("ioctl error: %s", strerror(errno)); (void) close(fd); thr_exit((void *)(intptr_t)errno); } switch (srn_event.ae_type) { case 3: /* S3 */ if (powerd_debug) logerror("ioctl returns type: %d", srn_event.ae_type); break; default: logerror("Unsupported target state %d", srn_event.ae_type); continue; } (void) poweroff("AutoS3", autoS3_cmd); continue; } return (NULL); } #endif static int read_cpr_config(void) { int asfd; if ((asfd = open(CPR_CONFIG, O_RDONLY)) < 0) { logerror("Unable to open CPR config file '%s'", CPR_CONFIG); return (-1); } if (read(asfd, (void *)&asinfo, sizeof (asinfo)) != sizeof (asinfo)) { logerror("Unable to read CPR config file '%s'", CPR_CONFIG); (void) close(asfd); return (-1); } (void) close(asfd); return (0); } /*ARGSUSED*/ static void thaw_handler(int sig) { start_calc = 0; last_resume = time(NULL); } /*ARGSUSED*/ static void kill_handler(int sig) { int ret_code = EXIT_SUCCESS; /* * Free resources */ free(info); if (pb_fd != -1) { (void) close(pb_fd); } (void) mutex_destroy(&poweroff_mutex); (void) unlink(pidpath); closelog(); exit(ret_code); } /*ARGSUSED*/ static void alarm_handler(int sig) { time_t now; hrtime_t hr_now; now = time(NULL); hr_now = gethrtime(); if (checkidle_time <= now && checkidle_time != 0) check_idleness(&now, &hr_now); if (shutdown_time <= now && shutdown_time != 0) check_shutdown(&now, &hr_now); set_alarm(now); } /*ARGSUSED*/ static void work_handler(int sig) { time_t now; hrtime_t hr_now; struct stat stat_buf; do_idlecheck = 0; info->pd_flags = PD_AC; /* * Parse the config file for autoshutdown and idleness entries. */ if (read_cpr_config() < 0) return; /* * Since Oct. 1, 1995, any new system shipped had root * property "energystar-v2" defined in its prom. Systems * shipped after July 1, 1999, will have "energystar-v3" * property. */ estar_v2_prop = asinfo.is_cpr_default; info->pd_flags |= asinfo.is_autowakeup_capable; if (strlen(asinfo.idlecheck_path) > 0) { if (stat(asinfo.idlecheck_path, &stat_buf) != 0) { logerror("unable to access idlecheck program \"%s\".", asinfo.idlecheck_path); } else if (!(stat_buf.st_mode & S_IXUSR)) { logerror("idlecheck program \"%s\" is not executable.", asinfo.idlecheck_path); } else { do_idlecheck = 1; } } if (strlen(asinfo.as_behavior) == 0 || strcmp(asinfo.as_behavior, "noshutdown") == 0 || strcmp(asinfo.as_behavior, "unconfigured") == 0) { info->pd_autoshutdown = 0; } else if (strcmp(asinfo.as_behavior, "default") == 0) { info->pd_autoshutdown = estar_v2_prop; } else if (strcmp(asinfo.as_behavior, "shutdown") == 0 || strcmp(asinfo.as_behavior, "autowakeup") == 0) { info->pd_autoshutdown = asinfo.is_cpr_capable; } else { logerror("autoshutdown behavior \"%s\" unrecognized.", asinfo.as_behavior); info->pd_autoshutdown = 0; } if (info->pd_autoshutdown) { info->pd_idle_time = asinfo.as_idle; info->pd_start_time = (asinfo.as_sh * 60 + asinfo.as_sm) % DAYS_TO_MINS; info->pd_finish_time = (asinfo.as_fh * 60 + asinfo.as_fm) % DAYS_TO_MINS; info->pd_autoresume = (strcmp(asinfo.as_behavior, "autowakeup") == 0) ? 1 : 0; } autoshutdown_en = (asinfo.as_idle >= 0 && info->pd_autoshutdown) ? 1 : 0; #ifdef DEBUG (void) fprintf(stderr, "autoshutdown_en = %d, as_idle = %d, " "pd_autoresume = %d\n", autoshutdown_en, asinfo.as_idle, info->pd_autoresume); (void) fprintf(stderr, " pd_start_time=%d, pd_finish_time=%d\n", info->pd_start_time, info->pd_finish_time); #endif got_sighup = 1; now = last_resume = time(NULL); hr_now = gethrtime(); check_idleness(&now, &hr_now); check_shutdown(&now, &hr_now); set_alarm(now); } static void check_shutdown(time_t *now, hrtime_t *hr_now) { int tod_fd = -1; int kbd, mouse, system, least_idle, idlecheck_time; int next_time; int s, f; struct tm tmp_time; time_t start_of_day, time_since_last_resume; time_t wakeup_time; extern long conskbd_idle_time(void); extern long consms_idle_time(void); static int warned_kbd, warned_ms; /* print error msg one time */ if (!autoshutdown_en) { shutdown_time = 0; return; } (void) localtime_r(now, &tmp_time); tmp_time.tm_sec = 0; tmp_time.tm_min = 0; tmp_time.tm_hour = 0; start_of_day = mktime(&tmp_time); s = start_of_day + info->pd_start_time * 60; f = start_of_day + info->pd_finish_time * 60; if ((s < f && *now >= s && *now < f) || (s >= f && (*now < f || *now >= s))) { if ((mouse = (int)consms_idle_time()) < 0) { if (! warned_ms) { warned_ms = 1; logerror("powerd: failed to get " "idle time for console mouse"); } return; } if ((kbd = (int)conskbd_idle_time()) < 0) { if (! warned_kbd) { warned_kbd = 1; logerror("powerd: failed to get " "idle time for console keyboard"); } return; } system = last_system_activity(hr_now); /* who is the last to go idle */ least_idle = MIN(system, MIN(kbd, mouse)); /* * Calculate time_since_last_resume and the next_time * to auto suspend. */ start_calc = 1; time_since_last_resume = time(NULL) - last_resume; next_time = info->pd_idle_time * 60 - MIN(least_idle, time_since_last_resume); #ifdef DEBUG fprintf(stderr, " check_shutdown: next_time=%d\n", next_time); #endif /* * If we have get the SIGTHAW signal at this point - our * calculation of time_since_last_resume is wrong so * - we need to recalculate. */ while (start_calc == 0) { /* need to redo calculation */ start_calc = 1; time_since_last_resume = time(NULL) - last_resume; next_time = info->pd_idle_time * 60 - MIN(least_idle, time_since_last_resume); } /* * Only when everything else is idle, run the user's idlecheck * script. */ if (next_time <= 0 && do_idlecheck) { got_sighup = 0; idlecheck_time = run_idlecheck(); next_time = info->pd_idle_time * 60 - MIN(idlecheck_time, MIN(least_idle, time_since_last_resume)); /* * If we have caught SIGTHAW or SIGHUP, need to * recalculate. */ while (start_calc == 0 || got_sighup == 1) { start_calc = 1; got_sighup = 0; idlecheck_time = run_idlecheck(); time_since_last_resume = time(NULL) - last_resume; next_time = info->pd_idle_time * 60 - MIN(idlecheck_time, MIN(least_idle, time_since_last_resume)); } } if (next_time <= 0) { if (is_ok2shutdown(now)) { /* * Setup the autowakeup alarm. Clear it * right after poweroff, just in case if * shutdown doesn't go through. */ if (info->pd_autoresume) tod_fd = open(TOD, O_RDWR); if (info->pd_autoresume && tod_fd != -1) { wakeup_time = (*now < f) ? f : (f + DAYS_TO_SECS); /* * A software fix for hardware * bug 1217415. */ if ((wakeup_time - *now) < 180) { logerror( "Since autowakeup time is less than 3 minutes away, " "autoshutdown will not occur."); shutdown_time = *now + 180; (void) close(tod_fd); return; } if (ioctl(tod_fd, TOD_SET_ALARM, &wakeup_time) == -1) { logerror("Unable to program TOD" " alarm for autowakeup."); (void) close(tod_fd); return; } } (void) poweroff("Autoshutdown", autoshutdown_cmd); if (info->pd_autoresume && tod_fd != -1) { if (ioctl(tod_fd, TOD_CLEAR_ALARM, NULL) == -1) logerror("Unable to clear " "alarm in TOD device."); (void) close(tod_fd); } (void) time(now); /* wait at least 5 mins */ shutdown_time = *now + ((info->pd_idle_time * 60) > 300 ? (info->pd_idle_time * 60) : 300); } else { /* wait 5 mins */ shutdown_time = *now + 300; } } else shutdown_time = *now + next_time; } else if (s < f && *now >= f) { shutdown_time = s + DAYS_TO_SECS; } else shutdown_time = s; } static int is_ok2shutdown(time_t *now) { int prom_fd = -1; char power_cycles_st[LLEN]; char power_cycle_limit_st[LLEN]; char system_board_date_st[LLEN]; int power_cycles, power_cycle_limit, free_cycles, scaled_cycles; time_t life_began, life_passed; int no_power_cycles = 0; int no_system_board_date = 0; int ret = 1; /* CONSTCOND */ while (1) { if ((prom_fd = open(PROM, O_RDWR)) == -1 && (errno == EAGAIN)) continue; break; } /* * when #power-cycles property does not exist * power cycles are unlimited. */ if (get_prom(prom_fd, options, "#power-cycles", power_cycles_st, sizeof (power_cycles_st)) == 0) goto ckdone; if (get_prom(prom_fd, root, "power-cycle-limit", power_cycle_limit_st, sizeof (power_cycle_limit_st)) == 0) { power_cycle_limit = DEFAULT_POWER_CYCLE_LIMIT; } else { power_cycle_limit = atoi(power_cycle_limit_st); } /* * Allow 10% of power_cycle_limit as free cycles. */ free_cycles = power_cycle_limit / 10; power_cycles = atoi(power_cycles_st); if (power_cycles < 0) no_power_cycles++; else if (power_cycles <= free_cycles) goto ckdone; if (no_power_cycles && log_power_cycles_error == 0) { logerror("Invalid PROM property \"#power-cycles\" was found."); log_power_cycles_error++; } if (get_prom(prom_fd, options, "system-board-date", system_board_date_st, sizeof (system_board_date_st)) == 0) { no_system_board_date++; } else { life_began = strtol(system_board_date_st, (char **)NULL, 16); if (life_began > *now) { no_system_board_date++; } } if (no_system_board_date) { if (log_system_board_date_error == 0) { logerror("No or invalid PROM property " "\"system-board-date\" was found."); log_system_board_date_error++; } life_began = DEFAULT_SYSTEM_BOARD_DATE; } life_passed = *now - life_began; /* * Since we don't keep the date that last free_cycle is ended, we * need to spread (power_cycle_limit - free_cycles) over the entire * 7-year life span instead of (lifetime - date free_cycles ended). */ scaled_cycles = (int)(((float)life_passed / (float)LIFETIME_SECS) * (power_cycle_limit - free_cycles)); if (no_power_cycles) goto ckdone; #ifdef DEBUG (void) fprintf(stderr, "Actual power_cycles = %d\t" "Scaled power_cycles = %d\n", power_cycles, scaled_cycles); #endif if (power_cycles > scaled_cycles) { if (log_no_autoshutdown_warning == 0) { logerror("Automatic shutdown has been temporarily " "suspended in order to preserve the reliability " "of this system."); log_no_autoshutdown_warning++; } ret = 0; goto ckdone; } ckdone: if (prom_fd != -1) (void) close(prom_fd); return (ret); } static void check_idleness(time_t *now, hrtime_t *hr_now) { /* * Check idleness only when autoshutdown is enabled. */ if (!autoshutdown_en) { checkidle_time = 0; return; } info->pd_ttychars_idle = check_tty(hr_now, asinfo.ttychars_thold); info->pd_loadaverage_idle = check_load_ave(hr_now, asinfo.loadaverage_thold); info->pd_diskreads_idle = check_disks(hr_now, asinfo.diskreads_thold); info->pd_nfsreqs_idle = check_nfs(hr_now, asinfo.nfsreqs_thold); #ifdef DEBUG (void) fprintf(stderr, "Idle ttychars for %d secs.\n", info->pd_ttychars_idle); (void) fprintf(stderr, "Idle loadaverage for %d secs.\n", info->pd_loadaverage_idle); (void) fprintf(stderr, "Idle diskreads for %d secs.\n", info->pd_diskreads_idle); (void) fprintf(stderr, "Idle nfsreqs for %d secs.\n", info->pd_nfsreqs_idle); #endif checkidle_time = *now + IDLECHK_INTERVAL; } static int last_system_activity(hrtime_t *hr_now) { int act_idle, latest; latest = info->pd_idle_time * 60; act_idle = last_tty_activity(hr_now, asinfo.ttychars_thold); latest = MIN(latest, act_idle); act_idle = last_load_ave_activity(hr_now); latest = MIN(latest, act_idle); act_idle = last_disk_activity(hr_now, asinfo.diskreads_thold); latest = MIN(latest, act_idle); act_idle = last_nfs_activity(hr_now, asinfo.nfsreqs_thold); latest = MIN(latest, act_idle); return (latest); } static int run_idlecheck() { char pm_variable[LLEN]; char *cp; int status; pid_t child; /* * Reap any child process which has been left over. */ while (waitpid((pid_t)-1, &status, WNOHANG) > 0) ; /* * Execute the user's idlecheck script and set variable PM_IDLETIME. * Returned exit value is the idle time in minutes. */ if ((child = fork1()) == 0) { (void) sprintf(pm_variable, "PM_IDLETIME=%d", info->pd_idle_time); (void) putenv(pm_variable); cp = strrchr(asinfo.idlecheck_path, '/'); if (cp == NULL) cp = asinfo.idlecheck_path; else cp++; (void) execl(asinfo.idlecheck_path, cp, NULL); exit(-1); } else if (child == -1) { return (info->pd_idle_time * 60); } /* * Wait until the idlecheck program completes. */ if (waitpid(child, &status, 0) != child) { /* * We get here if the calling process gets a signal. */ return (info->pd_idle_time * 60); } if (WEXITSTATUS(status) < 0) { return (info->pd_idle_time * 60); } else { return (WEXITSTATUS(status) * 60); } } static void set_alarm(time_t now) { time_t itime, stime, next_time, max_time; int next_alarm; max_time = MAX(checkidle_time, shutdown_time); if (max_time == 0) { (void) alarm(0); return; } itime = (checkidle_time == 0) ? max_time : checkidle_time; stime = (shutdown_time == 0) ? max_time : shutdown_time; next_time = MIN(itime, stime); next_alarm = (next_time <= now) ? 1 : (next_time - now); (void) alarm(next_alarm); #ifdef DEBUG (void) fprintf(stderr, "Currently @ %s", ctime(&now)); (void) fprintf(stderr, "Checkidle in %d secs\n", checkidle_time - now); (void) fprintf(stderr, "Shutdown in %d secs\n", shutdown_time - now); (void) fprintf(stderr, "Next alarm goes off in %d secs\n", next_alarm); (void) fprintf(stderr, "************************************\n"); #endif } static int poweroff(const char *msg, char **cmd_argv) { struct stat statbuf; pid_t pid, child; struct passwd *pwd; char *home, *user; char ehome[] = "HOME="; char euser[] = "LOGNAME="; int status; char **ca; if (mutex_trylock(&poweroff_mutex) != 0) return (0); if (stat("/dev/console", &statbuf) == -1 || (pwd = getpwuid(statbuf.st_uid)) == NULL) { (void) mutex_unlock(&poweroff_mutex); return (1); } if (msg) syslog(LOG_NOTICE, msg); if (*cmd_argv == NULL) { logerror("No command to run."); (void) mutex_unlock(&poweroff_mutex); return (1); } home = malloc(strlen(pwd->pw_dir) + sizeof (ehome)); user = malloc(strlen(pwd->pw_name) + sizeof (euser)); if (home == NULL || user == NULL) { free(home); free(user); logerror("No memory."); (void) mutex_unlock(&poweroff_mutex); return (1); } (void) strcpy(home, ehome); (void) strcat(home, pwd->pw_dir); (void) strcpy(user, euser); (void) strcat(user, pwd->pw_name); /* * Need to simulate the user enviroment, minimaly set HOME, and USER. */ if ((child = fork1()) == 0) { (void) putenv(home); (void) putenv(user); (void) setgid(pwd->pw_gid); (void) setuid(pwd->pw_uid); /* * check for shutdown flag and set environment */ for (ca = cmd_argv; *ca; ca++) { if (strcmp("-h", *ca) == 0) { (void) putenv("SYSSUSPENDDODEFAULT="); break; } } (void) execv(cmd_argv[0], cmd_argv); exit(EXIT_FAILURE); } else { free(home); free(user); if (child == -1) { (void) mutex_unlock(&poweroff_mutex); return (1); } } pid = 0; while (pid != child) pid = wait(&status); if (WEXITSTATUS(status)) { (void) syslog(LOG_ERR, "Failed to exec \"%s\".", cmd_argv[0]); (void) mutex_unlock(&poweroff_mutex); return (1); } (void) mutex_unlock(&poweroff_mutex); return (0); } #define PBUFSIZE 256 /* * Gets the value of a prom property at either root or options node. It * returns 1 if it is successful, otherwise it returns 0 . */ static int get_prom(int prom_fd, prom_node_t node_name, char *property_name, char *property_value, size_t len) { union { char buf[PBUFSIZE + sizeof (uint_t)]; struct openpromio opp; } oppbuf; register struct openpromio *opp = &(oppbuf.opp); int got_it = 0; if (prom_fd == -1) { return (0); } switch (node_name) { case root: (void) memset(oppbuf.buf, 0, PBUFSIZE); opp->oprom_size = PBUFSIZE; if (ioctl(prom_fd, OPROMNEXT, opp) < 0) { return (0); } /* * Passing null string will give us the first property. */ (void) memset(oppbuf.buf, 0, PBUFSIZE); do { opp->oprom_size = PBUFSIZE; if (ioctl(prom_fd, OPROMNXTPROP, opp) < 0) { return (0); } if (strcmp(opp->oprom_array, property_name) == 0) { got_it++; break; } } while (opp->oprom_size > 0); if (!got_it) { return (0); } if (got_it && property_value == NULL) { return (1); } opp->oprom_size = PBUFSIZE; if (ioctl(prom_fd, OPROMGETPROP, opp) < 0) { return (0); } if (opp->oprom_size == 0) { *property_value = '\0'; } else { estrcpy(property_value, opp->oprom_array, len); } break; case options: estrcpy(opp->oprom_array, property_name, PBUFSIZE); opp->oprom_size = PBUFSIZE; if (ioctl(prom_fd, OPROMGETOPT, opp) < 0) { return (0); } if (opp->oprom_size == 0) { return (0); } if (property_value != NULL) { estrcpy(property_value, opp->oprom_array, len); } break; default: logerror("Only root node and options node are supported.\n"); return (0); } return (1); } #define isspace(ch) ((ch) == ' ' || (ch) == '\t') #define iseol(ch) ((ch) == '\n' || (ch) == '\r' || (ch) == '\f') /*ARGSUSED*/ static void * power_button_monitor(void *arg) { struct pollfd pfd; int events, ret; if (ioctl(pb_fd, PB_BEGIN_MONITOR, NULL) == -1) { logerror("Failed to monitor the power button."); thr_exit((void *) 0); } pfd.fd = pb_fd; pfd.events = POLLIN; /*CONSTCOND*/ while (1) { if (poll(&pfd, 1, INFTIM) == -1) { logerror("Failed to poll for power button events."); thr_exit((void *) 0); } if (!(pfd.revents & POLLIN)) continue; /* * Monitor the power button, but only take action if * gnome-power-manager is not running. * * ret greater than 0 means could not find process. */ ret = system("/usr/bin/pgrep -fx gnome-power-manager"); if (ioctl(pfd.fd, PB_GET_EVENTS, &events) == -1) { logerror("Failed to get power button events."); thr_exit((void *) 0); } if ((ret > 0) && (events & PB_BUTTON_PRESS) && (poweroff(NULL, power_button_cmd) != 0)) { logerror("Power button is pressed, powering " "down the system!"); /* * Send SIGPWR signal to the init process to * shut down the system. */ if (kill(1, SIGPWR) == -1) (void) uadmin(A_SHUTDOWN, AD_POWEROFF, 0); } /* * Clear any power button event that has happened * meanwhile we were busy processing the last one. */ if (ioctl(pfd.fd, PB_GET_EVENTS, &events) == -1) { logerror("Failed to get power button events."); thr_exit((void *) 0); } } return (NULL); } static void do_attach(void) { if (read_cpr_config() < 0) return; /* * If autopm behavior is explicitly enabled for energystar-v2, or * set to default for energystar-v3, create a new thread to attach * all devices. */ estar_v3_prop = asinfo.is_autopm_default; if ((strcmp(asinfo.apm_behavior, "enable") == 0) || (estar_v3_prop && strcmp(asinfo.apm_behavior, "default") == 0)) { if (powerd_debug) logerror("powerd starting device attach thread."); if (thr_create(NULL, 0, attach_devices, NULL, THR_DAEMON, NULL) != 0) { logerror("Unable to create thread to attach devices."); } } } /*ARGSUSED*/ static void * attach_devices(void *arg) { di_node_t root_node; (void) sleep(60); /* let booting finish first */ if ((root_node = di_init("/", DINFOFORCE)) == DI_NODE_NIL) { logerror("Failed to attach devices."); return (NULL); } di_fini(root_node); /* * Unload all the modules. */ (void) modctl(MODUNLOAD, 0); return (NULL); } /* * Create a file which will contain our pid. Pmconfig will check this file * to see if we are running and can use the pid to signal us. Returns the * file descriptor if successful, -1 otherwise. * * Note: Deal with attempt to launch multiple instances and also with existence * of an obsolete pid file caused by an earlier abort. */ static int open_pidfile(char *me) { int fd; const char *e1 = "%s: Cannot open pid file for read: "; const char *e2 = "%s: Cannot unlink obsolete pid file: "; const char *e3 = "%s: Either another daemon is running or the" " process is defunct (pid %d). \n"; const char *e4 = "%s: Cannot create pid file: "; again: if ((fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0444)) == -1) { if (errno == EEXIST) { FILE *fp; pid_t pid; if ((fp = fopen(pidpath, "r")) == NULL) { (void) fprintf(stderr, e1, me); perror(NULL); return (-1); } /* Read the pid */ pid = (pid_t)-1; (void) fscanf(fp, "%ld", &pid); (void) fclose(fp); if (pid == -1) { if (unlink(pidpath) == -1) { (void) fprintf(stderr, e2, me); perror(NULL); return (-1); } else /* try without corrupted file */ goto again; } /* Is pid for a running process */ if (kill(pid, 0) == -1) { if (errno == ESRCH) { if (unlink(pidpath) == -1) { (void) fprintf(stderr, e2, me); perror(NULL); return (-1); } else /* try without obsolete file */ goto again; } } else { /* powerd deamon still running or defunct */ (void) fprintf(stderr, e3, me, pid); return (-1); } } else { /* create failure not due to existing file */ (void) fprintf(stderr, e4, me); perror(NULL); return (-1); } } (void) fchown(fd, (uid_t)-1, (gid_t)0); return (fd); } /* * Write a pid to the pid file. Report errors to syslog. * */ static int write_pidfile(int fd, pid_t pid) { int len; int rc = 0; /* assume success */ len = sprintf(scratch, "%ld\n", pid); if (write(fd, scratch, len) != len) { logerror("Cannot write pid file: %s", strerror(errno)); rc = -1; } return (rc); } /* * 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 * 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 (c) 1994 - 2001 by Sun Microsystems, Inc. */ /* All rights reserved. */ #ifndef _POWERD_H #define _POWERD_H #ifdef __cplusplus extern "C" { #endif /* * path shared by powerd and pmconfig */ #define PIDPATH "/var/run/powerd.pid" /* * AutoShutdown configuration data */ struct pwr_info { int pd_flags; /* Status of powerd */ int pd_idle_time; /* in minutes */ int pd_start_time; /* in minutes since midnight */ int pd_finish_time; /* in minutes since midnight */ int pd_autoresume; /* Autoresume active */ int pd_autoshutdown; /* Autoshutdown active */ int pd_ttychars_idle; /* Tty chars idle time */ int pd_ttychars_sum; /* Sum of tty chars */ int pd_loadaverage_idle; /* Load average idle time */ float pd_loadaverage; /* Load average */ int pd_diskreads_idle; /* Disk reads idle time */ int pd_diskreads_sum; /* Sum of disk reads */ int pd_nfsreqs_idle; /* NFS requests idle time */ int pd_nfsreqs_sum; /* Sum of NFS requests */ }; typedef struct pwr_info pwr_info_t; /* Various status flags for pd_flags */ #define PD_AC 0x02 #define PD_AUTORESUME 0x04 #define PD_RESUME_ON 0x08 #ifdef __cplusplus } #endif #endif /* _POWERD_H */ #!/sbin/sh # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # # If the /etc/power.conf file does not have a "statefile" entry # to specify the pathname of the cpr statefile, build one and # add the line. We choose the largest of the standard Sun partitions. init_statefile_entry() { [ ! -f /etc/power.conf -o ! -w /etc/power.conf ] && return # Whitespace regular expression below is [] pattern="^[ ]*statefile[ ][ ]*/" [ `/bin/grep -c "$pattern" /etc/power.conf` -ge 1 ] && return avail=0 # Free blocks in current filesystem max_avail=0 # Most available free blocks encountered so far statefile=.CPR # Default cpr statefile name # Remove old statefile (if any) from root [ -f /$statefile ] && /bin/rm -f /$statefile /usr/bin/df -k -F ufs | ( read line # Skip past the header line of the df output while read device kbytes used avail capacity filesys; do case $filesys in /|/usr|/var|/export/home) if [ $avail -gt $max_avail ]; then max_avail=$avail winner=$filesys fi ;; esac done if [ $max_avail -gt 0 ]; then echo "statefile ${winner}/${statefile}" \ >>/etc/power.conf fi return ) if [ $max_avail -eq 0 ]; then if [ X`df -n / | awk '{print $3}'` != "Xzfs" ] ; then return fi rootpool=`zfs mount | grep ' \/$' | awk '{print $1 }' |\ sed 's/\/.*$//'` if [ X$rootpool = "X" ] || \ [ ! -L /dev/zvol/dsk/$rootpool/dump ]; then return fi echo "statefile /dev/zvol/dsk/$rootpool/dump" \ >> /etc/power.conf fi } case "$1" in 'start') /sbin/uadmin 3 2 2>/dev/null if [ $? -eq 0 ]; then init_statefile_entry fi if [ -x /usr/sbin/pmconfig -a -r /etc/power.conf ]; then /usr/sbin/pmconfig >/dev/console 2>&1 fi ;; 'stop') if [ -x /usr/sbin/pmconfig ]; then /usr/sbin/pmconfig -r >/dev/null 2>/dev/null fi ;; *) echo "Usage: $0 { start | stop }" exit 1 ;; esac exit 0 /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2012 Milan Jurik. All rights reserved. * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ /* * This code has a lot in common with the original sys-suspend * code. Windowing facilities have been removed, and it has been * updated to use more recent API's. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For audit */ #include #include #include #include #include #include #include /* STATICUSED */ struct utmpx utmp; #define NMAX (sizeof (utmp.ut_name)) /* * Authorizations used by Power Management */ #define AUTHNAME_SHUTDOWN "solaris.system.shutdown" #define AUTHNAME_SUSPEND_RAM "solaris.system.power.suspend.ram" #define AUTHNAME_SUSPEND_DISK "solaris.system.power.suspend.disk" /* Platform specific definitions */ #ifdef i386 #define AD_CHECK_SUSPEND AD_CHECK_SUSPEND_TO_RAM #define AD_SUSPEND AD_SUSPEND_TO_RAM #define ADT_FCN ADT_UADMIN_FCN_AD_SUSPEND_TO_RAM #define AUTHNAME_SUSPEND AUTHNAME_SUSPEND_RAM #else #define AD_CHECK_SUSPEND AD_CHECK_SUSPEND_TO_DISK #define AD_SUSPEND AD_SUSPEND_TO_DISK #define ADT_FCN ADT_UADMIN_FCN_AD_SUSPEND_TO_DISK #define AUTHNAME_SUSPEND AUTHNAME_SUSPEND_DISK #endif static int flags = 0; static int no_tty = 0; /* * Flag definitions - could go in a header file, but there are just a few */ #define FORCE 0x001 #define NO_WARN 0x002 #define NO_XLOCK 0x004 #define SHUTDOWN 0x008 #define LOWPOWER 0x010 #define TEST 0x800 static sigjmp_buf jmp_stack; static char user[NMAX + 1]; static char **argvl; /* * Forward Declarations. */ static void pm_poweroff(void); static int bringto_lowpower(void); static int is_mou3(void); static void suspend_error(int); static int pm_check_suspend(void); static void pm_suspend(void); static void pm_do_auth(adt_session_data_t *); /* * External Declarations. */ extern int pam_tty_conv(int, const struct pam_message **, struct pam_response **, void *); extern char *optarg; /* * Audit related code. I would also think that some of this could be * in external code, as they could be useful of other apps. */ /* * Write audit event. Could be useful in the PM library, so it is * included here. For the most part it is only used by the PAM code. */ static void pm_audit_event(adt_session_data_t *ah, au_event_t event_id, int status) { adt_event_data_t *event; if ((event = adt_alloc_event(ah, event_id)) == NULL) { return; } (void) adt_put_event(event, status == PAM_SUCCESS ? ADT_SUCCESS : ADT_FAILURE, status == PAM_SUCCESS ? ADT_SUCCESS : ADT_FAIL_PAM + status); adt_free_event(event); } #define RETRY_COUNT 15 static int change_audit_file(void) { pid_t pid; if (!adt_audit_state(AUC_AUDITING)) { /* auditd not running, just return */ return (0); } if ((pid = fork()) == 0) { (void) execl("/usr/sbin/audit", "audit", "-n", NULL); (void) fprintf(stderr, gettext("error changing audit files: " "%s\n"), strerror(errno)); _exit(-1); } else if (pid == -1) { (void) fprintf(stderr, gettext("error changing audit files: " "%s\n"), strerror(errno)); return (-1); } else { pid_t rc; int retries = RETRY_COUNT; /* * Wait for audit(8) -n process to complete * */ do { if ((rc = waitpid(pid, NULL, WNOHANG)) == pid) { return (0); } else if (rc == -1) { return (-1); } else { (void) sleep(1); retries--; } } while (retries != 0); } return (-1); } static void wait_for_auqueue() { au_stat_t au_stat; int retries = 10; while (retries-- && auditon(A_GETSTAT, (caddr_t)&au_stat, 0) == 0) { if (au_stat.as_enqueue == au_stat.as_written) { break; } (void) sleep(1); } } /* End of Audit-related code */ /* ARGSUSED0 */ static void alarm_handler(int sig) { siglongjmp(jmp_stack, 1); } /* * These are functions that would be candidates for moving to a library. */ /* * pm_poweroff - similar to poweroff(8) * This should do the same auditing as poweroff(8) would do when it * becomes a libpower function. Till then we use poweroff(8). */ static void pm_poweroff(void) { if (chkauthattr(AUTHNAME_SHUTDOWN, user) != 1) { (void) printf(gettext("User %s does not have correct " "authorizations to shutdown this machine.\n"), user); exit(1); } openlog("suspend", 0, LOG_DAEMON); syslog(LOG_NOTICE, "System is being shut down."); closelog(); /* * Call poweroff(8) to shut down the system. */ (void) execl("/sbin/poweroff", "poweroff", NULL); } /* * pm_check_suspend() - Check to see if suspend is supported/enabled * on this machine. * Ultimately, we would prefer to get the "default" suspend type from * a PM property or some other API, but for now, we know that STR is * only available on x86 and STD is only available on Sparc. It does * make this function quite easy, though. */ static int pm_check_suspend(void) { /* * Use the uadmin(2) "CHECK" command to see if suspend is supported */ return (uadmin(A_FREEZE, AD_CHECK_SUSPEND, 0)); } /* * This entry point _should_ be the common entry to suspend. It is in * it's entirety here, but would be best moved to libpower when that * is available. */ static void pm_suspend(void) { int cprarg = AD_SUSPEND; enum adt_uadmin_fcn fcn_id = ADT_FCN; au_event_t event_id = ADT_uadmin_freeze; adt_event_data_t *event = NULL; /* event to be generated */ adt_session_data_t *ah = NULL; /* audit session handle */ /* * Does the user have permission to use this command? */ if (chkauthattr(AUTHNAME_SUSPEND, user) != 1) { (void) printf(gettext("User %s does not have correct " "authorizations to suspend this machine.\n"), user); exit(1); } if (flags & LOWPOWER) { if (bringto_lowpower() == -1) { (void) printf(gettext("LowPower Failed\n")); exit(1); } } else if (flags & TEST) { /* * Test mode, do checks as if a real suspend, but * don't actually do the suspend. */ /* Check if suspend is supported */ if (pm_check_suspend() == -1) { suspend_error(errno); } (void) printf(gettext("TEST: Suspend would have been" " performed\n")); } else { /* Check if suspend is supported */ if (pm_check_suspend() == -1) { suspend_error(errno); } /* * We are about to suspend this machine, try and * lock the screen. We don't really care if this * succeeds or not, but that we actually tried. We * also know that we have sufficient privileges to * be here, so we lock the screen now, even if * suspend actually fails. * Note that garbage is sometimes displayed, and * we don't really care about it, so we toss all * text response. * it would also be good if there were another option * instead of launcing a file, as the disk might be * spun down if we are suspending due to idle. */ if (!(flags & NO_XLOCK)) { (void) system("/usr/bin/xdg-screensaver lock " " >/dev/null 2>&1"); } /* Time to do the actual deed! */ /* * Before we actually suspend, we need to audit and * "suspend" the audit files. */ /* set up audit session and event */ if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) == 0) { if ((event = adt_alloc_event(ah, event_id)) != NULL) { event->adt_uadmin_freeze.fcn = fcn_id; event->adt_uadmin_freeze.mdep = NULL; if (adt_put_event(event, ADT_SUCCESS, 0) != 0) { (void) fprintf(stderr, gettext( "%s: can't put audit event\n"), argvl[0]); } else { wait_for_auqueue(); } } (void) change_audit_file(); } else { (void) fprintf(stderr, gettext( "%s: can't start audit session\n"), argvl[0]); } if (uadmin(A_FREEZE, cprarg, 0) != 0) { (void) printf(gettext("Suspend Failed\n")); if (flags & FORCE) { /* * Note, that if we actually poweroff, * that the poweroff function will handle * that audit trail, and the resume * trail is effectively done. */ pm_poweroff(); } else { /* suspend_error() will exit. */ suspend_error(errno); /* * Audit the suspend failure and * reuse the event, but don't create one * if we don't already have one. */ if (event != NULL) { (void) adt_put_event(event, ADT_FAILURE, 0); } } } /* * Write the thaw event. */ if (ah != NULL) { if ((event == NULL) && ((event = adt_alloc_event(ah, ADT_uadmin_thaw)) == NULL)) { (void) fprintf(stderr, gettext( "%s: can't allocate thaw audit event\n"), argvl[0]); } else { event->adt_uadmin_thaw.fcn = fcn_id; if (adt_put_event(event, ADT_SUCCESS, 0) != 0) { (void) fprintf(stderr, gettext( "%s: can't put thaw audit event\n"), argvl[0]); } (void) adt_free_event(event); } } } if ((no_tty ? 0 : 1) && !(flags & NO_XLOCK)) { pm_do_auth(ah); } (void) adt_end_session(ah); } /* End of "library" functions */ /* * Print an appropriate error message and exit. */ static void suspend_error(int error) { switch (error) { case EBUSY: (void) printf(gettext("suspend: " "Suspend already in progress.\n\n")); exit(1); /*NOTREACHED*/ case ENOMEM: /*FALLTHROUGH*/ case ENOSPC: (void) printf(gettext("suspend: " "Not enough resources to suspend.\n\n")); exit(1); /*NOTREACHED*/ case ENOTSUP: (void) printf(gettext("suspend: " "Suspend is not supported.\n\n")); exit(1); /*NOTREACHED*/ case EPERM: (void) printf(gettext("suspend: " "Not sufficient privileges.\n\n")); exit(1); /*NOTREACHED*/ default: (void) printf(gettext("suspend: " "unknown error.\n\n")); exit(1); } } /* * refresh_dt() - Refresh screen when 'dtgreet' is running. * This is here for compatibility reasons, and could be removed once * dtgreet is no longer part of the system. */ static int refresh_dt() { int status; struct stat stat_buf; /* * If dtgreet exists, HUP it, otherwise just let screenlock * do it's thing. */ if ((stat("/usr/dt/bin/dtgreet", &stat_buf) == 0) && (stat_buf.st_mode & S_IXUSR)) { switch (fork()) { case -1: break; case 0: (void) close(1); (void) execl("/usr/bin/pkill", "pkill", "-HUP", "-u", "0", "-x", "dtgreet", NULL); break; default: (void) wait(&status); } } return (0); } #define DT_TMP "/var/dt/tmp" /* * On enter, the "xauthority" string has the value "XAUTHORITY=". On * return, if a Xauthority file is found, concatenate it to this string, * otherwise, return "xauthority" as it is. */ static char * get_xauthority(char *xauthority, size_t buflen) { pid_t uid; char *home_dir; struct passwd *pwd; char filepath[MAXPATHLEN]; struct stat stat_buf; DIR *dirp; struct dirent *dp; char xauth[MAXPATHLEN] = ""; time_t latest = 0; uid = getuid(); /* * Determine home directory of the user. */ if ((home_dir = getenv("HOME")) == NULL) { if ((pwd = getpwuid(uid)) == NULL) { (void) printf(gettext("Error: unable to get passwd " "entry for user.\n")); exit(1); } home_dir = pwd->pw_dir; } if ((strlen(home_dir) + sizeof ("/.Xauthority")) >= MAXPATHLEN) { (void) printf(gettext("Error: path to home directory is too " "long.\n")); exit(1); } /* * If there is a .Xauthority file in home directory, reference it. */ (void) snprintf(filepath, sizeof (filepath), "%s/.Xauthority", home_dir); if (stat(filepath, &stat_buf) == 0) { (void) strlcpy(xauthority, filepath, buflen); return (xauthority); } /* * If Xsession can not access user's home directory, it creates the * Xauthority file in "/var/dt/tmp" directory. Since the exact * name of the Xauthority is not known, search the directory and * find the last changed file that starts with ".Xauth" and owned * by the user. Hopefully, that is the valid Xauthority file for * the current X session. */ if ((dirp = opendir(DT_TMP)) == NULL) return (xauthority); while ((dp = readdir(dirp)) != NULL) { if (strstr(dp->d_name, ".Xauth") != NULL) { (void) snprintf(filepath, sizeof (filepath), "%s/%s", DT_TMP, dp->d_name); if (stat(filepath, &stat_buf) == -1) continue; if (stat_buf.st_uid != uid) continue; if (stat_buf.st_ctime > latest) { (void) strlcpy(xauth, filepath, sizeof (xauth)); latest = stat_buf.st_ctime; } } } (void) closedir(dirp); (void) strlcpy(xauthority, xauth, buflen); return (xauthority); } /* * suspend can be called in following ways: * 1. from daemon (powerd) for auto-shutdown. * a. there might be a OW/CDE environment * b. there might not be any windowing environment * 2. by a user entered command. * a. the command can be entered from a cmdtool type OW/CDE tool * b. the command can be entered by a user logged in on a dumb * terminal. * i) there might be a OW/CDE running on console * and we have permission to talk to it. * ii) there is no OW/CDE running on console or we * don't have permission to talk to it or console * itself is the dumb terminal we have logged into. * * In main(), we decide on the correct case and call appropriate functions. */ int main(int argc, char **argv) { int c; char xauthority[MAXPATHLEN]; struct passwd *pw; (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGINT, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); (void) signal(SIGTSTP, SIG_IGN); (void) signal(SIGTTIN, SIG_IGN); (void) signal(SIGTTOU, SIG_IGN); /* * If suspend is invoked from a daemon (case 1 above), it * will not have a working stdin, stdout and stderr. We need * these to print proper error messages and possibly get user * input. We attach them to console and hope that attachment * works. */ if (ttyname(0) == NULL) { no_tty = 1; (void) dup2(open("/dev/console", O_RDONLY), 0); (void) dup2(open("/dev/console", O_WRONLY), 1); (void) dup2(open("/dev/console", O_WRONLY), 2); } while ((c = getopt(argc, argv, "fnxhtd:")) != EOF) { switch (c) { case 'f': /* * Force machine to poweroff if * suspend fails */ flags |= FORCE; break; case 'n': /* No warning popups - Obsolete */ flags |= NO_WARN; break; case 'x': /* Don't try to screenlock */ flags |= NO_XLOCK; break; case 'h': /* Do a shutdown instead of suspend */ flags |= SHUTDOWN; break; case 'd': /* Set the DISPLAY value in the environment */ if (setenv("DISPLAY", optarg, 1) != 0) { (void) printf(gettext("Error: " "unable to set DISPLAY " "environment variable.\n")); return (1); } break; case 't': /* Test, don't actually do any operation */ flags |= TEST; break; default: (void) printf(gettext("USAGE: suspend " "[-fnxh] [-d ]\n")); return (1); } } /* * The action of pressing power key and power button on a MOU-3 machine * causes suspend being invoked with SYSSUSPENDDODEFAULT * enviromental variable set - indicating the default action is machine * dependent: for MOU-3 type machine, "LowPower" mode is the default, * for all the rest, "Suspend" is the default. Existing suspend * flags works the same. */ if (getenv("SYSSUSPENDDODEFAULT")) if (is_mou3()) flags |= LOWPOWER; if ((flags & FORCE) && (flags & LOWPOWER)) flags &= ~LOWPOWER; /* * Flag "-h" overrides flag "-f". */ if ((flags & SHUTDOWN) && (flags & FORCE)) flags &= ~(FORCE | LOWPOWER); if (flags & FORCE) flags |= NO_WARN; /* * Check initally if the user has the authorizations to * do either a suspend or shutdown. pm_suspend() will also * make this test, so we could defer till then, but if we * do it now, we at least prevent a lot of unneeded setup. */ pw = getpwuid(getuid()); (void) strncpy(user, pw->pw_name, NMAX); if ((flags & (FORCE|SHUTDOWN)) && (chkauthattr(AUTHNAME_SHUTDOWN, pw->pw_name) != 1)) { (void) printf(gettext("User does not have correct " "authorizations to shutdown the machine.\n")); exit(1); } if (!(flags & SHUTDOWN) && (chkauthattr(AUTHNAME_SUSPEND, pw->pw_name) != 1)) { (void) printf(gettext("User does not have correct " "authorizations to suspend.\n")); exit(1); } /* * If we are only shutting down, there isn't much to do, just * call pm_poweroff(), and let it do all the work. */ if (flags & SHUTDOWN) { /* * pm_poweroff either powers off or exits, * so there is no return. */ if (flags & TEST) { (void) printf("TEST: This machine would have " "powered off\n"); exit(1); } else { pm_poweroff(); } /* NOTREACHED */ } /* * If XAUTHORITY environment variable is not set, try to set * one up. */ if (getenv("XAUTHORITY") == NULL) (void) setenv("XAUTHORITY", get_xauthority(xauthority, MAXPATHLEN), 1); /* * In case of "suspend" being called from daemon "powerd", * signal SIGALRM is blocked so use "sigset()" instead of "signal()". */ (void) sigset(SIGALRM, alarm_handler); /* Call the "suspend" function to do the last of the work */ pm_suspend(); if (refresh_dt() == -1) { (void) printf("%s: Failed to refresh screen.\n", argv[0]); return (1); } return (0); } #include /* * Note that some of these functions are more relevant to Sparc platforms, * but they do function properly on other platforms, they just don't do * as much. */ /* * bringto_lowpower() * This tells the PM framework to put the devices it controls in an idle * state. The framework only complains if a device that *must* be idle * doesn't succeed in getting there. */ static int bringto_lowpower() { int fd; if ((fd = open("/dev/pm", O_RDWR)) < 0) { (void) printf(gettext("Can't open /dev/pm\n")); return (-1); } if (ioctl(fd, PM_IDLE_DOWN, NULL) < 0) { (void) printf(gettext("Failed to bring system " "to low power mode.\n")); (void) close(fd); return (-1); } (void) close(fd); return (0); } #include /* * Though this test is predominantly used on Sparc, it will run on other * platforms, and might be usefull one day on those. */ static int is_mou3() { struct cprconfig cf; int fd; int found = 0; if ((fd = open(CPR_CONFIG, O_RDONLY)) < 0) { (void) printf(gettext("Can't open /etc/.cpr_config file.")); return (found); } if (read(fd, (void *) &cf, sizeof (cf)) != sizeof (cf)) { (void) printf(gettext("Can't read /etc/.cpr_config file.")); } else { found = cf.is_autopm_default; } (void) close(fd); return (found); } /* * Reauthenticate the user on return from suspend. * This is here and not in the PAM-specific file, as there are * items specific to sys-suspend, and not generic to PAM. This may * become part of a future PM library. The audit handle is passed, * as the pm_suspend code actually starts an audit session, so it * makes sense to just continue to use it. If it were separated * from the pm_suspend code, it will need to open a new session. */ #define DEF_ATTEMPTS 3 static void pm_do_auth(adt_session_data_t *ah) { pam_handle_t *pm_pamh; int err; int pam_flag = 0; int chpasswd_tries; struct pam_conv pam_conv = {pam_tty_conv, NULL}; if (user[0] == '\0') return; if ((err = pam_start("sys-suspend", user, &pam_conv, &pm_pamh)) != PAM_SUCCESS) return; pam_flag = PAM_DISALLOW_NULL_AUTHTOK; do { err = pam_authenticate(pm_pamh, pam_flag); if (err == PAM_SUCCESS) { err = pam_acct_mgmt(pm_pamh, pam_flag); if (err == PAM_NEW_AUTHTOK_REQD) { chpasswd_tries = 0; do { err = pam_chauthtok(pm_pamh, PAM_CHANGE_EXPIRED_AUTHTOK); chpasswd_tries++; } while ((err == PAM_AUTHTOK_ERR || err == PAM_TRY_AGAIN) && chpasswd_tries < DEF_ATTEMPTS); pm_audit_event(ah, ADT_passwd, err); } err = pam_setcred(pm_pamh, PAM_REFRESH_CRED); } if (err != PAM_SUCCESS) { (void) fprintf(stdout, "%s\n", pam_strerror(pm_pamh, err)); pm_audit_event(ah, ADT_screenunlock, err); } } while (err != PAM_SUCCESS); pm_audit_event(ah, ADT_passwd, 0); (void) pam_end(pm_pamh, err); } /* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include /* sleep() */ #include #include #include #include #include #include #include #include #include "powerd.h" /* * External Variables */ extern pwr_info_t *info; /* * State Variables */ static kstat_ctl_t *kc; /* libkstat cookie */ static int ncpus; static kstat_t **cpu_stats_list = NULL; static kstat_t old_cpu_stats, new_cpu_stats; static hrtime_t tty_snaptime; static kstat_t *load_ave_ksp; static ulong_t load_ave; static hrtime_t last_load_ave_change; static kstat_t *conskbd_ksp, *consms_ksp; static kstat_t *nfs_client2_kstat, *nfs_client3_kstat; static kstat_t *nfs_server2_kstat, *nfs_server3_kstat; static uint64_t old_nfs_calls, new_nfs_calls; typedef struct activity_data { struct activity_data *next; struct activity_data *prev; int activity_delta; hrtime_t snaptime; } activity_data_t; #define NULLACTIVITY (activity_data_t *)0 static activity_data_t *disk_act_start = NULLACTIVITY; static activity_data_t *disk_act_end = NULLACTIVITY; static activity_data_t *tty_act_start = NULLACTIVITY; static activity_data_t *tty_act_end = NULLACTIVITY; static activity_data_t *nfs_act_start = NULLACTIVITY; static activity_data_t *nfs_act_end = NULLACTIVITY; struct diskinfo { struct diskinfo *next; kstat_t *ks; kstat_io_t new_kios, old_kios; }; #define NULLDISK (struct diskinfo *)0 static struct diskinfo zerodisk = { NULL, NULL }; static struct diskinfo *firstdisk = NULLDISK; static struct diskinfo *lastdisk = NULLDISK; static struct diskinfo *snip = NULLDISK; #define CPU_STAT(ksp, name) (((kstat_named_t *)safe_kstat_data_lookup( \ (ksp), (name)))->value.ui64) #define DISK_DELTA(x) (disk->new_kios.x - disk->old_kios.x) #define CPU_DELTA(x) (CPU_STAT(&new_cpu_stats, (x)) - \ CPU_STAT(&old_cpu_stats, (x))) #define FSHIFT 8 #define FSCALE (1<pd_ttychars_sum = 0; info->pd_loadaverage = 0; info->pd_diskreads_sum = 0; info->pd_nfsreqs_sum = 0; if ((kc = kstat_open()) == NULL) { fail("kstat_open(): can't open /dev/kstat"); } init_all(); } static void init_all(void) { char *msg = "kstat_read(): can't read kstat"; init_disks(); if (diskinfo_load() != 0) { fail(msg); } cpu_stats_init(); if (cpu_stats_load() != 0) { fail(msg); } load_ave_init(); last_load_ave_change = gethrtime(); if (load_ave_load() != 0) { fail(msg); } nfs_init(); if (nfs_load() != 0) { fail(msg); } conskbd_init(); consms_init(); } int last_disk_activity(hrtime_t *hr_now, int threshold) { return (check_activity(disk_act_start, info->pd_diskreads_sum, hr_now, threshold)); } int last_tty_activity(hrtime_t *hr_now, int threshold) { return (check_activity(tty_act_start, info->pd_ttychars_sum, hr_now, threshold)); } int last_load_ave_activity(hrtime_t *hr_now) { return ((*hr_now - last_load_ave_change) / NANOSEC); } int last_nfs_activity(hrtime_t *hr_now, int threshold) { return (check_activity(nfs_act_start, info->pd_nfsreqs_sum, hr_now, threshold)); } static void init_disks(void) { struct diskinfo *disk, *prevdisk, *comp; kstat_t *ksp; disk = &zerodisk; /* * Patch the snip in the diskinfo list (see below) */ if (snip) { lastdisk->next = snip; } for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (ksp->ks_type != KSTAT_TYPE_IO || strcmp(ksp->ks_class, "disk") != 0) { continue; } prevdisk = disk; if (disk->next) { disk = disk->next; } else { safe_zalloc((void **)&disk->next, sizeof (struct diskinfo), 0); disk = disk->next; disk->next = NULLDISK; } disk->ks = ksp; (void) memset((void *)&disk->new_kios, 0, sizeof (kstat_io_t)); disk->new_kios.wlastupdate = disk->ks->ks_crtime; disk->new_kios.rlastupdate = disk->ks->ks_crtime; /* * Insertion sort on (ks_module, ks_instance, ks_name) */ comp = &zerodisk; while (kscmp(disk->ks, comp->next->ks) > 0) { comp = comp->next; } if (prevdisk != comp) { prevdisk->next = disk->next; disk->next = comp->next; comp->next = disk; disk = prevdisk; } } /* * Put a snip in the linked list of diskinfos. The idea: * If there was a state change such that now there are fewer * disks, we snip the list and retain the tail, rather than * freeing it. At the next state change, we clip the tail back on. * This prevents a lot of malloc/free activity, and it's simpler. */ lastdisk = disk; snip = disk->next; disk->next = NULLDISK; firstdisk = zerodisk.next; } static int diskinfo_load(void) { struct diskinfo *disk; for (disk = firstdisk; disk; disk = disk->next) { disk->old_kios = disk->new_kios; if (kstat_read(kc, disk->ks, (void *)&disk->new_kios) == -1) { return (1); } } return (0); } int check_disks(hrtime_t *hr_now, int threshold) { struct diskinfo *disk; int delta = 0; hrtime_t time = 0; while (kstat_chain_update(kc) || diskinfo_load()) { init_all(); } for (disk = firstdisk; disk; disk = disk->next) { if (time == 0) { time = disk->new_kios.wlastupdate; } delta += DISK_DELTA(reads); if (DISK_DELTA(reads) > 0) { time = MAX(time, disk->new_kios.wlastupdate); } } keep_activity_data(&disk_act_start, &disk_act_end, &info->pd_diskreads_sum, delta, time); #ifdef DEBUG (void) printf(" Disk reads = %d\n", delta); #endif return (check_activity(disk_act_start, info->pd_diskreads_sum, hr_now, threshold)); } static void cpu_stats_init(void) { kstat_t *ksp; ncpus = 0; for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (strcmp(ksp->ks_module, "cpu") == 0 && strcmp(ksp->ks_name, "sys") == 0) ncpus++; } safe_zalloc((void **)&cpu_stats_list, ncpus * sizeof (*cpu_stats_list), 1); ncpus = 0; for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (strcmp(ksp->ks_module, "cpu") == 0 && strcmp(ksp->ks_name, "sys") == 0 && kstat_read(kc, ksp, NULL) != -1) cpu_stats_list[ncpus++] = ksp; } if (ncpus == 0) fail("can't find any cpu statistics"); } static int cpu_stats_load(void) { int i, j; kstat_named_t *nkp, *tkp; tty_snaptime = 0; kstat_copy(&new_cpu_stats, &old_cpu_stats, 1); /* * Sum across all cpus */ for (i = 0; i < ncpus; i++) { if (kstat_read(kc, cpu_stats_list[i], NULL) == -1) return (1); if (i == 0) { kstat_copy(cpu_stats_list[i], &new_cpu_stats, 1); continue; } else { /* * Other CPUs' statistics are accumulated in * new_cpu_stats, initialized at the first iteration of * the loop. */ nkp = (kstat_named_t *)new_cpu_stats.ks_data; tkp = (kstat_named_t *)cpu_stats_list[i]->ks_data; for (j = 0; j < cpu_stats_list[i]->ks_ndata; j++) (nkp++)->value.ui64 += (tkp++)->value.ui64; tty_snaptime = MAX(tty_snaptime, cpu_stats_list[i]->ks_snaptime); } } return (0); } int check_tty(hrtime_t *hr_now, int threshold) { int delta; while (kstat_chain_update(kc) || cpu_stats_load()) { init_all(); } delta = CPU_DELTA("rawch") + CPU_DELTA("outch"); keep_activity_data(&tty_act_start, &tty_act_end, &info->pd_ttychars_sum, delta, tty_snaptime); #ifdef DEBUG (void) printf(" Tty chars = %d\n", delta); #endif return (check_activity(tty_act_start, info->pd_ttychars_sum, hr_now, threshold)); } static void load_ave_init(void) { if ((load_ave_ksp = kstat_lookup(kc, "unix", 0, "system_misc")) == NULL) { fail("kstat_lookup('unix', 0, 'system_misc') failed"); } } static int load_ave_load(void) { if (kstat_read(kc, load_ave_ksp, NULL) == -1) { return (1); } load_ave = ((kstat_named_t *)safe_kstat_data_lookup( load_ave_ksp, "avenrun_1min"))->value.l; return (0); } int check_load_ave(hrtime_t *hr_now, float threshold) { while (kstat_chain_update(kc) || load_ave_load()) { init_all(); } info->pd_loadaverage = (double)load_ave / FSCALE; if (info->pd_loadaverage > threshold) { last_load_ave_change = load_ave_ksp->ks_snaptime; } #ifdef DEBUG (void) printf(" Load average = %f\n", ((double)load_ave / FSCALE)); #endif return ((*hr_now - last_load_ave_change) / NANOSEC); } static void nfs_init(void) { nfs_client2_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v2"); nfs_client3_kstat = kstat_lookup(kc, "nfs", 0, "rfsreqcnt_v3"); nfs_server2_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v2"); nfs_server3_kstat = kstat_lookup(kc, "nfs", 0, "rfsproccnt_v3"); } static int nfs_load(void) { kstat_named_t *kstat_ptr; int index; uint64_t total_calls = 0; uint64_t getattr_calls = 0; uint64_t null_calls = 0; uint64_t access_calls = 0; if (!nfs_client2_kstat && !nfs_client3_kstat && !nfs_server2_kstat && !nfs_server3_kstat) { return (0); } /* * NFS client "getattr", NFS3 client "access", and NFS server "null" * requests are excluded from consideration. */ if (nfs_client2_kstat) { if (kstat_read(kc, nfs_client2_kstat, NULL) == -1) { return (1); } kstat_ptr = KSTAT_NAMED_PTR(nfs_client2_kstat); for (index = 0; index < nfs_client2_kstat->ks_ndata; index++) { total_calls += kstat_ptr[index].value.ui64; } getattr_calls = ((kstat_named_t *)safe_kstat_data_lookup( nfs_client2_kstat, "getattr"))->value.ui64; } if (nfs_client3_kstat) { if (kstat_read(kc, nfs_client3_kstat, NULL) == -1) { return (1); } kstat_ptr = KSTAT_NAMED_PTR(nfs_client3_kstat); for (index = 0; index < nfs_client3_kstat->ks_ndata; index++) { total_calls += kstat_ptr[index].value.ui64; } getattr_calls += ((kstat_named_t *)safe_kstat_data_lookup( nfs_client3_kstat, "getattr"))->value.ui64; access_calls = ((kstat_named_t *)safe_kstat_data_lookup( nfs_client3_kstat, "access"))->value.ui64; } if (nfs_server2_kstat) { if (kstat_read(kc, nfs_server2_kstat, NULL) == -1) { return (1); } kstat_ptr = KSTAT_NAMED_PTR(nfs_server2_kstat); for (index = 0; index < nfs_server2_kstat->ks_ndata; index++) { total_calls += kstat_ptr[index].value.ui64; } null_calls = ((kstat_named_t *)safe_kstat_data_lookup( nfs_server2_kstat, "null"))->value.ui64; } if (nfs_server3_kstat) { if (kstat_read(kc, nfs_server3_kstat, NULL) == -1) { return (1); } kstat_ptr = KSTAT_NAMED_PTR(nfs_server3_kstat); for (index = 0; index < nfs_server3_kstat->ks_ndata; index++) { total_calls += kstat_ptr[index].value.ui64; } null_calls += ((kstat_named_t *)safe_kstat_data_lookup( nfs_server3_kstat, "null"))->value.ui64; } old_nfs_calls = new_nfs_calls; new_nfs_calls = total_calls - (getattr_calls + access_calls + null_calls); return (0); } int check_nfs(hrtime_t *hr_now, int threshold) { int delta; hrtime_t time = 0; while (kstat_chain_update(kc) || nfs_load()) { init_all(); } if (!nfs_client2_kstat && !nfs_client3_kstat && !nfs_server2_kstat && !nfs_server3_kstat) { return (0); } if (nfs_client2_kstat) { time = MAX(time, nfs_client2_kstat->ks_snaptime); } if (nfs_client3_kstat) { time = MAX(time, nfs_client3_kstat->ks_snaptime); } if (nfs_server2_kstat) { time = MAX(time, nfs_server2_kstat->ks_snaptime); } if (nfs_server3_kstat) { time = MAX(time, nfs_server3_kstat->ks_snaptime); } delta = (int)(new_nfs_calls - old_nfs_calls); keep_activity_data(&nfs_act_start, &nfs_act_end, &info->pd_nfsreqs_sum, delta, time); #ifdef DEBUG (void) printf(" NFS requests = %d\n", delta); #endif return (check_activity(nfs_act_start, info->pd_nfsreqs_sum, hr_now, threshold)); } static void conskbd_init(void) { conskbd_ksp = kstat_lookup(kc, "conskbd", 0, "activity"); } /* * Return the number of seconds since the last keystroke on console keyboard. * Caller responsible for error reporting. */ long conskbd_idle_time(void) { void *p; if (conskbd_ksp == NULL || kstat_read(kc, conskbd_ksp, NULL) == -1 || (p = kstat_data_lookup(conskbd_ksp, "idle_sec")) == NULL) return ((time_t)-1); return (((kstat_named_t *)p)->value.l); } static void consms_init(void) { consms_ksp = kstat_lookup(kc, "consms", 0, "activity"); } /* * Return the number of seconds since the most recent action (movement or * click) of the console mouse. Caller responsible for error reporting. */ long consms_idle_time(void) { void *p; if (consms_ksp == NULL || kstat_read(kc, consms_ksp, NULL) == -1 || (p = kstat_data_lookup(consms_ksp, "idle_sec")) == NULL) return ((time_t)-1); return (((kstat_named_t *)p)->value.l); } static void fail(char *fmt, ...) { char new_fmt[256]; const char *fmtptr = new_fmt; va_list args; size_t len; len = sizeof (new_fmt); va_start(args, fmt); if (snprintf(new_fmt, len, "powerd: %s", fmt) > len) syslog(LOG_ERR, "powerd: syslog message too large"); else vsyslog(LOG_ERR, fmtptr, args); va_end(args); thr_exit((void *) 0); } static void safe_zalloc(void **ptr, int size, int free_first) { if (free_first && *ptr != NULL) { free(*ptr); } if ((*ptr = (void *) malloc(size)) == NULL) { fail("malloc failed"); } (void) memset(*ptr, 0, size); } static void * safe_kstat_data_lookup(kstat_t *ksp, char *name) { void *fp = kstat_data_lookup(ksp, name); if (fp == NULL) { fail("kstat_data_lookup('%s', '%s') failed", ksp->ks_name, name); } return (fp); } static int kscmp(kstat_t *ks1, kstat_t *ks2) { int cmp; cmp = strcmp(ks1->ks_module, ks2->ks_module); if (cmp != 0) { return (cmp); } cmp = ks1->ks_instance - ks2->ks_instance; if (cmp != 0) { return (cmp); } return (strcmp(ks1->ks_name, ks2->ks_name)); } static void keep_activity_data(activity_data_t **act_start, activity_data_t **act_end, int *delta_sum, int delta, hrtime_t time) { activity_data_t *node = NULLACTIVITY; hrtime_t hr_now; int idle_time = info->pd_idle_time * 60; /* * Add new nodes to the beginning of the list. */ safe_zalloc((void **)&node, sizeof (activity_data_t), 0); node->activity_delta = delta; *delta_sum += delta; node->snaptime = time; node->next = *act_start; if (*act_start == NULLACTIVITY) { *act_end = node; } else { (*act_start)->prev = node; } *act_start = node; /* * Remove nodes that are time-stamped later than the idle time. */ hr_now = gethrtime(); node = *act_end; while ((int)((hr_now - node->snaptime) / NANOSEC) > idle_time && node->prev != NULLACTIVITY) { *delta_sum -= node->activity_delta; *act_end = node->prev; (*act_end)->next = NULLACTIVITY; free(node); node = *act_end; } } static int check_activity(activity_data_t *act_start, int delta_sum, hrtime_t *time, int thold) { activity_data_t *node; int sum = 0; int idle_time = info->pd_idle_time * 60; /* * No need to walk the list if the sum of the deltas are not greater * than the threshold value. */ if (delta_sum <= thold) { return (idle_time); } /* * Walk through the list and add up the activity deltas. When the * sum is greater than the threshold value, difference of current * time and the snaptime of that node will give us the idle time. */ node = act_start; while (node->next != NULLACTIVITY) { sum += node->activity_delta; if (sum > thold) { return ((*time - node->snaptime) / NANOSEC); } node = node->next; } sum += node->activity_delta; if (sum > thold) { return ((*time - node->snaptime) / NANOSEC); } return (idle_time); } static void kstat_copy(kstat_t *src, kstat_t *dst, int fr) { if (fr) free(dst->ks_data); *dst = *src; if (src->ks_data != NULL) { safe_zalloc(&dst->ks_data, src->ks_data_size, 0); (void) memcpy(dst->ks_data, src->ks_data, src->ks_data_size); } else { dst->ks_data = NULL; dst->ks_data_size = 0; } }