/ src / xcselect / xcselect.c
xcselect.c
  1  /*
  2  This file is part of Darling.
  3  
  4  Copyright (C) 2017 Lubos Dolezel
  5  
  6  Darling is free software: you can redistribute it and/or modify
  7  it under the terms of the GNU General Public License as published by
  8  the Free Software Foundation, either version 3 of the License, or
  9  (at your option) any later version.
 10  
 11  Darling is distributed in the hope that it will be useful,
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  GNU General Public License for more details.
 15  
 16  You should have received a copy of the GNU General Public License
 17  along with Darling.  If not, see <http://www.gnu.org/licenses/>.
 18  */
 19  #include <stdlib.h>
 20  #include <fcntl.h>
 21  #include <unistd.h>
 22  #include <stdbool.h>
 23  #include <sys/stat.h>
 24  #include <stdlib.h>
 25  #include <errno.h>
 26  #include <string.h>
 27  #include <dlfcn.h>
 28  #include <stdio.h>
 29  #include "xcselect.h"
 30  
 31  static char path_buffer[1024];
 32  
 33  static const char* get_developer_dir_from_file(const char* file)
 34  {
 35  	int fd = open(file, O_RDONLY);
 36  	if (fd == -1)
 37  		return NULL;
 38  
 39  	int len = read(fd, path_buffer, sizeof(path_buffer)-1);
 40  	if (len <= 0)
 41  	{
 42  		close(fd);
 43  		return NULL;
 44  	}
 45  
 46  	path_buffer[len] = 0;
 47  	close(fd);
 48  
 49  	return path_buffer;
 50  }
 51  
 52  const char* get_developer_dir_from_symlink(const char* link)
 53  {
 54  	ssize_t len;
 55  
 56  	len = readlink(link, path_buffer, sizeof(path_buffer)-1);
 57  	if (len <= 0)
 58  		return NULL;
 59  
 60  	path_buffer[len] = 0;
 61  	return path_buffer;
 62  }
 63  
 64  static bool dir_exists(const char* dir)
 65  {
 66  	struct stat st;
 67  
 68  	if (stat(dir, &st) != 0)
 69  		return false;
 70  
 71  	return S_ISDIR(st.st_mode);
 72  }
 73  
 74  static bool valid_dev_path(const char* path)
 75  {
 76  	char buffer[1024];
 77  	size_t length;
 78  
 79  	strcpy(buffer, path);
 80  	strcat(buffer, "/");
 81  	
 82  	length = strlen(buffer);
 83  	strcat(buffer, "usr/lib/libxcrun.dylib");
 84  	if (access(buffer, F_OK) == 0)
 85  		return true;
 86  
 87  	buffer[length] = 0;
 88  	strcat(buffer, "usr/bin/xcrun");
 89  	if (access(buffer, F_OK) == 0)
 90  		return true;
 91  
 92  	return false;
 93  }
 94  
 95  bool xcselect_find_developer_contents_from_path(const char* p, char* dst, bool* is_cmd_line, size_t dst_size)
 96  {
 97  	size_t length;
 98  
 99  	if (*p != '/')
100  	{
101  		getcwd(dst, dst_size);
102  		strcat(dst, "/");
103  		strcat(dst, p);
104  	}
105  	else
106  		strlcpy(dst, p, dst_size);
107  
108  	length = strlen(dst);
109  	if (valid_dev_path(dst))
110  		return true;
111  
112  	dst[length++] = '/';
113  	dst[length] = 0;
114  
115  	strcat(dst, "Library/Developer/CommandLineTools");
116  	if (valid_dev_path(dst))
117  	{
118  		*is_cmd_line = true;
119  		return true;
120  	}
121  
122  	dst[length] = 0;
123  	strcat(dst, "CommandLineTools");
124  	if (valid_dev_path(dst))
125  	{
126  		*is_cmd_line = true;
127  		return true;
128  	}
129  
130  	dst[length] = 0;
131  	strcat(dst, "Contents/Developer");
132  	if (valid_dev_path(dst))
133  		return true;
134  
135  	return false;
136  }
137  
138  bool xcselect_get_developer_dir_path(char* path, size_t path_len, bool* is_cmd_line)
139  {
140  	const char* p;
141  	char* slash;
142  
143  	*is_cmd_line = false;
144  
145  	p = getenv("DEVELOPER_DIR");
146  	if (p)
147  	{
148  		if (xcselect_find_developer_contents_from_path(p, path_buffer, is_cmd_line, sizeof(path_buffer)))
149  		{
150  			p = path_buffer;
151  			goto have_path;
152  		}
153  	}
154  
155  	p = get_developer_dir_from_symlink("/var/db/xcode_select_link");
156  	if (p)
157  		goto have_path;
158  
159  	p = get_developer_dir_from_symlink("/usr/share/xcode-select/xcode_dir_link");
160  	if (p)
161  		goto have_path;
162  
163  	p = get_developer_dir_from_file("/usr/share/xcode-select/xcode_dir_path");
164  	if (p)
165  		goto have_path;
166  
167  	if (dir_exists("/Applications/Xcode.app"))
168  	{
169  		p = "/Applications/Xcode.app/Contents/Developer";
170  		goto have_path;
171  	}
172  	else if (dir_exists("/Library/Developer/CommandLineTools"))
173  	{
174  		p = "/Library/Developer/CommandLineTools";
175  		goto have_path;
176  	}
177  	else if (dir_exists("/Library/Developer/DarlingCLT"))
178  	{
179  		p = "/Library/Developer/DarlingCLT";
180  		*is_cmd_line = true;
181  		goto have_path;
182  	}
183  
184  	return false;
185  
186  have_path:
187  	slash = strrchr(p, '/');
188  	if (slash != NULL)
189  	{
190  		if (strcmp(slash+1, "CommandLineTools") == 0)
191  			*is_cmd_line = true;
192  	}
193  
194  	strlcpy(path, p, path_len);
195  	strlcat(path, "/", path_len);
196  	return true;
197  }
198  
199  
200  int xcselect_invoke_xcrun(const char* tool, int argc, char* argv[], int flags)
201  {
202  	char dev_dir[1024];
203  	bool is_cmdline;
204  
205  	if (xcselect_get_developer_dir_path(dev_dir, sizeof(dev_dir), &is_cmdline))
206  	{
207  		char* buf = (char*) malloc(2048);
208  		size_t length;
209  		void* lib;
210  
211  		if (is_cmdline && (flags & XCSELECT_FLAG_REQUIRE_XCODE))
212  		{
213  			fprintf(stderr, "xcrun: tool '%s' requires Xcode installation, command line tools are insufficient.\n", tool);
214  			exit(1);
215  		}
216  
217  		strcpy(buf, dev_dir);
218  		if (buf[strlen(buf) - 1] != '/')
219  		{
220  			strcat(buf, "/");
221  		}
222  		length = strlen(buf);
223  
224  		strcat(buf, "usr/lib/libxcrun.dylib");
225  		lib = dlopen(buf, RTLD_LAZY);
226  
227  		if (lib != NULL)
228  		{
229  			void(*fn)(const char*, int, char**, const char*);
230  
231  			*((void**)&fn) = dlsym(lib, "xcrun_main");
232  			if (!fn)
233  			{
234  				fprintf(stderr, "xcrun: broken libxcrun.dylib at '%s'\n", buf);
235  			}
236  			else
237  			{
238  				fn(tool, argc, argv, dev_dir);
239  				fprintf(stderr, "xcrun: xcrun_main unexpectedly exited\n");
240  			}
241  		}
242  		else
243  		{
244  			char** argv2;
245  			int j = 0;
246  
247  			buf[length] = 0;
248  			strcat(buf, "usr/bin/xcrun");
249  
250  			// +3: buf, tool, NULL
251  			argv2 = (char**) __builtin_alloca((argc+3) * sizeof(char*));
252  			argv2[j++] = buf;
253  
254  			if (tool != NULL)
255  				argv2[j++] = (char*) tool;
256  
257  			for (int i = 0; i < argc; i++, j++)
258  				argv2[j] = argv[i];
259  			argv2[j] = NULL;
260  
261  			execv(buf, argv2);
262  			fprintf(stderr, "xcrun: developer path '%s' is invalid, failed to execute '%s': %s\n",
263  					dev_dir, buf, strerror(errno));
264  		}
265  	}
266  	else 
267  	{
268  		if (dir_exists("/usr/libexec/DeveloperTools") && tool != NULL)
269  		{
270  			char* buf = __builtin_alloca(strlen(tool) + 30);
271  			strcpy(buf, "/usr/libexec/DeveloperTools/");
272  			strcat(buf, tool);
273  
274  			if (access(buf, F_OK) == 0)
275  			{
276  				char** argv2 = (char **) __builtin_alloca((argc+1+1) * sizeof(char*));
277  				argv2[0] = buf;
278  				for (int i = 0; i < argc; i++)
279  					argv2[i+1] = argv[i];
280  				argv2[argc+1] = NULL;
281  
282  				execv(buf, argv2);
283  				fprintf(stderr, "xcrun: failed to exec '%s': %s\n", buf, strerror(errno));
284  				exit(1);
285  			}
286  		}
287  
288  		fprintf(stderr, "xcrun: cannot find developer tools, set DEVELOPER_DIR if you are using a non-standard location.\n");
289  	}
290  
291  	exit(1);
292  }
293  
294  struct __xcselect_manpaths
295  {
296  	unsigned int count;
297  	char** paths;
298  };
299  
300  void xcselect_manpaths_append(xcselect_manpaths* paths, const char* path)
301  {
302  	paths->count++;
303  	paths->paths = realloc(paths->paths, sizeof(char*) * paths->count);
304  	paths->paths[paths->count - 1] = strdup(path);
305  }
306  
307  xcselect_manpaths* xcselect_get_manpaths(const char* sdkname)
308  {
309  	char path[1024];
310  	bool unused;
311  	size_t len;
312  	xcselect_manpaths* rv;
313  
314  	if (!xcselect_get_developer_dir_path(path, sizeof(path), &unused))
315  		return NULL;
316  
317  	len = strlen(path);
318  	strcat(path, "usr/lib/libxcrun.dylib");
319  
320  	rv = (xcselect_manpaths*) malloc(sizeof(*rv));
321  	memset(rv, 0, sizeof(*rv));
322  
323  	if (access(path, F_OK) == 0)
324  	{
325  		void* module = dlopen(path, RTLD_LAZY);
326  		void (*fn)(const char* devpath, const char* sdkname, void (^)(const char*));
327  
328  		if (!module)
329  		{
330  			free(rv);
331  			fprintf(stderr, "%s: error: cannot load libxcrun (%s)\n", getprogname(), dlerror());
332  			return NULL;
333  		}
334  
335  		*((void**)&fn) = dlsym(module, "xcrun_iter_manpaths");
336  
337  		if (fn != NULL)
338  		{
339  			path[len] = 0;
340  			fn(path, sdkname, ^(const char* path) {
341  				xcselect_manpaths_append(rv, path);
342  			});
343  		}
344  
345  		dlclose(module);
346  	}
347  
348  	// Add standard paths
349  	path[len] = 0;
350  	strcat(path, "usr/share/man");
351  	xcselect_manpaths_append(rv, path);
352  
353  	path[len] = 0;
354  	strcat(path, "usr/llvm-gcc-4,2/share/man");
355  	xcselect_manpaths_append(rv, path);
356  
357  	path[len] = 0;
358  	strcat(path, "Toolchains/XcodeDefault.xctoolchain/usr/share/man");
359  	xcselect_manpaths_append(rv, path);
360  
361  	return rv;
362  }
363  
364  unsigned int xcselect_manpaths_get_num_paths(xcselect_manpaths* p)
365  {
366  	return p->count;
367  }
368  
369  const char* xcselect_manpaths_get_path(xcselect_manpaths* p, unsigned int idx)
370  {
371  	if (idx < xcselect_manpaths_get_num_paths(p))
372  		return p->paths[idx];
373  	else
374  		return NULL;
375  }
376  
377  void xcselect_manpaths_free(xcselect_manpaths* p)
378  {
379  	for (unsigned int i = 0; i < p->count; i++)
380  		free(p->paths[i]);
381  	free(p->paths);
382  	free(p);
383  }
384