Skip to content
Snippets Groups Projects
ss_environ.c 12.47 KiB
/* 
 * ss_environ.c
 * 
 * Copyright (c) 2018-2020 Eric Vidal <eric@obarun.org>
 * 
 * All rights reserved.
 * 
 * This file is part of Obarun. It is subject to the license terms in
 * the LICENSE file found in the top-level directory of this
 * distribution.
 * This file may not be copied, modified, propagated, or distributed
 * except according to the terms contained in the LICENSE file./
 */

#include <sys/types.h>
#include <string.h>
#include <stdio.h>//rename
#include <errno.h>

#include <oblibs/environ.h>
#include <oblibs/sastr.h>
#include <oblibs/files.h>
#include <oblibs/string.h>
#include <oblibs/types.h>
#include <oblibs/directory.h>

#include <skalibs/unix-transactional.h>//atomic_symlink
#include <skalibs/stralloc.h>
#include <skalibs/djbunix.h>//rm_rf

#include <66/constants.h>
#include <66/utils.h>
#include <66/environ.h>

int env_resolve_conf(stralloc *env,char const *svname, uid_t owner)
{
	if (!owner)
	{
		if (!stralloc_cats(env,SS_SERVICE_ADMCONFDIR)) return 0 ;
	}
	else
	{
		if (!set_ownerhome(env,owner)) return 0 ;
		if (!stralloc_cats(env,SS_SERVICE_USERCONFDIR)) return 0 ;
	}
	if (!stralloc_cats(env,svname)) return 0 ;
	if (!stralloc_0(env)) return 0 ;
	env->len-- ;
	return 1 ;
}

int env_merge_conf(stralloc *result,stralloc *srclist,stralloc *modifs,uint8_t conf)
{
	int r, comment ;
	size_t pos = 0 ;
	char *end = 0 ;
	stralloc user = STRALLOC_ZERO ;
	stralloc key = STRALLOC_ZERO ;
	stralloc val = STRALLOC_ZERO ;
	
	if (!auto_stra(&user,"\n## diff from upstream ##\n")) goto err ;

	/** User can empty the file and the function
	 * fail. In that case we rewrite the file entirely */
	if (!env_clean_with_comment(srclist)) {
		if (!auto_stra(result,modifs->s)) goto err ;
		goto freed ;
	}

	if (!env_clean_with_comment(modifs)) goto err ;

	if (!sastr_split_string_in_nline(modifs) ||
	!sastr_split_string_in_nline(srclist)) goto err ;

	for (;pos < modifs->len; pos += strlen(modifs->s + pos) + 1)
	{
		comment = modifs->s[pos] == '#' ? 1 : 0 ;
		key.len = 0 ;
		char *line = modifs->s + pos ;

		/** keep a empty line between key=value pair and a comment */
		end = get_len_until(line,'=') < 0 ? "\n" : "\n\n" ;

		if (comment)
		{
			if (!auto_stra(result,line,end)) goto err ;
			continue ;
		}
		if (!stralloc_copy(&key,modifs)) goto err ;
		if (!environ_get_key_nclean(&key,&pos)) goto err ;
		key.len-- ;
		if (!auto_stra(&key,"=")) goto err ;
		r = sastr_find(srclist,key.s) ;
		if (r >= 0)
		{
			/** apply change from upstream */
			if (conf > 1)
			{
				if (!auto_stra(result,"\n",line,"\n\n")) goto err ;
				continue ;
			}
			/** keep user change */
			else
			{
				if (!stralloc_copy(&val,srclist)) goto err ;
				if (!environ_get_val_of_key(&val,key.s)) goto err ;
				if (!auto_stra(result,"\n",key.s,val.s,"\n\n")) goto err ;
				continue ;
			}
		}

		if (!auto_stra(result,"\n",line,end)) goto err ;	
	}
	/** search for a key added by user */
	for (pos = 0 ; pos < srclist->len ; pos += strlen(srclist->s + pos) + 1)
	{
		comment = srclist->s[pos] == '#' ? 1 : 0 ;
		key.len = 0 ;
		char *line = srclist->s + pos ;

		if (comment) continue ;

		if (!stralloc_copy(&key,srclist)) goto err ;
		if (!environ_get_key_nclean(&key,&pos)) goto err ;
		key.len-- ;
		if (!auto_stra(&key,"=")) goto err ;
		r = sastr_find(modifs,key.s) ;
		if (r >= 0) continue ;

		if (!auto_stra(&user,line,"\n")) goto err ;
	}

	if (user.len > 26)
		if (!auto_stra(result,user.s)) goto err ;

	freed:

	stralloc_free(&user) ; 
	stralloc_free(&key) ;
	stralloc_free(&val) ;
	return 1 ;
	err:
		stralloc_free(&user) ; 
		stralloc_free(&key) ;
		stralloc_free(&val) ;
		return 0 ;
}

