/* * This file is part of fwm-sysdaemon * main.c * * Copyright (c) 2022, 2023 firk (firk@cantconnect.ru) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The origin of this software must not be misrepresented; you must * not claim that you wrote the original software. * 4. Altered versions in any form must be plainly marked as such, and * must not be misinterpreted as being the original software. * * This software is provided by the author and contributors `as is' * without any express or implied warranty. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "fwatch.h" #include "get_powerstat.h" #include "backlight.h" #include "config.h" static volatile int got_sigusr1 = 0; static void usr1_sig_handle(int signum) { got_sigusr1 = 1; } static volatile int do_exit = 0; static void term_sig_handle(int signum) { do_exit = 1; } #ifdef WITH_SYSLOG #define SYSLOG_PRIO(X) do { syslog_level = (X); } while(0) #else #define SYSLOG_PRIO(X) #endif static int minpct = -1; static int statusfd = -1; static char status_last[240]; static int minpctfd = -1; static int bat_low = 0; static uint delay = 0; static powerstat ps; static int blfd = -1; static int cur_bl = -1; static int saved_bl = -1; static void bat_initlog(void); static void bat_loop(void); static void bl_loop(void); static int creat_ex(char const *title, char const *fn, uint flags) { int fd; flags |= O_CREAT; while((fd = open(fn, flags, 0644))<0) if((fd=errno)!=EINTR) { printlog("open %s file %\"S error %{ERR}", title, fn, fd); return -1; } return fd; } static int pidfd = -1; static int open_pidfile(void) { int e; if(!params.pidfn) return 0; while((pidfd = open(params.pidfn, O_CREAT|O_WRONLY|O_NOCTTY|O_NOFOLLOW, 0644))<0) if((e=errno)!=EINTR) { printlog("open pidfile %\"S error %{ERR}", params.pidfn, e); return -1; } while(flock(pidfd, LOCK_EX|LOCK_NB)<0) if((e=errno)!=EINTR) { if(e==EWOULDBLOCK) printlog("pidfile %\"S already locked; it seems that the daemon already running", params.pidfn); else printlog("flock() pidfile %\"S error %{ERR}", params.pidfn, e); return -1; } while(ftruncate(pidfd, 0)<0) if((e=errno)!=EINTR) { printlog("truncate pidfile %\"S error %{ERR}", params.pidfn, e); unlink(params.pidfn); return -1; } return 0; } static int save_pidfile(void) { char tmp[50]; if(pidfd<0) return 0; snprintf(tmp, sizeof(tmp), "%lu", (ulong)getpid()); return fd_set_str(pidfd, tmp); } static int do_daemonize(void) { pid_t r, rr; struct sigaction sa; int j, status; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = &usr1_sig_handle; sigaction(SIGUSR1, &sa, NULL); if((r = fork())<0) { printlog("fork() error %{ERR}", errno); return -1; } if(!r) { if(setsid()<0) { printlog("setsid() error %{ERR}", errno); return -1; } if(save_pidfile()<0) { printlog("can't save pidfile"); return -1; } sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = &term_sig_handle; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); params.no_stderr = 1; r = getppid(); if(r>1) kill(r, SIGUSR1); return 0; } for(j=0; j<30; j++) { if(got_sigusr1) { printlog("started with pid %d", (int)r); exit(0); } if((rr = waitpid(r, &status, WNOHANG))<0 && errno!=EINTR) { printlog("waiting for child with pid %d error %{ERR}, killing it and failing", (int)r, errno); goto err; } if(rr==r) { printlog("child with pid %d unexpectedly exited, failing", (int)r); exit(-1); } if(rr) { printlog("waited for pid %d, got %d", (int)r, (int)rr); abort(); } sleep(1); } printlog("child process with pid %d timed out, killing it and failing", (int)r); err: if(kill(r, 9)<0) printlog("kill(%d,9) error %{ERR}", (int)r, errno); exit(-1); } int main(int argc, char **argv) { int j; char const *a, *b; uint u; unsigned long old_ms, now_ms; qlog_set_times(QLOG_TIMES_SEC|QLOG_CHECK_ENV); if(argc<2 || !strcmp(argv[1],"help") || !strcmp(argv[1],"--help")) { print_usage(); return 0; } if(!strcmp(argv[1],"daemon")) params.daemonize = 1; else if(!strcmp(argv[1],"foreground")) params.daemonize = 0; else { printlog("Bad first argument. Try 'fwm-sysdaemon --help' for usage."); return -1; } if(parse_cmdline_options(argc, argv)<0) { printlog("Try 'fwm-sysdaemon --help' for usage."); return -1; } if(open_pidfile()<0) return -1; if(params.pwr.enabled) { if(params.pwr.minpctfn && (minpctfd=creat_ex("minpct",params.pwr.minpctfn,O_RDWR))>=0) { if(params.mgr_group_id && (fchown(minpctfd,-1,params.mgr_group_id)<0 || fchmod(minpctfd, 0664)<0)) printlog("WARNING: can't set power control file access rights"); fwatch_add(params.pwr.minpctfn); if(params.pwr.minpct>=0) fd_set_int_U(minpctfd, params.pwr.minpct); else params.pwr.minpct = fd_get_int_U(minpctfd); } if(params.pwr.minpct==-1) params.pwr.minpct = 10; } if(params.bl.enabled) { if(params.bl.max==-1) params.bl.max = 100; if(params.bl.linear==-1) params.bl.linear = 0; if(bl_init(params.bl.blname, params.bl.raw_max, params.bl.max, params.bl.linear)<0) params.bl.enabled = 0; else { saved_bl = cur_bl = bl_get(); if(params.bl.ctlfn && (blfd=creat_ex("backlight",params.bl.ctlfn,O_RDWR))>=0) { if(params.mgr_group_id && (fchown(blfd,-1,params.mgr_group_id)<0 || fchmod(blfd, 0664)<0)) printlog("WARNING: can't set backlight control file access rights"); fd_set_int_U(blfd, cur_bl); fwatch_add(params.bl.ctlfn); } } } if(params.daemonize && do_daemonize()<0) { if(params.pidfn) unlink(params.pidfn); return -1; } if(params.pwr.enabled) bat_initlog(); old_ms = get_ms(); while(!do_exit) { if(params.pwr.enabled) bat_loop(); if(params.bl.enabled) bl_loop(); if(fwatch_wait(params.poll_interval*1000)) { if(params.pwr.enabled && minpctfd>=0) { if((j = fd_get_int_U(minpctfd))>=0 && j!=params.pwr.minpct) { params.pwr.minpct = j; SYSLOG_PRIO(LOG_INFO); printlog("minpct set to %d", params.pwr.minpct); } } if(params.bl.enabled && blfd>=0) { if((j = fd_get_int_U(blfd))>=0 && j!=cur_bl) { if(1) { SYSLOG_PRIO(LOG_INFO); printlog("backlight set to %d", j); } bl_set(j); j = bl_get(); if(j>=0) saved_bl = cur_bl = j; } } } now_ms = get_ms(); old_ms = now_ms - old_ms; if(delay>old_ms) delay -= old_ms; else delay = 0; old_ms = now_ms; } if(params.pidfn) unlink(params.pidfn); return 0; } static void bat_initlog(void) { if(get_powerstat(&ps, params.pwr.batname, params.pwr.acname)<0) { SYSLOG_PRIO(LOG_ERR); printlog("battery monitor started, can't retrieve current status"); return; } SYSLOG_PRIO(LOG_INFO); printlog("battery monitor started, current status: %llu/%llu = %d%%%s%s, minpct = %d%%", ps.charged, ps.total, ps.charge_pctt/10, ps.is_ac_powered?", AC":", no AC", ps.is_charging?", Charging":"", params.pwr.minpct); if(ps.get_ac_err) { SYSLOG_PRIO(LOG_ERR); printlog("can't retrieve AC status"); } if(ps.get_bat_err) { SYSLOG_PRIO(LOG_ERR); printlog("can't retrieve complete battery status"); } } static int beep_ex(unsigned int volume, unsigned int freq); static void beep(void) { beep_ex(1,2000); } static void prebeep(void) { beep_ex(1,1200); } static void update_status(powerstat *s, char const *state) { char str[240]; if(statusfd<0) { if(!params.pwr.statusfn) return; if(statusfd<-1) return; statusfd = creat_ex("status", params.pwr.statusfn, O_WRONLY); if(statusfd<0) { statusfd = -2; return; } } qp_snprintf(str, sizeof(str), "%d %d %d %d %d %d %s %.*s\n", (int)s->charge_pctt, (int)s->is_ac_powered, (int)s->is_charging, (int)s->get_ac_err, (int)s->get_bat_err, (int)(delay/1000), state, sizeof(s->batname), s->batname); if(strcmp(str,status_last)) { strncpy(status_last, str, sizeof(status_last)-1); fd_set_str(statusfd, str); } } static void set_powersave(powerstat *newps) { int level, max_bl; if(newps->is_ac_powered) level = 0; else if(newps->charge_pctt>50) level = 1; else level = 2; if(params.bl.enabled) { max_bl = params.bl.max; if(level==1) max_bl -= params.bl.max/4; if(level==2) max_bl -= params.bl.max/3; if(cur_bl>max_bl) { cur_bl = max_bl; bl_set(cur_bl); if(blfd>=0) fd_set_int_U(blfd, cur_bl); } else if(cur_bl=0) fd_set_int_U(blfd, cur_bl); } } } static void bat_loop(void) { powerstat newps; if(get_powerstat(&newps, params.pwr.batname, params.pwr.acname)<0) { SYSLOG_PRIO(LOG_ERR); printlog("can't retrieve power status"); update_status(&ps, "unknown"); return; } if(statusfd<0 && (newps.get_ac_err==-1 || params.pwr.acname && !*params.pwr.acname) && (newps.get_bat_err==-1 || params.pwr.batname && !*params.pwr.batname)) return; if(newps.is_ac_powered!=ps.is_ac_powered) { SYSLOG_PRIO(LOG_INFO); if(newps.is_ac_powered) printlog("now AC powered, battery %d%%", newps.charge_pctt/10); else printlog("now Battery powered, battery %d%%", newps.charge_pctt/10); set_powersave(&newps); } if(newps.is_charging!=ps.is_charging) { SYSLOG_PRIO(LOG_INFO); if(newps.is_charging) printlog("now Charging, battery %d%%", newps.charge_pctt/10); else printlog("stopped Charging, battery %d%%", newps.charge_pctt/10); } ps = newps; if(!delay && !ps.is_ac_powered && !ps.is_charging && ps.charge_pctt>=0 && ps.charge_pctt=(params.pwr.minpct*10+10)) bat_low = 0; else if(!bat_low) { bat_low = 1; prebeep(); } } update_status(&ps, "idle"); } static void bl_loop(void) { int v; if((v=bl_get())<0 || v==cur_bl) return; saved_bl = cur_bl = v; if(blfd>=0) fd_set_int_U(blfd, cur_bl); } static unsigned char data[88200]; extern char **environ; static int beep_ex(unsigned int volume, unsigned int freq) { char *args[6]; pid_t r; int p[2], devnull; unsigned int i, value; int sv; if(!params.pwr.aplaycmd || !*params.pwr.aplaycmd) return 0; if((devnull=open("/dev/null",O_WRONLY))<0) { printlog("open(/dev/null) error %d (%s)",errno,strerror(errno)); return -1; } if(pipe(p)<0) { printlog("pipe() error %d (%s)",errno,strerror(errno)); close(devnull); return -1; } if((r=fork())<0) { printlog("fork() error %d (%s)",errno,strerror(errno)); close(p[0]); close(p[1]); close(devnull); return -1; } if(!r) { if(dup2(p[0],0)<0) { printlog("dup2(,0) error %d (%s)",errno,strerror(errno)); exit(-1); } if(dup2(2,p[0])<0) { printlog("dup2(2,) error %d (%s)",errno,strerror(errno)); exit(-1); } if(dup2(devnull,1)<0) { printlog("dup2(,1) error %d (%s)",errno,strerror(errno)); exit(-1); } if(dup2(devnull,2)<0) { printlog("dup2(,2) error %d (%s)",errno,strerror(errno)); exit(-1); } close(p[1]); args[0] = (char*)params.pwr.aplaycmd; args[1] = "-f"; args[2] = "U8"; args[3] = "-r"; args[4] = "22050"; args[5] = NULL; execve(args[0], args, environ); dup2(p[0],2); printlog("failed to call player: execve() error %d (%s)",errno,strerror(errno)); exit(-1); } close(p[0]); close(devnull); for(i=0; i2205) value=128; data[i] = value; } if(write_all(p[1], data, sizeof(data))<0) { printlog("failed to send sound data"); return -1; } close(p[1]); waitpid(-1, &sv, 0); return 0; }