aboutsummaryrefslogblamecommitdiffstats
path: root/src/common.c
blob: 2c6c7b45b31bbf54277820a10a980b6aab818da9 (plain) (tree)
1
2
   
                                                           


















                                                                             
                   

                   
                







                      



   




                                          
                            



                                                   
                          


                                                                            
                          
                               
                                       
                                      
            







                                                    










                                                                    
       

                                                          
                           












                                                       
       
                                                                 
 
                                 




                    












                                                                           
                                               

                                                      
                          
                            
 
                



                                                            
     
                              
















                                                            
                                                 
     
                  

 
















                                                             
                                         
                          
                                        
                                       
                 

 

   





                                                                   
   







                                                  
                                    


                                                                  
                                                                                                  
                                

                   
                                                                                      


                                                                                   
                                                                                      

         
                              
                                                                            

                                                
                                    
                        

         
                                                           
     
                                              




                                           


                                                       
                                                                                     






                                              

                         
                                               


                         
                                    
                                    








                                                           
                                                                               


                                                                                        
         
                                                                
                             

         
                                                      
                        
                                                                                                 



                                                                               
                                                           

                              
 





                                                                              

                                                     

         


                                                                                                            
                  

     
                                                                   
                  



   
                                                   
   
                                                                         



              
                                              











                                                                                        
                                                           
                               
                                                                                          

                             
                                         
                                


                            
                                     

                                 

                            

 




















                                                                                   
                                




























                                                                                        
                      




                                                                                 




                                          




























                                                                                              
                                





























































                                                                                   


                                      
                  
 












                                                                              

 
/**
 * Copyright © 2015, 2016  Mattias Andrée <m@maandree.se>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#include "common.h"
#include <ctype.h>
#include <stdarg.h>
#include <pwd.h>
#include <sys/wait.h>



/**
 * The environment.
 */
extern char **environ;



/**
 * Common code for `preadn` and `pwriten`.
 * 
 * @param  FUN  `pread` or `pwrite`.
 */
#define PIO(FUN)  \
	ssize_t r, n = 0;  \
	int saved_errno = 0;  \
	sigset_t mask, oldmask;  \
	sigfillset(&mask);  \
	sigprocmask(SIG_BLOCK, &mask, &oldmask);  \
	while (nbyte) {  \
		t (r = FUN(fildes, buffer, nbyte, (off_t)offset), r < 0);  \
		if (r == 0)  \
			break;  \
		n += r;  \
		buffer += r;  \
		offset += (size_t)r;  \
		nbyte -= (size_t)r;  \
	}  \
done:  \
	sigprocmask(SIG_SETMASK, &oldmask, NULL);  \
	errno = saved_errno;  \
	return n;  \
fail:  \
	saved_errno = errno;  \
	n = -1;  \
	goto done


/**
 * Wrapper for `pread` that reads the required amount of data.
 * 
 * @param   fildes  See pread(3).
 * @param   buf     See pread(3).
 * @param   nbyte   See pread(3).
 * @param   offset  See pread(3).
 * @return          See pread(3), only short if the file is shorter.
 */
ssize_t
preadn(int fildes, void *buf, size_t nbyte, size_t offset)
{
	char *buffer = buf;
	PIO(pread);
}


/**
 * Wrapper for `pwrite` that writes all specified data.
 * 
 * @param   fildes  See pwrite(3).
 * @param   buf     See pwrite(3).
 * @param   nbyte   See pwrite(3).
 * @param   offset  See pwrite(3).
 * @return          See pwrite(3).
 */
ssize_t
pwriten(int fildes, const void *buf, size_t nbyte, size_t offset)
{
	const char *buffer = buf;
	PIO(pwrite);
}


/**
 * Unmarshal a `NULL`-terminated string array.
 * 
 * The elements are not actually copied, subpointers
 * to `buf` are stored in the returned list.
 * 
 * @param   buf  The marshalled array. Must end with a NUL byte.
 * @param   len  The length of `buf`.
 * @param   n    Output parameter for the number of elements. May be `NULL`
 * @return       The list, `NULL` on error.
 * 
 * @throws  Any exception specified for realloc(3).
 */
