php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
		const char *path, const char *mode, int options, zend_string **opened_path,
		php_stream_context *context, int redirect_max, int flags STREAMS_DC) /* {{{ */
{
	php_stream *stream = NULL;
	php_url *resource = NULL;
	int use_ssl;
	int use_proxy = 0;
	char *scratch = NULL;
	zend_string *tmp = NULL;
	char *ua_str = NULL;
	zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
	size_t scratch_len = 0;
	char location[HTTP_HEADER_BLOCK_SIZE];
	zval response_header;
	int reqok = 0;
	char *http_header_line = NULL;
	char tmp_line[128];
	size_t chunk_size = 0, file_size = 0;
	int eol_detect = 0;
	char *transport_string;
	zend_string *errstr = NULL;
	size_t transport_len;
	int have_header = 0;
	zend_bool request_fulluri = 0, ignore_errors = 0;
	char *protocol_version = NULL;
	int protocol_version_len = 3; /* Default: "1.0" */
	struct timeval timeout;
	char *user_headers = NULL;
	int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
	int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
	zend_bool follow_location = 1;
	php_stream_filter *transfer_encoding = NULL;
	int response_code;
	zend_array *symbol_table;

	ZVAL_UNDEF(&response_header);
	tmp_line[0] = '\0';

	if (redirect_max < 1) {
		php_stream_wrapper_log_error(wrapper, options, "Redirection limit reached, aborting");
		return NULL;
	}

	resource = php_url_parse(path);
	if (resource == NULL) {
		return NULL;
	}

	if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) {
		if (!context ||
			(tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) == NULL ||
			Z_TYPE_P(tmpzval) != IS_STRING ||
			Z_STRLEN_P(tmpzval) <= 0) {
			php_url_free(resource);
			return php_stream_open_wrapper_ex(path, mode, REPORT_ERRORS, NULL, context);
		}
		/* Called from a non-http wrapper with http proxying requested (i.e. ftp) */
		request_fulluri = 1;
		use_ssl = 0;
		use_proxy = 1;

		transport_len = Z_STRLEN_P(tmpzval);
		transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval));
	} else {
		/* Normal http request (possibly with proxy) */

		if (strpbrk(mode, "awx+")) {
			php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper does not support writeable connections");
			php_url_free(resource);
			return NULL;
		}

		use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's';
		/* choose default ports */
		if (use_ssl && resource->port == 0)
			resource->port = 443;
		else if (resource->port == 0)
			resource->port = 80;

		if (context &&
			(tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) != NULL &&
			Z_TYPE_P(tmpzval) == IS_STRING &&
			Z_STRLEN_P(tmpzval) > 0) {
			use_proxy = 1;
			transport_len = Z_STRLEN_P(tmpzval);
			transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval));
		} else {
			transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port);
		}
	}

	if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) {
		double d = zval_get_double(tmpzval);
#ifndef PHP_WIN32
		timeout.tv_sec = (time_t) d;
		timeout.tv_usec = (size_t) ((d - timeout.tv_sec) * 1000000);
#else
		timeout.tv_sec = (long) d;
		timeout.tv_usec = (long) ((d - timeout.tv_sec) * 1000000);
#endif
	} else {
#ifndef PHP_WIN32
		timeout.tv_sec = FG(default_socket_timeout);
#else
		timeout.tv_sec = (long)FG(default_socket_timeout);
#endif
		timeout.tv_usec = 0;
	}

	stream = php_stream_xport_create(transport_string, transport_len, options,
			STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
			NULL, &timeout, context, &errstr, NULL);

	if (stream) {
		php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout);
	}

	if (errstr) {
		php_stream_wrapper_log_error(wrapper, options, "%s", ZSTR_VAL(errstr));
		zend_string_release(errstr);
		errstr = NULL;
	}

	efree(transport_string);

	if (stream && use_proxy && use_ssl) {
		smart_str header = {0};

		/* Set peer_name or name verification will try to use the proxy server name */
		if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) {
			ZVAL_STRING(&ssl_proxy_peer_name, resource->host);
			php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name);
			zval_ptr_dtor(&ssl_proxy_peer_name);
		}

		smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1);
		smart_str_appends(&header, resource->host);
		smart_str_appendc(&header, ':');
		smart_str_append_unsigned(&header, resource->port);
		smart_str_appendl(&header, " HTTP/1.0\r\n", sizeof(" HTTP/1.0\r\n")-1);

	    /* check if we have Proxy-Authorization header */
		if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) {
			char *s, *p;

			if (Z_TYPE_P(tmpzval) == IS_ARRAY) {
				zval *tmpheader = NULL;

				ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) {
					if (Z_TYPE_P(tmpheader) == IS_STRING) {
						s = Z_STRVAL_P(tmpheader);
						do {
							while (*s == ' ' || *s == '\t') s++;
							p = s;
							while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++;
							if (*p == ':') {
								p++;
								if (p - s == sizeof("Proxy-Authorization:") - 1 &&
								    zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1,
								        "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) {
									while (*p != 0 && *p != '\r' && *p !='\n') p++;
									smart_str_appendl(&header, s, p - s);
									smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
									goto finish;
								} else {
									while (*p != 0 && *p != '\r' && *p !='\n') p++;
								}
							}
							s = p;
							while (*s == '\r' || *s == '\n') s++;
						} while (*s != 0);
					}
				} ZEND_HASH_FOREACH_END();
			} else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) {
				s = Z_STRVAL_P(tmpzval);
				do {
					while (*s == ' ' || *s == '\t') s++;
					p = s;
					while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++;
					if (*p == ':') {
						p++;
						if (p - s == sizeof("Proxy-Authorization:") - 1 &&
						    zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1,
						        "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) {
							while (*p != 0 && *p != '\r' && *p !='\n') p++;
							smart_str_appendl(&header, s, p - s);
							smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
							goto finish;
						} else {
							while (*p != 0 && *p != '\r' && *p !='\n') p++;
						}
					}
					s = p;
					while (*s == '\r' || *s == '\n') s++;
				} while (*s != 0);
			}
		}