/** @Return 0 on fail
 * @Return 1 on success
 * @Return 2 if symlink was updated and old version > new version
 * @Return 3 if symlink was updated and old version < new version */
int env_make_symlink(stralloc *dst,stralloc *old_dst,sv_alltype *sv,uint8_t conf)
{
	/** dst-> e.g /etc/66/conf/<service_name> */
	int r ;
	size_t dstlen = dst->len ;
	uint8_t format = 0, update_symlink = 0, write = 0 ;
	struct stat st ;
	stralloc saversion = STRALLOC_ZERO ;
	char *name = keep.s + sv->cname.name ;
	char *version = keep.s + sv->cname.version ;
	char old[dst->len + 5] ;//.old
	char dori[dst->len + 1] ;
	char dname[dst->len + 1] ;
	char current_version[dst->len + 1] ;
	char sym_version[dst->len + SS_SYM_VERSION_LEN + 1] ;
	auto_strings(sym_version,dst->s,SS_SYM_VERSION) ;

	/** dst -> /etc/66/conf/<service> */
	auto_strings(dori,dst->s) ;

	r = scan_mode(dst->s, S_IFDIR) ;
	/** enforce to pass to new format*/
	if (r == -1) {
		/** last time to pass to the new format. 0.6.0.0 or higher
		 * version will remove this check */
		auto_strings(old,dst->s,".old") ;
		if (rename(dst->s,old) == -1)
			log_warnusys_return(LOG_EXIT_ZERO,"rename: ",dst->s," to: ",old) ;
		format = 1 ;
	}

	/** first activation of the service. The symlink doesn't exist yet.
	 * In that case we create it else we check if it point to a file or
	 * a directory. File means old format, in this case we enforce to pass
	 * to the new one. */
	r = lstat(sym_version,&st) ;
	if (S_ISLNK(st.st_mode))
	{
		if (sarealpath(&saversion,sym_version) == -1)
			log_warn_return(LOG_EXIT_ZERO,"sarealpath of: ",sym_version) ;

		r = scan_mode(saversion.s,S_IFREG) ;
		if (r > 0)
		{
			/** /etc/66/conf/service/version/confile */
			if (!ob_dirname(dname,saversion.s))
				log_warn_return(LOG_EXIT_ZERO,"get basename of: ",saversion.s) ;
			dname[strlen(dname) -1] = 0 ;
			saversion.len = 0 ;
			if (!auto_stra(&saversion,dname))
				log_warn_return(LOG_EXIT_ZERO,"stralloc") ;

			if (!ob_basename(current_version,dname))
				log_warn_return(LOG_EXIT_ZERO,"get basename of: ",dname) ;

			/** old format->point to a file instead of the directory,
			 * enforce to pass to new one */
			if (!atomic_symlink(dst->s,sym_version,"env_compute"))
				log_warnu_return(LOG_EXIT_ZERO,"symlink: ",sym_version," to: ",dst->s) ;
		}
		else
		{
			/** /etc/66/conf/service/version */
			if (!ob_basename(current_version,saversion.s))
				log_warn_return(LOG_EXIT_ZERO,"get basename of: ",saversion.s) ;
		}
		/** keep the path of the current symlink. env_compute need it
		 * to compare the two files. */
		if (sarealpath(old_dst,sym_version) == -1)
			log_warn_return(LOG_EXIT_ZERO,"sarealpath of: ",sym_version) ;
	}
	else
	{
		/** doesn't exist. First use of the service */
		update_symlink = 3 ;
		write = 1 ;
		if (!auto_stra(dst,"/",version))
			log_warnsys_return(LOG_EXIT_ZERO,"stralloc") ;
		goto create ;
	}

	/** 0.4.0.1 and previous 66 version doesn't set @version field as
	 * mandatory field. We need to check if we have a previous format. */
	r = version_scan(&saversion,current_version,SS_CONFIG_VERSION_NDOT) ;
	if (r == -1) log_warnu_return(LOG_EXIT_ZERO,"get version of: ",saversion.s) ;
	if (!r)
	{
		/** old format meaning /etc/66/conf/service/confile
		 * we consider it as old version */
		if (!auto_stra(dst,"/",current_version))
			log_warnsys_return(LOG_EXIT_ZERO,"stralloc") ;

		if (!atomic_symlink(dst->s,sym_version,"env_compute"))
			log_warnu_return(LOG_EXIT_ZERO,"symlink: ",sym_version," to: ",dst->s) ;
	}

	r = version_cmp(current_version,version,SS_CONFIG_VERSION_NDOT) ;
	if (r == -2) log_warn_return(LOG_EXIT_ZERO,"compare version: ",current_version," vs: ",version) ;
	if (r == -1 || r == 1)
	{
		if (conf) {
			update_symlink = r == 1 ? 2 : 3 ;
			if (r == 1 && conf < 3)
				log_1_warn("merging configuration file to a previous version is not allowed -- keeps as it") ;

			dst->len = dstlen ;
			if (!auto_stra(dst,"/",version))
				log_warnsys_return(LOG_EXIT_ZERO,"stralloc") ;
		}
		else
		{
			log_1_warn("configuration file version differ --  current: ",current_version," new: ",version) ;
			dst->len = dstlen ;
			if (!auto_stra(dst,SS_SYM_VERSION))
				log_warnsys_return(LOG_EXIT_ZERO,"stralloc") ;
		}
	}
	else
	{
		/** version is the same keep the previous symlink */
		dst->len = dstlen ;
		if (!auto_stra(dst,SS_SYM_VERSION))
			log_warnsys_return(LOG_EXIT_ZERO,"stralloc") ;
	}

	saversion.len = 0 ;
	if (!auto_stra(&saversion,dori,"/",version,"/",name))
		log_warn_return(LOG_EXIT_ZERO,"stralloc") ;

	r = scan_mode(saversion.s,S_IFREG) ;
	if (!r)
	{
		/** New version doesn't exist yet, we create it even
		 * if the symlink is not updated */
		saversion.len = 0 ;
		if (!auto_stra(&saversion,dori,"/",version))
			log_warn_return(LOG_EXIT_ZERO,"stralloc") ;
		if (!dir_create_parent(saversion.s,0755))
			log_warnsys_return(LOG_EXIT_ZERO,"create directory: ",saversion.s) ;
		if (!write_env(name,&sv->saenv,saversion.s))
			log_warnu_return(LOG_EXIT_ZERO,"write environment at: ",saversion.s) ;
	}

	create:
	if (!dir_create_parent(dst->s,0755))
		log_warnsys_return(LOG_EXIT_ZERO,"create directory: ",dst->s) ;

	/** atomic_symlink check if exist
	 * if it doesn't exist, it create it*/
	if (update_symlink)
		if (!atomic_symlink(dst->s,sym_version,"env_compute"))
			log_warnu_return(LOG_EXIT_ZERO,"symlink: ",sym_version," to: ",dst->s) ;

	if (write)
		if (!write_env(name,&sv->saenv,dst->s))
			log_warnu_return(LOG_EXIT_ZERO,"write environment at: ",dst->s) ;

	/** last time guys! */
	if (format)
	{
		char tmp[dst->len + 1 + strlen(name) + 1] ;
		auto_strings(tmp,dst->s,"/",name) ;

		if (rename(old,tmp) == -1)
			log_warnusys_return(LOG_EXIT_ZERO,"rename: ",old," to: ",tmp) ;
		if (rm_rf(old) == -1)
			log_warnusys_return(LOG_EXIT_ZERO,"remove: ",old) ;
	}

	stralloc_free(&saversion) ;
	return update_symlink ? update_symlink : 1 ;
}

