Line data Source code
1 : /*
2 : Copyright © 2018 by Danny Goossen, Gioxa Ltd. All rights reserved.
3 :
4 : This file is part of the oc-runner
5 :
6 : MIT License
7 :
8 : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : of this software and associated documentation files (the "Software"), to deal
10 : in the Software without restriction, including without limitation the rights
11 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : copies of the Software, and to permit persons to whom the Software is
13 : furnished to do so, subject to the following conditions:
14 :
15 : The above copyright notice and this permission notice shall be included in all
16 : copies or substantial portions of the Software.
17 :
18 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : SOFTWARE.
25 :
26 : */
27 : /*! \file wordexp_var.c
28 : * \briefexpand safely variables and commandlines
29 : * \author Created by Danny Goossen on 23/11/17.
30 : * \copyright 2018, Danny Goossen. MIT License
31 : *
32 : */
33 :
34 : #include "deployd.h"
35 : #include "wordexp_var.h"
36 :
37 :
38 : /**
39 : * \brief helper function to make socket non blocking
40 : * \param sock the socket
41 : * \return -1 on failure,
42 : * \note on failure, caller is responsible to close socket
43 : */
44 : static int socket_nonblocking (int sock);
45 :
46 13 : int word_exp_var(char * output_buf,int buff_len, const char* valuestring ,const cJSON * env_vars)
47 : {
48 :
49 : //alert("word_exp: start substitute %s\n",valuestring);
50 : // feedback buffer
51 13 : int v_exit_code=0;
52 13 : int * exitcode=&v_exit_code;
53 :
54 : pid_t child_pid;
55 :
56 : int aStdoutPipe[2];
57 13 : if (pipe(aStdoutPipe) < 0) {sock_error("allocating pipe for child stdout redirect");snprintf(output_buf,buff_len,"System error\n");return(15);}
58 : // Fork_it
59 13 : child_pid = fork();
60 :
61 26 : if(child_pid == 0)
62 : {
63 : //close uneeded read pipeend
64 13 : close(aStdoutPipe[PIPE_READ]);
65 :
66 : // set environment
67 13 : if (env_vars && cJSON_GetArraySize(env_vars)>0)
68 : {
69 13 : cJSON * pos=NULL;
70 46 : cJSON_ArrayForEach(pos, env_vars)
71 : {
72 33 : if (cJSON_IsString(pos))
73 : {
74 33 : setenv(pos->string,pos->valuestring,1);
75 : //fprintf(stderr,"add env var: %s\n",pos->valuestring);
76 : }
77 : }
78 : }
79 : // expand it
80 : wordexp_t word_re;
81 :
82 :
83 : // errors on undef and commands
84 13 : int res=0;
85 13 : switch (res=wordexp( valuestring, &word_re, WRDE_NOCMD|WRDE_SHOWERR))
86 : {
87 : case WRDE_NOSPACE:
88 : /* If the error was WRDE_NOSPACE,
89 : then perhaps part of the result was allocated. */
90 0 : wordfree (&word_re);
91 0 : break;
92 : default: /* Some other error. */
93 : break;
94 : }
95 13 : if (res==0)
96 : {
97 : // write back the result
98 : int i=0;
99 9 : for (i=0;i<word_re.we_wordc;i++)
100 : {
101 9 : write( aStdoutPipe[PIPE_WRITE],word_re.we_wordv[i],strlen(word_re.we_wordv[i]));
102 9 : if (i+1!=word_re.we_wordc) write( aStdoutPipe[PIPE_WRITE]," ",1);
103 : }
104 : //cleanup
105 11 : wordfree(&word_re);
106 : }
107 : //signal linux end, linux does not get signal on close
108 13 : shutdown(aStdoutPipe[PIPE_WRITE], SHUT_WR);
109 13 : close(aStdoutPipe[PIPE_WRITE]);
110 : //exit, job done succesfull
111 13 : exit(res);
112 : }
113 13 : else if (child_pid>0) // The Parent
114 : {
115 : // parent wait's and return result
116 : // close unused file descriptors, these are for child only
117 13 : close(aStdoutPipe[PIPE_WRITE]);
118 13 : usleep(100); // wait a bit to get things settled
119 : fd_set rds;
120 13 : int sfd=aStdoutPipe[PIPE_READ];
121 13 : int s_ret=0;
122 13 : int s_err=0;
123 13 : size_t o_read=0;
124 13 : int o_r_error=0;
125 : struct timeval tv;
126 13 : tv.tv_sec = 10;
127 13 : tv.tv_usec = 0;
128 13 : int timeout=0;
129 13 : memset(output_buf,0,buff_len); // clear buffer!!!
130 13 : socket_nonblocking(aStdoutPipe[PIPE_READ]);
131 : do
132 : {
133 22 : FD_ZERO( &rds );
134 22 : FD_SET( aStdoutPipe[PIPE_READ], &rds );
135 22 : tv.tv_sec = 10;
136 22 : s_ret=select(sfd+1, &rds, NULL, NULL, &tv);
137 22 : if (s_ret==SOCKET_ERROR) s_err=errno;
138 22 : else if (s_ret>0)
139 : {
140 22 : size_t len=strlen(output_buf);
141 22 : size_t read_now= buff_len-len-1;
142 22 : if (read_now<1)
143 : {
144 0 : error("bash_it truncating output\n");
145 : char trunck[1024];
146 0 : o_read=read(aStdoutPipe[PIPE_READ], trunck, 1024);
147 : }
148 : else
149 : {
150 22 : char * to_buf=output_buf+len;
151 22 : o_read=read(aStdoutPipe[PIPE_READ], to_buf, read_now);
152 : }
153 22 : if (o_read==0) break;
154 9 : else if (o_read >0);
155 0 : else if ((o_r_error=errno)!=EINTR && o_r_error!=EAGAIN) { sock_error_no("std_out pipe read", o_r_error);break; }
156 : }
157 : else
158 : {
159 0 : sprintf((output_buf),"System Error: timeout substituding commands!!!");
160 0 : *exitcode=1;
161 0 : timeout=1;
162 0 : break;
163 : }
164 9 : } while (s_ret!=SOCKET_ERROR || ( s_ret==SOCKET_ERROR && (s_err==EINTR || s_err==EAGAIN || s_err==EWOULDBLOCK)));
165 13 : close(aStdoutPipe[PIPE_READ]);
166 13 : usleep(100);
167 : int status;
168 13 : if (timeout)
169 : {
170 0 : debug("Timeout, kill child\n");
171 0 : kill(child_pid,SIGKILL); // TODO need to check with WHOANG
172 : }
173 : pid_t w=0;
174 : int this_error=0;
175 13 : while ((w= waitpid(child_pid,&status, 0)) ==-1 && (this_error=errno)==EINTR )
176 : {
177 0 : debug("bash_it: interupted Syscall\n");
178 : }
179 13 : if (w==-1) {
180 0 : sock_error_no("waitpid",this_error);
181 0 : if (!*exitcode) sprintf((output_buf),"System error: error on 'waitpid()': %s!!!",strerror(this_error));
182 0 : *exitcode=1;
183 : }
184 13 : else if (WIFEXITED(status))
185 : {
186 13 : *exitcode=WEXITSTATUS(status);
187 13 : if (*exitcode)
188 2 : info("exit cmd: bash_it w error: %d\n", *exitcode);
189 : else
190 : {
191 : //info("[OK]\n");
192 : //sprintf(output_buf+strlen(output_buf),"\t[OK]\n");
193 : ;
194 : }
195 : }
196 0 : else if (WIFSIGNALED(status) || WIFSTOPPED(status)) {
197 0 : alert("command stopped/killed by signal %d : %s", WTERMSIG(status),strsignal(WSTOPSIG(status)));
198 0 : *exitcode=1;
199 0 : if (!timeout)
200 0 : sprintf((output_buf),"System Error: Bash stopped/killed by signal %d : %s!!!", WTERMSIG(status),strsignal(WSTOPSIG(status)));
201 : }
202 : else
203 : { /* Non-standard case -- may never happen */
204 0 : error("Unexpected status child (0x%x)\n", status);*exitcode=status;}
205 : }
206 : else
207 : {
208 0 : error("Fork failed \n"); sprintf((output_buf),"System Error: FORK failed!!!");*exitcode=1;}
209 13 : return (*exitcode);
210 : }
211 :
212 :
213 :
214 0 : void word_exp_var_free(word_exp_var_t * wev)
215 : {
216 0 : if (wev->argv) free((char**)wev->argv);
217 0 : wev->argv=NULL;
218 0 : if(wev->args_obj) cJSON_Delete(wev->args_obj);
219 0 : wev->args_obj=NULL;
220 0 : if(wev->errmsg) free(wev->errmsg);
221 0 : wev->errmsg=NULL;
222 0 : wev->argc=0;
223 0 : }
224 :
225 :
226 0 : int word_exp_var_dyn(word_exp_var_t * wev, const char* valuestring ,const cJSON * env_vars,const char * dir)
227 : {
228 :
229 : //alert("word_exp: start substitute %s\n",valuestring);
230 : // feedback buffer
231 0 : int v_exit_code=0;
232 0 : int * exitcode=&v_exit_code;
233 :
234 : pid_t child_pid;
235 :
236 : int aStdoutPipe[2];
237 0 : if (pipe(aStdoutPipe) < 0)
238 : {
239 0 : sock_error("allocating pipe for child stdout redirect");
240 :
241 0 : asprintf(&wev->errmsg,"System error\n");
242 0 : return(15);
243 : }
244 : // Fork_it
245 0 : child_pid = fork();
246 :
247 0 : if(child_pid == 0)
248 : {
249 : //close uneeded read pipeend
250 0 : close(aStdoutPipe[PIPE_READ]);
251 0 : dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO);
252 : // set environment
253 0 : if (env_vars && cJSON_GetArraySize(env_vars)>0)
254 : {
255 0 : cJSON * pos=NULL;
256 0 : cJSON_ArrayForEach(pos, env_vars)
257 : {
258 0 : if (cJSON_IsString(pos))
259 : {
260 0 : setenv(pos->string,pos->valuestring,1);
261 : }
262 : }
263 : }
264 : //const char * project_dir=cJSON_get_key(env_vars, "CI_PROJECT_DIR");
265 : //int samedir=0;
266 : //if (dir && project_dir && strcmp(project_dir,dir)==0) samedir=1;
267 : // expand it
268 : wordexp_t word_re;
269 : // TODO need safe_chdir, stat first!!!
270 0 : if (dir && strlen(dir)>0) chdir(dir);
271 :
272 : // errors on undef and commands
273 0 : int res=0;
274 0 : switch (res=wordexp( valuestring, &word_re, WRDE_NOCMD))
275 : {
276 : case 0:
277 : {
278 : // write back the result
279 0 : cJSON * cjsonStringArray=cJSON_CreateStringArray((const char**)word_re.we_wordv,(int) word_re.we_wordc);
280 0 : if (cjsonStringArray)
281 : {
282 : /*
283 : cJSON * item_s=NULL;
284 : // loop results and prepend path if needed
285 :
286 : cJSON_ArrayForEach(item_s, cjsonStringArray)
287 : {
288 : if (!(strchr(item_s->valuestring,'/')==word_re.we_wordv[i] || samedir || !project_dir))
289 : {
290 : char *tmp=item_s->valuestring;
291 : asprintf(&item_s->valuestring, "%s/%s",project_dir,tmp);
292 : free(tmp);
293 : }
294 : }
295 : */
296 : // serialize
297 0 : char * result=cJSON_Print(cjsonStringArray);
298 0 : size_t size_total=strlen(result);
299 0 : size_t size_total_writen=0;
300 0 : size_t size_towrite=0;
301 0 : ssize_t size_writen=1;
302 0 : while (size_total_writen<size_total && size_writen>0)
303 : {
304 0 : size_towrite=size_total-size_total_writen;
305 0 : size_writen=write( aStdoutPipe[PIPE_WRITE],&result[size_total_writen],size_towrite);
306 0 : if (size_writen>=0) size_total_writen+=size_writen;
307 : }
308 0 : if (size_writen<0) res=-1;
309 0 : cJSON_Delete(cjsonStringArray);
310 0 : cjsonStringArray=NULL;
311 :
312 0 : free(result);
313 0 : result=NULL;
314 : }
315 0 : else fprintf(stderr, "No string array\n");
316 : //cleanup
317 0 : wordfree(&word_re);
318 :
319 : }
320 0 : break;
321 : case WRDE_BADVAL:
322 0 : fprintf(stderr,"An undefined shell variable was referenced.\n");
323 0 : break;
324 : case WRDE_BADCHAR:
325 0 : fprintf(stderr,"Illegal occurrence of newline or one of |, &, ;, <, >, (, ),{, }.\n");
326 0 : break;
327 : case WRDE_SYNTAX:
328 0 : fprintf(stderr,"Syntax error, such as unbalanced parentheses or unmatched quotes.\n");
329 0 : break;
330 : case WRDE_CMDSUB:
331 0 : fprintf(stderr,"Command substitution like `cmd` and $(cmd) are not allowed.\n");
332 0 : break;
333 : case WRDE_NOSPACE:
334 : /* If the error was WRDE_NOSPACE,
335 : then perhaps part of the result was allocated. */
336 0 : fprintf(stderr,"Out of Memory.\n");
337 0 : wordfree (&word_re);
338 0 : break;
339 : default: /* Some other error. */
340 : break;
341 : }
342 : //signal linux end, linux does not get signal on close
343 0 : shutdown(aStdoutPipe[PIPE_WRITE], SHUT_WR);
344 0 : close(aStdoutPipe[PIPE_WRITE]);
345 : //exit, job done succesfull
346 0 : exit(res);
347 : }
348 0 : else if (child_pid>0) // The Parent
349 : {
350 : // parent wait's and return result
351 : // close unused file descriptors, these are for child only
352 0 : close(aStdoutPipe[PIPE_WRITE]);
353 0 : usleep(100); // wait a bit to get things settled
354 : fd_set rds;
355 0 : int sfd=aStdoutPipe[PIPE_READ];
356 0 : int s_ret=0;
357 0 : int s_err=0;
358 0 : size_t o_read=0;
359 0 : int o_r_error=0;
360 : struct timeval tv;
361 0 : tv.tv_sec = 10;
362 0 : tv.tv_usec = 0;
363 0 : int timeout=0;
364 0 : dynbuffer * dyn_buff=dynbuffer_init();
365 0 : socket_nonblocking(aStdoutPipe[PIPE_READ]);
366 : do
367 : {
368 : char * read_buffer[1024];
369 0 : memset(read_buffer, 0, 1024);
370 0 : FD_ZERO( &rds );
371 0 : FD_SET( aStdoutPipe[PIPE_READ], &rds );
372 0 : tv.tv_sec = 10;
373 0 : s_ret=select(sfd+1, &rds, NULL, NULL, &tv);
374 0 : if (s_ret==SOCKET_ERROR) s_err=errno;
375 0 : else if (s_ret>0)
376 : {
377 :
378 :
379 0 : o_read=read(aStdoutPipe[PIPE_READ], read_buffer,1024);
380 0 : if (o_read==0) break;
381 0 : else if (o_read >0) dynbuffer_write_n(read_buffer, o_read, dyn_buff);
382 0 : else if ((o_r_error=errno)!=EINTR && o_r_error!=EAGAIN) { sock_error_no("std_out pipe read", o_r_error);break; }
383 : }
384 : else
385 : {
386 0 : dynbuffer_write_v(dyn_buff,"System Error: timeout substituding commands!!!");
387 0 : *exitcode=1;
388 0 : timeout=1;
389 0 : break;
390 : }
391 0 : } while (s_ret!=SOCKET_ERROR || ( s_ret==SOCKET_ERROR && (s_err==EINTR || s_err==EAGAIN || s_err==EWOULDBLOCK)));
392 0 : close(aStdoutPipe[PIPE_READ]);
393 0 : usleep(100);
394 : int status;
395 0 : if (timeout)
396 : {
397 0 : debug("Timeout, kill child\n");
398 0 : kill(child_pid,SIGKILL); // TODO need to check with WHOANG
399 : }
400 : pid_t w=0;
401 : int this_error=0;
402 0 : while ((w= waitpid(child_pid,&status, 0)) ==-1 && (this_error=errno)==EINTR )
403 : {
404 0 : debug("bash_it: interupted Syscall\n");
405 : }
406 0 : if (w==-1) {
407 0 : sock_error_no("waitpid",this_error);
408 0 : if (!*exitcode) dynbuffer_write_v(dyn_buff,"System error: error on 'waitpid()': %s!!!",strerror(this_error));
409 0 : *exitcode=1;
410 : }
411 0 : else if (WIFEXITED(status))
412 : {
413 0 : *exitcode=WEXITSTATUS(status);
414 0 : if (*exitcode)
415 0 : info("exit cmd: wordexp w error: %d\n", *exitcode);
416 : else
417 : {
418 : //info("[OK]\n");
419 : //sprintf(output_buf+strlen(output_buf),"\t[OK]\n");
420 : ;
421 : }
422 : }
423 0 : else if (WIFSIGNALED(status) || WIFSTOPPED(status)) {
424 0 : alert("command stopped/killed by signal %d : %s", WTERMSIG(status),strsignal(WSTOPSIG(status)));
425 0 : *exitcode=1;
426 0 : if (!timeout)
427 0 : dynbuffer_write_v(dyn_buff,"System Error: wordexp stopped/killed by signal %d : %s!!!", WTERMSIG(status),strsignal(WSTOPSIG(status)));
428 : }
429 : else
430 : { /* Non-standard case -- may never happen */
431 0 : error("Unexpected status child (0x%x)\n", status);*exitcode=status;
432 : }
433 0 : if (*exitcode)
434 0 : dynbuffer_pop(&dyn_buff, &wev->errmsg, NULL);
435 : else
436 : {
437 0 : char * tmp=NULL;
438 0 : dynbuffer_pop(&dyn_buff, &tmp, NULL);
439 0 : wev->args_obj=cJSON_Parse(tmp);
440 0 : if (wev->args_obj)
441 : {
442 0 : wev->argc=cJSON_GetArraySize(wev->args_obj);
443 0 : wev->argv=calloc(wev->argc+1,sizeof(char*const)); // end with null pointer, just in case we need!
444 0 : char**walk=(char**)wev->argv;
445 0 : cJSON*item=NULL;
446 0 : cJSON_ArrayForEach (item,wev->args_obj)
447 : {
448 0 : *walk=item->valuestring;
449 0 : walk++;
450 : }
451 : }
452 : }
453 0 : dynbuffer_clear(&dyn_buff);
454 : }
455 : else
456 : {
457 0 : error("Fork failed \n");
458 0 : asprintf(&wev->errmsg,"System Error: FORK failed!!!");
459 0 : *exitcode=1;
460 : }
461 :
462 0 : return (*exitcode);
463 : }
464 :
465 :
466 13 : static int socket_nonblocking (int sock)
467 : {
468 : int flags;
469 :
470 13 : if(sock != INVALID_SOCKET)
471 : {
472 13 : flags = fcntl (sock, F_GETFL, 0);
473 13 : return fcntl (sock, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC | SO_KEEPALIVE);//| O_NDELAY);
474 : }
475 : return -1;
476 :
477 : }
|