finish:
		smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);

		if (php_stream_write(stream, ZSTR_VAL(header.s), ZSTR_LEN(header.s)) != ZSTR_LEN(header.s)) {
			php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
			php_stream_close(stream);
			stream = NULL;
		}
 	 	smart_str_free(&header);

 	 	if (stream) {
 	 		char header_line[HTTP_HEADER_BLOCK_SIZE];

			/* get response header */
			while (php_stream_gets(stream, header_line, HTTP_HEADER_BLOCK_SIZE-1) != NULL) {
				if (header_line[0] == '\n' ||
				    header_line[0] == '\r' ||
				    header_line[0] == '\0') {
				  break;
				}
			}
		}

		/* enable SSL transport layer */
		if (stream) {
			if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
			    php_stream_xport_crypto_enable(stream, 1) < 0) {
				php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
				php_stream_close(stream);
				stream = NULL;
			}
		}
	}

	if (stream == NULL)
		goto out;

	/* avoid buffering issues while reading header */
	if (options & STREAM_WILL_CAST)
		chunk_size = php_stream_set_chunk_size(stream, 1);

	/* avoid problems with auto-detecting when reading the headers -> the headers
	 * are always in canonical \r\n format */
	eol_detect = stream->flags & (PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);
	stream->flags &= ~(PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC);

	php_stream_context_set(stream, context);

	php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);

	if (header_init && context && (tmpzval = php_stream_context_get_option(context, "http", "max_redirects")) != NULL) {
		redirect_max = (int)zval_get_long(tmpzval);
	}

	if (context && (tmpzval = php_stream_context_get_option(context, "http", "method")) != NULL) {
		if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) {
			/* As per the RFC, automatically redirected requests MUST NOT use other methods than
			 * GET and HEAD unless it can be confirmed by the user */
			if (!redirected
				|| (Z_STRLEN_P(tmpzval) == 3 && memcmp("GET", Z_STRVAL_P(tmpzval), 3) == 0)
				|| (Z_STRLEN_P(tmpzval) == 4 && memcmp("HEAD",Z_STRVAL_P(tmpzval), 4) == 0)
			) {
				scratch_len = strlen(path) + 29 + Z_STRLEN_P(tmpzval);
				scratch = emalloc(scratch_len);
				strlcpy(scratch, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval) + 1);
				strncat(scratch, " ", 1);
			}
		}
	}

	if (context && (tmpzval = php_stream_context_get_option(context, "http", "protocol_version")) != NULL) {
		protocol_version_len = (int)spprintf(&protocol_version, 0, "%.1F", zval_get_double(tmpzval));
	}

	if (!scratch) {
		scratch_len = strlen(path) + 29 + protocol_version_len;
		scratch = emalloc(scratch_len);
		strncpy(scratch, "GET ", scratch_len);
	}

	/* Should we send the entire path in the request line, default to no. */
	if (!request_fulluri && context &&
		(tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) {
		request_fulluri = zend_is_true(tmpzval);
	}

	if (request_fulluri) {
		/* Ask for everything */
		strcat(scratch, path);
	} else {
		/* Send the traditional /path/to/file?query_string */

		/* file */
		if (resource->path && *resource->path) {
			strlcat(scratch, resource->path, scratch_len);
		} else {
			strlcat(scratch, "/", scratch_len);
		}

		/* query string */
		if (resource->query) {
			strlcat(scratch, "?", scratch_len);
			strlcat(scratch, resource->query, scratch_len);
		}
	}

	/* protocol version we are speaking */
	if (protocol_version) {
		strlcat(scratch, " HTTP/", scratch_len);
		strlcat(scratch, protocol_version, scratch_len);
		strlcat(scratch, "\r\n", scratch_len);
	} else {
		strlcat(scratch, " HTTP/1.0\r\n", scratch_len);
	}

	/* send it */
	php_stream_write(stream, scratch, strlen(scratch));

	if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) {
		tmp = NULL;

		if (Z_TYPE_P(tmpzval) == IS_ARRAY) {
			zval *tmpheader = NULL;
			smart_str tmpstr = {0};

			ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) {
				if (Z_TYPE_P(tmpheader) == IS_STRING) {
					smart_str_append(&tmpstr, Z_STR_P(tmpheader));
					smart_str_appendl(&tmpstr, "\r\n", sizeof("\r\n") - 1);
				}
			} ZEND_HASH_FOREACH_END();
			smart_str_0(&tmpstr);
			/* Remove newlines and spaces from start and end. there's at least one extra \r\n at the end that needs to go. */
			if (tmpstr.s) {
				tmp = php_trim(tmpstr.s, NULL, 0, 3);
				smart_str_free(&tmpstr);
			}
		} else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) {
			/* Remove newlines and spaces from start and end php_trim will estrndup() */
			tmp = php_trim(Z_STR_P(tmpzval), NULL, 0, 3);
		}
		if (tmp && ZSTR_LEN(tmp)) {
			char *s;
			char *t;

			user_headers = estrndup(ZSTR_VAL(tmp), ZSTR_LEN(tmp));

			if (ZSTR_IS_INTERNED(tmp)) {
				tmp = zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0);
			} else if (GC_REFCOUNT(tmp) > 1) {
				GC_REFCOUNT(tmp)--;
				tmp = zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0);
			}

			/* Make lowercase for easy comparison against 'standard' headers */
			php_strtolower(ZSTR_VAL(tmp), ZSTR_LEN(tmp));
			t = ZSTR_VAL(tmp);

			if (!header_init) {
				/* strip POST headers on redirect */
				strip_header(user_headers, t, "content-length:");
				strip_header(user_headers, t, "content-type:");
			}

			if ((s = strstr(t, "user-agent:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_USER_AGENT;
			}
			if ((s = strstr(t, "host:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_HOST;
			}
			if ((s = strstr(t, "from:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_FROM;
				}
			if ((s = strstr(t, "authorization:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_AUTH;
			}
			if ((s = strstr(t, "content-length:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_CONTENT_LENGTH;
			}
			if ((s = strstr(t, "content-type:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_TYPE;
			}
			if ((s = strstr(t, "connection:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				 have_header |= HTTP_HEADER_CONNECTION;
			}
			/* remove Proxy-Authorization header */
			if (use_proxy && use_ssl && (s = strstr(t, "proxy-authorization:")) &&
			    (s == t || *(s-1) == '\r' || *(s-1) == '\n' ||
			                 *(s-1) == '\t' || *(s-1) == ' ')) {
				char *p = s + sizeof("proxy-authorization:") - 1;

				while (s > t && (*(s-1) == ' ' || *(s-1) == '\t')) s--;
				while (*p != 0 && *p != '\r' && *p != '\n') p++;
				while (*p == '\r' || *p == '\n') p++;
				if (*p == 0) {
					if (s == t) {
						efree(user_headers);
						user_headers = NULL;
					} else {
						while (s > t && (*(s-1) == '\r' || *(s-1) == '\n')) s--;
						user_headers[s - t] = 0;
					}
				} else {
					memmove(user_headers + (s - t), user_headers + (p - t), strlen(p) + 1);
				}
			}

		}
		if (tmp) {
			zend_string_release(tmp);
		}
	}

	/* auth header if it was specified */
	if (((have_header & HTTP_HEADER_AUTH) == 0) && resource->user) {
		zend_string *stmp;
		/* decode the strings first */
		php_url_decode(resource->user, strlen(resource->user));

		/* scratch is large enough, since it was made large enough for the whole URL */
		strcpy(scratch, resource->user);
		strcat(scratch, ":");

		/* Note: password is optional! */
		if (resource->pass) {
			php_url_decode(resource->pass, strlen(resource->pass));
			strcat(scratch, resource->pass);
		}

		stmp = php_base64_encode((unsigned char*)scratch, strlen(scratch));

		if (snprintf(scratch, scratch_len, "Authorization: Basic %s\r\n", ZSTR_VAL(stmp)) > 0) {
			php_stream_write(stream, scratch, strlen(scratch));
			php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, NULL, 0);
		}

		zend_string_free(stmp);
	}

	/* if the user has configured who they are, send a From: line */
	if (((have_header & HTTP_HEADER_FROM) == 0) && FG(from_address)) {
		if (snprintf(scratch, scratch_len, "From: %s\r\n", FG(from_address)) > 0)
			php_stream_write(stream, scratch, strlen(scratch));
	}

	/* Send Host: header so name-based virtual hosts work */
	if ((have_header & HTTP_HEADER_HOST) == 0) {
		if ((use_ssl && resource->port != 443 && resource->port != 0) ||
			(!use_ssl && resource->port != 80 && resource->port != 0)) {
			if (snprintf(scratch, scratch_len, "Host: %s:%i\r\n", resource->host, resource->port) > 0)
				php_stream_write(stream, scratch, strlen(scratch));
		} else {
			if (snprintf(scratch, scratch_len, "Host: %s\r\n", resource->host) > 0) {
				php_stream_write(stream, scratch, strlen(scratch));
			}
		}
	}

	/* Send a Connection: close header to avoid hanging when the server
	 * interprets the RFC literally and establishes a keep-alive connection,
	 * unless the user specifically requests something else by specifying a
	 * Connection header in the context options. Send that header even for
	 * HTTP/1.0 to avoid issues when the server respond with a HTTP/1.1
	 * keep-alive response, which is the preferred response type. */
	if ((have_header & HTTP_HEADER_CONNECTION) == 0) {
		php_stream_write_string(stream, "Connection: close\r\n");
	}

	if (context &&
	    (ua_zval = php_stream_context_get_option(context, "http", "user_agent")) != NULL &&
		Z_TYPE_P(ua_zval) == IS_STRING) {
		ua_str = Z_STRVAL_P(ua_zval);
	} else if (FG(user_agent)) {
		ua_str = FG(user_agent);
	}

	if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str) {
#define _UA_HEADER "User-Agent: %s\r\n"
		char *ua;
		size_t ua_len;

		ua_len = sizeof(_UA_HEADER) + strlen(ua_str);

		/* ensure the header is only sent if user_agent is not blank */
		if (ua_len > sizeof(_UA_HEADER)) {
			ua = emalloc(ua_len + 1);
			if ((ua_len = slprintf(ua, ua_len, _UA_HEADER, ua_str)) > 0) {
				ua[ua_len] = 0;
				php_stream_write(stream, ua, ua_len);
			} else {
				php_error_docref(NULL, E_WARNING, "Cannot construct User-agent header");
			}
			efree(ua);
		}
	}

	if (user_headers) {
		/* A bit weird, but some servers require that Content-Length be sent prior to Content-Type for POST
		 * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first.
		 */
		if (
				header_init &&
				context &&
				!(have_header & HTTP_HEADER_CONTENT_LENGTH) &&
				(tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL &&
				Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0
		) {
			scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_P(tmpzval));
			php_stream_write(stream, scratch, scratch_len);
			have_header |= HTTP_HEADER_CONTENT_LENGTH;
		}

		php_stream_write(stream, user_headers, strlen(user_headers));
		php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
		efree(user_headers);
	}

	/* Request content, such as for POST requests */
	if (header_init && context &&
		(tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL &&
		Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) {
		if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) {
			scratch_len = slprintf(scratch, scratch_len, "Content-Length: %d\r\n", Z_STRLEN_P(tmpzval));
			php_stream_write(stream, scratch, scratch_len);
		}
		if (!(have_header & HTTP_HEADER_TYPE)) {
			php_stream_write(stream, "Content-Type: application/x-www-form-urlencoded\r\n",
				sizeof("Content-Type: application/x-www-form-urlencoded\r\n") - 1);
			php_error_docref(NULL, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded");
		}
		php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
		php_stream_write(stream, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval));
	} else {
		php_stream_write(stream, "\r\n", sizeof("\r\n")-1);
	}

	location[0] = '\0';

	symbol_table = zend_rebuild_symbol_table();

	if (header_init) {
		zval ztmp;
		array_init(&ztmp);
		zend_set_local_var_str("http_response_header", sizeof("http_response_header")-1, &ztmp, 0);
	}

	{
		zval *response_header_ptr = zend_hash_str_find_ind(symbol_table, "http_response_header", sizeof("http_response_header")-1);
		if (!response_header_ptr || Z_TYPE_P(response_header_ptr) != IS_ARRAY) {
			ZVAL_UNDEF(&response_header);
			goto out;
		} else {
			ZVAL_COPY(&response_header, response_header_ptr);
		}
	}

	if (!php_stream_eof(stream)) {
		size_t tmp_line_len;
		/* get response header */

		if (php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL) {
			zval http_response;

			if (tmp_line_len > 9) {
				response_code = atoi(tmp_line + 9);
			} else {
				response_code = 0;
			}
			if (context && NULL != (tmpzval = php_stream_context_get_option(context, "http", "ignore_errors"))) {
				ignore_errors = zend_is_true(tmpzval);
			}
			/* when we request only the header, don't fail even on error codes */
			if ((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) {
				reqok = 1;
			}

			/* status codes of 1xx are "informational", and will be followed by a real response
			 * e.g "100 Continue". RFC 7231 states that unexpected 1xx status MUST be parsed,
			 * and MAY be ignored. As such, we need to skip ahead to the "real" status*/
			if (response_code >= 100 && response_code < 200) {
				/* consume lines until we find a line starting 'HTTP/1' */
				while (
					!php_stream_eof(stream)
					&& php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL
					&& ( tmp_line_len < sizeof("HTTP/1") - 1 || strncasecmp(tmp_line, "HTTP/1", sizeof("HTTP/1") - 1) )
				);

				if (tmp_line_len > 9) {
					response_code = atoi(tmp_line + 9);
				} else {
					response_code = 0;
				}
			}
			/* all status codes in the 2xx range are defined by the specification as successful;
			 * all status codes in the 3xx range are for redirection, and so also should never
			 * fail */
			if (response_code >= 200 && response_code < 400) {
				reqok = 1;
			} else {
				switch(response_code) {
					case 403:
						php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT,
								tmp_line, response_code);
						break;
					default:
						/* safety net in the event tmp_line == NULL */
						if (!tmp_line_len) {
							tmp_line[0] = '\0';
						}
						php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE,
								tmp_line, response_code);
				}
			}
			if (tmp_line[tmp_line_len - 1] == '\n') {
				--tmp_line_len;
				if (tmp_line[tmp_line_len - 1] == '\r') {
					--tmp_line_len;
				}
			}
			ZVAL_STRINGL(&http_response, tmp_line, tmp_line_len);
			zend_hash_next_index_insert(Z_ARRVAL(response_header), &http_response);
		}
	} else {
		php_stream_wrapper_log_error(wrapper, options, "HTTP request failed, unexpected end of socket!");
		goto out;
	}

	/* read past HTTP headers */

	http_header_line = emalloc(HTTP_HEADER_BLOCK_SIZE);

	while (!php_stream_eof(stream)) {
		size_t http_header_line_length;

		if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) && *http_header_line != '\n' && *http_header_line != '\r') {
			char *e = http_header_line + http_header_line_length - 1;
			char *http_header_value;
			if (*e != '\n') {
				do { /* partial header */
					if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) == NULL) {
						php_stream_wrapper_log_error(wrapper, options, "Failed to read HTTP headers");
						goto out;
					}
					e = http_header_line + http_header_line_length - 1;
				} while (*e != '\n');
				continue;
			}
			while (e >= http_header_line && (*e == '\n' || *e == '\r')) {
				e--;
			}

			/* The primary definition of an HTTP header in RFC 7230 states:
			 * > Each header field consists of a case-insensitive field name followed
			 * > by a colon (":"), optional leading whitespace, the field value, and
			 * > optional trailing whitespace. */

			/* Strip trailing whitespace */
			while (e >= http_header_line && (*e == ' ' || *e == '\t')) {
				e--;
			}

			/* Terminate header line */
			e++;
			*e = '\0';
			http_header_line_length = e - http_header_line;

			http_header_value = memchr(http_header_line, ':', http_header_line_length);
			if (http_header_value) {
				http_header_value++; /* Skip ':' */

				/* Strip leading whitespace */
				while (http_header_value < e
						&& (*http_header_value == ' ' || *http_header_value == '\t')) {
					http_header_value++;
				}
			} else {
				/* There is no colon. Set the value to the end of the header line, which is
				 * effectively an empty string. */
				http_header_value = e;
			}

			if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) {
				if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
					follow_location = zval_is_true(tmpzval);
				} else if (!((response_code >= 300 && response_code < 304)
						|| 307 == response_code || 308 == response_code)) {
					/* we shouldn't redirect automatically
					if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
					see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
					RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
					follow_location = 0;
				}
				strlcpy(location, http_header_value, sizeof(location));
			} else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
				php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0);
			} else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
				file_size = atoi(http_header_value);
				php_stream_notify_file_size(context, file_size, http_header_line, 0);
			} else if (
				!strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
				&& !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1)
			) {

				/* create filter to decode response body */
				if (!(options & STREAM_ONLY_GET_HEADERS)) {
					zend_long decode = 1;

					if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
						decode = zend_is_true(tmpzval);
					}
					if (decode) {
						transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream));
						if (transfer_encoding) {
							/* don't store transfer-encodeing header */
							continue;
						}
					}
				}
			}

			{
				zval http_header;
				ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length);
				zend_hash_next_index_insert(Z_ARRVAL(response_header), &http_header);
			}
		} else {
			break;
		}
	}

	if (!reqok || (location[0] != '\0' && follow_location)) {
		if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
			goto out;
		}

		if (location[0] != '\0')
			php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);

		php_stream_close(stream);
		stream = NULL;

		if (location[0] != '\0') {

			char new_path[HTTP_HEADER_BLOCK_SIZE];
			char loc_path[HTTP_HEADER_BLOCK_SIZE];

			*new_path='\0';
			if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
							strncasecmp(location, "https://", sizeof("https://")-1) &&
							strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
							strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
			{
				if (*location != '/') {
					if (*(location+1) != '\0' && resource->path) {
						char *s = strrchr(resource->path, '/');
						if (!s) {
							s = resource->path;
							if (!s[0]) {
								efree(s);
								s = resource->path = estrdup("/");
							} else {
								*s = '/';
							}
						}
						s[1] = '\0';
						if (resource->path && *(resource->path) == '/' && *(resource->path + 1) == '\0') {
							snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", resource->path, location);
						} else {
							snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", resource->path, location);
						}
					} else {
						snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
					}
				} else {
					strlcpy(loc_path, location, sizeof(loc_path));
				}
				if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
					snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", resource->scheme, resource->host, resource->port, loc_path);
				} else {
					snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", resource->scheme, resource->host, loc_path);
				}
			} else {
				strlcpy(new_path, location, sizeof(new_path));
			}

			php_url_free(resource);
			/* check for invalid redirection URLs */
			if ((resource = php_url_parse(new_path)) == NULL) {
				php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path);
				goto out;
			}

