aboutsummaryrefslogblamecommitdiffstats
path: root/src/libmdsserver/util.c
blob: 204d15563825b0f47b0f0ed344ef5bb31c3c2773 (plain) (tree)


















                                                                        
                   
 
                   



                   
                   

                       
                  

                     

 

   







                                        














                                                      

                                     




                















                                                                                 






























                                                                            


                                         





                                                                      


                                                       




                           





                                                           
                                








                                                  
                                                       

 



                                                   

                                                    



















                                                                   














                                                                            
            
                    
                                                                                              






                                
            



                      
                                  





                                    

























                                                                 

























                                                         
                                                  
   


                                                                                       
   
                                       
 


                               
              

                     

                                 
                                         





                                           
                                    
         

                                                        






                                               
                                                                    

                                        
                       



                      
                                

     


                         

 























                                                                                              




                                                                        



                                                                                    



























                                                                           
/**
 * mds — A micro-display server
 * Copyright © 2014  Mattias Andrée (maandree@member.fsf.org)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "util.h"
#include "config.h"
#include "macros.h"

#include <alloca.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <sys/wait.h>



/**
 * The content of `/proc/self/exe`, when
 * `prepare_reexec` was invoked.
 */
static char self_exe[PATH_MAX] = {0};



/**
 * Convert a client ID string into a client ID integer
 * 
 * @param   str  The client ID string
 * @return       The client ID integer
 */
uint64_t parse_client_id(const char* str)
{
  char client_words[22];
  char* client_high;
  char* client_low;
  uint64_t client;
  
  strcpy(client_high = client_words, str);
  client_low = rawmemchr(client_words, ':');
  *client_low++ = '\0';
  client = atou64(client_high) << 32;
  client |= atou64(client_low);
  
  return client;
}


/**
 * Read an environment variable, but handle it as undefined if empty
 * 
 * @param   var  The environment variable's name
 * @return       The environment variable's value, `NULL` if empty or not defined
 */
char* getenv_nonempty(const char* var)
{
  char* rc = getenv(var);
  if ((rc == NULL) || (*rc == '\0'))
    return NULL;
  return rc;
}


/**
 * Prepare the server so that it can reexec into
 * a newer version of the executed file.
 * 
 * This is required for two reasons:
 * 1:  We cannot use argv[0] as PATH-resolution may
 *     cause it to reexec into another pathname, and
 *     maybe to wrong program. Additionally argv[0]
 *     may not even refer to the program, and chdir
 *     could also hinter its use.
 * 2:  The kernel appends ` (deleted)` to
 *     `/proc/self/exe` once it has been removed,
 *     so it cannot be replaced.
 * 
 * The function will should be called immediately, it
 * will store the content of `/proc/self/exe`.
 * 
 * @return  Zero on success, -1 on error
 */
int prepare_reexec(void)
{
  ssize_t len;
  len = readlink(SELF_EXE, self_exe, (sizeof(self_exe) / sizeof(char)) - 1);
  if (len < 0)
    return -1;
  /* ‘readlink() does not append a null byte to buf.’ */
  self_exe[len] = '\0';
  return 0;
}


/**
 * Re-exec the server.
 * This function only returns on failure.
 * 
 * If `prepare_reexec` failed or has not been called,
 * `argv[0]` will be used as a fallback.
 * 
 * @param  argc      The number of elements in `argv`
 * @param  argv      The command line arguments
 * @param  reexeced  Whether the server has previously been re-exec:ed
 */
void reexec_server(int argc, char** argv, int reexeced)
{
  char** reexec_args;
  char** reexec_args_;
  int i;
  
  /* Re-exec the server. */
  reexec_args = alloca(((size_t)argc + 2) * sizeof(char*));
  reexec_args_ = reexec_args;
  if (reexeced == 0)
    {
      *reexec_args_++ = *argv;
      *reexec_args_ = strdup("--re-exec");
      if (*reexec_args_ == NULL)
	return;
      for (i = 1; i < argc; i++)
	reexec_args_[i] = argv[i];
    }
  else /* Don't let the --re-exec:s accumulate. */
    *reexec_args_ = *argv;
  for (i = 1; i < argc; i++)
    reexec_args_[i] = argv[i];
  reexec_args_[argc] = NULL;
  execv(self_exe[0] ? self_exe : argv[0], reexec_args);
}


/**
 * Set up a signal trap.
 * This function should only be used for common mds
 * signals and signals that does not require and
 * special settings. This function may choose to add
 * additional behaviour depending on the signal, such
 * as blocking other signals.
 * 
 * @param   signo     The signal to trap
 * @param   function  The function to run when the signal is caught
 * @return            Zero on success, -1 on error
 */
int xsigaction(int signo, void (*function)(int signo))
{
  struct sigaction action;
  sigset_t sigset;
  
  sigemptyset(&sigset);
  action.sa_handler = function;
  action.sa_mask = sigset;
  action.sa_flags = 0;
  
  return sigaction(signo, &action, NULL);
}


/**
 * Send a message over a socket
 * 
 * @param   socket   The file descriptor of the socket
 * @param   message  The message to send
 * @param   length   The length of the message
 * @return           The number of bytes that have been sent (even on error)
 */
size_t send_message(int socket, const char* message, size_t length)
{
  size_t block_size = length;
  size_t sent = 0;
  ssize_t just_sent;
  
  errno = 0;
  while (length > 0)
    if ((just_sent = send(socket, message + sent, min(block_size, length), MSG_NOSIGNAL)) < 0)
      {
	if (errno == EMSGSIZE)
	  {
	    block_size >>= 1;
	    if (block_size == 0)
	      return sent;
	  }
	else
	  return sent;
      }
    else
      {
	sent += (size_t)just_sent;
	length -= (size_t)just_sent;
      }
  
  return sent;
}


/**
 * A version of `atoi` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
int strict_atoi(const char* str, int* value, int min, int max)
{
  long long int r;
  char* endptr;
  
  r = strtoll(str, &endptr, 10);
  if ((*str == '\0') || isspace(*str) ||
      (endptr - str != (ssize_t)strlen(str)) ||
      (r < (long long int)min) ||
      (r > (long long int)max))
    return -1;
  
  *value = (int)r;
  return 0;
}


/**
 * Send a buffer into a file and ignore interruptions
 * 
 * @param   fd      The file descriptor
 * @param   buffer  The buffer
 * @param   length  The length of the buffer
 * @return          Zero on success, -1 on error
 */
int full_write(int fd, const char* buffer, size_t length)
{
  ssize_t wrote;
  while (length > 0)
    {
      errno = 0;
      wrote = write(fd, buffer, length);
      if (errno && (errno != EINTR))
	return -1;
      length -= (size_t)max(wrote, 0);
      buffer += (size_t)max(wrote, 0);
    }
  return 0;
}


/**
 * Read a file completely and ignore interruptions
 * 
 * @param   fd      The file descriptor
 * @param   length  Output parameter for the length of the file, may be `NULL`
 * @return          The content of the file, you will need to free it. `NULL` on error.
 */
char* full_read(int fd, size_t* length)
{
  size_t buffer_size = 8 << 10;
  size_t buffer_ptr = 0;
  char* buffer;
  ssize_t got;
  if (length != NULL)
    *length = 0;
  
  /* Allocate buffer for data. */
  if (xmalloc(buffer, buffer_size, char))
    return NULL;
  
  /* Read the file. */
  for (;;)
    {
      /* Grow buffer if it is too small. */
      if (buffer_size == buffer_ptr)
	{
	  char* old_buf = buffer;
	  if (xrealloc(buffer, buffer_size <<= 1, char))
	    {
	      free(old_buf);
	      return NULL;
	    }
	}
      
      /* Read from the file into the buffer. */
      got = read(fd, buffer + buffer_ptr, buffer_size - buffer_ptr);
      if ((got < 0) && (errno != EINTR))
	{
	  free(buffer);
	  return NULL;
	}
      if (got == 0)
	break;
      buffer_ptr += (size_t)got;
    }
  
  if (length != NULL)
    *length = buffer_ptr;
  return buffer;
}


/**
 * Check whether a string begins with a specific string,
 * where neither of the strings are necessarily NUL-terminated
 * 
 * @param   haystack    The string that should start with the other string
 * @param   needle      The string the first string should start with
 * @param   haystack_n  The length of `haystack`
 * @param   needle_n    The length of `needle`
 * @return              Whether the `haystack` begins with `needle`
 */
int startswith_n(const char* haystack, const char* needle, size_t haystack_n, size_t needle_n)
{
  size_t i;
  if (haystack_n < needle_n)
    return 0;
  
  for (i = 0; i < needle_n; i++)
    if (haystack[i] != needle[i])
      return 0;
  
  return 1;
}


/**
 * Wrapper around `waitpid` that never returns on an interruption unless
 * it is interrupted 100 times within the same second
 * 
 * @param   pid      See description of `pid` in the documentation for `waitpid`
 * @param   status   See description of `status` in the documentation for `waitpid`
 * @param   options  See description of `options` in the documentation for `waitpid`
 * @return           See the documentation for `waitpid`
 */
pid_t uninterruptable_waitpid(pid_t pid, int* restrict status, int options)
{
  struct timespec time_start;
  struct timespec time_intr;
  int intr_count = 0, have_time;
  pid_t rc;
  
  have_time = (monotone(&time_start) >= 0);
  
 rewait:
  rc = waitpid(pid, status, options);
  if (rc == (pid_t)-1)
    {
      if (errno != EINTR)
	goto fail;
      if (have_time && (monotone(&time_intr) >= 0))
	if (time_start.tv_sec != time_intr.tv_sec)
	  intr_count = 0;
      if (intr_count++ < 100)
	goto rewait;
      /* Don't let the CPU catch fire! */
      errno = EINTR;
    }
 fail:
  return rc;
}