aboutsummaryrefslogtreecommitdiff
blob: 0d1ce831ebe37dddd15bfa4d8c2f4672e49faa1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
 * Copyright 2014 Gentoo Foundation
 * Distributed under the terms of the GNU General Public License v2
 */

#include "main.h"

#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "xasprintf.h"
#include "prelink.h"

static const char prelink_bin[] = "prelink";

static int prelink_in_current_path(bool quiet_missing)
{
	pid_t pid;

	switch ((pid = fork())) {
	case -1: errp("error forking process");
	case 0: {
		/* we are the child */
		int dev_null;
		close(STDOUT_FILENO);
		dev_null = open("/dev/null", O_WRONLY);
		if (dev_null == -1) {
			warnp("Error opening /dev/null");
			_exit(2);
		}
		if (dup2(dev_null, STDOUT_FILENO) == -1) {
			warnp("Error redirecting output");
			_exit(2);
		}
		execlp(prelink_bin, prelink_bin, "--version", (char *)NULL);
		if (!quiet_missing || errno != ENOENT)
			warnp("error executing %s", prelink_bin);
		_exit(errno == ENOENT ? 1 : 2);
	}
	default: {
		/* we are the parent */
		int status;
		waitpid(pid, &status, 0);
		if (WIFEXITED(status))
			return MIN(WEXITSTATUS(status), 2);
		else
			errp("%s freaked out %#x", prelink_bin, status);
	}
	}
}

bool prelink_available(void)
{
	int status = prelink_in_current_path(true);

	if (status == 1) {
		/* extend path to include sbin and search again */
		static const char sbin_path[] = "/sbin:/usr/sbin:/usr/local/sbin";
		char *path;
		xasprintf(&path, "PATH=%s:%s", getenv("PATH") ? : "", sbin_path);
		putenv(path);
		status = prelink_in_current_path(0);
	}

	return status == 0 ? true : false;
}

#ifdef __linux__
#include <elf.h>
static bool is_prelink_elf(int fd, const char *filename)
{
	unsigned char header[EI_NIDENT + 2];
	ssize_t len;
	uint16_t e_type;

	len = read(fd, &header, sizeof(header));
	if (len == -1) {
		warnp("unable to read %s", filename);
		return false;
	}
	if (lseek(fd, 0, SEEK_SET) != 0)
		errp("unable to reset after ELF check %s\n", filename);
	if (len < (ssize_t)sizeof(header))
		return false;

	if (memcmp(header, ELFMAG, SELFMAG))
		return false;

	/* prelink only likes certain types of ELFs */
	switch (header[EI_DATA]) {
	case ELFDATA2LSB:
		e_type = header[EI_NIDENT] | (header[EI_NIDENT + 1] << 8);
		break;
	case ELFDATA2MSB:
		e_type = header[EI_NIDENT + 1] | (header[EI_NIDENT] << 8);
		break;
	default:
		return false;
	}
	switch (e_type) {
	case ET_EXEC:
	case ET_DYN:
		return true;
	default:
		return false;
	}

	/* XXX: should we also check OS's/arches that prelink supports ? */
}
#else
static bool is_prelink_elf(int fd, const char *filename)
{
	(void) fd;
	(void) filename;

	return false;
}
#endif

static int execvp_const(const char * const argv[])
{
	return execvp(argv[0], (void *)argv);
}

static int _hash_cb_prelink(int fd, const char *filename, const char * const argv[])
{
	int pipefd[2];

	if (!is_prelink_elf(fd, filename))
		return fd;

	if (pipe(pipefd))
		errp("unable to create pipe");

	switch (fork()) {
	case -1: errp("error forking process");
	case 0: {
		/* we are the child */
		static const char * const cat_argv[] = { "cat", NULL, };
		pid_t pid;

		/* make sure we get notified of the child exit */
		signal(SIGCHLD, SIG_DFL);

		/* connect up stdin/stdout for reading/writing the file */
		close(pipefd[0]);
		if (dup2(fd, STDIN_FILENO) == -1) {
			warnp("error redirecting input");
			_exit(EXIT_FAILURE);
		}
		if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
			warnp("error redirecting output");
			_exit(EXIT_FAILURE);
		}

		/*
		 * fork a monitor process ... this way the main qcheck program
		 * can simply read their side of the pipe without having to wait
		 * for the whole prelink program to run.  gives a bit of speed
		 * up on multicore systems and doesn't need as much mem to hold
		 * all the data in the pipe before we read it.  this also makes
		 * it easy to fall back to `cat` when prelink skipped the file
		 * that we fed it (like the split debug files).
		 */
		switch ((pid = fork())) {
		case -1: errp("error forking process");
		case 0:
			/* we are the child */
			execvp_const(argv);
			warnp("error executing %s", prelink_bin);
			_exit(1);
		default: {
			int status;
			waitpid(pid, &status, 0);
			if (WIFEXITED(status)) {
				if (WEXITSTATUS(status) == EXIT_SUCCESS)
					_exit(0);
				/* assume prelink printed its own error message */
			} else
				warnp("%s freaked out %#x", argv[0], status);
			/* we've come too far!  try one last thing ... */
			execvp_const(cat_argv);
			_exit(1);
		}
		}
	}
	default:
		/* we are the parent */
		close(pipefd[1]);
		/* we don't need this anymore since we've got a pipe to read */
		close(fd);
		/* ignore the monitor process exit status to avoid ZOMBIES */
		signal(SIGCHLD, SIG_IGN);
		/* assume child worked ... if it didn't, it will warn for us */
		return pipefd[0];
	}

	return fd;
}

int hash_cb_prelink_undo(int fd, const char *filename)
{
	static const char * const argv[] = {
		prelink_bin,
		"--undo",
		"--undo-output=-",
		"/dev/stdin",
		NULL,
	};
	return _hash_cb_prelink(fd, filename, argv);
}