#define CHECK_FOR_CNTRL_CHARS(val) { \
	if (val) { \
		unsigned char *s, *e; \
		size_t l; \
		l = php_url_decode(val, strlen(val)); \
		s = (unsigned char*)val; e = s + l; \
		while (s < e) { \
			if (iscntrl(*s)) { \
				php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \
				goto out; \
			} \
			s++; \
		} \
	} \
}
			/* check for control characters in login, password & path */
			if (strncasecmp(new_path, "http://", sizeof("http://") - 1) || strncasecmp(new_path, "https://", sizeof("https://") - 1)) {
				CHECK_FOR_CNTRL_CHARS(resource->user)
				CHECK_FOR_CNTRL_CHARS(resource->pass)
				CHECK_FOR_CNTRL_CHARS(resource->path)
			}
			stream = php_stream_url_wrap_http_ex(wrapper, new_path, mode, options, opened_path, context, --redirect_max, HTTP_WRAPPER_REDIRECTED STREAMS_CC);
		} else {
			php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
		}
	}
out:
	if (protocol_version) {
		efree(protocol_version);
	}

	if (http_header_line) {
		efree(http_header_line);
	}

	if (scratch) {
		efree(scratch);
	}

	if (resource) {
		php_url_free(resource);
	}

	if (stream) {
		if (header_init) {
			ZVAL_COPY(&stream->wrapperdata, &response_header);
		}
		php_stream_notify_progress_init(context, 0, file_size);

		/* Restore original chunk size now that we're done with headers */
		if (options & STREAM_WILL_CAST)
			php_stream_set_chunk_size(stream, (int)chunk_size);

		/* restore the users auto-detect-line-endings setting */
		stream->flags |= eol_detect;

		/* as far as streams are concerned, we are now at the start of
		 * the stream */
		stream->position = 0;

		/* restore mode */
		strlcpy(stream->mode, mode, sizeof(stream->mode));

		if (transfer_encoding) {
			php_stream_filter_append(&stream->readfilters, transfer_encoding);
		}
	} else {
		if (transfer_encoding) {
			php_stream_filter_free(transfer_encoding);
		}
	}

	zval_ptr_dtor(&response_header);

	return stream;
}


static void print_pack_info(void)
{
	struct packed_git *pack;
	char *offset;

	ctx.page.mimetype = "text/plain";
	ctx.page.filename = "objects/info/packs";
	cgit_print_http_headers();
	reprepare_packed_git(the_repository);
	for (pack = get_packed_git(the_repository); pack; pack = pack->next) {
		if (pack->pack_local) {
			offset = strrchr(pack->pack_name, '/');
			if (offset && offset[1] != '\0')
				++offset;
			else
				offset = pack->pack_name;
			htmlf("P %s\n", offset);
		}
	}
}


void cgit_clone_head(void)
{
	send_file(git_path("%s", "HEAD"));
}


static void send_file(const char *path)
{
	struct stat st;

	if (stat(path, &st)) {
		switch (errno) {
		case ENOENT:
			cgit_print_error_page(404, "Not found", "Not found");
			break;
		case EACCES:
			cgit_print_error_page(403, "Forbidden", "Forbidden");
			break;
		default:
			cgit_print_error_page(400, "Bad request", "Bad request");
		}
		return;
	}
	ctx.page.mimetype = "application/octet-stream";
	ctx.page.filename = path;
	skip_prefix(path, ctx.repo->path, &ctx.page.filename);
	skip_prefix(ctx.page.filename, "/", &ctx.page.filename);
	cgit_print_http_headers();
	html_include(path);
}


plain_format(void)
{
	/* Format is implicitly tested for 32 bit machines by plain_hash() */
	return 0;
}


int num_to_str(char *buf, int size, unsigned long long num)
{
	/* put_dec requires 2-byte alignment of the buffer. */
	char tmp[sizeof(num) * 3] __aligned(2);
	int idx, len;

	/* put_dec() may work incorrectly for num = 0 (generate "", not "0") */
	if (num <= 9) {
		tmp[0] = '0' + num;
		len = 1;
	} else {
		len = put_dec(tmp, num) - tmp;
	}

	if (len > size)
		return 0;
	for (idx = 0; idx < len; ++idx)
		buf[idx] = tmp[len - idx - 1];
	return len;
}


int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
	unsigned long long num;
	char *str, *end;
	struct printf_spec spec = {0};

	/* Reject out-of-range values early.  Large positive sizes are
	   used for unknown buffer sizes. */
	if (WARN_ON_ONCE(size > INT_MAX))
		return 0;

	str = buf;
	end = buf + size;

	/* Make sure end is always >= buf */
	if (end < buf) {
		end = ((void *)-1);
		size = end - buf;
	}

	while (*fmt) {
		const char *old_fmt = fmt;
		int read = format_decode(fmt, &spec);

		fmt += read;

		switch (spec.type) {
		case FORMAT_TYPE_NONE: {
			int copy = read;
			if (str < end) {
				if (copy > end - str)
					copy = end - str;
				memcpy(str, old_fmt, copy);
			}
			str += read;
			break;
		}

		case FORMAT_TYPE_WIDTH:
			set_field_width(&spec, va_arg(args, int));
			break;

		case FORMAT_TYPE_PRECISION:
			set_precision(&spec, va_arg(args, int));
			break;

		case FORMAT_TYPE_CHAR: {
			char c;

			if (!(spec.flags & LEFT)) {
				while (--spec.field_width > 0) {
					if (str < end)
						*str = ' ';
					++str;

				}
			}
			c = (unsigned char) va_arg(args, int);
			if (str < end)
				*str = c;
			++str;
			while (--spec.field_width > 0) {
				if (str < end)
					*str = ' ';
				++str;
			}
			break;
		}

		case FORMAT_TYPE_STR:
			str = string(str, end, va_arg(args, char *), spec);
			break;

		case FORMAT_TYPE_PTR:
			str = pointer(fmt, str, end, va_arg(args, void *),
				      spec);
			while (isalnum(*fmt))
				fmt++;
			break;

		case FORMAT_TYPE_PERCENT_CHAR:
			if (str < end)
				*str = '%';
			++str;
			break;

		case FORMAT_TYPE_INVALID:
			/*
			 * Presumably the arguments passed gcc's type
			 * checking, but there is no safe or sane way
			 * for us to continue parsing the format and
			 * fetching from the va_list; the remaining
			 * specifiers and arguments would be out of
			 * sync.
			 */
			goto out;

		default:
			switch (spec.type) {
			case FORMAT_TYPE_LONG_LONG:
				num = va_arg(args, long long);
				break;
			case FORMAT_TYPE_ULONG:
				num = va_arg(args, unsigned long);
				break;
			case FORMAT_TYPE_LONG:
				num = va_arg(args, long);
				break;
			case FORMAT_TYPE_SIZE_T:
				if (spec.flags & SIGN)
					num = va_arg(args, ssize_t);
				else
					num = va_arg(args, size_t);
				break;
			case FORMAT_TYPE_PTRDIFF:
				num = va_arg(args, ptrdiff_t);
				break;
			case FORMAT_TYPE_UBYTE:
				num = (unsigned char) va_arg(args, int);
				break;
			case FORMAT_TYPE_BYTE:
				num = (signed char) va_arg(args, int);
				break;
			case FORMAT_TYPE_USHORT:
				num = (unsigned short) va_arg(args, int);
				break;
			case FORMAT_TYPE_SHORT:
				num = (short) va_arg(args, int);
				break;
			case FORMAT_TYPE_INT:
				num = (int) va_arg(args, int);
				break;
			default:
				num = va_arg(args, unsigned int);
			}

			str = number(str, end, num, spec);
		}
	}

out:
	if (size > 0) {
		if (str < end)
			*str = '\0';
		else
			end[-1] = '\0';
	}

	/* the trailing null byte doesn't count towards the total */
	return str-buf;

}


plain(void)
{
	int err;

	err = plain_hash();
	if (err) {
		pr_warn("plain 'p' does not appear to be hashed\n");
		failed_tests++;
		return;
	}

	err = plain_format();
	if (err) {
		pr_warn("hashing plain 'p' has unexpected format\n");
		failed_tests++;
	}
}


char *ip6_addr_string(char *buf, char *end, const u8 *addr,
		      struct printf_spec spec, const char *fmt)
{
	char ip6_addr[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];

	if (fmt[0] == 'I' && fmt[2] == 'c')
		ip6_compressed_string(ip6_addr, addr);
	else
		ip6_string(ip6_addr, addr, fmt);

	return string(buf, end, ip6_addr, spec);
}


large_bitmap(void)
{
	const int nbits = 1 << 16;
	unsigned long *bits = kcalloc(BITS_TO_LONGS(nbits), sizeof(long), GFP_KERNEL);
	if (!bits)
		return;

	bitmap_set(bits, 1, 20);
	bitmap_set(bits, 60000, 15);
	test("1-20,60000-60014", "%*pbl", nbits, bits);
	kfree(bits);
}


char *on_path(const char *cmd, const char *rootfs) {
	char *path = NULL;
	char *entry = NULL;
	char *saveptr = NULL;
	char cmdpath[MAXPATHLEN];
	int ret;

	path = getenv("PATH");
	if (!path)
		return NULL;

	path = strdup(path);
	if (!path)
		return NULL;

	entry = strtok_r(path, ":", &saveptr);
	while (entry) {
		if (rootfs)
			ret = snprintf(cmdpath, MAXPATHLEN, "%s/%s/%s", rootfs, entry, cmd);
		else
			ret = snprintf(cmdpath, MAXPATHLEN, "%s/%s", entry, cmd);

		if (ret < 0 || ret >= MAXPATHLEN)
			goto next_loop;

		if (access(cmdpath, X_OK) == 0) {
			free(path);
			return strdup(cmdpath);
		}

next_loop:
		entry = strtok_r(NULL, ":", &saveptr);
	}

	free(path);
	return NULL;
}


