Exemple #1
0
void *
td_page_alloc(td_alloc *alloc, size_t size)
{
	int left = alloc->page_left;
	int page = alloc->page_index;
	char *addr;

	/* Round to 16 bytes */
	size = (size + 15) & ~15;

	if (left < (int) size)
	{
		if (page == alloc->total_page_count)
			td_croak("out of string page memory");

		page = alloc->page_index = page + 1;
		left = alloc->page_left = alloc->page_size;
		alloc->pages[page] = malloc(alloc->page_size);
		if (!alloc->pages[page])
			td_croak("out of memory allocating string page");
	}

	addr = alloc->pages[page] + alloc->page_size - left;
	alloc->page_left -= (int) size;

#ifndef NDEBUG
	memset(addr, 0xcc, size);
#endif

	return addr;
}
Exemple #2
0
void
td_load_ancestors(td_engine *engine)
{
	FILE* f;
	int i, count;
	long file_size;
	size_t read_count;

	if (NULL == (f = fopen(TD_ANCESTOR_FILE, "rb")))
	{
		if (td_debug_check(engine, TD_DEBUG_ANCESTORS))
			printf("couldn't open %s; no ancestor information present\n", TD_ANCESTOR_FILE);
		return;
	}

	fseek(f, 0, SEEK_END);
	file_size = ftell(f);
	rewind(f);

	if (file_size % sizeof(td_ancestor_data) != 0)
		td_croak("illegal ancestor file: %d not a multiple of %d bytes",
				(int) file_size, sizeof(td_ancestor_data));

	engine->ancestor_count = count = (int) (file_size / sizeof(td_ancestor_data));
	engine->ancestors = malloc(file_size);
	engine->ancestor_used = calloc(sizeof(td_node *), count);
	read_count = fread(engine->ancestors, sizeof(td_ancestor_data), count, f);

	if (td_debug_check(engine, TD_DEBUG_ANCESTORS))
		printf("read %d ancestors\n", count);

	if (read_count != (size_t) count)
		td_croak("only read %d items, wanted %d", read_count, count);

	for (i = 1; i < count; ++i)
	{
		int cmp = td_compare_ancestors(&engine->ancestors[i-1], &engine->ancestors[i]);
		if (cmp == 0)
			td_croak("bad ancestor file; duplicate item (%d/%d)", i, count);
		if (cmp > 0)
			td_croak("bad ancestor file; bad sort order on item (%d/%d)", i, count);
	}

	if (td_debug_check(engine, TD_DEBUG_ANCESTORS))
	{
		printf("full ancestor dump on load:\n");
		for (i = 0; i < count; ++i)
		{
			char guid[33], sig[33];
			td_digest_to_string(&engine->ancestors[i].guid, guid);
			td_digest_to_string(&engine->ancestors[i].input_signature, sig);
			printf("%s %s %ld %d\n", guid, sig, (long) engine->ancestors[i].access_time, engine->ancestors[i].job_result);
		}
	}

	fclose(f);
}
Exemple #3
0
static int
tokenize_path(char* scratch, strseg segments[], int maxseg)
{
	int segcount = 0;
	char *last = scratch;

	for (;;)
	{
		char ch = *scratch;
		if ('\\' == ch || '/' == ch || '\0' == ch)
		{
			int len = (int) (scratch - last);
			int is_dotdot = 2 == len && 0 == memcmp("..", last, 2);
			int is_dot = 1 == len && '.' == last[0];

			if (segcount == maxseg)
				td_croak("too many segments in path; limit is %d", maxseg);

			segments[segcount].ptr = last;
			segments[segcount].len = len;
			segments[segcount].dotdot = is_dotdot;
			segments[segcount].drop = is_dot;

			last = scratch + 1;
			++segcount;

			if ('\0' == ch)
				break;
		}

		++scratch;
	}

	return segcount;
}
Exemple #4
0
void
td_build_path(char *buffer, int buffer_size, const td_file *base, const char *subpath, int subpath_len, td_build_path_mode mode)
{
	int offset = 0;
	int path_len = 0;
	switch (mode)
	{
		case TD_BUILD_CONCAT:
			path_len = base->path_len;
			break;

		case TD_BUILD_REPLACE_NAME:
			path_len = (int) (base->name - base->path);
			break;

		default:
			assert(0);
			break;
	}

	if (path_len + subpath_len + 2 > buffer_size)
		td_croak("combined path too long: %s -> include %s (limit: %d)", base->path, subpath, buffer_size);

	memcpy(buffer, base->path, path_len);
	offset = path_len;
	if (path_len > 0 && buffer[offset-1] != '/' && buffer[offset-1] != '\\')
		buffer[offset++] = TD_PATHSEP;
	memcpy(buffer + offset, subpath, subpath_len);
	offset += subpath_len;
	buffer[offset] = '\0';
}
Exemple #5
0
static int push_include(include_set *set, td_file *f)
{
	int i, count;
	for (i = 0, count = set->count; i < count; ++i)
	{
		if (f == set->files[i])
			return 0;
	}

	if (TD_MAX_INCLUDES == set->count)
		td_croak("too many includes");

	set->files[set->count++] = f;
	return 1;
}
Exemple #6
0
static int
td_load_embedded_file(lua_State *L)
{
	int i, count;
	const char *module_name = luaL_checkstring(L, 1);

	for (i = 0, count = td_lua_file_count; i < count; ++i)
	{
		if (0 == strcmp(td_lua_files[i].module_name, module_name))
		{
			if (0 != luaL_loadbuffer(L, td_lua_files[i].data, td_lua_files[i].size, module_name))
				td_croak("couldn't load embedded chunk for %s", module_name);
			return 1;
		}
	}
	
	return 0;
}
Exemple #7
0
void
td_setup_ancestor_data(td_engine *engine, td_node *node)
{
	compute_node_guid(engine, node);

	++engine->stats.ancestor_checks;

	if (engine->ancestors)
	{
		td_ancestor_data key;
		key.guid = node->guid; /* only key field is relevant */

		node->ancestor_data = (td_ancestor_data *)
			bsearch(&key, engine->ancestors, engine->ancestor_count, sizeof(td_ancestor_data), td_compare_ancestors);

		if (node->ancestor_data)
		{
			int index = (int) (node->ancestor_data - engine->ancestors);
			td_node *other;
			if (NULL != (other = engine->ancestor_used[index]))
				td_croak("node error: nodes \"%s\" and \"%s\" share the same ancestor", node->annotation, other->annotation);
			engine->ancestor_used[index] = node;
			++engine->stats.ancestor_nodes;
		}
		else
		{
			if (td_debug_check(engine, TD_DEBUG_ANCESTORS))
			{
				char guidstr[33];
				td_digest_to_string(&node->guid, guidstr);
				printf("no ancestor for %s with guid %s\n", node->annotation, guidstr);
			}
		}
	}
	else
	{
		/* We didn't load any ancestor data, just set the ancestor to NULL.
		 * Everything will rebuild without ancestry. */
		node->ancestor_data = NULL;
	}
}
Exemple #8
0
void
td_save_ancestors(td_engine *engine, td_node *root)
{
	FILE* f;
	int i, count, max_count;
	int output_cursor, write_count;
	td_ancestor_data *output;
	unsigned char *visited;
	time_t now = time(NULL);
	const int dbg = td_debug_check(engine, TD_DEBUG_ANCESTORS);

	if (NULL == (f = fopen(TD_ANCESTOR_FILE ".tmp", "wb")))
	{
		fprintf(stderr, "warning: couldn't save ancestors\n");
		return;
	}

	max_count = engine->node_count + engine->ancestor_count;
	output = (td_ancestor_data *) malloc(sizeof(td_ancestor_data) * max_count);
	visited = (unsigned char *) calloc(engine->ancestor_count, 1);

	output_cursor = 0;
	update_ancestors(engine, root, now, &output_cursor, output, visited);

	if (dbg)
		printf("refreshed %d ancestors\n", output_cursor);

	for (i = 0, count = engine->ancestor_count; i < count; ++i)
	{
		const td_ancestor_data *a = &engine->ancestors[i];
		if (!visited[i] && !ancestor_timed_out(a, now))
			output[output_cursor++] = *a;
	}

	if (dbg)
		printf("%d ancestors to save in total\n", output_cursor);

	qsort(output, output_cursor, sizeof(td_ancestor_data), td_compare_ancestors);

	if (dbg)
	{
		printf("full ancestor dump on save:\n");
		for (i = 0; i < output_cursor; ++i)
		{
			char guid[33], sig[33];
			td_digest_to_string(&output[i].guid, guid);
			td_digest_to_string(&output[i].input_signature, sig);
			printf("%s %s %ld %d\n", guid, sig, (long) output[i].access_time, output[i].job_result);
		}
	}

	write_count = (int) fwrite(output, sizeof(td_ancestor_data), output_cursor, f);

	fclose(f);
	free(visited);
	free(output);

	if (write_count != output_cursor)
		td_croak("couldn't write %d entries; only wrote %d", output_cursor, write_count);

	if (0 != td_move_file(TD_ANCESTOR_FILE ".tmp", TD_ANCESTOR_FILE))
		td_croak("couldn't rename %s to %s", TD_ANCESTOR_FILE ".tmp", TD_ANCESTOR_FILE);
}
Exemple #9
0
static int
scan_file_data(td_alloc *scratch, td_file *file, td_include_data *out, int max_count, td_scanner *scanner)
{
	FILE *f;
	int file_count = 0;
	int at_start_of_file = 1;
	int at_end_of_file = 0;
	char line_buffer[1024];
	char *buffer_start = line_buffer;
	int buffer_size = sizeof(line_buffer);
	static const unsigned char utf8_mark[] = { 0xef, 0xbb, 0xbf };
	td_scan_fn *line_scanner = scanner->scan_fn;

	if (NULL == (f = fopen(file->path, "r")))
		return 0;

	for (; !at_end_of_file && buffer_size != 0 ;)
	{
		char *p, *line;
		int count, remain;
		count = (int) fread(buffer_start, 1, (int) buffer_size, f);
		if (0 == count)
		{
			/* add an implicit newline at the end of the file, which is C++11 conformant and good practical behaviour */
			buffer_start[0] = '\n';
			count = 1;
			at_end_of_file = 1;
		}

		/* skip past any UTF-8 bytemark, or isspace() and related functions trigger asserts in MSVC debug builds! */
		if (at_start_of_file && count >= sizeof(utf8_mark) && 0 == memcmp(buffer_start, utf8_mark, sizeof(utf8_mark)))
		{
			memmove(buffer_start, buffer_start + sizeof(utf8_mark), count - sizeof(utf8_mark));
			count -= sizeof(utf8_mark);
		}

		at_start_of_file = 0;

		buffer_start += count;

		line = line_buffer;
		for (p = line_buffer; p < buffer_start; ++p)
		{
			if ('\n' == *p)
			{
				*p = 0;
				if (file_count == max_count)
					td_croak("%s: too many includes", file->path);
				file_count += (*line_scanner)(scratch, line, &out[file_count], scanner);
				line = p+1;
			}
		}

		if (line > buffer_start)
			line = buffer_start;
		   
		remain = (int) (buffer_start - line);
		memmove(line_buffer, line, remain);
		buffer_start = line_buffer + remain;
		buffer_size = sizeof(line_buffer) - remain;
	}

	fclose(f);
	return file_count;
}
Exemple #10
0
td_build_result
td_build(td_engine *engine, td_node *node, int *jobs_run)
{
	int i;
	int thread_count;
	td_job_queue queue;

	if (engine->stats.build_called)
		td_croak("build() called more than once on same engine");

	if (0 != td_init_exec())
		td_croak("couldn't initialize command execution");

	engine->stats.build_called = 1;

	memset(&queue, 0, sizeof(queue));
	queue.engine = engine;

	pthread_mutex_init(&queue.mutex, NULL);
	pthread_cond_init(&queue.work_avail, NULL);

	queue.array_size = engine->node_count;
	queue.array = (td_node **) calloc(engine->node_count, sizeof(td_node*));

	queue.siginfo.mutex = &queue.mutex;
	queue.siginfo.cond = &queue.work_avail;

	thread_count = engine->settings.thread_count;
	if (thread_count > TD_MAX_THREADS)
		thread_count = TD_MAX_THREADS;

	if (td_debug_check(engine, TD_DEBUG_QUEUE))
		printf("using %d build threads\n", thread_count);

	td_block_signals(1);
	td_install_sighandler(&queue.siginfo);

	for (i = 0; i < thread_count-1; ++i)
	{
		int rc;
		if (td_debug_check(engine, TD_DEBUG_QUEUE))
			printf("starting thread %d\n", i);

		thread_arg[i].job_id = i + 2;
		thread_arg[i].queue = &queue;

		rc = pthread_create(&threads[i], NULL, build_worker, &thread_arg[i]);
		if (0 != rc)
			td_croak("couldn't start thread %d: %s", i, strerror(rc));
	}

	pthread_mutex_lock(&queue.mutex);

	node->job.flags |= TD_JOBF_ROOT;
	enqueue(&queue, node);

	pthread_mutex_unlock(&queue.mutex);
	pthread_cond_broadcast(&queue.work_avail);

	{
		thread_start_arg main_arg;
		main_arg.job_id = 1;
		main_arg.queue = &queue;

		build_worker(&main_arg);
	}

	if (td_verbosity_check(engine, 2) && -1 == queue.siginfo.flag)
		printf("*** aborted on signal %s\n", queue.siginfo.reason);

	for (i = thread_count - 2; i >= 0; --i)
	{
		void *res;
			int rc = pthread_join(threads[i], &res);
		if (0 != rc)
			td_croak("couldn't join thread %d: %s", i, strerror(rc));
	}

	if (0 != queue.thread_count)
		td_croak("threads are still running");

	/* there is a tiny race condition here if a user presses Ctrl-C just
	 * before the write to the static pointer occurs, but that's in nanosecond
	 * land */
	td_remove_sighandler();

	free(queue.array);
	pthread_cond_destroy(&queue.work_avail);
	pthread_mutex_destroy(&queue.mutex);

	*jobs_run = queue.jobs_run;

	if (queue.siginfo.flag < 0)
		return TD_BUILD_ABORTED;
	else if (queue.fail_count)
		return TD_BUILD_FAILED;
	else
		return TD_BUILD_SUCCESS;
}
Exemple #11
0
static void
advance_job(td_job_queue *queue, td_node *node, int job_id)
{
	td_jobstate state;
	while ((state = node->job.state) < TD_JOB_COMPLETED)
	{
		switch (state)
		{
		case TD_JOB_INITIAL:
			if (node->job.block_count > 0)
			{
				/* enqueue any blocking jobs and transition to the blocked state */
				int i, count, bc = 0;
				td_node **deps = node->deps;
				for (i = 0, count = node->dep_count; i < count; ++i)
				{
					td_node *dep = deps[i];
					if (!is_completed(dep))
					{
						++bc;
						if (!is_queued(dep) && dep->job.state < TD_JOB_BLOCKED)
							enqueue(queue, dep);
					}
				}
				assert(bc == node->job.block_count);
				transition_job(queue, node, TD_JOB_BLOCKED);
				pthread_cond_broadcast(&queue->work_avail);
				return;
			}
			else
			{
				/* nothing is blocking this job, so scan implicit deps immediately */
				transition_job(queue, node, TD_JOB_SCANNING);
			}
			break;

		case TD_JOB_BLOCKED:
			assert(0 == node->job.block_count);
			if (0 == node->job.failed_deps)
				transition_job(queue, node, TD_JOB_SCANNING);
			else
				transition_job(queue, node, TD_JOB_FAILED);
			break;

		case TD_JOB_SCANNING:

			if (0 == scan_implicit_deps(queue, node))
			{
				update_input_signature(queue, node);

				if (is_up_to_date(queue, node))
					transition_job(queue, node, TD_JOB_UPTODATE);
				else
					transition_job(queue, node, TD_JOB_RUNNING);
			}
			else
			{
				/* implicit dependency scanning failed */
				transition_job(queue, node, TD_JOB_FAILED);
			}
			break;

		case TD_JOB_RUNNING:
			if (0 != run_job(queue, node, job_id))
				transition_job(queue, node, TD_JOB_FAILED);
			else
				transition_job(queue, node, TD_JOB_COMPLETED);
			break;

		default:
			assert(0);
			td_croak("can't get here");
			break;
		}
	}

	if (is_completed(node))
	{
		int qcount = 0;
		td_job_chain *chain = node->job.pending_jobs;

		if (td_debug_check(queue->engine, TD_DEBUG_QUEUE))
			printf("%s completed - enqueing blocked jobs\n", node->annotation);

		/* unblock all jobs that are waiting for this job and enqueue them */
		while (chain)
		{
			td_node *n = chain->node;

			if (is_failed(node))
				n->job.failed_deps++;

			/* nodes blocked on this node can't be completed yet */
			assert(!is_completed(n));

			if (0 == --n->job.block_count)
			{
				if (!is_queued(n))
					enqueue(queue, n);
				++qcount;
			}
			chain = chain->next;
		}

		if (1 < qcount)
			pthread_cond_broadcast(&queue->work_avail);
		else if (1 == qcount)
			pthread_cond_signal(&queue->work_avail);
	}
}
Exemple #12
0
int main(int argc, char** argv)
{
	td_bin_allocator bin_alloc;
	const char *homedir;
	int res, rc, i;
	lua_State* L;

	td_init_portable();

#if !defined(TD_STANDALONE)
	if (NULL == (homedir = td_init_homedir()))
		return 1;
#else
	homedir = "";
#endif

	td_bin_allocator_init(&bin_alloc);

	L = lua_newstate(td_lua_alloc, &bin_alloc);
	if (!L)
		exit(1);

	lua_atpanic(L, on_lua_panic);

	luaL_openlibs(L);

	tundra_open(L);

#if defined(TD_STANDALONE)
	/* this is equivalent to table.insert(package.loaders, 1, td_load_embedded_file) */

	/* get the function */
	lua_getglobal(L, "table");
	lua_getfield(L, -1, "insert");
	lua_remove(L, -2);
	assert(!lua_isnil(L, -1));

	/* arg1: the package.loaders table */
	lua_getglobal(L, "package");
	lua_getfield(L, -1, "loaders");
	lua_remove(L, -2);
	assert(!lua_isnil(L, -1));

	lua_pushinteger(L, 1); /* arg 2 */
	lua_pushcfunction(L, td_load_embedded_file); /* arg 3 */

	lua_call(L, 3, 0);
#endif

	/* setup package.path */
	{
		char ppath[1024];
		snprintf(ppath, sizeof(ppath),
			"%s" TD_PATHSEP_STR "scripts" TD_PATHSEP_STR "?.lua;"
			"%s" TD_PATHSEP_STR "lua" TD_PATHSEP_STR "etc" TD_PATHSEP_STR "?.lua", homedir, homedir);
		lua_getglobal(L, "package");
		assert(LUA_TTABLE == lua_type(L, -1));
		lua_pushstring(L, ppath);
		lua_setfield(L, -2, "path");
	}

	/* push our error handler on the stack now (before the chunk to run) */
	lua_pushcclosure(L, get_traceback, 0);

	switch (luaL_loadbuffer(L, boot_snippet, sizeof(boot_snippet)-1, "boot_snippet"))
	{
	case LUA_ERRMEM:
		td_croak("out of memory");
		return 1;
	case LUA_ERRSYNTAX:
		td_croak("syntax error\n%s\n", lua_tostring(L, -1));
		return 1;
	}

	lua_newtable(L);
	lua_pushstring(L, homedir);
	lua_rawseti(L, -2, 1);

	for (i=1; i<argc; ++i)
	{
		lua_pushstring(L, argv[i]);
		lua_rawseti(L, -2, i+1);
	}

	{
		double t2;
		script_call_t1 = td_timestamp();
		res = lua_pcall(L, /*narg:*/1, /*nres:*/0, /*errorfunc:*/ -3);
		t2 = td_timestamp();
		if (global_tundra_stats)
			printf("total time spent in tundra: %.4fs\n", t2 - script_call_t1);
	}

	if (res == 0)
	{
		rc = global_tundra_exit_code;
	}
	else
	{
		fprintf(stderr, "%s\n", lua_tostring(L, -1));
		rc = 1;
	}

	lua_close(L);

	td_bin_allocator_cleanup(&bin_alloc);

	return rc;
}
Exemple #13
0
td_file *
td_engine_get_file(td_engine *engine, const char *input_path, td_get_file_mode mode)
{
	unsigned int hash;
	int slot;
	td_file *chain;
	td_file *f;
	int path_len;
	char path[1024];
	const char *out_path;
	size_t input_path_len = strlen(input_path);

	if (input_path_len >= sizeof(path))
		td_croak("path too long: %s", input_path);

	if (TD_COPY_STRING == mode)
	{
		if (isabspath(input_path))
		{
			strcpy(path, input_path);
		}
		else
		{
			snprintf(path, sizeof path, "%s%s", cwd, input_path);
		}

		sanitize_path(path, sizeof(path), input_path_len);
		hash = (unsigned int) djb2_hash(path);
		out_path = path;
	}
	else
	{
		hash = (unsigned int) djb2_hash(input_path);
		out_path = input_path;
	}

	td_mutex_lock_or_die(engine->lock);

	slot = (int) (hash % engine->file_hash_size);
	chain = engine->file_hash[slot];

	while (chain)
	{
		if (chain->hash == hash && 0 == strcmp(out_path, chain->path))
		{
			td_mutex_unlock_or_die(engine->lock);
			return chain;
		}

		chain = chain->bucket_next;
	}

	++engine->stats.file_count;
	f = td_page_alloc(&engine->alloc, sizeof(td_file));
	memset(f, 0, sizeof(td_file));

	f->path_len = path_len = (int) strlen(out_path);
	if (TD_COPY_STRING == mode)
		f->path = td_page_strdup(&engine->alloc, out_path, path_len);
	else
		f->path = out_path;
	f->hash = hash;
	f->name = find_basename(f->path, path_len);
	f->bucket_next = engine->file_hash[slot];
	f->signer = engine->default_signer;
	f->stat_dirty = 1;
	f->signature_dirty = 1;
	f->frozen_relstring_index = ~(0u);
	engine->file_hash[slot] = f;
	td_mutex_unlock_or_die(engine->lock);
	return f;
}