char **
restore_array(char *buf, size_t len, size_t *n)
{
	char **rc = malloc((len + 1) * sizeof(char*));
	char **new = NULL;
	size_t i = 0, e = 0;

	t (!rc);
	while (i < len)  i += strlen(rc[e++] = buf + i) + 1;
	if (n)           *n = e;
	rc[e++] = NULL;
	new = realloc(rc, e * sizeof(char*));
fail:
	return new ? new : rc;
}


/**
 * Create `NULL`-terminate subcopy of an list,
 * 
 * @param   list  The list.
 * @param   n     The number of elements in the new sublist.
 * @return        The sublist, `NULL` on error.
 * 
 * @throws  Any exception specified for malloc(3).
 */
char **
sublist(char *const *list, size_t n)
{
	char **rc = malloc((n + 1) * sizeof(char*));
	t (!rc);
	for (rc[n] = NULL; n--; rc[n] = list[n]);
fail:
	return rc;
}


/**
 * Create a new open file descriptor for an already
 * existing file descriptor.
 * 
 * @param   fd     The file descriptor that shall be promoted
 *                 to a new open file descriptor.
 * @param   oflag  See open(3), `O_CREAT` is not allowed.
 * @return         0 on success, -1 on error.
 */
int
reopen(int fd, int oflag)
{
	char path[sizeof("/dev/fd/") + 3 * sizeof(int)];
	int r, saved_errno;

	sprintf(path, "/dev/fd/%i", fd);
	if (r = open(path, oflag), r < 0)
		return -1;
	if (DUP2_AND_CLOSE(r, fd) == -1)
		return S(close(r)), -1;
	return 0;
}


/**
 * Run a job or a hook.
 * 
 * @param   job   The job.
 * @param   hook  The hook, `NULL` to run the job.
 * @return        0 on success, -1 on error, 1 if the child failed.
 */
int
run_job_or_hook(struct job *job, const char *hook)
{
	pid_t pid;
	char **args = NULL;
	char **argv = NULL;
	char **envp = NULL;
	size_t argsn;
	void *new;
	int status = 0, saved_errno;

	t (!(args = restore_array(job->payload, job->n, &argsn)));
	t (!(argv = sublist(args, (size_t)(job->argc))));
	t (!(envp = sublist(args + job->argc, argsn - (size_t)(job->argc)))); /* Includes wdir. */
	free(args), args = NULL;

	if (hook) {
		t (!(new = realloc(argv, ((size_t)(job->argc) + 3) * sizeof(*argv))));
		argv = new;
		memmove(argv + 2, argv, ((size_t)(job->argc) + 1) * sizeof(*argv));
		argv[0] = getenv("SAT_HOOK_PATH");
		argv[1] = (strstr)(hook, hook); /* strstr: just to remove a warning */
	}

	if (!(pid = fork())) {
		close(STATE_FILENO), close(BOOT_FILENO), close(REAL_FILENO);
		(void)(status = chdir(envp[0]));
		environ = envp + 1;
		execvp(*argv, argv);
		exit(1);
	}

	t ((pid < 0) || (waitpid(pid, &status, 0) != pid));
fail:
	S(free(args), free(argv), free(envp));
	return status ? 1 : -!!saved_errno;
}


/**
 * Removes (and optionally runs) a job.
 * 
 * @param   jobno   The job number, `NULL` for any job.
 * @param   runjob  Shall we run the job too? 2 if its time has expired (not forced).
 * @return          0 on success, -1 on error.
 * 
 * @throws  0  The job is not in the queue.
 */
