1 /*
2  * CDDL HEADER START
3  *
4  * This file and its contents are supplied under the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may use this file only in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.opensource.org/licenses/cddl1.txt
12  * See the License for the specific language governing permissions
13  * and limitations under the License.
14  *
15  * When distributing Covered Code, include this CDDL HEADER in each
16  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17  * If applicable, add the following below this CDDL HEADER, with the
18  * fields enclosed by brackets "[]" replaced with your own identifying
19  * information: Portions Copyright [yyyy] [name of copyright owner]
20  *
21  * CDDL HEADER END
22  */
23 /*
24  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
25  * Use is subject to license terms.
26  */
27 /*
28  * @(#)state.cc 1.27 06/12/12
29  */
30 
31 #pragma	ident	"@(#)state.cc	1.27	06/12/12"
32 
33 /*
34  * Copyright 2017-2018 J. Schilling
35  *
36  * @(#)state.cc	1.7 21/08/15 2017-2018 J. Schilling
37  */
38 #include <schily/mconfig.h>
39 #ifndef lint
40 static	UConst char sccsid[] =
41 	"@(#)state.cc	1.7 21/08/15 2017-2018 J. Schilling";
42 #endif
43 
44 /*
45  *	state.c
46  *
47  *	This file contains the routines that write the .make.state file
48  */
49 
50 /*
51  * Included files
52  */
53 #include <mk/defs.h>
54 #include <mksh/misc.h>		/* errmsg() */
55 #include <setjmp.h>		/* setjmp() */
56 #include <locale.h>		/* MB_CUR_MAX    */
57 
58 /*
59  * Defined macros
60  */
61 #define LONGJUMP_VALUE 17
62 #define XFWRITE(string, length, fd) {if (fwrite(string, 1, length, fd) == 0) \
63 					longjmp(long_jump, LONGJUMP_VALUE);}
64 #define XPUTC(ch, fd) { \
65 	if (putc((int) ch, fd) == EOF) \
66 		longjmp(long_jump, LONGJUMP_VALUE); \
67 	}
68 #define XFPUTS(string, fd) fputs(string, fd)
69 
70 /*
71  * typedefs & structs
72  */
73 
74 /*
75  * Static variables
76  */
77 
78 /*
79  * File table of contents
80  */
escape_target_name(Name np)81 static char * escape_target_name(Name np)
82 {
83 	if(np->dollar) {
84 		int len = strlen(np->string_mb);
85 		char * buff = (char*)malloc(2 * len);
86 		int pos = 0;
87 		wchar_t wc;
88 		int pp = 0;
89 		while(pos < len) {
90 			int n = mbtowc(&wc, np->string_mb + pos, MB_CUR_MAX);
91 			if(n < 0) { // error - this shouldn't happen
92 				(void)free(buff);
93 				return strdup(np->string_mb);
94 			}
95 			if(wc == dollar_char) {
96 				buff[pp] = '\\'; pp++;
97 				buff[pp] = '$'; pp++;
98 			} else {
99 				for(int j=0;j<n;j++) {
100 					buff[pp] = np->string_mb[pos+j]; pp++;
101 				}
102 			}
103 			pos += n;
104 		}
105 		buff[pp] = '\0';
106 		return buff;
107 	} else {
108 		return strdup(np->string_mb);
109 	}
110 }
111 
112 static	void		print_auto_depes(register Dependency dependency, register FILE *fd, register Boolean built_this_run, register int *line_length, register char *target_name, jmp_buf long_jump);
113 
114 /*
115  *	write_state_file(report_recursive, exiting)
116  *
117  *	Write a new version of .make.state
118  *
119  *	Parameters:
120  *		report_recursive	Should only be done at end of run
121  *		exiting			true if called from the exit handler
122  *
123  *	Global variables used:
124  *		built_last_make_run The Name ".BUILT_LAST_MAKE_RUN", written
125  *		command_changed	If no command changed we do not need to write
126  *		current_make_version The Name "<current version>", written
127  *		do_not_exec_rule If -n is on we do not write statefile
128  *		hashtab		The hashtable that contains all names
129  *		keep_state	If .KEEP_STATE is no on we do not write file
130  *		make_state	The Name ".make.state", used for opening file
131  *		make_version	The Name ".MAKE_VERSION", written
132  *		recursive_name	The Name ".RECURSIVE", written
133  *		rewrite_statefile Indicates that something changed
134  */
135 
136 void
137 #ifdef NSE
write_state_file(int report_recursive,Boolean exiting)138 write_state_file(int report_recursive, Boolean exiting)
139 #else
140 write_state_file(int, Boolean exiting)
141 #endif
142 {
143 	register FILE		*fd;
144 	int			lock_err;
145 	char			buffer[MAXPATHLEN];
146 	char			make_state_tempfile[MAXPATHLEN];
147 	jmp_buf			long_jump;
148 	register int		attempts = 0;
149 	Name_set::iterator	np, e;
150 	register Property	lines;
151 	register int		m;
152 	Dependency		dependency;
153 	register Boolean	name_printed;
154 	Boolean			built_this_run = false;
155 	char			*target_name;
156 	int			line_length;
157 	register Cmd_line	cp;
158 
159 
160 	if (!rewrite_statefile ||
161 	    !command_changed ||
162 	    !keep_state ||
163 	    do_not_exec_rule ||
164 	    (report_dependencies_level > 0)) {
165 		return;
166 	}
167 	/* Lock the file for writing. */
168  	make_state_lockfile = getmem(strlen(make_state->string_mb) + strlen(NOCATGETS(".lock")) + 1);
169  	(void) sprintf(make_state_lockfile,
170  	               NOCATGETS("%s.lock"),
171  	               make_state->string_mb);
172 	if ((lock_err = file_lock(make_state->string_mb,
173 				 make_state_lockfile,
174 				 (int *) &make_state_locked, 0)) != 0) {
175  		retmem_mb(make_state_lockfile);
176 		make_state_lockfile = NULL;
177 
178 		/*
179 		 * We need to make sure that we are not being
180 		 * called by the exit handler so we don't call
181 		 * it again.
182 		 */
183 
184 		if (exiting) {
185 			(void) sprintf(buffer, NOCATGETS("%s/.make.state.%d.XXXXXX"), tmpdir, getpid());
186 			report_pwd = true;
187 			warning(gettext("Writing to %s"), buffer);
188 			int fdes = mkstemp(buffer);
189 			if ((fdes < 0) || (fd = fdopen(fdes, "w")) == NULL) {
190 				fprintf(stderr,
191 					gettext("Could not open statefile `%s': %s"),
192 					buffer,
193 					errmsg(errno));
194 				return;
195 			}
196 		} else {
197 			report_pwd = true;
198 			fatal(gettext("Can't lock .make.state"));
199 		}
200 	}
201 
202 	(void) sprintf(make_state_tempfile,
203 	               NOCATGETS("%s.tmp"),
204 	               make_state->string_mb);
205 	/* Delete old temporary statefile (in case it exists) */
206 	(void) unlink(make_state_tempfile);
207 	if ((fd = fopen(make_state_tempfile, "w")) == NULL) {
208 		lock_err = errno; /* Save it! unlink() can change errno */
209 		(void) unlink(make_state_lockfile);
210  		retmem_mb(make_state_lockfile);
211 		make_state_lockfile = NULL;
212 		make_state_locked = false;
213 		fatal(gettext("Could not open temporary statefile `%s': %s"),
214 		      make_state_tempfile,
215 		      errmsg(lock_err));
216 	}
217 #ifdef NSE
218 	if (nse) {
219 		(void) fchmod(fileno(fd), 0666);
220 	}
221 #endif
222 	/*
223 	 * Set a trap for failed writes. If a write fails, the routine
224 	 * will try saving the .make.state file under another name in /tmp.
225 	 */
226 	if (setjmp(long_jump)) {
227 		(void) fclose(fd);
228 		if (attempts++ > 5) {
229 			if ((make_state_lockfile != NULL) &&
230 			    make_state_locked) {
231 				(void) unlink(make_state_lockfile);
232  				retmem_mb(make_state_lockfile);
233 				make_state_lockfile = NULL;
234 				make_state_locked = false;
235 			}
236 			fatal(gettext("Giving up on writing statefile"));
237 		}
238 		sleep(10);
239 		(void) sprintf(buffer, NOCATGETS("%s/.make.state.%d.XXXXXX"), tmpdir, getpid());
240 		int fdes = mkstemp(buffer);
241 		if ((fdes < 0) || (fd = fdopen(fdes, "w")) == NULL) {
242 			fatal(gettext("Could not open statefile `%s': %s"),
243 			      buffer,
244 			      errmsg(errno));
245 		}
246 		warning(gettext("Initial write of statefile failed. Trying again on %s"),
247 			buffer);
248 	}
249 
250 	/* Write the version stamp. */
251 	XFWRITE(make_version->string_mb,
252 		strlen(make_version->string_mb),
253 		fd);
254 	XPUTC(colon_char, fd);
255 	XPUTC(tab_char, fd);
256 	XFWRITE(current_make_version->string_mb,
257 		strlen(current_make_version->string_mb),
258 		fd);
259 	XPUTC(newline_char, fd);
260 
261 	/*
262 	 * Go through all the targets, dump their dependencies and
263 	 * command used.
264 	 */
265 	for (np = hashtab.begin(), e = hashtab.end(); np != e; np++) {
266 		/*
267 		 * If the target has no command used nor dependencies,
268 		 * we can go to the next one.
269 		 */
270 		if ((lines = get_prop(np->prop, line_prop)) == NULL) {
271 			continue;
272 		}
273 		/* If this target is a special target, don't print. */
274 		if (np->special_reader != no_special) {
275 			continue;
276 		}
277 		/*
278 		 * Find out if any of the targets dependencies should
279 		 * be written to .make.state.
280 		 */
281 		for (m = 0, dependency = lines->body.line.dependencies;
282 		     dependency != NULL;
283 		     dependency = dependency->next) {
284 			if ((m = !dependency->stale) != 0
285 			    && (dependency->name != force)
286 #ifndef PRINT_EXPLICIT_DEPEN
287 			    && dependency->automatic
288 #endif
289 			    ) {
290 				break;
291 			}
292 		}
293 		/* Only print if dependencies listed. */
294 		if (m || (lines->body.line.command_used != NULL)) {
295 			name_printed = false;
296 			/*
297 			 * If this target was built during this make run,
298 			 * we mark it.
299 			 */
300 			built_this_run = false;
301 			if (np->has_built) {
302 				built_this_run = true;
303 				XFWRITE(built_last_make_run->string_mb,
304 					strlen(built_last_make_run->string_mb),
305 					fd);
306 				XPUTC(colon_char, fd);
307 				XPUTC(newline_char, fd);
308 			}
309 			/* If the target has dependencies, we dump them. */
310 			target_name = escape_target_name(np);
311 			if (np->has_long_member_name) {
312 				target_name =
313 				  get_prop(np->prop, long_member_name_prop)
314 				    ->body.long_member_name.member_name->
315 				      string_mb;
316 			}
317 			if (m) {
318 				XFPUTS(target_name, fd);
319 				XPUTC(colon_char, fd);
320 				XFPUTS("\t", fd);
321 				name_printed = true;
322 				line_length = 0;
323 				for (dependency =
324 				     lines->body.line.dependencies;
325 				     dependency != NULL;
326 				     dependency = dependency->next) {
327 					print_auto_depes(dependency,
328 							 fd,
329 							 built_this_run,
330 							 &line_length,
331 							 target_name,
332 							 long_jump);
333 				}
334 				XFPUTS("\n", fd);
335 			}
336 			/* If there is a command used, we dump it. */
337 			if (lines->body.line.command_used != NULL) {
338 				/*
339 				 * Only write the target name if it
340 				 * wasn't done for the dependencies.
341 				 */
342 				if (!name_printed) {
343 					XFPUTS(target_name, fd);
344 					XPUTC(colon_char, fd);
345 					XPUTC(newline_char, fd);
346 				}
347 				/*
348 				 * Write the command lines.
349 				 * Prefix each textual line with a tab.
350 				 */
351 				for (cp = lines->body.line.command_used;
352 				     cp != NULL;
353 				     cp = cp->next) {
354 					char		*csp;
355 					int		n;
356 
357 					XPUTC(tab_char, fd);
358 					if (cp->command_line != NULL) {
359 						for (csp = cp->
360 						           command_line->
361 						           string_mb,
362 						     n = strlen(cp->
363 						                command_line->
364 						                string_mb);
365 						     n > 0;
366 						     n--, csp++) {
367 							XPUTC(*csp, fd);
368 							if (*csp ==
369 							    (int) newline_char) {
370 								XPUTC(tab_char,
371 								      fd);
372 							}
373 						}
374 					}
375 					XPUTC(newline_char, fd);
376 				}
377 			}
378 			(void)free(target_name);
379 		}
380 	}
381 	if (fclose(fd) == EOF) {
382 		longjmp(long_jump, LONGJUMP_VALUE);
383 	}
384 	if (attempts == 0) {
385 		if (unlink(make_state->string_mb) != 0 && errno != ENOENT) {
386 			lock_err = errno; /* Save it! unlink() can change errno */
387 			/* Delete temporary statefile */
388 			(void) unlink(make_state_tempfile);
389 			(void) unlink(make_state_lockfile);
390 	 		retmem_mb(make_state_lockfile);
391 			make_state_lockfile = NULL;
392 			make_state_locked = false;
393 			fatal(gettext("Could not delete old statefile `%s': %s"),
394 			      make_state->string_mb,
395 			      errmsg(lock_err));
396 		}
397 		if (rename(make_state_tempfile, make_state->string_mb) != 0) {
398 			lock_err = errno; /* Save it! unlink() can change errno */
399 			/* Delete temporary statefile */
400 			(void) unlink(make_state_tempfile);
401 			(void) unlink(make_state_lockfile);
402 	 		retmem_mb(make_state_lockfile);
403 			make_state_lockfile = NULL;
404 			make_state_locked = false;
405 			fatal(gettext("Could not rename `%s' to `%s': %s"),
406 			      make_state_tempfile,
407 			      make_state->string_mb,
408 			      errmsg(lock_err));
409 		}
410 	}
411 	if ((make_state_lockfile != NULL) && make_state_locked) {
412 		(void) unlink(make_state_lockfile);
413  		retmem_mb(make_state_lockfile);
414 		make_state_lockfile = NULL;
415 		make_state_locked = false;
416 	}
417 #ifdef NSE
418 	if (report_recursive) {
419 		report_recursive_done();
420 	}
421 #endif
422 }
423 
424 /*
425  *	print_auto_depes(dependency, fd, built_this_run,
426  *			 line_length, target_name, long_jump)
427  *
428  *	Will print a dependency list for automatic entries.
429  *
430  *	Parameters:
431  *		dependency	The dependency to print
432  *		fd		The file to print it to
433  *		built_this_run	If on we prefix each line with .BUILT_THIS...
434  *		line_length	Pointer to line length var that we update
435  *		target_name	We need this when we restart line
436  *		long_jump	setjmp/longjmp buffer used for IO error action
437  *
438  *	Global variables used:
439  *		built_last_make_run The Name ".BUILT_LAST_MAKE_RUN", written
440  *		force		The Name " FORCE", compared against
441  */
442 static void
print_auto_depes(register Dependency dependency,register FILE * fd,register Boolean built_this_run,register int * line_length,register char * target_name,jmp_buf long_jump)443 print_auto_depes(register Dependency dependency, register FILE *fd, register Boolean built_this_run, register int *line_length, register char *target_name, jmp_buf long_jump)
444 {
445 	if (!dependency->automatic ||
446 	    dependency->stale ||
447 	    (dependency->name == force)) {
448 		return;
449 	}
450 	XFWRITE(dependency->name->string_mb,
451 		strlen(dependency->name->string_mb),
452 		fd);
453 	/*
454 	 * Check if the dependency line is too long.
455 	 * If so, break it and start a new one.
456 	 */
457 	if ((*line_length += (int) strlen(dependency->name->string_mb) + 1) > 450) {
458 		*line_length = 0;
459 		XPUTC(newline_char, fd);
460 		if (built_this_run) {
461 			XFPUTS(built_last_make_run->string_mb, fd);
462 			XPUTC(colon_char, fd);
463 			XPUTC(newline_char, fd);
464 		}
465 		XFPUTS(target_name, fd);
466 		XPUTC(colon_char, fd);
467 		XPUTC(tab_char, fd);
468 	} else {
469 		XFPUTS(" ", fd);
470 	}
471 	return;
472 }
473 
474 
475