/* @Return 0 on crash
 * @Return 1 if no need to write
 * @Return 2 if need to write 
 * it appends @result with the user file if
 * conf = 0 otherwise it appends @result with
 * the upstream file modified by env_merge_conf function*/
int env_compute(stralloc *result,sv_alltype *sv, uint8_t conf)
{
	int r, write = 1 ;
	uint8_t symlink_updated = 0 ;
	stralloc dst = STRALLOC_ZERO ;
	stralloc old_dst = STRALLOC_ZERO ;
	stralloc salist = STRALLOC_ZERO ;

	if (!auto_stra(&dst,keep.s + sv->srconf))
		log_warn_return(LOG_EXIT_ZERO,"stralloc") ;

	symlink_updated = env_make_symlink(&dst,&old_dst,sv,conf) ;
	if (!symlink_updated) return 0 ;

	if (!auto_stra(&dst,"/",keep.s + sv->cname.name))
		log_warn_return(LOG_EXIT_ZERO,"stralloc") ;

	/** dst-> /etc/66/conf/service/version/confile */
	r = scan_mode(dst.s,S_IFREG) ;
	if (!r || conf > 2)
	{ 
		// copy config file from upstream in sysadmin
		if (!stralloc_copy(result,&sv->saenv))
			log_warnsys_return(LOG_EXIT_ZERO,"stralloc") ;
		write = 2 ;
		goto freed ;
	}
	else if (conf > 0)
	{
		if ((symlink_updated == 3) && old_dst.len)
		{
			if (!auto_stra(&old_dst,"/",keep.s + sv->cname.name))
				log_warn_return(LOG_EXIT_ZERO,"stralloc") ;

			if (!file_readputsa_g(&salist,old_dst.s))
				log_warnusys_return(LOG_EXIT_ZERO,"read: ",old_dst.s) ;
		}
		else
		{
			if (!file_readputsa_g(&salist,dst.s))
				log_warnusys_return(LOG_EXIT_ZERO,"read: ",dst.s) ;
		}

		//merge config from upstream to sysadmin
		if (!env_merge_conf(result,&salist,&sv->saenv,conf))
			log_warnu_return(LOG_EXIT_ZERO,"merge environment file") ;
		write = 2 ;
		goto freed ;
	}

	if (!file_readputsa_g(result,dst.s))
		log_warnusys_return(LOG_EXIT_ZERO,"read: ",dst.s) ;
	
	freed:
	stralloc_free(&dst) ;
	stralloc_free(&old_dst) ;
	stralloc_free(&salist) ;
	
	return write ;
}

