/*
 * Copyright (C) 2011-2014 Felix Fietkau <nbd@openwrt.org>
 *
 * SPDX-License-Identifier: LGPL-2.1-only
 * 
 * All modifications to the original source file are:
 *  - Copyright (C) 2022 Aitor Cuadrado Zubizarreta <aitor_czr@gnuinos.org> 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <fcntl.h>
#include <stdbool.h>
#include <ctype.h>       /*    */

#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef FreeBSD
#include <sys/param.h>
#endif
#include <string.h>
#include <syslog.h>

#include <libubox/usock.h>

#include "def.h"
#include "ubusd.h"
#include "ubusd_sigact.h"

#define MAX_SIZE 256

const char *progname = "ubus";
int pid_fd = -1;      // lock file

const char *pid_file="/var/run/ubus/ubus.pid";
const char *ubus_socket = UBUS_UNIX_SOCKET;

pid_t get_pid_t(void);
int wait_on_kill(int s, int m);
void daemonize();
bool check_runsv_supervisor();

static void handle_client_disconnect(struct ubus_client *cl)
{
	struct ubus_msg_buf_list *ubl, *ubl2;
	list_for_each_entry_safe(ubl, ubl2, &cl->tx_queue, list)
		ubus_msg_list_free(ubl);

	ubusd_monitor_disconnect(cl);
	ubusd_proto_free_client(cl);
	if (cl->pending_msg_fd >= 0)
		close(cl->pending_msg_fd);
	uloop_fd_delete(&cl->sock);
	close(cl->sock.fd);
	free(cl);
}