int
remove_job(const char *jobno, int runjob)
{
	char *end;
	char *buf = NULL;
	size_t no = 0, off = sizeof(size_t), n;
	ssize_t r;
	struct stat attr;
	struct job job;
	struct job *job_full = NULL;
	int rc = 0, saved_errno = 0;

	if (jobno) {
		no = (errno = 0, strtoul)(jobno, &end, 10);
		if (errno || *end || !isdigit(*jobno))
			return 0;
	}

	t (flock(STATE_FILENO, LOCK_EX));
	t (fstat(STATE_FILENO, &attr));
	for (n = (size_t)(attr.st_size); off < n; off += sizeof(job) + job.n) {
		t (preadn(STATE_FILENO, &job, sizeof(job), off) < (ssize_t)sizeof(job));
		if (!jobno || (job.no == no))
			goto found_it;
	}
	flock(STATE_FILENO, LOCK_UN); /* Failure isn't fatal. */
	return errno = 0, -1;

found_it:
	t (!(job_full = malloc(sizeof(job) + job.n)));
	*job_full = job;
	t (preadn(STATE_FILENO, job_full->payload, job.n, off + sizeof(job)) < (ssize_t)(job.n));
	n -= off + sizeof(job) + job.n;
	t (!(buf = malloc(n)));
	t (r = preadn(STATE_FILENO, buf, n, off + sizeof(job) + job.n), r < 0);
	t (pwriten(STATE_FILENO, buf, (size_t)r, off) < 0);
	t (ftruncate(STATE_FILENO, (off_t)r + (off_t)off));
	free(buf), buf = NULL;
	fsync(STATE_FILENO);

	if (runjob) {
		run_job_or_hook(job_full, runjob == 2 ? "expired" : "forced");
		rc = run_job_or_hook(job_full, NULL);
		saved_errno = errno;
		run_job_or_hook(job_full, rc ? "failure" : "success");
		rc = rc == 1 ? 0 : rc;
	} else {
		run_job_or_hook(job_full, "removed");
	}

	free(job_full);
	flock(STATE_FILENO, LOCK_UN); /* Unlock late so that hooks are synchronised. Failure isn't fatal. */
	errno = saved_errno;
	return rc;

fail:
	S(flock(STATE_FILENO, LOCK_UN), free(buf), free(job_full));
	return -1;
}


/**
 * Get a `NULL`-terminated list of all queued jobs.
 * 
 * @return  A `NULL`-terminated list of all queued jobs. `NULL` on error.
 */
struct job **
get_jobs(void)
{
	size_t off = sizeof(size_t), n, j = 0;
	struct stat attr;
	struct job **js = NULL;
	struct job job;
	int saved_errno;

	t (flock(STATE_FILENO, LOCK_SH));
	t (fstat(STATE_FILENO, &attr));
	n = (size_t)(attr.st_size);
	t (!(js = malloc((n / sizeof(**js) + 1) * sizeof(*js))));
	while (off < n) {
		t (preadn(STATE_FILENO, &job, sizeof(job), off) < (ssize_t)sizeof(job));
		off += sizeof(job);
		t (!(js[j] = malloc(sizeof(job) + job.n)));
		*(js[j]) = job;
		t (preadn(STATE_FILENO, js[j++]->payload, job.n, off) < (ssize_t)(job.n));
		off += job.n;
	}
	t (flock(STATE_FILENO, LOCK_UN));
	return js[j] = NULL, js;

fail:
	saved_errno = errno;
	flock(STATE_FILENO, LOCK_UN);
	while (j--)  free(js[j]);
	free(js);
	errno = saved_errno;
	return NULL;
}


/**
 * Duplicate a file descriptor, and
 * open /dev/null to the old file descriptor.
 * However, if `old` is 3 or greater, it will
 * be closed rather than /dev/null.
 * 
 * @param   old  The old file descriptor.
 * @param   new  The new file descriptor.
 * @return       `new`, -1 on error.
 */
int
dup2_and_null(int old, int new)
{
	int fd = -1, saved_errno;

	if (old == new)  return new;  t (DUP2_AND_CLOSE(old, new));
	if (old >= 3)    return new;  t (fd = open("/dev/null", O_RDWR), fd == -1);
	if (fd == old)   return new;  t (DUP2_AND_CLOSE(fd, old));
	return new;
fail:
	return S(close(fd)), -1;
}


/**
 * Create or open the state file.
 * 
 * @param   open_flags  Flags (the second parameter) for `open`.
 * @param   state_path  Output parameter for the state file's pathname.
 *                      May be `NULL`;
 * @return              A file descriptor to the state file, -1 on error.
 * 
 * @throws  0  `!(open_flags & O_CREAT)` and the file does not exist.
 */