char *lxc_append_paths(const char *first, const char *second)
{
	int ret;
	size_t len;
	char *result = NULL;
	const char *pattern = "%s%s";

	len = strlen(first) + strlen(second) + 1;
	if (second[0] != '/') {
		len += 1;
		pattern = "%s/%s";
	}

	result = calloc(1, len);
	if (!result)
		return NULL;

	ret = snprintf(result, len, pattern, first, second);
	if (ret < 0 || (size_t)ret >= len) {
		free(result);
		return NULL;
	}

	return result;
}


int lxc_prepare_loop_dev(const char *source, char *loop_dev, int flags)
{
	int ret;
	struct loop_info64 lo64;
	int fd_img = -1, fret = -1, fd_loop = -1;

	fd_loop = lxc_get_unused_loop_dev(loop_dev);
	if (fd_loop < 0) {
		if (fd_loop == -ENODEV)
			fd_loop = lxc_get_unused_loop_dev_legacy(loop_dev);
		else
			goto on_error;
	}

	fd_img = open(source, O_RDWR | O_CLOEXEC);
	if (fd_img < 0)
		goto on_error;

	ret = ioctl(fd_loop, LOOP_SET_FD, fd_img);
	if (ret < 0)
		goto on_error;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = flags;

	ret = ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
	if (ret < 0)
		goto on_error;

	fret = 0;

on_error:
	if (fd_img >= 0)
		close(fd_img);

	if (fret < 0 && fd_loop >= 0) {
		close(fd_loop);
		fd_loop = -1;
	}

	return fd_loop;
}


int lxc_safe_long_long(const char *numstr, long long int *converted)
{
	char *err = NULL;
	signed long long int sli;

	errno = 0;
	sli = strtoll(numstr, &err, 0);
	if (errno == ERANGE && (sli == LLONG_MAX || sli == LLONG_MIN))
		return -ERANGE;

	if (errno != 0 && sli == 0)
		return -EINVAL;

	if (err == numstr || *err != '\0')
		return -EINVAL;

	*converted = sli;
	return 0;
}


static int create_nic(char *nic, char *br, int pid, char **cnic)
{
	char veth1buf[IFNAMSIZ], veth2buf[IFNAMSIZ];
	int mtu, ret;

	ret = snprintf(veth1buf, IFNAMSIZ, "%s", nic);
	if (ret < 0 || ret >= IFNAMSIZ) {
		usernic_error("%s", "Could not create nic name\n");
		return -1;
	}

	ret = snprintf(veth2buf, IFNAMSIZ, "%sp", veth1buf);
	if (ret < 0 || ret >= IFNAMSIZ) {
		usernic_error("%s\n", "Could not create nic name");
		return -1;
	}
	/* create the nics */
	ret = instantiate_veth(veth1buf, veth2buf);
	if (ret < 0) {
		usernic_error("%s", "Error creating veth tunnel\n");
		return -1;
	}

	if (strcmp(br, "none")) {
		/* copy the bridge's mtu to both ends */
		mtu = get_mtu(br);
		if (mtu > 0) {
			ret = lxc_netdev_set_mtu(veth1buf, mtu);
			if (ret < 0) {
				usernic_error("Failed to set mtu to %d on %s\n",
					      mtu, veth1buf);
				goto out_del;
			}

			ret = lxc_netdev_set_mtu(veth2buf, mtu);
			if (ret < 0) {
				usernic_error("Failed to set mtu to %d on %s\n",
					      mtu, veth2buf);
				goto out_del;
			}
		}

		/* attach veth1 to bridge */
		ret = lxc_bridge_attach(br, veth1buf);
		if (ret < 0) {
			usernic_error("Error attaching %s to %s\n", veth1buf, br);
			goto out_del;
		}
	}

	/* pass veth2 to target netns */
	ret = lxc_netdev_move_by_name(veth2buf, pid, NULL);
	if (ret < 0) {
		usernic_error("Error moving %s to network namespace of %d\n",
			      veth2buf, pid);
		goto out_del;
	}

	*cnic = strdup(veth2buf);
	if (!*cnic) {
		usernic_error("Failed to copy string \"%s\"\n", veth2buf);
		return -1;
	}

	return 0;

out_del:
	lxc_netdev_delete_by_name(veth1buf);
	return -1;
}


int lxc_safe_long(const char *numstr, long int *converted)
{
	char *err = NULL;
	signed long int sli;

	errno = 0;
	sli = strtol(numstr, &err, 0);
	if (errno == ERANGE && (sli == LONG_MAX || sli == LONG_MIN))
		return -ERANGE;

	if (errno != 0 && sli == 0)
		return -EINVAL;

	if (err == numstr || *err != '\0')
		return -EINVAL;

	*converted = sli;
	return 0;
}


bool task_blocking_signal(pid_t pid, int signal)
{
	bool bret = false;
	char *line = NULL;
	long unsigned int sigblk = 0;
	size_t n = 0;
	int ret;
	FILE *f;

	char status[__PROC_STATUS_LEN];

	ret = snprintf(status, __PROC_STATUS_LEN, "/proc/%d/status", pid);
	if (ret < 0 || ret >= __PROC_STATUS_LEN)
		return bret;

	f = fopen(status, "r");
	if (!f)
		return bret;

	while (getline(&line, &n, f) != -1) {
		if (strncmp(line, "SigBlk:\t", 8))
			continue;

		if (sscanf(line + 8, "%lx", &sigblk) != 1)
			goto out;
	}

	if (sigblk & (1LU << (signal - 1)))
		bret = true;

out:
	free(line);
	fclose(f);
	return bret;
}


char *lxc_deslashify(const char *path)
{
	char *dup, *p;
	char **parts = NULL;
	size_t n, len;

	dup = strdup(path);
	if (!dup)
		return NULL;

	parts = lxc_normalize_path(dup);
	if (!parts) {
		free(dup);
		return NULL;
	}

	/* We'll end up here if path == "///" or path == "". */
	if (!*parts) {
		len = strlen(dup);
		if (!len) {
			lxc_free_array((void **)parts, free);
			return dup;
		}

		n = strcspn(dup, "/");
		if (n == len) {
			free(dup);
			lxc_free_array((void **)parts, free);

			p = strdup("/");
			if (!p)
				return NULL;

			return p;
		}
	}

	p = lxc_string_join("/", (const char **)parts, *dup == '/');
	free(dup);
	lxc_free_array((void **)parts, free);
	return p;
}


void lxc_free_array(void **array, lxc_free_fn element_free_fn)
{
	void **p;

	for (p = array; p && *p; p++)
		element_free_fn(*p);

	free((void*)array);
}


int lxc_read_from_file(const char *filename, void *buf, size_t count)
{
	int fd = -1, saved_errno;
	ssize_t ret;

	fd = open(filename, O_RDONLY | O_CLOEXEC);
	if (fd < 0)
		return -1;

	if (!buf || !count) {
		char buf2[100];
		size_t count2 = 0;

		while ((ret = lxc_read_nointr(fd, buf2, 100)) > 0)
			count2 += ret;

		if (ret >= 0)
			ret = count2;
	} else {
		memset(buf, 0, count);
		ret = lxc_read_nointr(fd, buf, count);
	}

	if (ret < 0)
		SYSERROR("Read %s", filename);

	saved_errno = errno;
	close(fd);
	errno = saved_errno;
	return ret;
}


static int open_if_safe(int dirfd, const char *nextpath)
{
	int newfd = openat(dirfd, nextpath, O_RDONLY | O_NOFOLLOW);
	if (newfd >= 0) /* Was not a symlink, all good. */
		return newfd;

	if (errno == ELOOP)
		return newfd;

	if (errno == EPERM || errno == EACCES) {
		/* We're not root (cause we got EPERM) so try opening with
		 * O_PATH.
		 */
		newfd = openat(dirfd, nextpath, O_PATH | O_NOFOLLOW);
		if (newfd >= 0) {
			/* O_PATH will return an fd for symlinks. We know
			 * nextpath wasn't a symlink at last openat, so if fd is
			 * now a link, then something * fishy is going on.
			 */
			int ret = check_symlink(newfd);
			if (ret < 0) {
				close(newfd);
				newfd = ret;
			}
		}
	}

	return newfd;
}


bool lxc_string_in_list(const char *needle, const char *haystack, char _sep)
{
	char *token, *str;
	char sep[2] = { _sep, '\0' };
	size_t len;

	if (!haystack || !needle)
		return 0;

	len = strlen(haystack);
	str = alloca(len + 1);
	(void)strlcpy(str, haystack, len + 1);

	lxc_iterate_parts(token, str, sep)
		if (strcmp(needle, token) == 0)
			return 1;

	return 0;
}


char *must_copy_string(const char *entry)
{
	char *ret;

	if (!entry)
		return NULL;

	do {
		ret = strdup(entry);
	} while (!ret);

	return ret;
}


int null_stdfds(void)
{
	int ret = -1;
	int fd;

	fd = open_devnull();
	if (fd >= 0) {
		ret = set_stdfds(fd);
		close(fd);
	}

	return ret;
}


static char **get_groupnames(void)
{
	int ngroups;
	gid_t *group_ids;
	int ret, i;
	char **groupnames;
	struct group grent;
	struct group *grentp = NULL;
	char *buf;
	size_t bufsize;

	ngroups = getgroups(0, NULL);
	if (ngroups < 0) {
		usernic_error("Failed to get number of groups the user "
			      "belongs to: %s\n", strerror(errno));
		return NULL;
	}
	if (ngroups == 0)
		return NULL;

	group_ids = malloc(sizeof(gid_t) * ngroups);
	if (!group_ids) {
		usernic_error("Failed to allocate memory while getting groups "
			      "the user belongs to: %s\n",
			      strerror(errno));
		return NULL;
	}

	ret = getgroups(ngroups, group_ids);
	if (ret < 0) {
		free(group_ids);
		usernic_error("Failed to get process groups: %s\n",
			      strerror(errno));
		return NULL;
	}

	groupnames = malloc(sizeof(char *) * (ngroups + 1));
	if (!groupnames) {
		free(group_ids);
		usernic_error("Failed to allocate memory while getting group "
			      "names: %s\n",
			      strerror(errno));
		return NULL;
	}

	memset(groupnames, 0, sizeof(char *) * (ngroups + 1));

	bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
	if (bufsize == -1)
		bufsize = 1024;

	buf = malloc(bufsize);
	if (!buf) {
		free(group_ids);
		free_groupnames(groupnames);
		usernic_error("Failed to allocate memory while getting group "
			      "names: %s\n",
			      strerror(errno));
		return NULL;
	}

	for (i = 0; i < ngroups; i++) {
		ret = getgrgid_r(group_ids[i], &grent, buf, bufsize, &grentp);
		if (!grentp) {
			if (ret == 0)
				usernic_error("%s", "Could not find matched group record\n");

			usernic_error("Failed to get group name: %s(%u)\n",
			      strerror(errno), group_ids[i]);
			free(buf);
			free(group_ids);
			free_groupnames(groupnames);
			return NULL;
		}

		groupnames[i] = strdup(grent.gr_name);
		if (!groupnames[i]) {
			usernic_error("Failed to copy group name \"%s\"",
				      grent.gr_name);
			free(buf);
			free(group_ids);
			free_groupnames(groupnames);
			return NULL;
		}
	}

	free(buf);
	free(group_ids);

	return groupnames;
}