static void client_cb(struct uloop_fd *sock, unsigned int events)
{
	struct ubus_client *cl = container_of(sock, struct ubus_client, sock);
	uint8_t fd_buf[CMSG_SPACE(sizeof(int))] = { 0 };
	struct msghdr msghdr = { 0 };
	struct ubus_msg_buf *ub;
	struct ubus_msg_buf_list *ubl, *ubl2;
	static struct iovec iov;
	struct cmsghdr *cmsg;
	int *pfd;

	msghdr.msg_iov = &iov,
	msghdr.msg_iovlen = 1,
	msghdr.msg_control = fd_buf;
	msghdr.msg_controllen = sizeof(fd_buf);

	cmsg = CMSG_FIRSTHDR(&msghdr);
	cmsg->cmsg_type = SCM_RIGHTS;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_len = CMSG_LEN(sizeof(int));

	pfd = (int *) CMSG_DATA(cmsg);
	msghdr.msg_controllen = cmsg->cmsg_len;

	/* first try to tx more pending data */
	list_for_each_entry_safe(ubl, ubl2, &cl->tx_queue, list) {
		ssize_t written;

		ub = ubl->msg;
		written = ubus_msg_writev(sock->fd, ub, cl->txq_ofs);
		if (written < 0) {
			switch(errno) {
			case EINTR:
			case EAGAIN:
				break;
			default:
				goto disconnect;
			}
			break;
		}

		cl->txq_ofs += written;
		cl->txq_len -= written;
		if (cl->txq_ofs < ub->len + sizeof(ub->hdr))
			break;

		cl->txq_ofs = 0;
		ubus_msg_list_free(ubl);
	}

	/* prevent further ULOOP_WRITE events if we don't have data
	 * to send anymore */
	if (list_empty(&cl->tx_queue) && (events & ULOOP_WRITE))
		uloop_fd_add(sock, ULOOP_READ | ULOOP_EDGE_TRIGGER);

retry:
	if (!sock->eof && cl->pending_msg_offset < (int) sizeof(cl->hdrbuf)) {
		int offset = cl->pending_msg_offset;
		int bytes;

		*pfd = -1;

		iov.iov_base = ((char *) &cl->hdrbuf) + offset;
		iov.iov_len = sizeof(cl->hdrbuf) - offset;

		if (cl->pending_msg_fd < 0) {
			msghdr.msg_control = fd_buf;
			msghdr.msg_controllen = cmsg->cmsg_len;
		} else {
			msghdr.msg_control = NULL;
			msghdr.msg_controllen = 0;
		}

		bytes = recvmsg(sock->fd, &msghdr, 0);
		if (bytes < 0)
			goto out;

		if (*pfd >= 0)
			cl->pending_msg_fd = *pfd;

		cl->pending_msg_offset += bytes;
		if (cl->pending_msg_offset < (int) sizeof(cl->hdrbuf))
			goto out;

		if (blob_raw_len(&cl->hdrbuf.data) < sizeof(struct blob_attr))
			goto disconnect;
		if (blob_pad_len(&cl->hdrbuf.data) > UBUS_MAX_MSGLEN)
			goto disconnect;

		cl->pending_msg = ubus_msg_new(NULL, blob_raw_len(&cl->hdrbuf.data), false);
		if (!cl->pending_msg)
			goto disconnect;

		cl->hdrbuf.hdr.seq = be16_to_cpu(cl->hdrbuf.hdr.seq);
		cl->hdrbuf.hdr.peer = be32_to_cpu(cl->hdrbuf.hdr.peer);

		memcpy(&cl->pending_msg->hdr, &cl->hdrbuf.hdr, sizeof(cl->hdrbuf.hdr));
		memcpy(cl->pending_msg->data, &cl->hdrbuf.data, sizeof(cl->hdrbuf.data));
	}

	ub = cl->pending_msg;
	if (ub) {
		int offset = cl->pending_msg_offset - sizeof(ub->hdr);
		int len = blob_raw_len(ub->data) - offset;
		int bytes = 0;

		if (len > 0) {
			bytes = read(sock->fd, (char *) ub->data + offset, len);
			if (bytes <= 0)
				goto out;
		}

		if (bytes < len) {
			cl->pending_msg_offset += bytes;
			goto out;
		}

		/* accept message */
		ub->fd = cl->pending_msg_fd;
		cl->pending_msg_fd = -1;
		cl->pending_msg_offset = 0;
		cl->pending_msg = NULL;
		ubusd_monitor_message(cl, ub, false);
		ubusd_proto_receive_message(cl, ub);
		goto retry;
	}

out:
	if (!sock->eof || !list_empty(&cl->tx_queue))
		return;

disconnect:
	handle_client_disconnect(cl);
}

static bool get_next_connection(int fd)
{
	struct ubus_client *cl;
	int client_fd;

	client_fd = accept(fd, NULL, 0);
	if (client_fd < 0) {
		switch (errno) {
		case ECONNABORTED:
		case EINTR:
			return true;
		default:
			return false;
		}
	}

	cl = ubusd_proto_new_client(client_fd, client_cb);
	if (cl)
		uloop_fd_add(&cl->sock, ULOOP_READ | ULOOP_EDGE_TRIGGER);
	else
		close(client_fd);

	return true;
}

static void server_cb(struct uloop_fd *fd, unsigned int events)
{
	bool next;

	do {
		next = get_next_connection(fd->fd);
	} while (next);
}

static struct uloop_fd server_fd = {
	.cb = server_cb,
};

static int usage(const char *progname)
{
	fprintf(stderr, "Usage: %s [<options>]\n"
		"Options: \n"
		"  -A <path>:			Set the path to ACL files\n"
		"  -s <socket>:			Set the unix domain socket to listen on\n"
		"  -f <foreground>		Run in the foreground\n"
		"  -k <kill>			Kill running daemon\n"
		"  -c <check>			Check whether or not an instance is already running\n"
		"\n", progname);
	return 1;
}

static void mkdir_sockdir()
{
	char *ubus_sock_dir, *tmp;

	ubus_sock_dir = strdup(UBUS_UNIX_SOCKET);
	tmp = strrchr(ubus_sock_dir, '/');
	if (tmp) {
		*tmp = '\0';
		mkdir(ubus_sock_dir, 0755);
	}
	free(ubus_sock_dir);
}

