1 /*
2  * ngspice-watcher.c
3  *
4  *
5  * Authors:
6  *  Michi <st101564@stud.uni-stuttgart.de>
7  *
8  * Web page: https://ahoi.io/project/oregano
9  *
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public
22  * License along with this program; if not, write to the
23  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  */
26 
27 #include <glib.h>
28 #include <glib/gprintf.h>
29 #include <math.h>
30 
31 #include "../tools/thread-pipe.h"
32 #include "ngspice.h"
33 #include "ngspice-analysis.h"
34 #include "../log-interface.h"
35 #include "ngspice-watcher.h"
36 
37 enum ERROR_STATE {
38 	ERROR_STATE_NO_ERROR,
39 	ERROR_STATE_NO_SUCH_FILE_OR_DIRECTORY,
40 	ERROR_STATE_ERROR_IN_NETLIST
41 };
42 
43 //data wrapper
44 typedef struct {
45 	GMutex mutex;
46 	GCond cond;
47 	gboolean boolean;
48 } IsNgspiceStderrDestroyed;
49 
50 //data wrapper
51 typedef struct {
52 	gchar *path_to_file;
53 	ThreadPipe *pipe;
54 	CancelInfo *cancel_info;
55 } NgSpiceSaverResources;
56 
57 //data wrapper
58 typedef struct {
59 	ThreadPipe *thread_pipe_worker;
60 	ThreadPipe *thread_pipe_saver;
61 } NgSpiceWatchForkResources;
62 
63 //data wrapper
64 typedef struct {
65 	NgSpiceWatchForkResources ngspice_watch_fork_resources;
66 	guint cancel_info_count;
67 	CancelInfo *cancel_info;
68 } NgSpiceWatchSTDOUTResources;
69 
70 //data wrapper
71 typedef struct {
72 	ProgressResources *progress_ngspice;
73 	LogInterface log;
74 	const SimSettings *sim_settings;
75 	enum ERROR_STATE *error_state;
76 	IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed;
77 } NgSpiceWatchSTDERRResources;
78 
79 //data wrapper
80 typedef struct {
81 	GThread *worker;
82 	GThread *saver;
83 	LogInterface log;
84 	const void* emit_instance;
85 	GPid *child_pid;
86 	gboolean *aborted;
87 	guint *num_analysis;
88 	GMainLoop *main_loop;
89 	gchar *netlist_file;
90 	gchar *ngspice_result_file;
91 	enum ERROR_STATE *error_state;
92 	IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed;
93 	CancelInfo *cancel_info;
94 } NgSpiceWatcherWatchNgSpiceResources;
95 
96 /**
97  * Wraps the heavy work of a function into a thread.
98  */
ngspice_worker(NgspiceAnalysisResources * resources)99 static gpointer ngspice_worker (NgspiceAnalysisResources *resources) {
100 
101 	ngspice_analysis(resources);
102 
103 	cancel_info_unsubscribe(resources->cancel_info);
104 	g_free(resources);
105 
106 	return NULL;
107 }
108 
109 /**
110  * Wraps the heavy work of a function into a thread.
111  */
ngspice_saver(NgSpiceSaverResources * resources)112 static gpointer ngspice_saver (NgSpiceSaverResources *resources)
113 {
114 	ngspice_save(resources->path_to_file, resources->pipe, resources->cancel_info);
115 
116 	cancel_info_unsubscribe(resources->cancel_info);
117 	g_free(resources->path_to_file);
118 	g_free(resources);
119 
120 	return NULL;
121 }
122 
123 /**
124  * returns the number of strings in a NULL terminated array of strings
125  */
get_count(gchar ** array)126 static int get_count(gchar** array) {
127 	int i = 0;
128 	while (array[i] != NULL)
129 		i++;
130 	return i;
131 }
132 
133 /**
134  * adds the line number followed by a colon and a space at the beginning of each line
135  */
add_line_numbers(gchar ** string)136 static void add_line_numbers(gchar **string) {
137 	gchar **splitted = g_regex_split_simple("\\n", *string, 0, 0);
138 	GString *new_string = g_string_new("");
139 	//why -1? Because g_regex_split_simple adds one empty string too much at the end
140 	//of the array.
141 	int count = get_count(splitted) - 1;
142 	int max_length = floor(log10((double) count)) + 1;
143 	//splitted[i+1] != NULL (why not only i but i+1?) because g_regex_split_simple
144 	//adds one empty string too much at the end of the array
145 	for (int i = 0; splitted[i+1] != NULL; i++)
146 		g_string_append_printf(new_string, "%0*d: %s\n", max_length, i+1, splitted[i]);
147 	//remove the last newline, which was added additionally
148 	new_string = g_string_truncate(new_string, new_string->len - 1);
149 	g_free(*string);
150 	*string = new_string->str;
151 	g_string_free(new_string, FALSE);
152 }
153 
154 //data wrapper
155 typedef struct {
156 	const void* emit_instance;
157 	gchar *signal_name;
158 } NgspiceEmitData;
159 
160 /**
161  * Use this function to return the program main control flow to the
162  * main thread (which is the gui thread).
163  */
g_signal_emit_by_name_main_thread(NgspiceEmitData * data)164 static gboolean g_signal_emit_by_name_main_thread(NgspiceEmitData *data) {
165 	const void* emit_instance = data->emit_instance;
166 	gchar *signal_name = data->signal_name;
167 	g_free(data);
168 	g_signal_emit_by_name (G_OBJECT (emit_instance), signal_name);
169 	g_free(signal_name);
170 	return G_SOURCE_REMOVE;
171 }
172 
173 /**
174  * main function of the ngspice watcher
175  */
ngspice_watcher_main(GMainLoop * main_loop)176 static gpointer ngspice_watcher_main(GMainLoop *main_loop) {
177 	g_main_loop_run(main_loop);
178 
179 	// unrefs its GMainContext by 1
180 	g_main_loop_unref(main_loop);
181 
182 	return NULL;
183 }
184 
185 /**
186  * forks data to file and heap
187  */
ngspice_watcher_fork_data(NgSpiceWatchForkResources * resources,gpointer data,gsize size)188 static void ngspice_watcher_fork_data(NgSpiceWatchForkResources *resources, gpointer data, gsize size) {
189 	thread_pipe_push(resources->thread_pipe_worker, data, size);
190 	/**
191 	 * size_in = size - 1, because the trailing 0 of the string should not
192 	 * be written to file.
193 	 */
194 	thread_pipe_push(resources->thread_pipe_saver, data, size - 1);
195 }
196 
197 /**
198  * forks eof to file-pipe and heap-pipe
199  */
ngspice_watcher_fork_eof(NgSpiceWatchForkResources * resources)200 static void ngspice_watcher_fork_eof(NgSpiceWatchForkResources *resources) {
201 	thread_pipe_set_write_eof(resources->thread_pipe_worker);
202 	thread_pipe_set_write_eof(resources->thread_pipe_saver);
203 }
204 
205 /**
206  * Does not handle input resources.
207  */
ngspice_watcher_watch_stdout_resources(GIOChannel * channel,GIOCondition condition,NgSpiceWatchSTDOUTResources * resources)208 static gboolean ngspice_watcher_watch_stdout_resources(GIOChannel *channel, GIOCondition condition, NgSpiceWatchSTDOUTResources *resources) {
209 	gchar *str_return = NULL;
210 	gsize length;
211 	gsize terminator_pos;
212 	GError *error = NULL;
213 
214 	resources->cancel_info_count++;
215 	if (resources->cancel_info_count % 50 == 0 && cancel_info_is_cancel(resources->cancel_info)) {
216 		return G_SOURCE_REMOVE;
217 	}
218 
219 	GIOStatus status = g_io_channel_read_line(channel, &str_return, &length, &terminator_pos, &error);
220 	if (error) {
221 		gchar *message = g_strdup_printf ("spice pipe stdout: %s - %i", error->message, error->code);
222 		g_printf("%s", message);
223 		g_free(message);
224 		g_clear_error (&error);
225 	} else if (status == G_IO_STATUS_NORMAL && length > 0) {
226 		ngspice_watcher_fork_data(&resources->ngspice_watch_fork_resources, str_return, length + 1);
227 	} else if (status == G_IO_STATUS_EOF) {
228 		return G_SOURCE_REMOVE;
229 	}
230 	if (str_return)
231 		g_free(str_return);
232 	return G_SOURCE_CONTINUE;
233 }
234 
235 /**
236  * ngspice reading source function
237  *
238  * reads the pipe (stdout) of ngspice
239  */
ngspice_watcher_watch_stdout(GIOChannel * channel,GIOCondition condition,NgSpiceWatchSTDOUTResources * resources)240 static gboolean ngspice_watcher_watch_stdout(GIOChannel *channel, GIOCondition condition, NgSpiceWatchSTDOUTResources *resources) {
241 
242 	gboolean g_source_continue = ngspice_watcher_watch_stdout_resources(channel, condition, resources);
243 
244 	if (g_source_continue == G_SOURCE_CONTINUE)
245 		return G_SOURCE_CONTINUE;
246 
247 	ngspice_watcher_fork_eof(&resources->ngspice_watch_fork_resources);
248 	g_source_destroy(g_main_current_source());
249 	cancel_info_unsubscribe(resources->cancel_info);
250 	g_free(resources);
251 	return G_SOURCE_REMOVE;
252 }
253 
ngspice_watch_ngspice_resources_finalize(NgSpiceWatcherWatchNgSpiceResources * resources)254 static void ngspice_watch_ngspice_resources_finalize(NgSpiceWatcherWatchNgSpiceResources *resources) {
255 	g_source_destroy(g_main_current_source());
256 	g_main_loop_quit(resources->main_loop);
257 	g_spawn_close_pid (*resources->child_pid);
258 	*resources->child_pid = 0;
259 
260 	g_free(resources->ngspice_result_file);
261 	g_free(resources->netlist_file);
262 	g_free(resources->error_state);
263 	g_mutex_clear(&resources->is_ngspice_stderr_destroyed->mutex);
264 	g_cond_clear(&resources->is_ngspice_stderr_destroyed->cond);
265 	g_free(resources);
266 }
267 
print_additional_info(LogInterface log,const gchar * ngspice_result_file,const gchar * netlist_file)268 static void print_additional_info(LogInterface log, const gchar *ngspice_result_file, const gchar *netlist_file) {
269 	log.log_append_error(log.log, "\n### spice output: ###\n\n");
270 	gchar *ngspice_error_contents = NULL;
271 	gsize ngspice_error_length;
272 	GError *ngspice_error_read_error = NULL;
273 
274 	g_file_get_contents(ngspice_result_file, &ngspice_error_contents, &ngspice_error_length, &ngspice_error_read_error);
275 	add_line_numbers(&ngspice_error_contents);
276 	log.log_append_error(log.log, ngspice_error_contents);
277 	g_free(ngspice_error_contents);
278 	if (ngspice_error_read_error != NULL)
279 		g_error_free(ngspice_error_read_error);
280 
281 	gchar *netlist_contents = NULL;
282 	gsize netlist_lentgh;
283 	GError *netlist_read_error = NULL;
284 	g_file_get_contents(netlist_file, &netlist_contents, &netlist_lentgh, &netlist_read_error);
285 	add_line_numbers(&netlist_contents);
286 	log.log_append_error(log.log, "\n\n### netlist: ###\n\n");
287 	log.log_append_error(log.log, netlist_contents);
288 	g_free(netlist_contents);
289 	if (netlist_read_error != NULL)
290 		g_error_free(netlist_read_error);
291 }
292 
293 enum NGSPICE_WATCHER_RETURN_VALUE {
294 	NGSPICE_WATCHER_RETURN_VALUE_DONE,
295 	NGSPICE_WATCHER_RETURN_VALUE_ABORTED,
296 	NGSPICE_WATCHER_RETURN_VALUE_CANCELED
297 };
298 
299 /**
300  * Does not care about input resource handling.
301  */
302 static enum NGSPICE_WATCHER_RETURN_VALUE
ngspice_watcher_watch_ngspice_resources(GPid pid,gint status,NgSpiceWatcherWatchNgSpiceResources * resources)303 ngspice_watcher_watch_ngspice_resources (GPid pid, gint status, NgSpiceWatcherWatchNgSpiceResources *resources) {
304 	GThread *worker = resources->worker;
305 	GThread *saver = resources->saver;
306 	LogInterface log = resources->log;
307 	guint *num_analysis = resources->num_analysis;
308 	enum ERROR_STATE *error_state = resources->error_state;
309 	IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed = resources->is_ngspice_stderr_destroyed;
310 
311 	// wait for stderr to finish reading
312 	g_mutex_lock(&is_ngspice_stderr_destroyed->mutex);
313 	while (!is_ngspice_stderr_destroyed->boolean)
314 		g_cond_wait(&is_ngspice_stderr_destroyed->cond, &is_ngspice_stderr_destroyed->mutex);
315 	g_mutex_unlock(&is_ngspice_stderr_destroyed->mutex);
316 
317 	GError *exit_error = NULL;
318 	gboolean exited_normal = g_spawn_check_exit_status(status, &exit_error);
319 	if (exit_error != NULL)
320 		g_error_free(exit_error);
321 
322 	g_thread_join(worker);
323 
324 	if (cancel_info_is_cancel(resources->cancel_info))
325 		return NGSPICE_WATCHER_RETURN_VALUE_CANCELED;
326 
327 
328 	if (!exited_normal) {
329 		// check for exit via return in main, exit() or _exit() of the child, see man
330 		// waitpid(2)
331 		//       WIFEXITED(wstatus)
332 		//              returns true if the child terminated normally, that is, by call‐
333 		//              ing exit(3) or _exit(2), or by returning from main().
334 		if (!(WIFEXITED (status)))
335 			log.log_append_error(log.log, "### spice exited with exception ###\n");
336 		else
337 			log.log_append_error(log.log, "### spice exited abnormally ###\n");
338 
339 		g_thread_join(saver);
340 
341 		switch (*error_state) {
342 		case ERROR_STATE_NO_ERROR:
343 			log.log_append_error(log.log, "### unknown error detected ###\n");
344 			log.log_append_error(log.log, "The following information might help you to analyze the error.\n");
345 
346 			print_additional_info(log, resources->ngspice_result_file, resources->netlist_file);
347 			break;
348 		case ERROR_STATE_NO_SUCH_FILE_OR_DIRECTORY:
349 			log.log_append_error(log.log, "spice could not simulate because netlist generation failed.\n");
350 			break;
351 		case ERROR_STATE_ERROR_IN_NETLIST:
352 			log.log_append_error(log.log, "### netlist error detected ###\n");
353 			log.log_append_error(log.log, "You made a mistake in the simulation settings or part properties.\n");
354 			log.log_append_error(log.log, "The following information will help you to analyze the error.\n");
355 
356 			print_additional_info(log, resources->ngspice_result_file, resources->netlist_file);
357 			break;
358 		}
359 
360 		return NGSPICE_WATCHER_RETURN_VALUE_ABORTED;
361 	}
362 	// saver not needed any more. It could have been needed by error handling.
363 	g_thread_unref(saver);
364 
365 	if (*num_analysis == 0) {
366 		log.log_append_error(log.log, _("### Too few or none analysis found ###\n"));
367 		return NGSPICE_WATCHER_RETURN_VALUE_ABORTED;
368 	}
369 
370 	return NGSPICE_WATCHER_RETURN_VALUE_DONE;
371 
372 }
373 
374 /**
375  * function is called after ngspice process has died and the ngspice reading
376  * source function is finished with reading to
377  * - clean up,
378  * - check if all went good or fail,
379  * - wait for data conversion thread,
380  * - return the main program flow to the gui thread.
381  */
ngspice_watcher_watch_ngspice(GPid pid,gint status,NgSpiceWatcherWatchNgSpiceResources * resources)382 static void ngspice_watcher_watch_ngspice (GPid pid, gint status, NgSpiceWatcherWatchNgSpiceResources *resources) {
383 	enum NGSPICE_WATCHER_RETURN_VALUE ret_val = ngspice_watcher_watch_ngspice_resources (pid, status, resources);
384 
385 	NgspiceEmitData *emitData = g_malloc(sizeof(NgspiceEmitData));
386 	emitData->emit_instance = resources->emit_instance;
387 
388 	switch(ret_val) {
389 	case NGSPICE_WATCHER_RETURN_VALUE_ABORTED:
390 	case NGSPICE_WATCHER_RETURN_VALUE_CANCELED:
391 		emitData->signal_name = g_strdup("aborted");
392 		*resources->aborted = TRUE;
393 		break;
394 	case NGSPICE_WATCHER_RETURN_VALUE_DONE:
395 		emitData->signal_name = g_strdup("done");
396 		break;
397 	}
398 
399 	cancel_info_unsubscribe(resources->cancel_info);
400 	ngspice_watch_ngspice_resources_finalize(resources);
401 
402 	/*
403 	 * return to main thread
404 	 *
405 	 * Don't return too early, because if you do, the ngspice
406 	 * object could be finalized but some resources depend on it.
407 	 */
408 	g_main_context_invoke(NULL, (GSourceFunc)g_signal_emit_by_name_main_thread, emitData);
409 }
410 
411 /**
412  * Extracts a progress number (time of transient analysis)
413  * out of a string (if existing) and saves it to the thread-shared
414  * progress variable.
415  */
read_progress_ngspice(ProgressResources * progress_ngspice,gdouble progress_end,const gchar * line)416 static void read_progress_ngspice(ProgressResources *progress_ngspice, gdouble progress_end, const gchar *line) {
417 	if (!g_regex_match_simple("Reference value.*\\r", line, 0, 0))
418 		return;
419 	gchar **splitted = g_regex_split_simple(".* (.+)\\r", line, 0, 0);
420 	gchar **ptr;
421 	for (ptr = splitted; *ptr != NULL; ptr++)
422 		if (**ptr != 0)
423 			break;
424 	if (*ptr != NULL) {
425 		gdouble progress_absolute = g_ascii_strtod(*ptr, NULL);
426 
427 		g_mutex_lock(&progress_ngspice->progress_mutex);
428 		progress_ngspice->progress = progress_absolute / progress_end;
429 		if (g_str_has_suffix(line, "\r\n"))
430 			progress_ngspice->progress = 1;
431 		progress_ngspice->time = g_get_monotonic_time();
432 		g_mutex_unlock(&progress_ngspice->progress_mutex);
433 	}
434 	g_strfreev(splitted);
435 }
436 
437 /**
438  * Reads stderr of ngspice.
439  *
440  * stderr of ngspice might contain progress information.
441  */
ngspice_child_stderr_cb(GIOChannel * channel,GIOCondition condition,NgSpiceWatchSTDERRResources * resources)442 static gboolean ngspice_child_stderr_cb (GIOChannel *channel, GIOCondition condition,
443                                          NgSpiceWatchSTDERRResources *resources)
444 {
445 	LogInterface log = resources->log;
446 	const SimSettings* const sim_settings = resources->sim_settings;
447 	ProgressResources *progress_ngspice = resources->progress_ngspice;
448 	enum ERROR_STATE *error_state = resources->error_state;
449 	IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed = resources->is_ngspice_stderr_destroyed;
450 
451 	gchar *line = NULL;
452 	gsize len, terminator;
453 	GError *e = NULL;
454 
455 	GIOStatus status = g_io_channel_read_line (channel, &line, &len, &terminator, &e);
456 	if (e) {
457 		gchar *message = g_strdup_printf("spice pipe stderr: %s - %i", e->message, e->code);
458 		log.log_append_error(log.log, message);
459 		g_free(message);
460 		g_clear_error (&e);
461 	} else if (status == G_IO_STATUS_NORMAL && len > 0) {
462 		log.log_append_error(log.log, line);
463 
464 		if (g_str_has_suffix(line, ": No such file or directory\n"))
465 			*error_state = ERROR_STATE_NO_SUCH_FILE_OR_DIRECTORY;
466 		if (g_str_equal(line, "spice stopped due to error, no simulation run!\n"))
467 			*error_state = ERROR_STATE_ERROR_IN_NETLIST;
468 
469 		gdouble progress_end = sim_settings_get_trans_stop(sim_settings);
470 		read_progress_ngspice(progress_ngspice, progress_end, line);
471 	} else if (status == G_IO_STATUS_EOF) {
472 		g_source_destroy(g_main_current_source());
473 		g_free(resources);
474 
475 		// emit signal, that stderr reading has finished
476 		g_mutex_lock(&is_ngspice_stderr_destroyed->mutex);
477 		is_ngspice_stderr_destroyed->boolean = TRUE;
478 		g_cond_signal(&is_ngspice_stderr_destroyed->cond);
479 		g_mutex_unlock(&is_ngspice_stderr_destroyed->mutex);
480 
481 		return G_SOURCE_REMOVE;
482 	}
483 	if (line)
484 		g_free (line);
485 
486 	return G_SOURCE_CONTINUE;
487 }
488 
489 /**
490  * @resources: caller frees
491  *
492  * Prepares data structures to launch some threads and finally launches them.
493  *
494  * The launched threads are:
495  * - process ngspice
496  * - thread watcher
497  * - thread saver
498  * - thread worker
499  *
500  * As you should know ngspice is the program that actually simulates the simulation.
501  *
502  * The watcher thread handles stdout- and death-events of the ngspice process.
503  * stdout data is forked to the threads "saver" and "worker".
504  * As response to the death-event of ngspice, the watcher
505  * - cleans the field of war,
506  * - checks if all went good and creates error messages if not all went good,
507  * - waits for the worker to finish work,
508  * - finally returns the main program flow to the gui thread.
509  *
510  * The stderr-events are handled by the main (gui) thread, because it is not heavy work.
511  * Furthermore additionally shared variables can be avoided.
512  *
513  * The saver saves the data to SSD/HDD (temporary folder). It is needed to create
514  * good error messages in case of failure. Besides of that the user can analyze
515  * the data with external/other programs.
516  *
517  * The worker parses the stream of data, interprets and converts it to structured data
518  * so it can be plotted by gui.
519  */
ngspice_watcher_build_and_launch(const NgspiceWatcherBuildAndLaunchResources * resources)520 void ngspice_watcher_build_and_launch(const NgspiceWatcherBuildAndLaunchResources *resources) {
521 	LogInterface log = resources->log;
522 	const SimSettings* const sim_settings = resources->sim_settings;
523 	gboolean is_vanilla = resources->is_vanilla;
524 	GPid *child_pid = resources->child_pid;
525 	gboolean *aborted = resources->aborted;
526 	const void* emit_instance = resources->emit_instance;
527 	guint *num_analysis = resources->num_analysis;
528 	ProgressResources *progress_ngspice = resources->progress_ngspice;
529 	ProgressResources *progress_reader = resources->progress_reader;
530 	GList **analysis = resources->analysis;
531 	AnalysisTypeShared *current = resources->current;
532 	GError *e = NULL;
533 
534 	char *argv[] = {NULL, "-b", resources->netlist_file, NULL};
535 
536 	if (is_vanilla)
537 		argv[0] = SPICE_EXE;
538 	else
539 		argv[0] = NGSPICE_EXE;
540 
541 	gint ngspice_stdout_fd;
542 	gint ngspice_stderr_fd;
543 	// Launch ngspice
544 	if (!g_spawn_async_with_pipes (NULL, // Working directory
545 		                              argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL,
546 		                              NULL, child_pid,
547 		                              NULL,                // STDIN
548 		                              &ngspice_stdout_fd, // STDOUT
549 		                              &ngspice_stderr_fd,  // STDERR
550 		                              &e)) {
551 
552 		*aborted = TRUE;
553 		log.log_append_error(log.log, _("Unable to execute NgSpice."));
554 		g_signal_emit_by_name (G_OBJECT (emit_instance), "aborted");
555 		g_clear_error (&e);
556 		return;
557 
558 	}
559 
560 	// synchronizes stderr listener with is_ngspice_finished listener (needed for error handling)
561 	IsNgspiceStderrDestroyed *is_ngspice_stderr_destroyed = g_new0(IsNgspiceStderrDestroyed, 1);
562 	g_mutex_init(&is_ngspice_stderr_destroyed->mutex);
563 	g_cond_init(&is_ngspice_stderr_destroyed->cond);
564 	is_ngspice_stderr_destroyed->boolean = FALSE;
565 
566 	// variable needed for error handling
567 	enum ERROR_STATE *error_state = g_new0(enum ERROR_STATE, 1);
568 
569 	GMainContext *forker_context = g_main_context_new();
570 	GMainLoop *forker_main_loop = g_main_loop_new(forker_context, FALSE);
571 	g_main_context_unref(forker_context);
572 
573 	// Create pipes to fork the stdout data of ngspice
574 	ThreadPipe *thread_pipe_worker = thread_pipe_new(20, 2048);
575 	ThreadPipe *thread_pipe_saver = thread_pipe_new(20, 2048);
576 
577 	/**
578 	 * Launch analyzer
579 	 */
580 	NgspiceAnalysisResources *ngspice_worker_resources = g_new0(NgspiceAnalysisResources, 1);
581 	ngspice_worker_resources->analysis = analysis;
582 	ngspice_worker_resources->buf = NULL;
583 	ngspice_worker_resources->is_vanilla = is_vanilla;
584 	ngspice_worker_resources->current = current;
585 	ngspice_worker_resources->no_of_data_rows_ac = 0;
586 	ngspice_worker_resources->no_of_data_rows_dc = 0;
587 	ngspice_worker_resources->no_of_data_rows_op = 0;
588 	ngspice_worker_resources->no_of_data_rows_transient = 0;
589 	ngspice_worker_resources->no_of_data_rows_noise = 0;
590 	ngspice_worker_resources->no_of_variables = 0;
591 	ngspice_worker_resources->num_analysis = num_analysis;
592 	ngspice_worker_resources->pipe = thread_pipe_worker;
593 	ngspice_worker_resources->progress_reader = progress_reader;
594 	ngspice_worker_resources->sim_settings = sim_settings;
595 	ngspice_worker_resources->cancel_info = resources->cancel_info;
596 	cancel_info_subscribe(ngspice_worker_resources->cancel_info);
597 
598 	GThread *worker = g_thread_new("spice worker", (GThreadFunc)ngspice_worker, ngspice_worker_resources);
599 
600 	/**
601 	 * Launch output saver
602 	 */
603 	NgSpiceSaverResources *ngspice_saver_resources = g_new0(NgSpiceSaverResources, 1);
604 	ngspice_saver_resources->path_to_file = g_strdup(resources->ngspice_result_file);
605 	ngspice_saver_resources->pipe = thread_pipe_saver;
606 	ngspice_saver_resources->cancel_info = resources->cancel_info;
607 	cancel_info_subscribe(ngspice_saver_resources->cancel_info);
608 
609 	GThread *saver = g_thread_new("spice saver", (GThreadFunc)ngspice_saver, ngspice_saver_resources);
610 
611 	/**
612 	 * Add an ngspice-is-finished watcher
613 	 */
614 	NgSpiceWatcherWatchNgSpiceResources *ngspice_watcher_watch_ngspice_resources = g_new0(NgSpiceWatcherWatchNgSpiceResources, 1);
615 	ngspice_watcher_watch_ngspice_resources->emit_instance = emit_instance;
616 	ngspice_watcher_watch_ngspice_resources->aborted = aborted;
617 	ngspice_watcher_watch_ngspice_resources->child_pid = child_pid;
618 	ngspice_watcher_watch_ngspice_resources->log = log;
619 	ngspice_watcher_watch_ngspice_resources->num_analysis = num_analysis;
620 	ngspice_watcher_watch_ngspice_resources->worker = worker;
621 	ngspice_watcher_watch_ngspice_resources->saver = saver;
622 	ngspice_watcher_watch_ngspice_resources->main_loop = forker_main_loop;
623 	ngspice_watcher_watch_ngspice_resources->ngspice_result_file = g_strdup(resources->ngspice_result_file);
624 	ngspice_watcher_watch_ngspice_resources->netlist_file = g_strdup(resources->netlist_file);
625 	ngspice_watcher_watch_ngspice_resources->error_state = error_state;
626 	ngspice_watcher_watch_ngspice_resources->is_ngspice_stderr_destroyed = is_ngspice_stderr_destroyed;
627 	ngspice_watcher_watch_ngspice_resources->cancel_info = resources->cancel_info;
628 	cancel_info_subscribe(ngspice_watcher_watch_ngspice_resources->cancel_info);
629 
630 	GSource *child_watch_source = g_child_watch_source_new (*child_pid);
631 	g_source_set_priority (child_watch_source, G_PRIORITY_LOW);
632 	g_source_set_callback (child_watch_source, (GSourceFunc)ngspice_watcher_watch_ngspice, ngspice_watcher_watch_ngspice_resources, NULL);
633 	g_source_attach (child_watch_source, forker_context);
634 	g_source_unref (child_watch_source);
635 
636 	/**
637 	 * Add a GIOChannel to read from process stdout
638 	 */
639 	NgSpiceWatchSTDOUTResources *ngspice_watch_stdout_resources = g_new0(NgSpiceWatchSTDOUTResources, 1);
640 	ngspice_watch_stdout_resources->ngspice_watch_fork_resources.thread_pipe_worker = thread_pipe_worker;
641 	ngspice_watch_stdout_resources->ngspice_watch_fork_resources.thread_pipe_saver = thread_pipe_saver;
642 	ngspice_watch_stdout_resources->cancel_info = resources->cancel_info;
643 	cancel_info_subscribe(ngspice_watch_stdout_resources->cancel_info);
644 
645 	GIOChannel *ngspice_stdout_channel = g_io_channel_unix_new(ngspice_stdout_fd);
646 	g_io_channel_set_close_on_unref(ngspice_stdout_channel, TRUE);
647 	GSource *ngspice_stdout_source = g_io_create_watch (ngspice_stdout_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_NVAL);
648 	g_io_channel_unref(ngspice_stdout_channel);
649 	g_source_set_priority (ngspice_stdout_source, G_PRIORITY_HIGH);
650 	g_source_set_callback (ngspice_stdout_source, (GSourceFunc)ngspice_watcher_watch_stdout, ngspice_watch_stdout_resources, NULL);
651 	g_source_attach (ngspice_stdout_source, forker_context);
652 	g_source_unref (ngspice_stdout_source);
653 
654 	/**
655 	 * Add a GIOChannel to read from process stderr (attach to gui thread because it prints to log).
656 	 * I hope that ngspice does not print too much errors so that it is a minor work
657 	 * that does not hold the gui back from paint and user events
658 	 */
659 	NgSpiceWatchSTDERRResources *ngspice_watch_stderr_resources = g_new0(NgSpiceWatchSTDERRResources, 1);
660 	ngspice_watch_stderr_resources->log = log;
661 	ngspice_watch_stderr_resources->sim_settings = sim_settings;
662 	ngspice_watch_stderr_resources->progress_ngspice = progress_ngspice;
663 	ngspice_watch_stderr_resources->error_state = error_state;
664 	ngspice_watch_stderr_resources->is_ngspice_stderr_destroyed = is_ngspice_stderr_destroyed;
665 
666 	GIOChannel *ngspice_stderr_channel = g_io_channel_unix_new (ngspice_stderr_fd);
667 	g_io_channel_set_close_on_unref(ngspice_stderr_channel, TRUE);
668 	// sometimes there is no data and then the GUI will hang up if NONBLOCK not set
669 	g_io_channel_set_flags(ngspice_stderr_channel, g_io_channel_get_flags(ngspice_stderr_channel) | G_IO_FLAG_NONBLOCK, NULL);
670 	GSource *channel_stderr_watch_source = g_io_create_watch(ngspice_stderr_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_NVAL);
671 	g_io_channel_unref(ngspice_stderr_channel);
672 	g_source_set_priority (channel_stderr_watch_source, G_PRIORITY_LOW);
673 	g_source_set_callback (channel_stderr_watch_source, (GSourceFunc)ngspice_child_stderr_cb, ngspice_watch_stderr_resources, NULL);
674 	g_source_attach (channel_stderr_watch_source, NULL);
675 	g_source_unref (channel_stderr_watch_source);
676 
677 	// Launch watcher
678 	g_thread_unref(g_thread_new("spice forker", (GThreadFunc)ngspice_watcher_main, forker_main_loop));
679 }
680 
ngspice_watcher_build_and_launch_resources_new(OreganoNgSpice * ngspice)681 NgspiceWatcherBuildAndLaunchResources *ngspice_watcher_build_and_launch_resources_new(OreganoNgSpice *ngspice) {
682 
683 	NgspiceWatcherBuildAndLaunchResources *resources = g_new0(NgspiceWatcherBuildAndLaunchResources, 1);
684 
685 	resources->is_vanilla = ngspice->priv->is_vanilla;
686 	resources->aborted = &ngspice->priv->aborted;
687 	resources->analysis = &ngspice->priv->analysis;
688 	resources->child_pid = &ngspice->priv->child_pid;
689 	resources->current = &ngspice->priv->current;
690 	resources->emit_instance = ngspice;
691 
692 	resources->log.log = ngspice->priv->schematic;
693 	resources->log.log_append = (LogFunction)schematic_log_append;
694 	resources->log.log_append_error = (LogFunction)schematic_log_append_error;
695 
696 	resources->num_analysis = &ngspice->priv->num_analysis;
697 	resources->progress_ngspice = &ngspice->priv->progress_ngspice;
698 	resources->progress_reader = &ngspice->priv->progress_reader;
699 	resources->sim_settings = schematic_get_sim_settings(ngspice->priv->schematic);
700 
701 	resources->netlist_file = g_strdup("/tmp/netlist.tmp");
702 	resources->ngspice_result_file = g_strdup("/tmp/netlist.lst");
703 
704 	resources->cancel_info = ngspice->priv->cancel_info;
705 	cancel_info_subscribe(resources->cancel_info);
706 
707 	return resources;
708 }
709 
ngspice_watcher_build_and_launch_resources_finalize(NgspiceWatcherBuildAndLaunchResources * resources)710 void ngspice_watcher_build_and_launch_resources_finalize(NgspiceWatcherBuildAndLaunchResources *resources) {
711 	cancel_info_unsubscribe(resources->cancel_info);
712 	g_free(resources->netlist_file);
713 	g_free(resources->ngspice_result_file);
714 	g_free(resources);
715 }
716