int run_command(char *buf, size_t buf_size, int (*child_fn)(void *), void *args)
{
	pid_t child;
	int ret, fret, pipefd[2];
	ssize_t bytes;

	/* Make sure our callers do not receive uninitialized memory. */
	if (buf_size > 0 && buf)
		buf[0] = '\0';

	if (pipe(pipefd) < 0) {
		SYSERROR("failed to create pipe");
		return -1;
	}

	child = lxc_raw_clone(0);
	if (child < 0) {
		close(pipefd[0]);
		close(pipefd[1]);
		SYSERROR("failed to create new process");
		return -1;
	}

	if (child == 0) {
		/* Close the read-end of the pipe. */
		close(pipefd[0]);

		/* Redirect std{err,out} to write-end of the
		 * pipe.
		 */
		ret = dup2(pipefd[1], STDOUT_FILENO);
		if (ret >= 0)
			ret = dup2(pipefd[1], STDERR_FILENO);

		/* Close the write-end of the pipe. */
		close(pipefd[1]);

		if (ret < 0) {
			SYSERROR("failed to duplicate std{err,out} file descriptor");
			_exit(EXIT_FAILURE);
		}

		/* Does not return. */
		child_fn(args);
		ERROR("failed to exec command");
		_exit(EXIT_FAILURE);
	}

	/* close the write-end of the pipe */
	close(pipefd[1]);

	if (buf && buf_size > 0) {
		bytes = lxc_read_nointr(pipefd[0], buf, buf_size - 1);
		if (bytes > 0)
			buf[bytes - 1] = '\0';
	}

	fret = wait_for_pid(child);
	/* close the read-end of the pipe */
	close(pipefd[0]);

	return fret;
}


void **lxc_append_null_to_array(void **array, size_t count)
{
	void **temp;

	/* Append NULL to the array */
	if (count) {
		temp = realloc(array, (count + 1) * sizeof(*array));
		if (!temp) {
			size_t i;
			for (i = 0; i < count; i++)
				free(array[i]);
			free(array);
			return NULL;
		}

		array = temp;
		array[count] = NULL;
	}

	return array;
}


zcurrentpagedevice(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    gx_device *dev = gs_currentdevice(igs);

    push(2);
    if ((*dev_proc(dev, get_page_device))(dev) != 0) {
        op[-1] = istate->pagedevice;
        make_true(op);
    } else {
        make_null(op - 1);
        make_false(op);
    }
    return 0;
}


zcallinstall(i_ctx_t *i_ctx_p)
{
    gx_device *dev = gs_currentdevice(igs);

    if ((dev = (*dev_proc(dev, get_page_device))(dev)) != 0) {
        int code = (*dev->page_procs.install) (dev, igs);

        if (code < 0)
            return code;
    }
    return 0;
}


dorestore(i_ctx_t *i_ctx_p, alloc_save_t *asave)
{
    os_ptr op = osp;
    bool last;
    vm_save_t *vmsave;
    int code;

    osp--;

    /* Reset l_new in all stack entries if the new save level is zero. */
    /* Also do some special fixing on the e-stack. */
    restore_fix_stack(i_ctx_p, &o_stack, asave, false);
    restore_fix_stack(i_ctx_p, &e_stack, asave, true);
    restore_fix_stack(i_ctx_p, &d_stack, asave, false);
    /* Iteratively restore the state of memory, */
    /* also doing a grestoreall at each step. */
    do {
        vmsave = alloc_save_client_data(alloc_save_current(idmemory));
        /* Restore the graphics state. */
        gs_grestoreall_for_restore(igs, vmsave->gsave);
        /*
         * If alloc_save_space decided to do a second save, the vmsave
         * object was allocated one save level less deep than the
         * current level, so ifree_object won't actually free it;
         * however, it points to a gsave object that definitely
         * *has* been freed.  In order not to trip up the garbage
         * collector, we clear the gsave pointer now.
         */
        vmsave->gsave = 0;
        /* Now it's safe to restore the state of memory. */
        code = alloc_restore_step_in(idmemory, asave);
        if (code < 0)
            return code;
        last = code;
    }
    while (!last);
    {
        uint space = icurrent_space;

        ialloc_set_space(idmemory, avm_local);
        ifree_object(vmsave, "zrestore");
        ialloc_set_space(idmemory, space);
    }
    dict_set_top();		/* reload dict stack cache */
    if (I_VALIDATE_AFTER_RESTORE)
        ivalidate_clean_spaces(i_ctx_p);
    /* If the i_ctx_p LockFilePermissions is true, but the userparams */
    /* we just restored is false, we need to make sure that we do not */
    /* cause an 'invalidaccess' in setuserparams. Temporarily set     */
    /* LockFilePermissions false until the gs_lev2.ps can do a        */
    /* setuserparams from the restored userparam dictionary.          */
    /* NOTE: This is safe to do here, since the restore has           */
    /* successfully completed - this should never come before any     */
    /* operation that can trigger an error                            */
    i_ctx_p->LockFilePermissions = false;
    return 0;
}


static RELOC_PTRS_WITH(device_forward_reloc_ptrs, gx_device_forward *fdev)
{
    fdev->target = gx_device_reloc_ptr(fdev->target, gcst);
}


gx_set_device_only(gs_gstate * pgs, gx_device * dev)
{
    rc_assign(pgs->device, dev, "gx_set_device_only");
}


gs_copydevice2(gx_device ** pnew_dev, const gx_device * dev, bool keep_open,
               gs_memory_t * mem)
{
    gx_device *new_dev;
    const gs_memory_struct_type_t *std = dev->stype;
    const gs_memory_struct_type_t *new_std;
    gs_memory_struct_type_t *a_std = 0;
    int code;

    if (dev->stype_is_dynamic) {
        /*
         * We allocated the stype for this device previously.
         * Just allocate a new stype and copy the old one into it.
         */
        a_std = (gs_memory_struct_type_t *)
            gs_alloc_bytes_immovable(mem->non_gc_memory, sizeof(*std),
                                     "gs_copydevice(stype)");
        if (!a_std)
            return_error(gs_error_VMerror);
        *a_std = *std;
        new_std = a_std;
    } else if (std != 0 && std->ssize == dev->params_size) {
        /* Use the static stype. */
        new_std = std;
    } else {
        /* We need to figure out or adjust the stype. */
        a_std = (gs_memory_struct_type_t *)
            gs_alloc_bytes_immovable(mem->non_gc_memory, sizeof(*std),
                                     "gs_copydevice(stype)");
        if (!a_std)
            return_error(gs_error_VMerror);
        gx_device_make_struct_type(a_std, dev);
        new_std = a_std;
    }
    /*
     * Because command list devices have complicated internal pointer
     * structures, we allocate all device instances as immovable.
     */
    new_dev = gs_alloc_struct_immovable(mem, gx_device, new_std,
                                        "gs_copydevice(device)");
    if (new_dev == 0) {
        gs_free_object(mem->non_gc_memory, a_std, "gs_copydevice(stype)");
        return_error(gs_error_VMerror);
    }
    gx_device_init(new_dev, dev, mem, false);
    gx_device_set_procs(new_dev);
    new_dev->stype = new_std;
    new_dev->stype_is_dynamic = new_std != std;
    /*
     * keep_open is very dangerous.  On the other hand, so is copydevice in
     * general, since it just copies the bits without any regard to pointers
     * (including self-pointers) that they may contain.  We handle this by
     * making the default finish_copydevice forbid copying of anything other
     * than the device prototype.
     */
    new_dev->is_open = dev->is_open && keep_open;
    fill_dev_proc(new_dev, finish_copydevice, gx_default_finish_copydevice);
    code = dev_proc(new_dev, finish_copydevice)(new_dev, dev);
    if (code < 0) {
        gs_free_object(mem, new_dev, "gs_copydevice(device)");
#if 0 /* gs_free_object above calls gx_device_finalize,
         which closes the device and releaszes its stype, i.e. a_std. */
        if (a_std)
            gs_free_object(dev->memory->non_gc_memory, a_std, "gs_copydevice(stype)");
#endif
        return code;
    }
    *pnew_dev = new_dev;
    return 0;
}


gx_device_make_struct_type(gs_memory_struct_type_t *st,
                           const gx_device *dev)
{
    if (dev->stype)
        *st = *dev->stype;
    else if (dev_proc(dev, get_xfont_procs) == gx_forward_get_xfont_procs)
        *st = st_device_forward;
    else
        *st = st_device;
    st->ssize = dev->params_size;
}


gx_device_finalize(const gs_memory_t *cmem, void *vptr)
{
    gx_device * const dev = (gx_device *)vptr;
    (void)cmem; /* unused */

    if (dev->icc_struct != NULL) {
        rc_decrement(dev->icc_struct, "gx_device_finalize(icc_profile)");
    }
    if (dev->finalize)
        dev->finalize(dev);

    /* Deal with subclassed devices. Ordinarily these should not be a problem, we
     * will never see them, but if ths is a end of job restore we can end up
     * with the 'child' device(s) being freed before their parents. We need to make
     * sure we don't leave any dangling pointers in that case.
     */
    if (dev->child)
        dev->child->parent = dev->parent;
    if (dev->parent)
        dev->parent->child = dev->child;
    if (dev->PageList) {
        rc_decrement(dev->PageList, "gx_device_finalize(PageList)");
        dev->PageList = 0;
    }

    discard(gs_closedevice(dev));
    if (dev->stype_is_dynamic)
        gs_free_const_object(dev->memory->non_gc_memory, dev->stype,
                             "gx_device_finalize");

#ifdef DEBUG
    /* Slightly ugly hack: because the garbage collector makes no promises
     * about the order objects can be garbage collected, it is possible for
     * a forwarding device to remain in existence (awaiting garbage collection
     * itself) after it's target marked as free memory by the garbage collector.
     * In such a case, the normal reference counting is fine (since the garbage
     * collector leaves the object contents alone until is has completed its
     * sweep), but the reference counting debugging attempts to access the
     * memory header to output type information - and the header has been
     * overwritten by the garbage collector, causing a crash.
     * Setting the rc memory to NULL here should be safe, since the memory
     * is now in the hands of the garbage collector, and means we can check in
     * debugging code to ensure we don't try to use values that not longer exist
     * in the memmory header.
     * In the non-gc case, finalize is the very last thing to happen before the
     * memory is actually freed, so the rc.memory pointer is moot.
     * See rc_object_type_name()
     */
    if (gs_debug_c('^'))
        dev->rc.memory = NULL;
#endif
}


gx_device_set_resolution(gx_device * dev, double x_dpi, double y_dpi)
{
    dev->HWResolution[0] = x_dpi;
    dev->HWResolution[1] = y_dpi;
    gx_device_set_hwsize_from_media(dev);
}


gs_flushpage(gs_gstate * pgs)
{
    gx_device *dev = gs_currentdevice(pgs);

    return (*dev_proc(dev, sync_output)) (dev);
}


static int sepvalidate(i_ctx_t *i_ctx_p, ref *space, float *values, int num_comps)
{
    os_ptr op = osp;

    if (num_comps < 1)
        return_error(gs_error_stackunderflow);

    if (!r_has_type(op, t_integer) && !r_has_type(op, t_real))
        return_error(gs_error_typecheck);

    if (*values > 1.0)
        *values = 1.0;

    if (*values < 0.0)
        *values = 0.0;

    return 0;
}