#include <libubox/ulog.h>

int main(int argc, char **argv)
{    
    int ret = 0;
	int ch;
    bool foreground = false;
    int _kill = 0, _check = 0;

	while ((ch = getopt(argc, argv, "A:s:fkc")) != -1) {
		switch (ch) {
		case 's':
			ubus_socket = optarg;
			break;
		case 'A':
			ubusd_acl_dir = optarg;
			break;
		case 'f':
			foreground = true;
			break;
        case 'k':
            _kill = 1;
            break;
        case 'c':
            _check = 1;
            break;
		default:
			return usage(argv[0]);
		}
	}
   
   if (_kill) {
		
      int rc = 0;
      
      if ((rc = wait_on_kill(SIGTERM, 60)) < 0) {
         ULOG_ERR("Failed to kill daemon with SIGTERM: %s\n", strerror(errno));
         exit(1);
      }
	        
      exit(EXIT_SUCCESS);
   }
   
   if (_check) {
      
      pid_t pid = get_pid_t();
      
       if (pid == (pid_t) -1 || pid == 0) {
          // Do not print anything here. Empty output is used in snetaid.postinst script 
          // to check whether or not sysvinit is supervising snetaid
          // printf("%s not running.\n", progname);
          exit(EXIT_FAILURE);
       } else {
          printf("%s process running as pid %u.\n", progname, pid);
          exit(EXIT_SUCCESS);
       }
   }
   
    if (_kill) {
		int rc = 0;
      
        if ((rc = wait_on_kill(SIGTERM, 60)) < 0) {
            ULOG_ERR("Failed to kill daemon with SIGTERM: %s\n", strerror(errno));
            exit(1);
        }
           
        if( access( pid_file, F_OK ) == 0 ) {		
		   if (pid_fd != -1) {
		      int rc = lockf(pid_fd, F_ULOCK, 0);
			  if(rc != 0) exit(EXIT_FAILURE);
			  close(pid_fd);
		   }
		   
		   unlink(pid_file);
	    }
	       
	    if( access( ubus_socket, F_OK ) == 0 ) {
           unlink(ubus_socket);
        }
      
        exit(EXIT_SUCCESS);
    }

	mkdir_sockdir();
	
	if(!foreground) { 
		
		daemonize();
		
    } else {
		
       // HACK ALERT: this is hack for runit
       // Previous instances running in the background will trigger zombie processes 
       if(access(pid_file, F_OK ) == 0) {
		    
          if(check_runsv_supervisor() == true) {
			 FILE *fptr; 
             char cpid[16] = {0};
             
             if((fptr = fopen(pid_file, "r")) == NULL) {
                ULOG_ERR("fopen(): %s\n", strerror(errno));
                exit(EXIT_FAILURE);
             }

             // reads text until new line is encountered
             if(fscanf(fptr, "%[^\n]", cpid) == 0) {
                ULOG_ERR("fscanf(): %s\n", strerror(errno));
                fclose(fptr);
                exit(EXIT_FAILURE);
             }
             
             fclose(fptr);
             cpid[strcspn(cpid, "\n")] = 0;
             kill(atoi(cpid), SIGTERM);
          }
       }
    }
	
	signal(SIGPIPE, SIG_IGN);
	
	sigaction_init();

	ulog_open(ULOG_KMSG | ULOG_SYSLOG, LOG_DAEMON, "ubusd");
	openlog("ubusd", LOG_PID, LOG_DAEMON);
	uloop_init();
	
	unlink(ubus_socket);
	umask(0111);
	server_fd.fd = usock(USOCK_UNIX | USOCK_SERVER | USOCK_NONBLOCK, ubus_socket, NULL);
	if (server_fd.fd < 0) {
		perror("usock");
		ret = -1;
		goto out;
	}
	uloop_fd_add(&server_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER);
	ubusd_acl_load();

	uloop_run();
	unlink(ubus_socket);

out:
	uloop_done();
	return ret;
}