int
open_state(int open_flags, char **state_path)
{
	const char *dir;
	char *path;
	int fd = -1, saved_errno;

	/* Create directory. */
	dir = getenv("XDG_RUNTIME_DIR"), dir = (dir ? dir : "/run");
	t (!(path = malloc(strlen(dir) * sizeof(char) + sizeof("/" PACKAGE "/state"))));
	stpcpy(stpcpy(path, dir), "/" PACKAGE "/state");
	t (fd = open(path, open_flags, S_IRUSR | S_IWUSR), fd == -1);

	if (state_path)  *state_path = path, path = NULL;
	else             free(path), path = NULL;
fail:
	S(free(path));
	if (!(open_flags & O_CREAT) && ((errno == ENOENT) || (errno == ENOTDIR)))
		errno = 0;
	return fd;
}


/**
 * Let the daemon know that it may need to
 * update the timers, and perhaps exit.
 * 
 * @param   start  Start the daemon if it is not running?
 * @param   name   The name of the process.
 * @return         0 on success, -1 on error.
 */
int
poke_daemon(int start, const char *name)
{
	char *path = NULL;
	const char *dir;
	pid_t pid;
	int fd = -1, status, saved_errno;

	/* Get the lock file's pathname. */
	dir = getenv("XDG_RUNTIME_DIR"), dir = (dir ? dir : "/run");
	t (!(path = malloc(strlen(dir) * sizeof(char) + sizeof("/" PACKAGE "/lock"))));
	stpcpy(stpcpy(path, dir), "/" PACKAGE "/lock");

	/* Any daemon listening? */
	fd = open(path, O_RDONLY);
	if (fd == -1) {
		t ((errno != ENOENT) && (errno != ENOTDIR));
	} else {
		if (flock(fd, LOCK_SH | LOCK_NB /* and LOCK_DRY if that was ever added... */))
			t (start = 0, errno != EWOULDBLOCK);
		else
			flock(fd, LOCK_UN);
		t (read(fd, &pid, sizeof(pid)) < (ssize_t)sizeof(pid));
		close(fd), fd = -1;
	}
	free(path), path = NULL;

	/* Start daemon if not running, otherwise poke it. */
	if (start) {
		switch ((pid = fork())) {
		case -1:
			goto fail;
		case 0:
			execl(BINDIR "/satd", BINDIR "/satd", NULL);
			perror(name);
			exit(1);
		default:
			t (waitpid(pid, &status, 0) != pid);
			t (errno = 0, status);
			break;
		}
	} else {
		t (kill(pid, SIGCHLD));
	}

	return 0;
fail:
	return S(close(fd), free(path)), -1;
}


/**
 * Construct the pathname for the hook script.
 * 
 * @param   env     The environment variable to use for the beginning
 *                  of the pathname, `NULL` for the home directory.
 * @param   suffix  The rest of the pathname.
 * @return          The pathname.
 * 
 * @throws  0  The environment variable is not set, or, if `env` is
 *             `NULL` the user is root or homeless.
 */
static char *
hookpath(const char *env, const char *suffix)
{
	struct passwd *pwd;
	const char *prefix = NULL;
	char *path;

	if (env) {
		prefix = getenv(env);
	} else if (getuid()) {
		pwd = getpwuid(getuid());
		prefix = pwd ? pwd->pw_dir : NULL;
	}
	if (!prefix || !*prefix)
		return errno = 0, NULL;

	t (!(path = malloc((strlen(prefix) + strlen(suffix) + 1) * sizeof(char))));
	stpcpy(stpcpy(path, prefix), suffix);
fail:
	return path;
}


/**
 * Set SAT_HOOK_PATH.
 * 
 * @return  0 on success, -1 on error.
 */
int
set_hookpath(void)
{
#define HOOKPATH(PRE, SUF)  \
	t (path = path ? path : hookpath(PRE, SUF), !path && errno)
	char *path = NULL;
	int saved_errno;
	if (!getenv("SAT_HOOK_PATH")) {
		HOOKPATH("XDG_CONFIG_HOME", "/sat/hook");
		HOOKPATH("HOME", "/.config/sat/hook");
		HOOKPATH(NULL, "/.config/sat/hook");
		t (setenv("SAT_HOOK_PATH", path ? path : "/etc/sat/hook", 1));
	}
	return free(path), 0;
fail:
	return S(free(path)), -1;
}