xref: /xnu-10002.1.13/SETUP/json_compilation_db/json_compilation_db.c (revision 1031c584a5e37aff177559b9f69dbd3c8c3fd30a)
1 /*
2  * Copyright (c) 2013 Apple Inc. All rights reserved.
3  *
4  * @APPLE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. Please obtain a copy of the License at
10  * http://www.opensource.apple.com/apsl/ and read it before using this
11  * file.
12  *
13  * The Original Code and all software distributed under the License are
14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18  * Please see the License for the specific language governing rights and
19  * limitations under the License.
20  *
21  * @APPLE_LICENSE_HEADER_END@
22  */
23 
24 /*
25  * json_compilation_db is a helper tool that takes a compiler invocation, and
26  * appends it in JSON format to the specified database.
27  */
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <stdbool.h>
34 #include <errno.h>
35 #include <err.h>
36 #include <sysexits.h>
37 
38 #include <sys/stat.h>
39 #include <sys/fcntl.h>
40 #include <sys/param.h>
41 
42 void usage(void);
43 char *escape_string(const char *);
44 
45 /*
46  * We support appending to two databases.
47  *
48  * 0-byte: ""
49  *
50  * or
51  *
52  * "["
53  * "{"
54  * "  ..."
55  * "}"
56  * "]"
57  */
58 
59 int
main(int argc,char * argv[])60 main(int argc, char * argv[])
61 {
62 	struct stat sb;
63 	int ret;
64 	int dstfd;
65 	FILE *dst = NULL;
66 	const char *json_output = NULL;
67 	const char *cwd = NULL;
68 	const char *input_file = NULL;
69 	char start[2];
70 	size_t read_bytes;
71 	int i;
72 	size_t input_file_len;
73 
74 	if (argc < 5) {
75 		usage();
76 	}
77 
78 	json_output = argv[1];
79 	cwd = argv[2];
80 	input_file = argv[3];
81 
82 	argv += 4;
83 	argc -= 4;
84 
85 	input_file_len = strlen(input_file);
86 	if (!(input_file_len > 2 && 0 == strcmp(".c", input_file + input_file_len - 2)) &&
87 	    !(input_file_len > 3 && 0 == strcmp(".cp", input_file + input_file_len - 3)) &&
88 	    !(input_file_len > 4 && 0 == strcmp(".cpp", input_file + input_file_len - 4))) {
89 		/* Not a C/C++ file, just skip it */
90 		return 0;
91 	}
92 
93 	dstfd = open(json_output, O_RDWR | O_CREAT | O_EXLOCK, DEFFILEMODE);
94 	if (dstfd < 0) {
95 		err(EX_NOINPUT, "open(%s)", json_output);
96 	}
97 
98 	ret = fstat(dstfd, &sb);
99 	if (ret < 0) {
100 		err(EX_NOINPUT, "fstat(%s)", json_output);
101 	}
102 
103 	if (!S_ISREG(sb.st_mode)) {
104 		err(EX_USAGE, "%s is not a regular file", json_output);
105 	}
106 
107 	dst = fdopen(dstfd, "w+");
108 	if (dst == NULL) {
109 		err(EX_UNAVAILABLE, "fdopen");
110 	}
111 
112 	read_bytes = fread(start, sizeof(start[0]), sizeof(start) / sizeof(start[0]), dst);
113 	if ((read_bytes != sizeof(start)) || (0 != memcmp(start, "[\n", sizeof(start) / sizeof(start[0])))) {
114 		/* no JSON start, we don't really care why */
115 		ret = fseeko(dst, 0, SEEK_SET);
116 		if (ret < 0) {
117 			err(EX_UNAVAILABLE, "fseeko");
118 		}
119 
120 		ret = fputs("[", dst);
121 		if (ret < 0) {
122 			err(EX_UNAVAILABLE, "fputs");
123 		}
124 	} else {
125 		/* has at least two bytes at the start. Seek to 3 bytes before the end */
126 		ret = fseeko(dst, -3, SEEK_END);
127 		if (ret < 0) {
128 			err(EX_UNAVAILABLE, "fseeko");
129 		}
130 
131 		ret = fputs(",", dst);
132 		if (ret < 0) {
133 			err(EX_UNAVAILABLE, "fputs");
134 		}
135 	}
136 
137 	fprintf(dst, "\n");
138 	fprintf(dst, "{\n");
139 	fprintf(dst, "  \"directory\": \"%s\",\n", cwd);
140 	fprintf(dst, "  \"file\": \"%s\",\n", input_file);
141 	fprintf(dst, "  \"command\": \"");
142 	for (i = 0; i < argc; i++) {
143 		bool needs_escape = strchr(argv[i], '\\') || strchr(argv[i], '"') || strchr(argv[i], ' ');
144 
145 		if (needs_escape) {
146 			char *escaped_string = escape_string(argv[i]);
147 			fprintf(dst, "%s\\\"%s\\\"", i == 0 ? "" : " ", escaped_string);
148 			free(escaped_string);
149 		} else {
150 			fprintf(dst, "%s%s", i == 0 ? "" : " ", argv[i]);
151 		}
152 	}
153 	fprintf(dst, "\"\n");
154 	fprintf(dst, "}\n");
155 	fprintf(dst, "]\n");
156 
157 	ret = fclose(dst);
158 	if (ret < 0) {
159 		err(EX_UNAVAILABLE, "fclose");
160 	}
161 
162 	return 0;
163 }
164 
165 void
usage(void)166 usage(void)
167 {
168 	fprintf(stderr, "Usage: %s <json_output> <cwd> <input_file> <compiler> [<invocation> ...]\n", getprogname());
169 	exit(EX_USAGE);
170 }
171 
172 /*
173  * A valid JSON string can't contain \ or ", so we look for these in our argv[] array (which
174  * our parent shell would have done shell metacharacter evaluation on, and escape just these.
175  * The entire string is put in \" escaped quotes to handle spaces that are valid JSON
176  * but should be used for grouping when running the compiler for real.
177  */
178 char *
escape_string(const char * input)179 escape_string(const char *input)
180 {
181 	size_t len = strlen(input);
182 	size_t i, j;
183 	char *output = malloc(len * 4 + 1);
184 
185 	for (i = 0, j = 0; i < len; i++) {
186 		char ch = input[i];
187 
188 		if (ch == '\\' || ch == '"') {
189 			output[j++] = '\\';
190 			output[j++] = '\\'; /* output \\ in JSON, which the final shell will see as \ */
191 			output[j++] = '\\'; /* escape \ or ", which the final shell will see and pass to the compiler */
192 		}
193 		output[j++] = ch;
194 	}
195 
196 	output[j] = '\0';
197 
198 	return output;
199 }
200