pid_t get_pid_t(void)
{
   static char txt[MAX_SIZE];
   int fd = -1;
   pid_t ret = (pid_t) -1, pid;
   ssize_t l;
   long lpid;
   char *e = NULL;
   
   if ((fd = open(pid_file, O_RDWR, 0644)) < 0) {
      if ((fd = open(pid_file, O_RDONLY, 0644)) < 0) {
         if (errno != ENOENT) {
			 ULOG_ERR("Failed to open pidfile '%s': %s\n", pid_file, strerror(errno));
         }
         goto finish;
      }
   }
 
   if ((l = read(fd, txt, MAX_SIZE-1)) < 0) {
      int saved_errno = errno;
      ULOG_ERR("read(): %s\n", strerror(errno));
      unlink(pid_file);
      errno = saved_errno;
      goto finish;
   }
   
   txt[l] = 0;
   txt[strcspn(txt, "\r\n")] = 0;
   
   errno = 0;
   lpid = strtol(txt, &e, 10);
   pid = (pid_t) lpid;

   if (errno != 0 || !e || *e || (long) pid != lpid) {
      unlink(pid_file);
      errno = EINVAL;
      ULOG_ERR("PID file corrupt, removing. (%s) , %s", pid_file, strerror(errno));
      goto finish;
   }
     
   if (kill(pid, 0) != 0 && errno != EPERM) {
      ULOG_WARN("Process %lu died: %s; trying to remove PID file. (%s)", (unsigned long) pid, strerror(errno), pid_file);
      unlink(pid_file);
      goto finish;
   }
   
   ret = pid;

finish:
   if (fd != -1) {
      int rc = 0;
      if((rc=lockf(fd, F_ULOCK, 0)) < 0) {
		  ULOG_ERR("Cannot unlock file: %s\n", strerror(errno));
		  exit(EXIT_FAILURE);
	  }
	  close(fd);
   }
   
   return ret;
}

int wait_on_kill(int s, int m)
{
   pid_t pid;
   time_t t;

   if ((pid = get_pid_t()) < 0) 
      return -1;
      
   if (kill(pid, s) < 0)
      return -1;

   t = time(NULL) + m;

   for (;;) {
      int r;
      struct timeval tv = { 0, 100000 };

      if (time(NULL) > t) {
         errno = ETIME;
         return -1;
      }

      if ((r = kill(pid, 0)) < 0 && errno != ESRCH)
         return -1;

      if (r)
         return 0;
         
      kill(pid, SIGKILL);
      
      if (select(0, NULL, NULL, NULL, &tv) < 0)
         return -1;
    }
}
  
/**
 * \brief Create the directory to store the pidfile and the logfile.
 */
static
void mkdir_piddir(const char *s)
{
   char *piddir, *tmp;

   piddir = strdup(s);
   tmp = strrchr(piddir, '/');
   if (tmp) {
      *tmp = '\0';
      mkdir(piddir, 0755);
   }
   free(piddir);
}