static int ciearange(i_ctx_t * i_ctx_p, ref *space, float *ptr)
{
    int code;
    ref CIEdict, *tempref;

    code = array_get(imemory, space, 1, &CIEdict);
    if (code < 0)
        return code;

    /* If we have a RangeA entry in the dictionary, get the
     * values from that
     */
    code = dict_find_string(&CIEdict, "RangeA", &tempref);
    if (code > 0 && !r_has_type(tempref, t_null)) {
        code = get_cie_param_array(imemory, tempref, 2, ptr);
        if (code < 0)
            return code;
    } else {
        /* Default values for CIEBasedA */
        ptr[0] = 0;
        ptr[1] = 1;
    }
    return 0;
}


static int ciedefgrange(i_ctx_t * i_ctx_p, ref *space, float *ptr)
{
    int code;
    ref CIEdict, *tempref;

    code = array_get(imemory, space, 1, &CIEdict);
    if (code < 0)
        return code;

    /* If we have a RangeDEFG, get the values from that */
    code = dict_find_string(&CIEdict, "RangeDEFG", &tempref);
    if (code > 0 && !r_has_type(tempref, t_null)) {
        code = get_cie_param_array(imemory, tempref, 8, ptr);
        if (code < 0)
            return code;
    } else {
        /* Default values for a CIEBasedDEFG */
    	memcpy(ptr, default_0_1, 8*sizeof(float));
    }
    return 0;
}


setdevicecolor_cont(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    es_ptr ep = esp, pstage;
    int code = 0, stage, base;

    pstage = ep;
    base = (int)ep[-1].value.intval;
    stage = (int)pstage->value.intval;
    /* If we get a continuation from a sub-procedure, we will want to come back
     * here afterward, to do any remaining stages. We need to set up for that now.
     * so that our continuation is ahead of the sub-proc's continuation.
     */
    check_estack(1);
    /* May need to push a /Device... name on the stack so make sure we have space */
    check_ostack(1);
    /* The push_op_estack macro increments esp before use, so we don't need to */
    push_op_estack(setdevicecolor_cont);

    do {
        switch(stage) {
            case 0:
                make_int(pstage, ++stage);
                push(1);
                switch(base) {
                    case 0: /* DeviceGray */
                        code = name_enter_string(imemory, "DeviceGray", op);
                        break;
                    case 1: /* DeviceRGB */
                        code = name_enter_string(imemory, "DeviceRGB", op);
                        break;
                    case 2: /* DeviceCMYK */
                        code = name_enter_string(imemory, "DeviceCMYK", op);
                        break;
                }
                if (code < 0)
                    return code;
                code = zsetcolorspace(i_ctx_p);
                if (code != 0)
                    return code;
                break;
            case 1:
                make_int(pstage, ++stage);
                code = zsetcolor(i_ctx_p);
                if (code != 0)
                    return code;
                break;
            case 2:
                esp -= 3;
                return o_pop_estack;
                break;
        }
    }while(1);
    return 0;
}


zprocesscolors(i_ctx_t * i_ctx_p)
{
    os_ptr  op = osp;

    push(1);
    make_int(op, gs_currentdevice(igs)->color_info.num_components);
    return 0;
}


static int dummyrange(i_ctx_t * i_ctx_p, ref *space, float *ptr)
{
    return 0;
}


static int validateseparationspace(i_ctx_t * i_ctx_p, ref **space)
{
    int code = 0;
    ref *sepspace = *space;
    ref nameref, sref, sname, altspace, tref;

    if (!r_is_array(sepspace))
        return_error(gs_error_typecheck);
    /* Validate parameters, check we have enough operands */
    if (r_size(sepspace) != 4)
        return_error(gs_error_rangecheck);

    /* Check separation name is a string or name object */
    code = array_get(imemory, sepspace, 1, &sname);
    if (code < 0)
        return code;
    if (!r_has_type(&sname, t_name)) {
        if (!r_has_type(&sname, t_string))
            return_error(gs_error_typecheck);
        else {
            code = name_from_string(imemory, &sname, &sname);
            if (code < 0)
                return code;
        }
    }

    /* Check the tint transform is a procedure */
    code = array_get(imemory, sepspace, 3, &tref);
    if (code < 0)
        return code;
    check_proc(tref);

    /* Get the name of the alternate space */
    code = array_get(imemory, sepspace, 2, &altspace);
    if (code < 0)
        return code;
    if (r_has_type(&altspace, t_name))
        ref_assign(&nameref, &altspace);
    else {
        /* Make sure the alternate space is an array */
        if (!r_is_array(&altspace))
            return_error(gs_error_typecheck);
        /* And has a name for its type */
        code = array_get(imemory, &altspace, 0, &tref);
        if (code < 0)
            return code;
        if (!r_has_type(&tref, t_name))
            return_error(gs_error_typecheck);
        ref_assign(&nameref, &tref);
    }

    /* Convert alternate space name to string */
    name_string_ref(imemory, &nameref, &sref);
    /* Check its not /Indexed or /Pattern or /DeviceN */
    if (r_size(&sref) == 7) {
        if (strncmp((const char *)sref.value.const_bytes, "Indexed", 7) == 0)
            return_error(gs_error_typecheck);
        if (strncmp((const char *)sref.value.const_bytes, "Pattern", 7) == 0)
            return_error(gs_error_typecheck);
        if (strncmp((const char *)sref.value.const_bytes, "DeviceN", 7) == 0)
            return_error(gs_error_typecheck);
    }
    /* and also not /Separation */
    if (r_size(&sref) == 9 && strncmp((const char *)sref.value.const_bytes, "Separation", 9) == 0)
        return_error(gs_error_typecheck);

    ref_assign(*space, &altspace);
    return 0;
}


static int labvalidate(i_ctx_t *i_ctx_p, ref *space, float *values, int num_comps)
{
    os_ptr op = osp;
    int i;

    if (num_comps < 3)
        return_error(gs_error_stackunderflow);
    op -= 2;
    for (i=0;i<3;i++) {
        if (!r_has_type(op, t_integer) && !r_has_type(op, t_real))
            return_error(gs_error_typecheck);
        op++;
    }
    return 0;
}


static int sepinitialproc(i_ctx_t *i_ctx_p, ref *space)
{
    gs_client_color cc;

    cc.pattern = 0x00;
    cc.paint.values[0] = 1.0;
    return gs_setcolor(igs, &cc);
}


static int checkDecodeLMN(i_ctx_t * i_ctx_p, ref *CIEdict)
{
    int code = 0, i;
    ref *tempref, valref;

    code = dict_find_string(CIEdict, "DecodeLMN", &tempref);
    if (code > 0 && !r_has_type(tempref, t_null)) {
        if (!r_is_array(tempref))
            return_error(gs_error_typecheck);
        if (r_size(tempref) != 3)
            return_error(gs_error_rangecheck);

        for (i=0;i<3;i++) {
            code = array_get(imemory, tempref, i, &valref);
            if (code < 0)
                return code;
            check_proc(valref);
        }
    }
    return 0;
}


setstrokecolorspace_cont(i_ctx_t * i_ctx_p)
{
    return zswapcolors(i_ctx_p);
}


static int devicenbasecolor(i_ctx_t * i_ctx_p, ref *space, int base, int *stage, int *cont, int *stack_depth)
{
    os_ptr  op = osp;   /* required by "push" macro */
    int code, use, n_comp;
    ref narray;

    code = devicentransform(i_ctx_p, space, &use, stage, stack_depth);
    if (code !=  0)
        return code;
    if (!use) {
        *stage = 0;
        *cont = 0;
        code = array_get(imemory, space, 1, &narray);
        if (code < 0)
            return code;
        n_comp = r_size(&narray);
        pop(n_comp);
        op = osp;
        switch(base) {
        case 0:
            push(1);
            make_real(op, 0.0);
            break;
        case 1:
        case 2:
            push(3);
            make_real(&op[-2], 0.0);
            make_real(&op[-1], 0.0);
            make_real(op, 0.0);
            break;
        case 3:
            push(4);
            make_real(&op[-3], 0.0);
            make_real(&op[-2], 0.0);
            make_real(&op[-1], 0.0);
            make_real(op, 0.0);
            break;
        }
    } else {
        *stage = 0;
        *cont = 1;
    }
    return 0;
}


ztempfile(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    const char *pstr;
    char fmode[4];
    int code = parse_file_access_string(op, fmode);
    char *prefix = NULL;
    char *fname= NULL;
    uint fnlen;
    FILE *sfile;
    stream *s;
    byte *buf, *sbody;

    if (code < 0)
        return code;
    prefix = (char *)gs_alloc_bytes(imemory, gp_file_name_sizeof, "ztempfile(prefix)");
    fname = (char *)gs_alloc_bytes(imemory, gp_file_name_sizeof, "ztempfile(fname)");
    if (!prefix || !fname) {
        code = gs_note_error(gs_error_VMerror);
        goto done;
    }

    strcat(fmode, gp_fmode_binary_suffix);
    if (r_has_type(op - 1, t_null))
        pstr = gp_scratch_file_name_prefix;
    else {
        uint psize;

        check_read_type(op[-1], t_string);
        psize = r_size(op - 1);
        if (psize >= gp_file_name_sizeof) {
            code = gs_note_error(gs_error_rangecheck);
            goto done;
        }
        memcpy(prefix, op[-1].value.const_bytes, psize);
        prefix[psize] = 0;
        pstr = prefix;
    }

    if (gp_file_name_is_absolute(pstr, strlen(pstr))) {
        int plen = strlen(pstr);
        const char *sep = gp_file_name_separator();
#ifdef DEBUG
        int seplen = strlen(sep);
        if (seplen != 1)
            return_error(gs_error_Fatal);
#endif
        /* strip off the file name prefix, leave just the directory name
         * so we can check if we are allowed to write to it
         */
        for ( ; plen >=0; plen--) {
            if (pstr[plen] == sep[0])
                break;
        }
        memcpy(fname, pstr, plen);
        fname[plen] = '\0';
        if (check_file_permissions(i_ctx_p, fname, strlen(fname),
                                   NULL, "PermitFileWriting") < 0) {
            code = gs_note_error(gs_error_invalidfileaccess);
            goto done;
        }
    } else if (!prefix_is_simple(pstr)) {
        code = gs_note_error(gs_error_invalidfileaccess);
        goto done;
    }

    s = file_alloc_stream(imemory, "ztempfile(stream)");
    if (s == 0) {
        code = gs_note_error(gs_error_VMerror);
        goto done;
    }
    buf = gs_alloc_bytes(imemory, file_default_buffer_size,
                         "ztempfile(buffer)");
    if (buf == 0) {
        code = gs_note_error(gs_error_VMerror);
        goto done;
    }
    sfile = gp_open_scratch_file(imemory, pstr, fname, fmode);
    if (sfile == 0) {
        gs_free_object(imemory, buf, "ztempfile(buffer)");
        code = gs_note_error(gs_error_invalidfileaccess);
        goto done;
    }
    fnlen = strlen(fname);
    sbody = ialloc_string(fnlen, ".tempfile(fname)");
    if (sbody == 0) {
        gs_free_object(imemory, buf, "ztempfile(buffer)");
        code = gs_note_error(gs_error_VMerror);
        goto done;
    }
    memcpy(sbody, fname, fnlen);
    file_init_stream(s, sfile, fmode, buf, file_default_buffer_size);
    code = ssetfilename(s, (const unsigned char*) fname, fnlen);
    if (code < 0) {
        gx_io_device *iodev_dflt = iodev_default(imemory);
        sclose(s);
        iodev_dflt->procs.delete_file(iodev_dflt, fname);
        ifree_string(sbody, fnlen, ".tempfile(fname)");
        code = gs_note_error(gs_error_VMerror);
        goto done;
    }
    make_string(op - 1, a_readonly | icurrent_space, fnlen, sbody);
    make_stream_file(op, s, fmode);

done:
    if (prefix)
        gs_free_object(imemory, prefix, "ztempfile(prefix)");
    if (fname)
        gs_free_object(imemory, fname, "ztempfile(fname)");
    return code;
}


zstatus(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;

    switch (r_type(op)) {
        case t_file:
            {
                stream *s;

                make_bool(op, (file_is_valid(s, op) ? 1 : 0));
            }
            return 0;
        case t_string:
            {
                gs_parsed_file_name_t pname;
                struct stat fstat;
                int code = parse_file_name(op, &pname,
                                           i_ctx_p->LockFilePermissions, imemory);
                if (code < 0) {
                    if (code == gs_error_undefinedfilename) {
                        make_bool(op, 0);
                        code = 0;
                    }
                    return code;
                }
                code = gs_terminate_file_name(&pname, imemory, "status");
                if (code < 0)
                    return code;
                if ((code = check_file_permissions(i_ctx_p, pname.fname, pname.len,
                                       pname.iodev, "PermitFileReading")) >= 0) {
                    code = (*pname.iodev->procs.file_status)(pname.iodev,
                                                       pname.fname, &fstat);
                }
                switch (code) {
                    case 0:
                        check_ostack(4);
                        /*
                         * Check to make sure that the file size fits into
                         * a PostScript integer.  (On some systems, long is
                         * 32 bits, but file sizes are 64 bits.)
                         */
                        push(4);
                        make_int(op - 4, stat_blocks(&fstat));
                        make_int(op - 3, fstat.st_size);
                        /*
                         * We can't check the value simply by using ==,
                         * because signed/unsigned == does the wrong thing.
                         * Instead, since integer assignment only keeps the
                         * bottom bits, we convert the values to double
                         * and then test for equality.  This handles all
                         * cases of signed/unsigned or width mismatch.
                         */
                        if ((double)op[-4].value.intval !=
                              (double)stat_blocks(&fstat) ||
                            (double)op[-3].value.intval !=
                              (double)fstat.st_size
                            )
                            return_error(gs_error_limitcheck);
                        make_int(op - 2, fstat.st_mtime);
                        make_int(op - 1, fstat.st_ctime);
                        make_bool(op, 1);
                        break;
                    case gs_error_undefinedfilename:
                        make_bool(op, 0);
                        code = 0;
                }
                gs_free_file_name(&pname, "status");
                return code;
            }
        default:
            return_op_typecheck(op);
    }
}


zcvs(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    int code;

    check_write_type(*op, t_string);
    check_op(2);
    code = convert_to_string(imemory, op - 1, op);
    if (code >= 0)
        pop(1);
    return code;
}


gs_interp_free_stacks(gs_ref_memory_t * smem, gs_context_state_t * pcst)
{
    /* Free the stacks in inverse order of allocation. */
    ref_stack_release(&pcst->dict_stack.stack);
    ref_stack_release(&pcst->exec_stack.stack);
    ref_stack_release(&pcst->op_stack.stack);
}


zsetstackprotect(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    ref *ep = oparray_find(i_ctx_p);

    check_type(*op, t_boolean);
    if (ep == 0)
        return_error(gs_error_rangecheck);
    ep->value.opproc =
        (op->value.boolval ? oparray_cleanup : oparray_no_cleanup);
    pop(1);
    return 0;
}


gs_interp_make_oper(ref * opref, op_proc_t proc, int idx)
{
    int i;

    for (i = num_special_ops; i > 0 && proc != interp1_op_defs[i].proc; --i)
        DO_NOTHING;
    if (i > 0)
        make_tasv(opref, tx_op + (i - 1), a_executable, i, opproc, proc);
    else
        make_tasv(opref, t_operator, a_executable, idx, opproc, proc);
}


errorexec_pop(i_ctx_t *i_ctx_p)
{
    esp -= 2;
    return o_pop_estack;
}


unsigned char *cipso_v4_optptr(const struct sk_buff *skb)
{
	const struct iphdr *iph = ip_hdr(skb);
	unsigned char *optptr = (unsigned char *)&(ip_hdr(skb)[1]);
	int optlen;
	int taglen;

	for (optlen = iph->ihl*4 - sizeof(struct iphdr); optlen > 0; ) {
		switch (optptr[0]) {
		case IPOPT_CIPSO:
			return optptr;
		case IPOPT_END:
			return NULL;
		case IPOPT_NOOP:
			taglen = 1;
			break;
		default:
			taglen = optptr[1];
		}
		optlen -= taglen;
		optptr += taglen;
	}

	return NULL;
}


int cipso_v4_req_setattr(struct request_sock *req,
			 const struct cipso_v4_doi *doi_def,
			 const struct netlbl_lsm_secattr *secattr)
{
	int ret_val = -EPERM;
	unsigned char *buf = NULL;
	u32 buf_len;
	u32 opt_len;
	struct ip_options_rcu *opt = NULL;
	struct inet_request_sock *req_inet;

	/* We allocate the maximum CIPSO option size here so we are probably
	 * being a little wasteful, but it makes our life _much_ easier later
	 * on and after all we are only talking about 40 bytes. */
	buf_len = CIPSO_V4_OPT_LEN_MAX;
	buf = kmalloc(buf_len, GFP_ATOMIC);
	if (!buf) {
		ret_val = -ENOMEM;
		goto req_setattr_failure;
	}

	ret_val = cipso_v4_genopt(buf, buf_len, doi_def, secattr);
	if (ret_val < 0)
		goto req_setattr_failure;
	buf_len = ret_val;

	/* We can't use ip_options_get() directly because it makes a call to
	 * ip_options_get_alloc() which allocates memory with GFP_KERNEL and
	 * we won't always have CAP_NET_RAW even though we _always_ want to
	 * set the IPOPT_CIPSO option. */
	opt_len = (buf_len + 3) & ~3;
	opt = kzalloc(sizeof(*opt) + opt_len, GFP_ATOMIC);
	if (!opt) {
		ret_val = -ENOMEM;
		goto req_setattr_failure;
	}
	memcpy(opt->opt.__data, buf, buf_len);
	opt->opt.optlen = opt_len;
	opt->opt.cipso = sizeof(struct iphdr);
	kfree(buf);
	buf = NULL;

	req_inet = inet_rsk(req);
	opt = xchg(&req_inet->opt, opt);
	if (opt)
		kfree_rcu(opt, rcu);

	return 0;

req_setattr_failure:
	kfree(buf);
	kfree(opt);
	return ret_val;
}


int cipso_v4_doi_add(struct cipso_v4_doi *doi_def,
		     struct netlbl_audit *audit_info)
{
	int ret_val = -EINVAL;
	u32 iter;
	u32 doi;
	u32 doi_type;
	struct audit_buffer *audit_buf;

	doi = doi_def->doi;
	doi_type = doi_def->type;

	if (doi_def->doi == CIPSO_V4_DOI_UNKNOWN)
		goto doi_add_return;
	for (iter = 0; iter < CIPSO_V4_TAG_MAXCNT; iter++) {
		switch (doi_def->tags[iter]) {
		case CIPSO_V4_TAG_RBITMAP:
			break;
		case CIPSO_V4_TAG_RANGE:
		case CIPSO_V4_TAG_ENUM:
			if (doi_def->type != CIPSO_V4_MAP_PASS)
				goto doi_add_return;
			break;
		case CIPSO_V4_TAG_LOCAL:
			if (doi_def->type != CIPSO_V4_MAP_LOCAL)
				goto doi_add_return;
			break;
		case CIPSO_V4_TAG_INVALID:
			if (iter == 0)
				goto doi_add_return;
			break;
		default:
			goto doi_add_return;
		}
	}

	refcount_set(&doi_def->refcount, 1);

	spin_lock(&cipso_v4_doi_list_lock);
	if (cipso_v4_doi_search(doi_def->doi)) {
		spin_unlock(&cipso_v4_doi_list_lock);
		ret_val = -EEXIST;
		goto doi_add_return;
	}
	list_add_tail_rcu(&doi_def->list, &cipso_v4_doi_list);
	spin_unlock(&cipso_v4_doi_list_lock);
	ret_val = 0;

doi_add_return:
	audit_buf = netlbl_audit_start(AUDIT_MAC_CIPSOV4_ADD, audit_info);
	if (audit_buf) {
		const char *type_str;
		switch (doi_type) {
		case CIPSO_V4_MAP_TRANS:
			type_str = "trans";
			break;
		case CIPSO_V4_MAP_PASS:
			type_str = "pass";
			break;
		case CIPSO_V4_MAP_LOCAL:
			type_str = "local";
			break;
		default:
			type_str = "(unknown)";
		}
		audit_log_format(audit_buf,
				 " cipso_doi=%u cipso_type=%s res=%u",
				 doi, type_str, ret_val == 0 ? 1 : 0);
		audit_log_end(audit_buf);
	}

	return ret_val;
}


void cipso_v4_req_delattr(struct request_sock *req)
{
	struct ip_options_rcu *opt;
	struct inet_request_sock *req_inet;

	req_inet = inet_rsk(req);
	opt = req_inet->opt;
	if (!opt || opt->opt.cipso == 0)
		return;

	cipso_v4_delopt(&req_inet->opt);
}


find_slot_by_slotlabel(pkcs11_handle_t *h, const char *wanted_slot_label,
    unsigned int *slot_num)
{
  unsigned long idx;
  size_t len;

  if (slot_num == NULL || wanted_slot_label == NULL ||
      strlen(wanted_slot_label) == 0)
    return (-1);

  if (strcmp(wanted_slot_label, "none") == 0) {
    for (idx = 0; idx < h->slot_count; idx++) {
      if (h->slots[idx].token_present) {
	*slot_num = idx;
	return (0);
      }
    }
  } else {
    /* Look up the slot by it's slotDescription */
    len = strlen(wanted_slot_label);
    for (idx = 0; idx < h->slot_count; idx++) {
      if (h->slots[idx].token_present
	  && memcmp_pad_max(h->slots[idx].slotDescription, 64,
	  (void *)wanted_slot_label, len, 64) == 0) {
	*slot_num = idx;
	return (0);
      }
    }
  }

  return (-1);
}


int pkcs11_login(pkcs11_handle_t *h, char *password)
{
  int rv;

  DBG("login as user CKU_USER");
  if (password)
	  rv = h->fl->C_Login(h->session, CKU_USER, (unsigned char*)password, strlen(password));
  else
	  rv = h->fl->C_Login(h->session, CKU_USER, NULL, 0);
  if ((rv != CKR_OK) && (rv != CKR_USER_ALREADY_LOGGED_IN)) {
    set_error("C_Login() failed: 0x%08lX", rv);
    return -1;
  }
  return 0;
}


int get_private_key(pkcs11_handle_t *h, cert_object_t *cert) {
  /* all certs returned from NSS are user certs, and the private key
   * has already been identified */
  return 0;
}


