#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
import os
import shutil
import simplejson
import six
import sys
import threading
import traceback
# redirecting stdout for proper WSGI portability
p5p_indices = {}
# A lock to prevent two threads from rebuilding our catalog parts cache
# at the same time.
"""An exception thrown when a client requests a path within a p5p file
which does not exist."""
"""An exception thrown when this wsgi application cannot parse a query
from the client."""
"""An exception thrown when the p5p file referred to by the
configuration does not exist."""
"""An object to handle a request for p5p file contents from the
system repository."""
"""Release any resources we have used."""
"""Print some information in the Apache log that will help
determine what went wrong as well as updating the client
response code. The WSGI spec says we can call
start_response multiple times, but must include exc_info
if we do so."""
# we only want error_log output if our status is not 4xx
if status != SERVER_NOTFOUND_STATUS and \
print(traceback.format_exc())
"""Determine if we need to update our cached catalog and
reload the index by comparing the last modification time of a
file we create per p5p archive, and the p5p archive itself."""
timestamp_path = \
"{htdocs_path}/{pub}/{hsh}/sysrepo.timestamp".format(
**locals())
# Locking here is quite basic: we want to ensure that no two
# threads simultaneously decide that they need to rebuild our
# local catalog cache, stepping on each others toes. It is
# possible that while processing a single query, a user will
# replace the p5p file on the server after this method has been
# called, causing stale data to be returned at best, and a HTTP
# 500 response at worst (as the p5p index used by this web
# application will not match the one in the new archive)
try:
# don't write a timestamp if we're testing
return True
try:
except OSError as e:
raise MissingArchiveException(
try:
except OSError as e:
except MissingArchiveException as e:
raise
except Exception as e:
finally:
return update
"""Process our file query."""
# use the basename of the path, which is the pkg(7) hash
try:
except Exception as e:
"""Process our catalog query"""
cat_path = \
"{htdocs_path}/{pub}/{hsh}/catalog/1/{cat_part}".format(
**locals())
# this is unlikely to happen: it implies a catalog part has been
# requested that wasn't listed in the catalog.attrs file
# extracted during _precache_catalog() or the file has been
# removed on the server. Do our best to return the content.
try:
try:
except Exception as e:
finally:
except OSError as e:
else:
raise
"""Return our manifest_response. """
mf = None
try:
return mf
except Exception as e:
"""Extract the parts from the catalog_dir to the given path."""
**locals())
try:
"rb") as catalog_attrs:
# if the catalog part is unavailable,
# we ignore this for now. It will be
# reported later anyway.
pass
"""Parse our query, returning publisher, hash, and path
values."""
attrs = {}
try:
except ValueError:
if not hsh:
"missing hash.")
if not pub:
"missing publisher.")
if not path:
"missing path.")
"""Process a query of the form:
pub=<publisher>&hash=<hash>&path=<path>
where:
<publisher> the name of the publisher from the p5p file
<hash> the sha1 hash of the location of the p5p file
<path> the path of the pkg(7) client request
In the environment of this WSGI application, apart from the
default WSGI values, defined in PEP333, we expect:
"SYSREPO_RUNTIME_DIR", a location pointing to the runtime
directory, allowing us to serve static html from beneath a
"htdocs" subdir.
<hash>, which maps the sha1 hash of the p5p archive path, to the
path itself, which is not visible to clients.
"""
buf = []
try:
# The pathname return from environ contains
# hex escaped sequences, but we need its unicode
# character to be able to find the file.
# In order to keep only one copy of the p5p index in
# memory, we cache it locally, and reuse it any time
# we're opening the same p5p file. Before doing
# so, we need to ensure the p5p file hasn't been
# modified since we last looked at it.
try:
except:
raise
finally:
else:
else:
raise UnknownPathException(path)
except OSError as e:
print(e.errno)
except UnknownPathException as e:
except MalformedQueryException as e:
except MissingArchiveException as e:
except Exception as e:
return buf
#
# CloseGenerator, AppWrapper and _application as an idiom together
# are described at
# and exist to ensure that we close any server-side resources used by
# our application at the end of the request (i.e. after the client has
# received it)
#
"""A wrapper class to ensure we have a close() method on the iterable
returned from the mod_wsgi application, see PEP333."""
# if we haven't produced an iterable, that's
# likely because of an exception. Do nothing.
if not self.__iterable:
return
yield item
try:
finally:
"""Wrap a callable application with this class in order for its results
to be handled by CloseGenerator when that callable is called."""
if __name__ == "__main__":
"""A dummy response function."""
if exc_info:
query = \
("'pub=test&hash=de5acae11333890c457665379eec812a67f78dd3"
"&path=manifest/0/mypackage@1.2.9%2C5.11-1%3A20110617T204846Z'")
alias = \
"de5acae11333890c457665379eec812a67f78dd3=/tmp/archive.p5p"
print("usage: sysrepo_p5p <query> <hash>=<path to p5p file>")
environ = {}
# Apache logs when testing.
elif response: