1/*
2 * Copyright (c) 2004 Apple Computer, 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#include <stdbool.h>
25#include <stdlib.h>
26#include <stdio.h>
27#include <fcntl.h>
28#include <sys/types.h>
29#include <sys/mman.h>
30#include <sys/stat.h>
31#include <string.h>
32#include <pthread.h>
33
34/* Catastrophic errors only, including "out of memory" */
35static bool parse_error = false;
36static bool bootstrapping = false;
37static bool parsed = false;
38#define UNIX2003_DEFAULT_MODE true
39static bool unix2003_mode = UNIX2003_DEFAULT_MODE;
40
41static pthread_once_t threadsafe = PTHREAD_ONCE_INIT;
42
43/* There was once a lot of parsing and the ability to set diffrent commands
44  to diffrent modes.  That is gone, but some of the scaffolding remains */
45
46static void check_env_var(void) {
47	char *mode = getenv("COMMAND_MODE");
48
49	if (mode) {
50		if (!strcasecmp(mode, "legacy")) {
51			unix2003_mode = false;
52		} else {
53			if (!strcasecmp(mode, "unix2003")) {
54				unix2003_mode = true;
55			} else {
56				parse_error = true;
57				unix2003_mode = UNIX2003_DEFAULT_MODE;
58			}
59		}
60	}
61}
62
63/* Function is expected to be something like libc/malloc for a libc call,
64or bin/date for command line utilities.  Modes are currently:
65  Legacy - pre-tiger behaviour, presumably UNIX2003 incompatable
66  UNIX2003 - Unix 2003 spec compliant
67  Bootstrap - only seen by (parts of) libc.  The compat_mode system is
68    still starting up.  This will be seen while compat_mode parses it's
69	config file, or reads the cache file, and only by libc functions it calls.
70  Error - the conf file could not be parsed, either due to a severe
71    syntax error, an I/O error, or an out of memory condition
72
73  mode names are case insensitatave.  You can use | ^ & and even !
74  but no () yet, and that stuff hasn't been tested much yet, nor
75  has it been optimised.
76*/
77
78bool
79compat_mode(const char *function, const char *mode) {
80	if (!parsed && !bootstrapping) {
81		pthread_once(&threadsafe, check_env_var);
82		parsed = true;
83	}
84
85	bool want2003 = !strcasecmp("unix2003", mode);
86
87	if (want2003) {
88		return unix2003_mode;
89	}
90
91	bool want_legacy = !strcasecmp("legacy", mode);
92
93	if (want_legacy) {
94		return !unix2003_mode;
95	}
96
97	bool want_bootstrap = !strcasecmp("bootstrap", mode);
98
99	if (want_bootstrap) {
100		return bootstrapping;
101	}
102
103	bool want_error = !strcasecmp("error", mode);
104
105	if (want_error) {
106		return parse_error;
107	}
108
109	char *op = NULL;
110
111	if ((op = strpbrk(mode, "!^&|"))) {
112		if (*op == '!') {
113			if (op != mode) goto syn_error;
114			return !compat_mode(function, mode +1);
115		}
116
117		/* XXX char tmp[] would be better for left_arg, but
118		  we are not sure what the max size should be...  is
119		  alloca(3) frowned on? */
120		size_t left_sz = 1 + (op - mode);
121		char *left_arg = malloc(left_sz);
122		strlcpy(left_arg, mode, left_sz);
123		bool left = compat_mode(function, left_arg);
124		free(left_arg);
125		bool right = compat_mode(function, op +1);
126
127		/* XXX check leftOPright syntax errors */
128
129		switch(*op) {
130			case '^':
131				return left ^ right;
132			case '&':
133				return left && right;
134			case '|':
135				return left || right;
136			default:
137				goto syn_error;
138		}
139	}
140
141syn_error:
142	fprintf(stderr, "invalid mode %s (while checking for %s)\n",
143	  mode, function);
144
145	return false;
146}
147
148#ifdef SELF_TEST_COMPAT_MODE
149
150#include <assert.h>
151
152#define NEXPECTED 3
153
154typedef struct {
155	char *mode;
156	/* [0] is unix2003 mode, [1] is legacy, [2] is Invalid_Mode */
157	bool expected[NEXPECTED];
158} testcase;
159
160testcase cases[] = {
161	{ "unix2003", {true, false, true}},
162	{ "legacy", {false, true, false}},
163	{ "bootstrap", {false, false, false}},
164	{ "unix2003|legacy", {true, true, true}},
165	{ "unix2003&legacy", {false, false, false}},
166	{ "unix2003|legacy|bootstrap", {true, true, true}},
167	{ "unix2003&legacy&bootstrap", {false, false, false}},
168	{ "!unix2003", {false, true, false}},
169	{ "!legacy", {true, false, true}},
170	/* XXX ! with compound statments */
171	{ "unix2003^bootstrap", {true, false, true}},
172	{ "unix2003^legacy", {true, true, true}},
173	{ "&unix2003", {false, false, false}},
174	{ "unix2003&", {false, false, false}},
175	{ "unix2003!legacy", {false, false, false}},
176	{ "unix2003&error", {false, false, true}},
177	{ "error", {false, false, true}},
178	{ "error|unix2003", {true, false, true}},
179	{ "error|legacy", {false, true, true}},
180	{ "error&legacy", {false, false, false}},
181};
182
183int ncases = sizeof(cases)/sizeof(testcase);
184
185int
186main(int argc, char *argv[]) {
187	int i, j;
188	int failures = 0;
189	char *settings[] = { "unix2003", "legacy", "Invalid Mode"};
190
191	assert(sizeof(settings) / sizeof(char *) == NEXPECTED);
192
193	for(i = 0; i < NEXPECTED; ++i) {
194		setenv("COMMAND_MODE", settings[i], 1);
195		char *compat_env = getenv("COMMAND_MODE");
196		printf("$COMMAND_MODE = %s\n", compat_env);
197		if (i != 0) {
198			/* here we force COMMAND_MODE to be checked again, which
199			  is normally impossiable because check_env_var() is a static
200			  function, but really nothing except the test cases wants to
201			  try it anyway... */
202			check_env_var();
203		}
204		for(j = 0; j < ncases; ++j) {
205			bool ret = compat_mode("bin/compat_mode", cases[j].mode);
206			bool expect = cases[j].expected[i];
207
208			if (expect != ret) {
209				failures++;
210			}
211
212			printf("Case %s got %d expected %d%s\n",
213			  cases[j].mode, ret, expect, (ret == expect) ? "" : " FAILED");
214		}
215	}
216
217	/* We have ncases entries in cases[], but each is run multiple
218	  times (once per entry in the settings array) thus the multiply */
219	printf("Passed %d of %d cases\n",
220	  NEXPECTED*ncases - failures, NEXPECTED*ncases);
221
222	return failures ? 1 : 0;
223}
224
225#endif
226