blob: f88db9796ef7fa7103ba456b3ce9e2b92e1399ef [file]
# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Archive reader library for the .deb file testing."""
import io
import os
class SimpleArReader(object):
"""A simple AR file reader.
This enable to read AR file (System V variant) as described
in https://en.wikipedia.org/wiki/Ar_(Unix).
The standard usage of this class is:
with SimpleArReader(filename) as ar:
nextFile = ar.next()
while nextFile:
print('This archive contains', nextFile.filename)
nextFile = ar.next()
Upon error, this class will raise a ArError exception.
"""
class ArError(Exception):
pass
class SimpleArFileEntry(object):
"""Represent one entry in a AR archive.
Attributes:
filename: the filename of the entry, as described in the archive.
timestamp: the timestamp of the file entry.
owner_id: numeric id of the user and group owning the file.
group_id: numeric id of the user and group owning the file.
mode: unix permission mode of the file
size: size of the file
data: the content of the file.
"""
def __init__(self, f):
self.filename = f.read(16).decode('utf-8').strip()
if self.filename.endswith('/'): # SysV variant
self.filename = self.filename[:-1]
self.timestamp = int(f.read(12).strip())
self.owner_id = int(f.read(6).strip())
self.group_id = int(f.read(6).strip())
self.mode = int(f.read(8).strip(), 8)
self.size = int(f.read(10).strip())
pad = f.read(2)
if pad != b'\x60\x0a':
raise SimpleArReader.ArError('Invalid AR file header')
self.data = f.read(self.size)
MAGIC_STRING = b'!<arch>\n'
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.f = open(self.filename, 'rb')
if self.f.read(len(self.MAGIC_STRING)) != self.MAGIC_STRING:
raise self.ArError('Not a ar file: ' + self.filename)
return self
def __exit__(self, t, v, traceback):
self.f.close()
def next(self):
"""Read the next file. Returns None when reaching the end of file."""
# AR sections are two bit aligned using new lines.
if self.f.tell() % 2 != 0:
self.f.read(1)
# An AR sections is at least 60 bytes. Some file might contains garbage
# bytes at the end of the archive, ignore them.
if self.f.tell() > os.fstat(self.f.fileno()).st_size - 60:
return None
return self.SimpleArFileEntry(self.f)