void daemonize()
{
   int ret = 0;
   pid_t pid = 0;

   /* Fork off the parent process.
    * The first fork will change our pid
    * but the sid and pgid will be the
    * calling process
    */
   pid = fork();

   /* An error occurred */   
   if (pid < 0) {
      exit(EXIT_FAILURE);
   }
   
   /* Fork off for the second time.
    * The magical double fork. We're the session
    * leader from the code above. Since only the
    * session leader can take control of a tty
    * we will fork and exit the session leader.
    * Once the fork is done below and we use
    * the child process we will ensure we're
    * not the session leader, thus, we cannot
    * take control of a tty.
    */   
   if (pid > 0) {
      exit(EXIT_SUCCESS);
   } 

   /* On success: The child process becomes session leader */
   if (setsid() < 0) {
      exit(EXIT_FAILURE);
   }

   /* Ignore signal sent from child to parent process */   
   signal(SIGCHLD, SIG_IGN);
   
   /* Fork off for the second time*/
   pid = fork();

   /* An error occurred */
   if (pid < 0) {
      exit(EXIT_FAILURE);
   }

   /* Success: Let the parent terminate */   
   if (pid > 0) {
      exit(EXIT_SUCCESS);
   }
   
   /* Set new file permissions */
   umask(0);
   
   /* Change the working directory to /tmp */
   /* or another appropriated directory */   
   ret = chdir("/tmp");
   if(ret != 0) exit(EXIT_FAILURE);
   
   stdin  = fopen("/dev/null", "r");
   stdout = fopen("/dev/null", "w+");
   stderr = fopen("/dev/null", "w+");
   
   if (pid_file)
   {
      mkdir_piddir(pid_file);
     
      pid_fd = open(pid_file, O_RDWR|O_CREAT, 0640);
      if (pid_fd < 0) { 
         ULOG_ERR("Cannot open pidfile: %s\n", strerror(errno));
         exit(EXIT_FAILURE);
      }
      
      if (lockf(pid_fd, F_TLOCK, 0) < 0) {
         /* Can't lock file */
         ULOG_ERR("Unable to lock file: %s\n", strerror(errno));
         exit(EXIT_FAILURE);
      }

      // Get current PID
      char *mypid = NULL;
      if(asprintf(&mypid, "%jd", (intmax_t) getpid()) != -1) {
         ssize_t sz = 0;
         // Write PID to lockfile
         sz = write(pid_fd, mypid, strlen(mypid));
         if(sz == -1) { 
           ULOG_ERR("Unable to write pid: %s\n", strerror(errno));
           exit(EXIT_FAILURE);
         }
      } else {  
         ULOG_ERR("Unable to write pid: %s\n", strerror(errno));
         exit(EXIT_FAILURE);
      }   
   }   
}

// remove a character from char*
static
char *strdelch(char *str, char ch)
{
    char *current = str;
    char *tail = str;

    while(*tail)
    {
        if(*tail == ch)
        {
            tail++;
        }
        else
        {
            *current++ = *tail++;
        }
    }
    *current = 0;
    return str;
}

bool check_runsv_supervisor()
{
   char output[128] = {0};
   char cmd[128] = {0};
   FILE *pf = NULL;
   char *str = NULL;
   bool do_match = false;    
   
   if(asprintf(&str, "%jd", (intmax_t)getppid()) != -1) {
     
      // Get the output of "pstat PID" and inspect PSTAT_BINARY="value" key pair
      if( access("/usr/bin/pstat", F_OK ) == 0 ) {       
         strcpy(cmd, "/usr/bin/pstat ");
      } else if( access("/usr/local/bin/pstat", F_OK ) == 0 ) {      
         strcpy(cmd, "/usr/local/bin/pstat ");
      } else {
         ULOG_INFO("libpstat is not installed in your system\n");
         return false;
      } 
      strcat(cmd, str);
  
      pf = popen(cmd, "r");
      if(!pf) {
         ULOG_ERR("popen(): %s\n", strerror(errno));
         exit(EXIT_FAILURE);
      }
   
      while(fgets(output, 128, pf)) {
         const char delim[2] = "=";
         char *token = strtok( (char*)output, delim );
         if(!strcmp(token, "PSTAT_BINARY")) {
            int rc __attribute__((unused)) = 0;
            token = strtok(NULL, delim);
            token[strcspn(token, "\n")] = 0;
            const char *res = strdelch(token, '"');
            if(!strcmp(basename(res), "runsv")) { 
               do_match = true;
            }
         }
      }
      
      pclose(pf);
   
   } else {
      
      ULOG_ERR("Cannot get the pid of the parent: %s\n", strerror(errno));
      exit(EXIT_FAILURE);
   }
   
   return do_match;
}