int env_clean_with_comment(stralloc *sa)
{
	ssize_t pos = 0, r ;
	char *end = 0, *start = 0 ;
	stralloc final = STRALLOC_ZERO ;
	stralloc tmp = STRALLOC_ZERO ;

	if (!sastr_split_string_in_nline(sa)) 
		log_warnu_return(LOG_EXIT_ZERO,"split environment value") ;

	for (; pos < sa->len ; pos += strlen(sa->s + pos) + 1)
	{
		tmp.len = 0 ;
		if (!sastr_clean_string(&tmp,sa->s + pos))
			log_warnu_return(LOG_EXIT_ZERO,"clean environment string") ;
		/** keep a empty line between key=value pair and a comment */
		r = get_len_until(tmp.s,'=') ;
		end = r < 0 ? "\n" : "\n\n" ;
		start = r < 0 ? "" : "\n" ;
		
		if (tmp.s[0] == '#')
		{
			if (!sastr_rebuild_in_oneline(&tmp))
				log_warnu_return(LOG_EXIT_ZERO,"rebuild environment string in one line") ;
		}
		else
		{
			if (!environ_rebuild_line(&tmp))
				log_warnu_return(LOG_EXIT_ZERO,"rebuild environment line") ;
		}
				
		if (!auto_stra(&final,start,tmp.s,end))
			log_warn_return(LOG_EXIT_ZERO,"append stralloc") ;		
	}
	sa->len = 0 ;
	if (!auto_stra(sa,final.s))
		log_warnu_return(LOG_EXIT_ZERO,"store environment value") ;

	stralloc_free(&tmp) ;
	stralloc_free(&final) ;
	
	return 1 ;
}

int env_find_current_version(stralloc *sa,char const *svconf)
{
	size_t svconflen = strlen(svconf) ;
	char tmp[svconflen + SS_SYM_VERSION_LEN + 1] ;
	auto_strings(tmp,svconf,SS_SYM_VERSION) ;
	if (sareadlink(sa,tmp) == -1) return 0 ;
	if (!stralloc_0(sa)) log_warnusys_return(LOG_EXIT_ZERO,"stralloc") ;
	return 1 ;
}