1 /* direvent - directory content watcher daemon
2    Copyright (C) 2012-2016 Sergey Poznyakoff
3 
4    Direvent is free software; you can redistribute it and/or modify it
5    under the terms of the GNU General Public License as published by the
6    Free Software Foundation; either version 3 of the License, or (at your
7    option) any later version.
8 
9    Direvent is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with direvent. If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "direvent.h"
18 #include "wordsplit.h"
19 #include <ctype.h>
20 
21 extern char **environ;    /* Environment */
22 
23 #define DEBUG_ENVIRON(l,env) do {				\
24 	if (debug_level >= (l)) {				\
25 		diag(LOG_DEBUG, _("environment: "));		\
26 		for (i = 0; (env)[i]; i++)			\
27 			diag(LOG_DEBUG, "%s ", (env)[i]);	\
28 		diag(LOG_DEBUG, "\n");				\
29 	}							\
30 	} while (0)
31 
32 static int
find_env_pos(char ** env,char * name,size_t * idx,size_t * valoff)33 find_env_pos(char **env, char *name, size_t *idx, size_t *valoff)
34 {
35         size_t nlen = strcspn(name, "+=");
36         size_t i;
37 
38         for (i = 0; env[i]; i++) {
39                 size_t elen = strcspn(env[i], "=");
40                 if (elen == nlen && memcmp(name, env[i], nlen) == 0) {
41 			if (idx)
42 				*idx = i;
43 			if (valoff)
44 				*valoff = elen + 1;
45 			return 0;
46 		}
47         }
48         return -1;
49 }
50 
51 static char *
find_env_ptr(char ** env,char * name,int val)52 find_env_ptr(char **env, char *name, int val)
53 {
54 	size_t i, j;
55 	if (find_env_pos(env, name, &i, &j))
56 		return NULL;
57 	return val ? env[i] + j : env[i];
58 }
59 
60 static int
var_is_unset(char ** env,const char * name)61 var_is_unset(char **env, const char *name)
62 {
63 	int i;
64 	int nlen = strcspn(name, "=");
65 
66 	for (i = 0; env[i]; i++) {
67 		if (env[i][0] == '-') {
68 			size_t elen = strcspn(env[i] + 1, "=");
69 			if (elen == nlen &&
70 			    memcmp(name, env[i] + 1, nlen) == 0) {
71 				if (env[i][nlen + 1])
72 					return strcmp(name + nlen,
73 						      env[i] + 1 + nlen) == 0;
74 				else
75 					return 1;
76 			}
77 		}
78 	}
79 	return 0;
80 }
81 
82 static char *
env_concat(const char * name,size_t namelen,const char * a,const char * b)83 env_concat(const char *name, size_t namelen, const char *a, const char *b)
84 {
85 	char *res;
86 	size_t len;
87 
88 	if (a && b) {
89 		res = emalloc(namelen + 1 + strlen(a) + strlen(b) + 1);
90 		strcpy(res + namelen + 1, a);
91 		strcat(res + namelen + 1, b);
92 	} else if (a) {
93 		len = strlen(a);
94 		if (ispunct(a[len-1]))
95 			len--;
96 		res = emalloc(namelen + 1 + len + 1);
97 		memcpy(res + namelen + 1, a, len);
98 		res[namelen + 1 + len] = 0;
99 	}
100 	else { /* if (a == NULL) */
101 		if (ispunct(b[0]))
102 			b++;
103 		len = strlen(b);
104 		res = emalloc(namelen + 1 + len + 1);
105 		strcpy(res + namelen + 1, b);
106 	}
107 	memcpy(res, name, namelen);
108 	res[namelen] = '=';
109 	return res;
110 }
111 
112 static char *defenv[] = {
113 	"DIREVENT_SYSEV_CODE=${sysev_code}",
114 	"DIREVENT_SYSEV_NAME=${sysev_name}",
115 	"DIREVENT_GENEV_CODE=${genev_code}",
116 	"DIREVENT_GENEV_NAME=${genev_name}",
117 	"DIREVENT_FILE=${file}",
118 	NULL
119 };
120 
121 char **
environ_setup(char ** hint,char ** kve)122 environ_setup(char **hint, char **kve)
123 {
124 	char *empty[1] = { NULL };
125 	char **old_env = environ;
126 	char **new_env;
127 	char **addenv = defenv;
128 	char *var;
129 	size_t count, i, j, n;
130 	struct wordsplit ws;
131 	int wsflags = WRDSF_NOCMD | WRDSF_QUOTE | WRDSF_NOSPLIT |
132 		      WRDSF_ENV | WRDSF_ENV_KV;
133 
134 	ws.ws_env = (const char **) kve;
135 
136 	if (!hint)
137 		hint = empty;
138 	else if (strcmp(hint[0], "-") == 0 || strcmp(hint[0], "--") == 0) {
139 		old_env = NULL;
140 		if (hint[0][1] == '-')
141 			addenv = empty;
142 		hint++;
143         }
144 
145 	/* Count new environment size */
146 	count = 0;
147 	if (old_env)
148 		for (i = 0; old_env[i]; i++)
149 			count++;
150 
151 	for (i = 0; addenv[i]; i++)
152 		count++;
153 
154 	for (i = 0; hint[i]; i++)
155 		count++;
156 
157 	if (self_test_pid)
158 		count++;
159 
160 	/* Allocate new environment. */
161 	new_env = ecalloc(count + 1, sizeof new_env[0]);
162 
163 	/* Populate the environment. */
164 	n = 0;
165 
166 	if (old_env)
167 		for (i = 0; old_env[i]; i++) {
168 			if (!var_is_unset(hint, old_env[i]))
169 				new_env[n++] = old_env[i];
170 		}
171 
172 	for (i = 0; addenv[i]; i++)
173 		if (!var_is_unset(hint, addenv[i])) {
174 			if (wordsplit(addenv[i], &ws, wsflags)) {
175 				diag(LOG_CRIT, "wordsplit: %s",
176 				     wordsplit_strerror(&ws));
177 				_exit(127);
178 			}
179 			wsflags |= WRDSF_REUSE;
180 			new_env[n++] = estrdup(ws.ws_wordv[0]);
181 		}
182 
183 	for (i = 0; hint[i]; i++) {
184 		char *p;
185 
186 		if (hint[i][0] == '-') {
187 			/* Skip unset directives. */
188 			continue;
189 		}
190 
191 		if (wordsplit(hint[i], &ws, wsflags)) {
192 			diag(LOG_CRIT, "wordsplit: %s",
193 			     wordsplit_strerror(&ws));
194 			_exit(127);
195 		}
196 		wsflags |= WRDSF_REUSE;
197 		var = ws.ws_wordv[0];
198 
199 		/* Find the slot for the variable.  Use next available
200 		   slot if there's no such variable in new_env */
201 		if (find_env_pos(new_env, hint[i], &j, NULL))
202 			j = n;
203 
204 		if ((p = strchr(var, '='))) {
205 			if (p == var)
206 				continue; /* Ignore erroneous entry */
207 
208 			if (p[-1] == '+')
209 				new_env[j] = env_concat(var,
210 							p - var - 1,
211 							find_env_ptr(environ,
212 								     var, 1),
213 							p + 1);
214 			else if (p[1] == '+')
215 				new_env[j] = env_concat(var,
216 							p - var,
217 							p + 2,
218 							find_env_ptr(environ,
219 								     var, 1));
220 			else
221 				new_env[j] = estrdup(var);
222                 } else if ((p = find_env_ptr(environ, hint[i], 0)))
223 			new_env[j] = p;
224 		else
225 			continue;
226 		/* Adjust environment size */
227 		if (j == n)
228 			++n;
229 	}
230 	if (self_test_pid) {
231 		char buf[512];
232 		snprintf(buf, sizeof buf, "DIREVENT_SELF_TEST_PID=%lu",
233 			 (unsigned long)self_test_pid);
234 		new_env[n++] = estrdup(buf);;
235 	}
236 	new_env[n] = NULL;
237 
238 	if (wsflags & WRDSF_REUSE)
239 		wordsplit_free(&ws);
240 	return new_env;
241 }
242