int find_slot_by_number(pkcs11_handle_t *h, unsigned int slot_num, unsigned int *slotID)
{
  SECMODModule *module = h->module;
  int i;

  /* if module is null,
   * any of the PKCS #11 modules specified in the system config
   * is available, find one */
  if (module == NULL) {
    PK11SlotList *list;
    PK11SlotListElement *le;
    PK11SlotInfo *slot = NULL;

    /* find a slot, we haven't specifically selected a module,
     * so find an appropriate one. */
    /* get them all */
    list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_TRUE, NULL);
    if (list == NULL) {
	return -1;
    }
    for (le = list->head; le; le = le->next) {
      CK_SLOT_INFO slInfo;
      SECStatus rv;

      slInfo.flags = 0;
      rv = PK11_GetSlotInfo(le->slot, &slInfo);
      if (rv == SECSuccess && (slInfo.flags & CKF_REMOVABLE_DEVICE)) {
	slot = PK11_ReferenceSlot(le->slot);
	module = SECMOD_ReferenceModule(PK11_GetModule(le->slot));
	break;
      }
    }
    PK11_FreeSlotList(list);
    if (slot == NULL) {
	return -1;
    }
    h->slot = slot;
    h->module = module;
    *slotID = PK11_GetSlotID(slot);
    return 0;
  }

  /*
   * we're configured with a specific module, look for a present slot
   * on that module. */
  if (slot_num == 0) {
    /* threaded applications should also acquire the
     * DefaultModuleListLock */
    for (i=0; i < module->slotCount; i++) {
      if (module->slots[i] && PK11_IsPresent(module->slots[i])) {
        h->slot = PK11_ReferenceSlot(module->slots[i]);
        *slotID = PK11_GetSlotID(h->slot);
        return 0;
      }
    }
  }
  /* we're configured for a specific module and token, see if it's present */
  slot_num--;
  if (slot_num < module->slotCount && module->slots &&
      module->slots[slot_num] && PK11_IsPresent(module->slots[slot_num])) {
    h->slot = PK11_ReferenceSlot(module->slots[slot_num]);
    *slotID = PK11_GetSlotID(h->slot);
    return 0;
  }
  return -1;
}


int get_slot_protected_authentication_path(pkcs11_handle_t *h)
{
  if (h->slot == NULL) {
    DBG("Login failed: No Slot selected");
    return -1;
  }

  return PK11_ProtectedAuthenticationPath(h->slot);
}


static int openssh_mapper_match_keys(X509 *x509, const char *filename) {
#ifdef HAVE_NSS
	return -1;
#else
	FILE *fd;
	char line[OPENSSH_LINE_MAX];
	int i;
	int nkeys =0;
	EVP_PKEY **keys = NULL;
	EVP_PKEY *authkey = X509_get_pubkey(x509);

	if (!authkey) {
	    DBG("Cannot locate Cert Public key");
	    return 0;
        }
        /* parse list of authorized keys until match */
	fd=fopen(filename,"rt");
	if (!fd) {
	    DBG2("fopen('%s') : '%s'",filename,strerror(errno));
	    return 0; /* no authorized_keys file -> no match :-) */
	}
	/* read pkey files and compose chain */
	for (;;) {
                char *cp;
                if (!fgets(line, OPENSSH_LINE_MAX, fd)) break;
                /* Skip leading whitespace, empty and comment lines. */
                for (cp = line; *cp == ' ' || *cp == '\t'; cp++)

                        if (!*cp || *cp == '\n' || *cp == '#') continue;
                if (*cp >= '0' && *cp <= '9') {
                        /* ssh v1 key format */
                        EVP_PKEY *key = ssh1_line_to_key(cp);
                        if (key) add_key(key, &keys, &nkeys);
                }
                if (strncmp("ssh-rsa", cp, 7) == 0) {
                        /* ssh v2 rsa key format */
                        EVP_PKEY *key = ssh2_line_to_key(cp);
                        if (key) add_key(key, &keys, &nkeys);
	    }
        }
	fclose(fd);
        for (i = 0; i < nkeys; i++) {
                RSA *authrsa, *rsa;
		BIGNUM *authrsa_n, *authrsa_e;
		BIGNUM *rsa_n, *rsa_e;
                authrsa = EVP_PKEY_get1_RSA(authkey);
                if (!authrsa) continue;       /* not RSA */
                rsa = EVP_PKEY_get1_RSA(keys[i]);
                if (!rsa) continue;       /* not RSA */
                if (BN_cmp(rsa_e, authrsa_e) != 0) continue;
                if (BN_cmp(rsa_n, authrsa_n) != 0) continue;
                return 1;       /* FOUND */
        }
        DBG("User authorized_keys file doesn't match cert public key(s)");
        return 0;
#endif
}


static void hidp_session_run(struct hidp_session *session)
{
	struct sock *ctrl_sk = session->ctrl_sock->sk;
	struct sock *intr_sk = session->intr_sock->sk;
	struct sk_buff *skb;
	DEFINE_WAIT_FUNC(wait, woken_wake_function);

	add_wait_queue(&hidp_session_wq, &wait);
	for (;;) {
		/*
		 * This thread can be woken up two ways:
		 *  - You call hidp_session_terminate() which sets the
		 *    session->terminate flag and wakes this thread up.
		 *  - Via modifying the socket state of ctrl/intr_sock. This
		 *    thread is woken up by ->sk_state_changed().
		 */

		/* Ensure session->terminate is updated */
		smp_mb__before_atomic();
		if (atomic_read(&session->terminate))
			break;

		if (ctrl_sk->sk_state != BT_CONNECTED ||
		    intr_sk->sk_state != BT_CONNECTED)
			break;

		/* parse incoming intr-skbs */
		while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) {
			skb_orphan(skb);
			if (!skb_linearize(skb))
				hidp_recv_intr_frame(session, skb);
			else
				kfree_skb(skb);
		}

		/* send pending intr-skbs */
		hidp_process_transmit(session, &session->intr_transmit,
				      session->intr_sock);

		/* parse incoming ctrl-skbs */
		while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) {
			skb_orphan(skb);
			if (!skb_linearize(skb))
				hidp_recv_ctrl_frame(session, skb);
			else
				kfree_skb(skb);
		}

		/* send pending ctrl-skbs */
		hidp_process_transmit(session, &session->ctrl_transmit,
				      session->ctrl_sock);

		wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
	}
	remove_wait_queue(&hidp_session_wq, &wait);

	atomic_inc(&session->terminate);

	/* Ensure session->terminate is updated */
	smp_mb__after_atomic();
}


static int hidp_session_new(struct hidp_session **out, const bdaddr_t *bdaddr,
			    struct socket *ctrl_sock,
			    struct socket *intr_sock,
			    struct hidp_connadd_req *req,
			    struct l2cap_conn *conn)
{
	struct hidp_session *session;
	int ret;
	struct bt_sock *ctrl, *intr;

	ctrl = bt_sk(ctrl_sock->sk);
	intr = bt_sk(intr_sock->sk);

	session = kzalloc(sizeof(*session), GFP_KERNEL);
	if (!session)
		return -ENOMEM;

	/* object and runtime management */
	kref_init(&session->ref);
	atomic_set(&session->state, HIDP_SESSION_IDLING);
	init_waitqueue_head(&session->state_queue);
	session->flags = req->flags & BIT(HIDP_BLUETOOTH_VENDOR_ID);

	/* connection management */
	bacpy(&session->bdaddr, bdaddr);
	session->conn = l2cap_conn_get(conn);
	session->user.probe = hidp_session_probe;
	session->user.remove = hidp_session_remove;
	INIT_LIST_HEAD(&session->user.list);
	session->ctrl_sock = ctrl_sock;
	session->intr_sock = intr_sock;
	skb_queue_head_init(&session->ctrl_transmit);
	skb_queue_head_init(&session->intr_transmit);
	session->ctrl_mtu = min_t(uint, l2cap_pi(ctrl)->chan->omtu,
					l2cap_pi(ctrl)->chan->imtu);
	session->intr_mtu = min_t(uint, l2cap_pi(intr)->chan->omtu,
					l2cap_pi(intr)->chan->imtu);
	session->idle_to = req->idle_to;

	/* device management */
	INIT_WORK(&session->dev_init, hidp_session_dev_work);
	timer_setup(&session->timer, hidp_idle_timeout, 0);

	/* session data */
	mutex_init(&session->report_mutex);
	init_waitqueue_head(&session->report_queue);

	ret = hidp_session_dev_init(session, req);
	if (ret)
		goto err_free;

	get_file(session->intr_sock->file);
	get_file(session->ctrl_sock->file);
	*out = session;
	return 0;

err_free:
	l2cap_conn_put(session->conn);
	kfree(session);
	return ret;
}


static void hidp_idle_timeout(struct timer_list *t)
{
	struct hidp_session *session = from_timer(session, t, timer);

	/* The HIDP user-space API only contains calls to add and remove
	 * devices. There is no way to forward events of any kind. Therefore,
	 * we have to forcefully disconnect a device on idle-timeouts. This is
	 * unfortunate and weird API design, but it is spec-compliant and
	 * required for backwards-compatibility. Hence, on idle-timeout, we
	 * signal driver-detach events, so poll() will be woken up with an
	 * error-condition on both sockets.
	 */

	session->intr_sock->sk->sk_err = EUNATCH;
	session->ctrl_sock->sk->sk_err = EUNATCH;
	wake_up_interruptible(sk_sleep(session->intr_sock->sk));
	wake_up_interruptible(sk_sleep(session->ctrl_sock->sk));

	hidp_session_terminate(session);
}


int hidp_connection_add(struct hidp_connadd_req *req,
			struct socket *ctrl_sock,
			struct socket *intr_sock)
{
	u32 valid_flags = BIT(HIDP_VIRTUAL_CABLE_UNPLUG) |
			  BIT(HIDP_BOOT_PROTOCOL_MODE);
	struct hidp_session *session;
	struct l2cap_conn *conn;
	struct l2cap_chan *chan;
	int ret;

	ret = hidp_verify_sockets(ctrl_sock, intr_sock);
	if (ret)
		return ret;

	if (req->flags & ~valid_flags)
		return -EINVAL;

	chan = l2cap_pi(ctrl_sock->sk)->chan;
	conn = NULL;
	l2cap_chan_lock(chan);
	if (chan->conn)
		conn = l2cap_conn_get(chan->conn);
	l2cap_chan_unlock(chan);

	if (!conn)
		return -EBADFD;

	ret = hidp_session_new(&session, &chan->dst, ctrl_sock,
			       intr_sock, req, conn);
	if (ret)
		goto out_conn;

	ret = l2cap_register_user(conn, &session->user);
	if (ret)
		goto out_session;

	ret = 0;

out_session:
	hidp_session_put(session);
out_conn:
	l2cap_conn_put(conn);
	return ret;
}


MagickExport const char *GetLogName(void)
{
  return(log_name);
}


MagickExport char *GetDelegateCommand(const ImageInfo *image_info,Image *image,
  const char *decode,const char *encode,ExceptionInfo *exception)
{
  char
    *command,
    **commands;

  const DelegateInfo
    *delegate_info;

  register ssize_t
    i;

  assert(image_info != (ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  delegate_info=GetDelegateInfo(decode,encode,exception);
  if (delegate_info == (const DelegateInfo *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),DelegateError,
        "NoTagFound","`%s'",decode ? decode : encode);
      return((char *) NULL);
    }
  commands=StringToList(delegate_info->commands);
  if (commands == (char **) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),
        ResourceLimitError,"MemoryAllocationFailed","`%s'",decode ? decode :
        encode);
      return((char *) NULL);
    }
  command=InterpretDelegateProperties((ImageInfo *) image_info,image,
    commands[0],exception);
  if (command == (char *) NULL)
    (void) ThrowMagickException(exception,GetMagickModule(),ResourceLimitError,
      "MemoryAllocationFailed","`%s'",commands[0]);
  /*
    Relinquish resources.
  */
  for (i=0; commands[i] != (char *) NULL; i++)
    commands[i]=DestroyString(commands[i]);
  commands=(char **) RelinquishMagickMemory(commands);
  return(command);
}