/* * 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 ; }