diff --git a/swh/fuse/fs/artifact.py b/swh/fuse/fs/artifact.py index db05133..9118c9a 100644 --- a/swh/fuse/fs/artifact.py +++ b/swh/fuse/fs/artifact.py @@ -1,93 +1,178 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Any, AsyncIterator +from dataclasses import dataclass +from pathlib import Path +from typing import Any, AsyncIterator, List from swh.fuse.fs.entry import EntryMode, FuseEntry -from swh.model.identifiers import CONTENT, DIRECTORY, SWHID - -# Avoid cycling import -Fuse = "Fuse" +from swh.fuse.fs.symlink import SymlinkEntry +from swh.model.from_disk import DentryPerms +from swh.model.identifiers import CONTENT, DIRECTORY, REVISION, SWHID +@dataclass class ArtifactEntry(FuseEntry): """ FUSE virtual entry for a Software Heritage Artifact Attributes: swhid: Software Heritage persistent identifier prefetch: optional prefetched metadata used to set entry attributes """ - def __init__( - self, name: str, mode: int, fuse: Fuse, swhid: SWHID, prefetch: Any = None - ): - super().__init__(name, mode, fuse) - self.swhid = swhid - self.prefetch = prefetch - - -def typify( - name: str, mode: int, fuse: Fuse, swhid: SWHID, prefetch: Any = None -) -> ArtifactEntry: - """ Create an artifact entry corresponding to the given artifact type """ - - getters = {CONTENT: Content, DIRECTORY: Directory} - return getters[swhid.object_type](name, mode, fuse, swhid, prefetch) + swhid: SWHID + prefetch: Any = None class Content(ArtifactEntry): """ Software Heritage content artifact. Content leaves (AKA blobs) are represented on disks as regular files, containing the corresponding bytes, as archived. Note that permissions are associated to blobs only in the context of directories. Hence, when accessing blobs from the top-level `archive/` directory, the permissions of the `archive/SWHID` file will be arbitrary and not meaningful (e.g., `0x644`). """ - async def content(self) -> bytes: - return await self.fuse.get_blob(self.swhid) + async def get_content(self) -> bytes: + data = await self.fuse.get_blob(self.swhid) + self.prefetch["length"] = len(data) + return data - async def length(self) -> int: - # When listing entries from a directory, the API already gave us information + async def size(self) -> int: if self.prefetch: return self.prefetch["length"] - return len(await self.content()) + else: + return len(await self.get_content()) async def __aiter__(self): raise ValueError("Cannot iterate over a content type artifact") class Directory(ArtifactEntry): """ Software Heritage directory artifact. Directory nodes are represented as directories on the file-system, containing one entry for each entry of the archived directory. Entry names and other metadata, including permissions, will correspond to the archived entry metadata. Note that the FUSE mount is read-only, no matter what the permissions say. So it is possible that, in the context of a directory, a file is presented as writable, whereas actually writing to it will fail with `EPERM`. """ - async def __aiter__(self) -> AsyncIterator[ArtifactEntry]: + async def __aiter__(self) -> AsyncIterator[FuseEntry]: metadata = await self.fuse.get_metadata(self.swhid) for entry in metadata: - yield typify( - name=entry["name"], - # Use default read-only permissions for directories, and - # archived permissions for contents - mode=( - entry["perms"] - if entry["target"].object_type == CONTENT - else int(EntryMode.RDONLY_DIR) - ), - fuse=self.fuse, - swhid=entry["target"], - # The directory API has extra info we can use to set attributes - # without additional Software Heritage API call - prefetch=entry, + name = entry["name"] + swhid = entry["target"] + mode = ( + # Archived permissions for directories are always set to + # 0o040000 so use a read-only permission instead + int(EntryMode.RDONLY_DIR) + if swhid.object_type == DIRECTORY + else entry["perms"] + ) + + # 1. Symlinks + if mode == DentryPerms.symlink: + yield self.create_child( + SymlinkEntry, + name=name, + # Symlink target is stored in the blob content + target=await self.fuse.get_blob(swhid), + ) + # 2. Submodules + elif swhid.object_type == REVISION: + # Make sure the revision metadata is fetched and create a + # symlink to distinguish it with regular directories + await self.fuse.get_metadata(swhid) + yield self.create_child( + SymlinkEntry, + name=name, + target=Path(self.get_relative_root_path(), f"archive/{swhid}"), + ) + # 3. Regular entries (directories, contents) + else: + yield self.create_child( + OBJTYPE_GETTERS[swhid.object_type], + name=name, + mode=mode, + swhid=swhid, + # The directory API has extra info we can use to set + # attributes without additional Software Heritage API call + prefetch=entry, + ) + + +class Revision(ArtifactEntry): + """ Software Heritage revision artifact. + + Revision (AKA commit) nodes are represented on the file-system as + directories with the following entries: + + - `root`: source tree at the time of the commit, as a symlink pointing into + `archive/`, to a SWHID of type `dir` + - `parents/` (note the plural): a virtual directory containing entries named + `1`, `2`, `3`, etc., one for each parent commit. Each of these entry is a + symlink pointing into `archive/`, to the SWHID file for the given parent + commit + - `parent` (note the singular): present if and only if the current commit + has at least one parent commit (which is the most common case). When + present it is a symlink pointing into `parents/1/` + - `meta.json`: metadata for the current node, as a symlink pointing to the + relevant `meta/.json` file """ + + async def __aiter__(self) -> AsyncIterator[FuseEntry]: + metadata = await self.fuse.get_metadata(self.swhid) + directory = metadata["directory"] + parents = metadata["parents"] + + # Make sure all necessary metadatas are fetched + await self.fuse.get_metadata(directory) + for parent in parents: + await self.fuse.get_metadata(parent["id"]) + + root_path = self.get_relative_root_path() + + yield self.create_child( + SymlinkEntry, name="root", target=Path(root_path, f"archive/{directory}"), + ) + yield self.create_child( + SymlinkEntry, + name="meta.json", + target=Path(root_path, f"meta/{self.swhid}.json"), + ) + yield self.create_child( + RevisionParents, + name="parents", + mode=int(EntryMode.RDONLY_DIR), + parents=[x["id"] for x in parents], + ) + + if len(parents) >= 1: + yield self.create_child( + SymlinkEntry, name="parent", target="parents/1/", ) + + +@dataclass +class RevisionParents(FuseEntry): + """ Revision virtual `parents/` directory """ + + parents: List[SWHID] + + async def __aiter__(self) -> AsyncIterator[FuseEntry]: + root_path = self.get_relative_root_path() + for i, parent in enumerate(self.parents): + yield self.create_child( + SymlinkEntry, + name=str(i + 1), + target=Path(root_path, f"archive/{parent}"), + ) + + +OBJTYPE_GETTERS = {CONTENT: Content, DIRECTORY: Directory, REVISION: Revision} diff --git a/swh/fuse/fs/entry.py b/swh/fuse/fs/entry.py index bb2866a..af4158f 100644 --- a/swh/fuse/fs/entry.py +++ b/swh/fuse/fs/entry.py @@ -1,48 +1,75 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +from __future__ import annotations + +from dataclasses import dataclass, field from enum import IntEnum -from stat import S_IFDIR, S_IFREG +from pathlib import Path +from stat import S_IFDIR, S_IFLNK, S_IFREG +from typing import Any, AsyncIterator, Union # Avoid cycling import Fuse = "Fuse" class EntryMode(IntEnum): """ Default entry mode and permissions for the FUSE. The FUSE mount is always read-only, even if permissions contradict this statement (in a context of a directory, entries are listed with permissions taken from the archive). """ RDONLY_FILE = S_IFREG | 0o444 RDONLY_DIR = S_IFDIR | 0o555 + SYMLINK = S_IFLNK | 0o444 +@dataclass class FuseEntry: """ Main wrapper class to manipulate virtual FUSE entries Attributes: name: entry filename mode: entry permission mode fuse: internal reference to the main FUSE class inode: unique integer identifying the entry """ - def __init__(self, name: str, mode: int, fuse: Fuse): - self.name = name - self.mode = mode - self.fuse = fuse - self.inode = fuse._alloc_inode(self) + name: str + mode: int + depth: int + fuse: Fuse + inode: int = field(init=False) - async def length(self) -> int: - return 0 + def __post_init__(self): + self.inode = self.fuse._alloc_inode(self) + + async def get_content(self) -> bytes: + """ Return the content of a file entry """ - async def content(self): return None - async def __aiter__(self): + async def size(self) -> int: + """ Return the size of a file entry """ + + return 0 + + async def __aiter__(self) -> AsyncIterator[FuseEntry]: + """ Return the child entries of a directory entry """ + + yield None + + def get_target(self) -> Union[str, bytes, Path]: + """ Return the path target of a symlink entry """ + return None + + def get_relative_root_path(self) -> str: + return "../" * (self.depth - 1) + + def create_child(self, constructor: Any, **kwargs) -> FuseEntry: + return constructor(depth=self.depth + 1, fuse=self.fuse, **kwargs) diff --git a/swh/fuse/fs/mountpoint.py b/swh/fuse/fs/mountpoint.py index bc1d7cd..ccaa0f1 100644 --- a/swh/fuse/fs/mountpoint.py +++ b/swh/fuse/fs/mountpoint.py @@ -1,77 +1,86 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +from dataclasses import dataclass, field import json from typing import AsyncIterator -from swh.fuse.fs.artifact import typify +from swh.fuse.fs.artifact import OBJTYPE_GETTERS from swh.fuse.fs.entry import EntryMode, FuseEntry from swh.model.identifiers import CONTENT, SWHID -# Avoid cycling import -Fuse = "Fuse" - +@dataclass class Root(FuseEntry): """ The FUSE mountpoint, consisting of the archive/ and meta/ directories """ - def __init__(self, fuse: Fuse): - super().__init__(name="root", mode=int(EntryMode.RDONLY_DIR), fuse=fuse) + name: str = field(init=False, default=None) + mode: int = field(init=False, default=int(EntryMode.RDONLY_DIR)) + depth: int = field(init=False, default=1) async def __aiter__(self) -> AsyncIterator[FuseEntry]: - for entry in [ArchiveDir(self.fuse), MetaDir(self.fuse)]: - yield entry + yield self.create_child(ArchiveDir) + yield self.create_child(MetaDir) +@dataclass class ArchiveDir(FuseEntry): """ The archive/ directory is lazily populated with one entry per accessed SWHID, having actual SWHIDs as names """ - def __init__(self, fuse: Fuse): - super().__init__(name="archive", mode=int(EntryMode.RDONLY_DIR), fuse=fuse) + name: str = field(init=False, default="archive") + mode: int = field(init=False, default=int(EntryMode.RDONLY_DIR)) async def __aiter__(self) -> AsyncIterator[FuseEntry]: async for swhid in self.fuse.cache.get_cached_swhids(): if swhid.object_type == CONTENT: mode = EntryMode.RDONLY_FILE else: mode = EntryMode.RDONLY_DIR - yield typify(str(swhid), int(mode), self.fuse, swhid) + yield self.create_child( + OBJTYPE_GETTERS[swhid.object_type], + name=str(swhid), + mode=int(mode), + swhid=swhid, + ) +@dataclass class MetaDir(FuseEntry): """ The meta/ directory contains one SWHID.json file for each SWHID entry under archive/. The JSON file contain all available meta information about the given SWHID, as returned by the Software Heritage Web API for that object. Note that, in case of pagination (e.g., snapshot objects with many branches) the JSON file will contain a complete version with all pages merged together. """ - def __init__(self, fuse: Fuse): - super().__init__(name="meta", mode=int(EntryMode.RDONLY_DIR), fuse=fuse) + name: str = field(init=False, default="meta") + mode: int = field(init=False, default=int(EntryMode.RDONLY_DIR)) async def __aiter__(self) -> AsyncIterator[FuseEntry]: async for swhid in self.fuse.cache.get_cached_swhids(): - yield MetaEntry(swhid, self.fuse) + yield self.create_child( + MetaEntry, + name=f"{swhid}.json", + mode=int(EntryMode.RDONLY_FILE), + swhid=swhid, + ) +@dataclass class MetaEntry(FuseEntry): """ An entry from the meta/ directory, containing for each accessed SWHID a corresponding SWHID.json file with all the metadata from the Software Heritage archive. """ - def __init__(self, swhid: SWHID, fuse: Fuse): - super().__init__( - name=str(swhid) + ".json", mode=int(EntryMode.RDONLY_FILE), fuse=fuse - ) - self.swhid = swhid + swhid: SWHID - async def content(self) -> bytes: + async def get_content(self) -> bytes: # Get raw JSON metadata from API (un-typified) metadata = await self.fuse.cache.metadata.get(self.swhid, typify=False) return json.dumps(metadata).encode() - async def length(self) -> int: - return len(await self.content()) + async def size(self) -> int: + return len(await self.get_content()) diff --git a/swh/fuse/fs/symlink.py b/swh/fuse/fs/symlink.py new file mode 100644 index 0000000..2151e9f --- /dev/null +++ b/swh/fuse/fs/symlink.py @@ -0,0 +1,28 @@ +# Copyright (C) 2020 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Union + +from swh.fuse.fs.entry import EntryMode, FuseEntry + + +@dataclass +class SymlinkEntry(FuseEntry): + """ FUSE virtual entry for symlinks + + Attributes: + target: path to symlink target + """ + + mode: int = field(init=False, default=int(EntryMode.SYMLINK)) + target: Union[str, bytes, Path] + + async def size(self) -> int: + return len(str(self.target)) + + def get_target(self) -> Union[str, bytes, Path]: + return self.target diff --git a/swh/fuse/fuse.py b/swh/fuse/fuse.py index 26b827a..086ffb5 100644 --- a/swh/fuse/fuse.py +++ b/swh/fuse/fuse.py @@ -1,219 +1,223 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import asyncio import errno import logging import os from pathlib import Path import time from typing import Any, Dict, List import pyfuse3 import pyfuse3_asyncio import requests from swh.fuse.cache import FuseCache from swh.fuse.fs.entry import FuseEntry from swh.fuse.fs.mountpoint import Root from swh.model.identifiers import CONTENT, SWHID from swh.web.client.client import WebAPIClient class Fuse(pyfuse3.Operations): """ Software Heritage Filesystem in Userspace (FUSE). Locally mount parts of the archive and navigate it as a virtual file system. """ def __init__( self, root_path: Path, cache: FuseCache, conf: Dict[str, Any], ): super(Fuse, self).__init__() self._next_inode: int = pyfuse3.ROOT_INODE self._inode2entry: Dict[int, FuseEntry] = {} self.root = Root(fuse=self) self.time_ns: int = time.time_ns() # start time, used as timestamp self.gid = os.getgid() self.uid = os.getuid() self.web_api = WebAPIClient( conf["web-api"]["url"], conf["web-api"]["auth-token"] ) self.cache = cache def shutdown(self) -> None: pass def _alloc_inode(self, entry: FuseEntry) -> int: """ Return a unique inode integer for a given entry """ inode = self._next_inode self._next_inode += 1 self._inode2entry[inode] = entry # TODO add inode recycling with invocation to invalidate_inode when # the dicts get too big return inode def inode2entry(self, inode: int) -> FuseEntry: """ Return the entry matching a given inode """ try: return self._inode2entry[inode] except KeyError: raise pyfuse3.FUSEError(errno.ENOENT) async def get_metadata(self, swhid: SWHID) -> Any: """ Retrieve metadata for a given SWHID using Software Heritage API """ cache = await self.cache.metadata.get(swhid) if cache: return cache try: # TODO: swh-graph API typify = False # Get the raw JSON from the API # TODO: async web API loop = asyncio.get_event_loop() metadata = await loop.run_in_executor(None, self.web_api.get, swhid, typify) await self.cache.metadata.set(swhid, metadata) # Retrieve it from cache so it is correctly typed return await self.cache.metadata.get(swhid) except requests.HTTPError: logging.error(f"Unknown SWHID: '{swhid}'") async def get_blob(self, swhid: SWHID) -> bytes: """ Retrieve the blob bytes for a given content SWHID using Software Heritage API """ if swhid.object_type != CONTENT: raise pyfuse3.FUSEError(errno.EINVAL) # Make sure the metadata cache is also populated with the given SWHID await self.get_metadata(swhid) cache = await self.cache.blob.get(swhid) if cache: return cache loop = asyncio.get_event_loop() resp = await loop.run_in_executor(None, self.web_api.content_raw, swhid) blob = b"".join(list(resp)) await self.cache.blob.set(swhid, blob) return blob async def get_attrs(self, entry: FuseEntry) -> pyfuse3.EntryAttributes: """ Return entry attributes """ attrs = pyfuse3.EntryAttributes() attrs.st_size = 0 attrs.st_atime_ns = self.time_ns attrs.st_ctime_ns = self.time_ns attrs.st_mtime_ns = self.time_ns attrs.st_gid = self.gid attrs.st_uid = self.uid attrs.st_ino = entry.inode attrs.st_mode = entry.mode - attrs.st_size = await entry.length() + attrs.st_size = await entry.size() return attrs async def getattr( self, inode: int, _ctx: pyfuse3.RequestContext ) -> pyfuse3.EntryAttributes: """ Get attributes for a given inode """ entry = self.inode2entry(inode) return await self.get_attrs(entry) async def opendir(self, inode: int, _ctx: pyfuse3.RequestContext) -> int: """ Open a directory referred by a given inode """ # Re-use inode as directory handle return inode async def readdir(self, fh: int, offset: int, token: pyfuse3.ReaddirToken) -> None: """ Read entries in an open directory """ # opendir() uses inode as directory handle inode = fh # TODO: add cache on direntry list? direntry = self.inode2entry(inode) next_id = offset + 1 i = 0 async for entry in direntry: if i < offset: i += 1 continue name = os.fsencode(entry.name) attrs = await self.get_attrs(entry) if not pyfuse3.readdir_reply(token, name, attrs, next_id): break next_id += 1 self._inode2entry[attrs.st_ino] = entry async def open( self, inode: int, _flags: int, _ctx: pyfuse3.RequestContext ) -> pyfuse3.FileInfo: """ Open an inode and return a unique file handle """ # Re-use inode as file handle return pyfuse3.FileInfo(fh=inode, keep_cache=True) async def read(self, fh: int, offset: int, length: int) -> bytes: """ Read `length` bytes from file handle `fh` at position `offset` """ # open() uses inode as file handle inode = fh entry = self.inode2entry(inode) - data = await entry.content() + data = await entry.get_content() return data[offset : offset + length] async def lookup( self, parent_inode: int, name: str, _ctx: pyfuse3.RequestContext ) -> pyfuse3.EntryAttributes: """ Look up a directory entry by name and get its attributes """ name = os.fsdecode(name) parent_entry = self.inode2entry(parent_inode) async for entry in parent_entry: if name == entry.name: attr = await self.get_attrs(entry) return attr logging.error(f"Unknown name during lookup: '{name}'") raise pyfuse3.FUSEError(errno.ENOENT) + async def readlink(self, inode: int, _ctx: pyfuse3.RequestContext) -> bytes: + entry = self.inode2entry(inode) + return os.fsencode(entry.get_target()) + async def main(swhids: List[SWHID], root_path: Path, conf: Dict[str, Any]) -> None: """ swh-fuse CLI entry-point """ # Use pyfuse3 asyncio layer to match the rest of Software Heritage codebase pyfuse3_asyncio.enable() async with FuseCache(conf["cache"]) as cache: fs = Fuse(root_path, cache, conf) # Initially populate the cache for swhid in swhids: await fs.get_metadata(swhid) fuse_options = set(pyfuse3.default_options) fuse_options.add("fsname=swhfs") fuse_options.add("debug") pyfuse3.init(fs, root_path, fuse_options) try: await pyfuse3.main() finally: fs.shutdown() pyfuse3.close(unmount=True) diff --git a/swh/fuse/tests/api_data.py b/swh/fuse/tests/api_data.py index 9d1cb97..2285826 100644 --- a/swh/fuse/tests/api_data.py +++ b/swh/fuse/tests/api_data.py @@ -1,445 +1,523 @@ # GENERATED FILE, DO NOT EDIT. # Run './gen-api-data.py > api_data.py' instead. # fmt: off API_URL = 'https://invalid-test-only.archive.softwareheritage.org/api/1' -ROOT_SWHID = 'swh:1:dir:9eb62ef7dd283f7385e7d31af6344d9feedd25de' -ROOT_URL = 'directory/9eb62ef7dd283f7385e7d31af6344d9feedd25de/' +ROOTREV_SWHID = 'swh:1:rev:d012a7190fc1fd72ed48911e77ca97ba4521bccd' +ROOTDIR_SWHID = 'swh:1:dir:9eb62ef7dd283f7385e7d31af6344d9feedd25de' +ROOTREV_URL = 'revision/d012a7190fc1fd72ed48911e77ca97ba4521bccd/' +ROOTREV_PARENT_URL = 'revision/cb95712138ec5e480db5160b41172bbc6f6494cc/' +ROOTDIR_URL = 'directory/9eb62ef7dd283f7385e7d31af6344d9feedd25de/' README_URL = 'content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/' README_RAW_URL = 'content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/raw/' MOCK_ARCHIVE = { + 'revision/d012a7190fc1fd72ed48911e77ca97ba4521bccd/': # NoQA: E501 + r"""{ + "author": { + "email": "torvalds@linux-foundation.org", + "fullname": "Linus Torvalds ", + "name": "Linus Torvalds" + }, + "committer": { + "email": "torvalds@linux-foundation.org", + "fullname": "Linus Torvalds ", + "name": "Linus Torvalds" + }, + "committer_date": "2020-08-23T14:08:43-07:00", + "date": "2020-08-23T14:08:43-07:00", + "directory": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", + "directory_url": "https://archive.softwareheritage.org/api/1/directory/9eb62ef7dd283f7385e7d31af6344d9feedd25de/", + "extra_headers": [], + "history_url": "https://archive.softwareheritage.org/api/1/revision/d012a7190fc1fd72ed48911e77ca97ba4521bccd/log/", + "id": "d012a7190fc1fd72ed48911e77ca97ba4521bccd", + "merge": false, + "message": "Linux 5.9-rc2\n", + "metadata": {}, + "parents": [ + { + "id": "cb95712138ec5e480db5160b41172bbc6f6494cc", + "url": "https://archive.softwareheritage.org/api/1/revision/cb95712138ec5e480db5160b41172bbc6f6494cc/" + } + ], + "synthetic": false, + "type": "git", + "url": "https://archive.softwareheritage.org/api/1/revision/d012a7190fc1fd72ed48911e77ca97ba4521bccd/" +} +""", # NoQA: E501 + 'revision/cb95712138ec5e480db5160b41172bbc6f6494cc/': # NoQA: E501 + r"""{ + "author": { + "email": "torvalds@linux-foundation.org", + "fullname": "Linus Torvalds ", + "name": "Linus Torvalds" + }, + "committer": { + "email": "torvalds@linux-foundation.org", + "fullname": "Linus Torvalds ", + "name": "Linus Torvalds" + }, + "committer_date": "2020-08-23T11:37:23-07:00", + "date": "2020-08-23T11:37:23-07:00", + "directory": "4fa3b43d90ce69b46916cc3fd3ea1d15de70443d", + "directory_url": "https://archive.softwareheritage.org/api/1/directory/4fa3b43d90ce69b46916cc3fd3ea1d15de70443d/", + "extra_headers": [ + [ + "mergetag", + "object 64ef8f2c4791940d7f3945507b6a45c20d959260\ntype commit\ntag powerpc-5.9-3\ntagger Michael Ellerman 1598185676 +1000\n\npowerpc fixes for 5.9 #3\n\nAdd perf support for emitting extended registers for power10.\n\nA fix for CPU hotplug on pseries, where on large/loaded systems we may not wait\nlong enough for the CPU to be offlined, leading to crashes.\n\nAddition of a raw cputable entry for Power10, which is not required to boot, but\nis required to make our PMU setup work correctly in guests.\n\nThree fixes for the recent changes on 32-bit Book3S to move modules into their\nown segment for strict RWX.\n\nA fix for a recent change in our powernv PCI code that could lead to crashes.\n\nA change to our perf interrupt accounting to avoid soft lockups when using some\nevents, found by syzkaller.\n\nA change in the way we handle power loss events from the hypervisor on pseries.\nWe no longer immediately shut down if we're told we're running on a UPS.\n\nA few other minor fixes.\n\nThanks to:\n Alexey Kardashevskiy, Andreas Schwab, Aneesh Kumar K.V, Anju T Sudhakar,\n Athira Rajeev, Christophe Leroy, Frederic Barrat, Greg Kurz, Kajol Jain,\n Madhavan Srinivasan, Michael Neuling, Michael Roth, Nageswara R Sastry, Oliver\n O'Halloran, Thiago Jung Bauermann, Vaidyanathan Srinivasan, Vasant Hegde.\n-----BEGIN PGP SIGNATURE-----\n\niQJHBAABCAAxFiEEJFGtCPCthwEv2Y/bUevqPMjhpYAFAl9CYMwTHG1wZUBlbGxl\ncm1hbi5pZC5hdQAKCRBR6+o8yOGlgC/wEACljEVnfHzUObmIgqn9Ru3JlfEI6Hlk\nts7kajCgS/I/bV6DoDMZ8rlZX87QFOwiBkNM1I+vGHSLAuzsmFAnbFPyxw/idxpQ\nXUoNy8OCvbbzCPzChYdiU0PxW2h2i+QxkmktlWSN1SAPudJUWvoPS2Y4+sC4zksk\nB4B6tbW2DT8TFO1kKeZsU9r2t+EH5KwlIOi+uxbH8d76lJINKkBNSnjzMytl7drM\nTZx/HWr8+s/WJo1787x6bv8gxs5tV9b4vIKt2YZNTY2kvYsEDE+fBR1XfCAneXMw\nASYnZV+/xCLIUpRF6DI4RAShLBT/Sfiy1yMTndZgfqAgquokFosszNx2zrk0IzCd\nAgqX93YGbGz/H72W3Y/B0W9+74XyO/u2D9zhNpkCRMpdcsM5MbvOQrQA5Ustu47E\nav5MOaF/nNCd8J+OC4Qjgt5VFb/s0h4FdtrwT80srOa2U6Of9cD/T6xAfOszSJ96\ncWdSb5qhn5wuD9pP32KjwdmWBiUw38/gnRGKpRlOVzyHL/GKZijyaBbWBlkoEmty\n0nbjWW/IVfsOb5Weuiybg541h/QOVuOkb2pOvPClITiH83MY/AciDJ+auo4M//hW\nhaKz9IgV/KctmzDE+v9d0BD8sGmW03YUcQAPdRufI0eGXijDLcnHeuk2B3Nu84Pq\n8mtev+VQ+T6cZA==\n=sdJ1\n-----END PGP SIGNATURE-----" + ] + ], + "history_url": "https://archive.softwareheritage.org/api/1/revision/cb95712138ec5e480db5160b41172bbc6f6494cc/log/", + "id": "cb95712138ec5e480db5160b41172bbc6f6494cc", + "merge": true, + "message": "Merge tag 'powerpc-5.9-3' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux\n\nPull powerpc fixes from Michael Ellerman:\n\n - Add perf support for emitting extended registers for power10.\n\n - A fix for CPU hotplug on pseries, where on large/loaded systems we\n may not wait long enough for the CPU to be offlined, leading to\n crashes.\n\n - Addition of a raw cputable entry for Power10, which is not required\n to boot, but is required to make our PMU setup work correctly in\n guests.\n\n - Three fixes for the recent changes on 32-bit Book3S to move modules\n into their own segment for strict RWX.\n\n - A fix for a recent change in our powernv PCI code that could lead to\n crashes.\n\n - A change to our perf interrupt accounting to avoid soft lockups when\n using some events, found by syzkaller.\n\n - A change in the way we handle power loss events from the hypervisor\n on pseries. We no longer immediately shut down if we're told we're\n running on a UPS.\n\n - A few other minor fixes.\n\nThanks to Alexey Kardashevskiy, Andreas Schwab, Aneesh Kumar K.V, Anju T\nSudhakar, Athira Rajeev, Christophe Leroy, Frederic Barrat, Greg Kurz,\nKajol Jain, Madhavan Srinivasan, Michael Neuling, Michael Roth,\nNageswara R Sastry, Oliver O'Halloran, Thiago Jung Bauermann,\nVaidyanathan Srinivasan, Vasant Hegde.\n\n* tag 'powerpc-5.9-3' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux:\n powerpc/perf/hv-24x7: Move cpumask file to top folder of hv-24x7 driver\n powerpc/32s: Fix module loading failure when VMALLOC_END is over 0xf0000000\n powerpc/pseries: Do not initiate shutdown when system is running on UPS\n powerpc/perf: Fix soft lockups due to missed interrupt accounting\n powerpc/powernv/pci: Fix possible crash when releasing DMA resources\n powerpc/pseries/hotplug-cpu: wait indefinitely for vCPU death\n powerpc/32s: Fix is_module_segment() when MODULES_VADDR is defined\n powerpc/kasan: Fix KASAN_SHADOW_START on BOOK3S_32\n powerpc/fixmap: Fix the size of the early debug area\n powerpc/pkeys: Fix build error with PPC_MEM_KEYS disabled\n powerpc/kernel: Cleanup machine check function declarations\n powerpc: Add POWER10 raw mode cputable entry\n powerpc/perf: Add extended regs support for power10 platform\n powerpc/perf: Add support for outputting extended regs in perf intr_regs\n powerpc: Fix P10 PVR revision in /proc/cpuinfo for SMT4 cores\n", + "metadata": {}, + "parents": [ + { + "id": "550c2129d93d5eb198835ac83c05ef672e8c491c", + "url": "https://archive.softwareheritage.org/api/1/revision/550c2129d93d5eb198835ac83c05ef672e8c491c/" + }, + { + "id": "64ef8f2c4791940d7f3945507b6a45c20d959260", + "url": "https://archive.softwareheritage.org/api/1/revision/64ef8f2c4791940d7f3945507b6a45c20d959260/" + } + ], + "synthetic": false, + "type": "git", + "url": "https://archive.softwareheritage.org/api/1/revision/cb95712138ec5e480db5160b41172bbc6f6494cc/" +} +""", # NoQA: E501 'directory/9eb62ef7dd283f7385e7d31af6344d9feedd25de/': # NoQA: E501 r"""[ { "checksums": { "sha1": "39a0a88cd8eae4504e1d33b0c0f88059044d761f", "sha1_git": "a0a96088c74f49a961a80bc0851a84214b0a9f83", "sha256": "7c0f4eaf45838f26ae951b490beb0d11034a30b21e5c39a54c4223f5c2018890" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 16166, "name": ".clang-format", "perms": 33188, "status": "visible", "target": "a0a96088c74f49a961a80bc0851a84214b0a9f83", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:a0a96088c74f49a961a80bc0851a84214b0a9f83/", "type": "file" }, { "checksums": { "sha1": "0e31de4130c64f23e9ee5fc761fdbd807dc94360", "sha1_git": "43967c6b20151ee126db08e24758e3c789bcb844", "sha256": "dbd64d3f532b962d4681d79077cc186340f5f439de7f99c709b01892332af866" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 59, "name": ".cocciconfig", "perms": 33188, "status": "visible", "target": "43967c6b20151ee126db08e24758e3c789bcb844", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:43967c6b20151ee126db08e24758e3c789bcb844/", "type": "file" }, { "checksums": { "sha1": "52b62d115dfae2ed19561db14e8d10ee22659e7f", "sha1_git": "a64d219137455f407a7b1f2c6b156c5575852e9e", "sha256": "4c9ba8e0ef521ce01474e98eddfc77afaec8a8e259939a139590c00505646527" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 71, "name": ".get_maintainer.ignore", "perms": 33188, "status": "visible", "target": "a64d219137455f407a7b1f2c6b156c5575852e9e", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:a64d219137455f407a7b1f2c6b156c5575852e9e/", "type": "file" }, { "checksums": { "sha1": "6cc5a38e6f6ca93e21c78cb9c54794a42c3031c3", "sha1_git": "4b32eaa9571e64e47b51c43537063f56b204d8b3", "sha256": "dc52a4e1ee3615c87691aca7f667c7e49f6900f36b5c20339ac497366ba9406c" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 62, "name": ".gitattributes", "perms": 33188, "status": "visible", "target": "4b32eaa9571e64e47b51c43537063f56b204d8b3", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:4b32eaa9571e64e47b51c43537063f56b204d8b3/", "type": "file" }, { "checksums": { "sha1": "1966a794db7d9518f321a8ecb0736c16de59bd91", "sha1_git": "162bd2b67bdf6a28be7a361b8418e4e31d542854", "sha256": "a9766c936a81df2ed3ea41f506ddaad88a78d9cf47e093df414b3b6f2e6d8e14" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 1852, "name": ".gitignore", "perms": 33188, "status": "visible", "target": "162bd2b67bdf6a28be7a361b8418e4e31d542854", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:162bd2b67bdf6a28be7a361b8418e4e31d542854/", "type": "file" }, { "checksums": { "sha1": "2bb15bd51c981842b6f710d2e057972e5f22cfcc", "sha1_git": "332c7833057f51da02805add9b60161ff31aee71", "sha256": "d7b69571529964b3c8444a73ac720bbb883cc70fc4b78a36d1ac277f660c50bb" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 17283, "name": ".mailmap", "perms": 33188, "status": "visible", "target": "332c7833057f51da02805add9b60161ff31aee71", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:332c7833057f51da02805add9b60161ff31aee71/", "type": "file" }, { "checksums": { "sha1": "0473e748fee37c7b68487fb102c0d563bbc641b3", "sha1_git": "a635a38ef9405fdfcfe97f3a435393c1e9cae971", "sha256": "fb5a425bd3b3cd6071a3a9aff9909a859e7c1158d54d32e07658398cd67eb6a0" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 496, "name": "COPYING", "perms": 33188, "status": "visible", "target": "a635a38ef9405fdfcfe97f3a435393c1e9cae971", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:a635a38ef9405fdfcfe97f3a435393c1e9cae971/", "type": "file" }, { "checksums": { "sha1": "f27043aefa4b69b921df3728ee906c3f03087d29", "sha1_git": "32ee70a7562eec7345e98841473abb438379a4fd", "sha256": "23242c7183ee2815e27fea2346b0e5ad9131b8b611b200ae0418d4027cea2a3d" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 99788, "name": "CREDITS", "perms": 33188, "status": "visible", "target": "32ee70a7562eec7345e98841473abb438379a4fd", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:32ee70a7562eec7345e98841473abb438379a4fd/", "type": "file" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "Documentation", "perms": 16384, "target": "1ba46735273aa020a173c0ad0c813179530dd117", "target_url": "https://archive.softwareheritage.org/api/1/directory/1ba46735273aa020a173c0ad0c813179530dd117/", "type": "dir" }, { "checksums": { "sha1": "2491dd3bed10f6918ed1657ab5a7a8efbddadf5d", "sha1_git": "fa441b98c9f6eac1617acf1772ae8b371cfd42aa", "sha256": "75df66064f75e91e6458862cd9413b19e65b77eefcc8a95dcbd6bf36fd2e4b59" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 1327, "name": "Kbuild", "perms": 33188, "status": "visible", "target": "fa441b98c9f6eac1617acf1772ae8b371cfd42aa", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:fa441b98c9f6eac1617acf1772ae8b371cfd42aa/", "type": "file" }, { "checksums": { "sha1": "6b9b12a6bbff219dfbb45ec068af9aa7cf1b7288", "sha1_git": "745bc773f567067a85ce6574fb41ce80833247d9", "sha256": "a592dae7d067cd8e5dc43e3f9dc363eba9eb1f7cf80c6178b5cd291c0b76d3ec" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 555, "name": "Kconfig", "perms": 33188, "status": "visible", "target": "745bc773f567067a85ce6574fb41ce80833247d9", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:745bc773f567067a85ce6574fb41ce80833247d9/", "type": "file" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "LICENSES", "perms": 16384, "target": "a49a894ea3684b6c044448c37f812356550d14a2", "target_url": "https://archive.softwareheritage.org/api/1/directory/a49a894ea3684b6c044448c37f812356550d14a2/", "type": "dir" }, { "checksums": { "sha1": "eb207b62f2fe0225dc55d9b87d82f6009e864117", "sha1_git": "f0068bceeb6158a30c6eee430ca6d2a7e4c4013a", "sha256": "3c81b34eaf99d943e4c2fac2548f3d9d740a9e9683ccedbaacfb82796c7965e1" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 569101, "name": "MAINTAINERS", "perms": 33188, "status": "visible", "target": "f0068bceeb6158a30c6eee430ca6d2a7e4c4013a", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:f0068bceeb6158a30c6eee430ca6d2a7e4c4013a/", "type": "file" }, { "checksums": { "sha1": "9bfa205a4d23b9a60889bd9b59010574670b8b90", "sha1_git": "f2116815416091dbfa7dcf58ae179ae3241ec1b1", "sha256": "e87fb2b9482b9066b47c1656e55ebc4897bbb226daa122bcd4a2858fef19e597" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 63305, "name": "Makefile", "perms": 33188, "status": "visible", "target": "f2116815416091dbfa7dcf58ae179ae3241ec1b1", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:f2116815416091dbfa7dcf58ae179ae3241ec1b1/", "type": "file" }, { "checksums": { "sha1": "ca1dc365022dcaa728dfb11bcde40ad3cce0574b", "sha1_git": "669ac7c32292798644b21dbb5a0dc657125f444d", "sha256": "bad58d396f62102befaf23a8a2ab6b1693fdc8f318de3059b489781f28865612" }, "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": 727, "name": "README", "perms": 33188, "status": "visible", "target": "669ac7c32292798644b21dbb5a0dc657125f444d", "target_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/", "type": "file" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "arch", "perms": 16384, "target": "cf12c1ce4de958ab4ddcb008fe89118b82a3c7b7", "target_url": "https://archive.softwareheritage.org/api/1/directory/cf12c1ce4de958ab4ddcb008fe89118b82a3c7b7/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "block", "perms": 16384, "target": "a77c89fa64b8ec37c9aa0fa98add54bfb6075257", "target_url": "https://archive.softwareheritage.org/api/1/directory/a77c89fa64b8ec37c9aa0fa98add54bfb6075257/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "certs", "perms": 16384, "target": "527d8f94235029c6f571414df5f8ed2951a0ca5b", "target_url": "https://archive.softwareheritage.org/api/1/directory/527d8f94235029c6f571414df5f8ed2951a0ca5b/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "crypto", "perms": 16384, "target": "1fb1357e2d22af4332091937ed960a47f78d0b5e", "target_url": "https://archive.softwareheritage.org/api/1/directory/1fb1357e2d22af4332091937ed960a47f78d0b5e/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "drivers", "perms": 16384, "target": "3b5be1ee0216ec59c70e132681be4a5d79e7da9b", "target_url": "https://archive.softwareheritage.org/api/1/directory/3b5be1ee0216ec59c70e132681be4a5d79e7da9b/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "fs", "perms": 16384, "target": "1dbf8d211613db72f5b83b0987023bd5acf866ee", "target_url": "https://archive.softwareheritage.org/api/1/directory/1dbf8d211613db72f5b83b0987023bd5acf866ee/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "include", "perms": 16384, "target": "74991fd1a983c6b3f72c8815f7de81a3abddb255", "target_url": "https://archive.softwareheritage.org/api/1/directory/74991fd1a983c6b3f72c8815f7de81a3abddb255/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "init", "perms": 16384, "target": "c944a589113271d878e27bbc31ae369edecaff90", "target_url": "https://archive.softwareheritage.org/api/1/directory/c944a589113271d878e27bbc31ae369edecaff90/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "ipc", "perms": 16384, "target": "ff553b9398fea6b2e290ea4a95f7a94f1cf3c22c", "target_url": "https://archive.softwareheritage.org/api/1/directory/ff553b9398fea6b2e290ea4a95f7a94f1cf3c22c/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "kernel", "perms": 16384, "target": "8c700fd3589e6d2befa4d9b2cc79471eac37da38", "target_url": "https://archive.softwareheritage.org/api/1/directory/8c700fd3589e6d2befa4d9b2cc79471eac37da38/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "lib", "perms": 16384, "target": "0f2936da43bebe4f26b3be83e8fa392c4f9e82cf", "target_url": "https://archive.softwareheritage.org/api/1/directory/0f2936da43bebe4f26b3be83e8fa392c4f9e82cf/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "mm", "perms": 16384, "target": "e15d954c1ed09e6fc29c184515834696d8e70e7c", "target_url": "https://archive.softwareheritage.org/api/1/directory/e15d954c1ed09e6fc29c184515834696d8e70e7c/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "net", "perms": 16384, "target": "41e1603b37542d265eade0555e0db66668135575", "target_url": "https://archive.softwareheritage.org/api/1/directory/41e1603b37542d265eade0555e0db66668135575/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "samples", "perms": 16384, "target": "9fa649fea3c8ab6b4926f0e7721a21a36b685153", "target_url": "https://archive.softwareheritage.org/api/1/directory/9fa649fea3c8ab6b4926f0e7721a21a36b685153/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "scripts", "perms": 16384, "target": "e4e5b45d7c44d0bd2c6feb1a257fff7303d2c67e", "target_url": "https://archive.softwareheritage.org/api/1/directory/e4e5b45d7c44d0bd2c6feb1a257fff7303d2c67e/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "security", "perms": 16384, "target": "a4a58d89fc506c3660610105a08de60614cdc980", "target_url": "https://archive.softwareheritage.org/api/1/directory/a4a58d89fc506c3660610105a08de60614cdc980/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "sound", "perms": 16384, "target": "bf9e1568b8ce61157a322fddbaab1a0c76be15ef", "target_url": "https://archive.softwareheritage.org/api/1/directory/bf9e1568b8ce61157a322fddbaab1a0c76be15ef/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "tools", "perms": 16384, "target": "83d6279411023bf7edf6bde6ce2e3748912f4936", "target_url": "https://archive.softwareheritage.org/api/1/directory/83d6279411023bf7edf6bde6ce2e3748912f4936/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "usr", "perms": 16384, "target": "aae2ca939e0f7ac6b5e489e4c7835e1a15588cff", "target_url": "https://archive.softwareheritage.org/api/1/directory/aae2ca939e0f7ac6b5e489e4c7835e1a15588cff/", "type": "dir" }, { "dir_id": "9eb62ef7dd283f7385e7d31af6344d9feedd25de", "length": null, "name": "virt", "perms": 16384, "target": "d7f6f10a8509839e404d1cc5af51317ac8b26276", "target_url": "https://archive.softwareheritage.org/api/1/directory/d7f6f10a8509839e404d1cc5af51317ac8b26276/", "type": "dir" } ] """, # NoQA: E501 'content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/': # NoQA: E501 r"""{ "checksums": { "blake2s256": "746aaa0816ffc8cadf5e7f70b8bb93a47a76299ef263c743dbfef2644c6a0245", "sha1": "ca1dc365022dcaa728dfb11bcde40ad3cce0574b", "sha1_git": "669ac7c32292798644b21dbb5a0dc657125f444d", "sha256": "bad58d396f62102befaf23a8a2ab6b1693fdc8f318de3059b489781f28865612" }, "data_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/raw/", "filetype_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/filetype/", "language_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/language/", "length": 727, "license_url": "https://archive.softwareheritage.org/api/1/content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/license/", "status": "visible" } """, # NoQA: E501 'content/sha1_git:669ac7c32292798644b21dbb5a0dc657125f444d/raw/': # NoQA: E501 r"""Linux kernel ============ There are several guides for kernel developers and users. These guides can be rendered in a number of formats, like HTML and PDF. Please read Documentation/admin-guide/README.rst first. In order to build the documentation, use ``make htmldocs`` or ``make pdfdocs``. The formatted documentation can also be read online at: https://www.kernel.org/doc/html/latest/ There are various text files in the Documentation/ subdirectory, several of them using the Restructured Text markup notation. Please read the Documentation/process/changes.rst file, as it contains the requirements for building and running the kernel, and information about the problems which may result by upgrading your kernel. """, # NoQA: E501 } diff --git a/swh/fuse/tests/conftest.py b/swh/fuse/tests/conftest.py index 03554a8..d81c256 100644 --- a/swh/fuse/tests/conftest.py +++ b/swh/fuse/tests/conftest.py @@ -1,67 +1,74 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from multiprocessing import Process from os import listdir from pathlib import Path import subprocess from tempfile import NamedTemporaryFile, TemporaryDirectory import time from click.testing import CliRunner import pytest import yaml from swh.fuse import cli -from .api_data import API_URL, MOCK_ARCHIVE, ROOT_SWHID +from .api_data import API_URL, MOCK_ARCHIVE, ROOTDIR_SWHID, ROOTREV_SWHID @pytest.fixture def web_api_mock(requests_mock): for api_call, data in MOCK_ARCHIVE.items(): requests_mock.get(f"{API_URL}/{api_call}", text=data) return requests_mock @pytest.fixture def fuse_mntdir(web_api_mock): tmpdir = TemporaryDirectory(suffix=".swh-fuse-test") tmpfile = NamedTemporaryFile(suffix=".swh-fuse-test.yml") config = { "cache": {"metadata": {"in-memory": True}, "blob": {"in-memory": True}}, "web-api": {"url": API_URL, "auth-token": None}, } # Run FUSE in foreground mode but in a separate process, so it does not # block execution and remains easy to kill during teardown def fuse_process(tmpdir, tmpfile): with tmpdir as mntdir, tmpfile as config_path: config_path = Path(config_path.name) config_path.write_text(yaml.dump(config)) CliRunner().invoke( cli.mount, - args=[mntdir, ROOT_SWHID, "--foreground", "--config-file", config_path], + args=[ + mntdir, + ROOTDIR_SWHID, + ROOTREV_SWHID, + "--foreground", + "--config-file", + config_path, + ], ) fuse = Process(target=fuse_process, args=[tmpdir, tmpfile]) fuse.start() # Wait max 3 seconds for the FUSE to correctly mount for i in range(30): try: root = listdir(tmpdir.name) if root: break except FileNotFoundError: pass time.sleep(0.1) else: raise FileNotFoundError(f"Could not mount FUSE in {tmpdir.name}") yield tmpdir.name subprocess.run(["fusermount", "-u", tmpdir.name], check=True) fuse.join() diff --git a/swh/fuse/tests/gen-api-data.py b/swh/fuse/tests/gen-api-data.py index 08a7ced..5906648 100755 --- a/swh/fuse/tests/gen-api-data.py +++ b/swh/fuse/tests/gen-api-data.py @@ -1,51 +1,57 @@ #!/usr/bin/env python3 # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import json import requests API_URL_real = "https://archive.softwareheritage.org/api/1" API_URL_test = "https://invalid-test-only.archive.softwareheritage.org/api/1" # Use the Linux kernel as a testing repository -ROOT_HASH = "9eb62ef7dd283f7385e7d31af6344d9feedd25de" +ROOTREV_HASH = "d012a7190fc1fd72ed48911e77ca97ba4521bccd" +ROOTREV_PARENT_HASH = "cb95712138ec5e480db5160b41172bbc6f6494cc" +ROOTDIR_HASH = "9eb62ef7dd283f7385e7d31af6344d9feedd25de" README_HASH = "669ac7c32292798644b21dbb5a0dc657125f444d" -ROOT_SWHID = f"swh:1:dir:{ROOT_HASH}" +ROOTREV_SWHID = f"swh:1:rev:{ROOTREV_HASH}" +ROOTDIR_SWHID = f"swh:1:dir:{ROOTDIR_HASH}" urls = { - "ROOT": f"directory/{ROOT_HASH}/", + "ROOTREV": f"revision/{ROOTREV_HASH}/", + "ROOTREV_PARENT": f"revision/{ROOTREV_PARENT_HASH}/", + "ROOTDIR": f"directory/{ROOTDIR_HASH}/", "README": f"content/sha1_git:{README_HASH}/", "README_RAW": f"content/sha1_git:{README_HASH}/raw/", } print("# GENERATED FILE, DO NOT EDIT.") print("# Run './gen-api-data.py > api_data.py' instead.") print("# fmt: off") print("") print(f"API_URL = '{API_URL_test}'") -print(f"ROOT_SWHID = '{ROOT_SWHID}'") +print(f"ROOTREV_SWHID = '{ROOTREV_SWHID}'") +print(f"ROOTDIR_SWHID = '{ROOTDIR_SWHID}'") for name, url in urls.items(): print(f"{name}_URL = '{url}'") print("") print("MOCK_ARCHIVE = {") for url in urls.values(): print(f" '{url}': # NoQA: E501") print(' r"""', end="") data = requests.get(f"{API_URL_real}/{url}").text if url.endswith("/raw/"): print(data, end="") else: parsed = json.loads(data) print(json.dumps(parsed, indent=2, sort_keys=True)) print('""", # NoQA: E501') print("}") diff --git a/swh/fuse/tests/test_cli.py b/swh/fuse/tests/test_cli.py index 59290e3..b821c28 100644 --- a/swh/fuse/tests/test_cli.py +++ b/swh/fuse/tests/test_cli.py @@ -1,17 +1,17 @@ # Copyright (C) 2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from pathlib import Path -from .api_data import ROOT_SWHID +from .api_data import ROOTDIR_SWHID def test_mountpoint(fuse_mntdir): archive_dir = Path(fuse_mntdir, "archive") meta_dir = Path(fuse_mntdir, "meta") - swhid_dir = Path(fuse_mntdir, "archive", ROOT_SWHID) + swhid_dir = Path(fuse_mntdir, "archive", ROOTDIR_SWHID) assert archive_dir.is_dir() assert meta_dir.is_dir() assert swhid_dir.is_dir() diff --git a/swh/fuse/tests/test_content.py b/swh/fuse/tests/test_content.py index 8b23dc5..ccf33e5 100644 --- a/swh/fuse/tests/test_content.py +++ b/swh/fuse/tests/test_content.py @@ -1,16 +1,16 @@ from pathlib import Path -from .api_data import MOCK_ARCHIVE, README_RAW_URL, ROOT_SWHID +from .api_data import MOCK_ARCHIVE, README_RAW_URL, ROOTDIR_SWHID def test_file_exists(fuse_mntdir): - readme_path = Path(fuse_mntdir, "archive", ROOT_SWHID, "README") + readme_path = Path(fuse_mntdir, "archive", ROOTDIR_SWHID, "README") assert readme_path.is_file() def test_cat_file(fuse_mntdir): - readme_path = Path(fuse_mntdir, "archive", ROOT_SWHID, "README") + readme_path = Path(fuse_mntdir, "archive", ROOTDIR_SWHID, "README") expected = MOCK_ARCHIVE[README_RAW_URL] with open(readme_path, "r") as f: actual = f.read() assert actual == expected diff --git a/swh/fuse/tests/test_directory.py b/swh/fuse/tests/test_directory.py index 1eabb95..e3cf5af 100644 --- a/swh/fuse/tests/test_directory.py +++ b/swh/fuse/tests/test_directory.py @@ -1,14 +1,18 @@ import json from os import listdir from pathlib import Path -from .api_data import MOCK_ARCHIVE, ROOT_SWHID, ROOT_URL +from .api_data import MOCK_ARCHIVE, ROOTDIR_SWHID, ROOTDIR_URL -def test_ls_root_swhid(fuse_mntdir): - root_resp = json.loads(MOCK_ARCHIVE[ROOT_URL]) - expected = [entry["name"] for entry in root_resp] +def get_rootdir_entries(): + rootdir_resp = json.loads(MOCK_ARCHIVE[ROOTDIR_URL]) + return [entry["name"] for entry in rootdir_resp] - swhid_dir = Path(fuse_mntdir, "archive", ROOT_SWHID) - actual = listdir(swhid_dir) + +def test_ls_rootdir(fuse_mntdir): + expected = get_rootdir_entries() + + rootdir_path = Path(fuse_mntdir, "archive", ROOTDIR_SWHID) + actual = listdir(rootdir_path) assert actual == expected diff --git a/swh/fuse/tests/test_revision.py b/swh/fuse/tests/test_revision.py new file mode 100644 index 0000000..b9eb428 --- /dev/null +++ b/swh/fuse/tests/test_revision.py @@ -0,0 +1,22 @@ +from os import listdir +from pathlib import Path + +from swh.fuse.tests.test_directory import get_rootdir_entries + +from .api_data import ROOTREV_SWHID + + +def test_symlinks_exist(fuse_mntdir): + rootrev_dir = Path(fuse_mntdir, "archive", ROOTREV_SWHID) + assert Path(rootrev_dir, "root").is_symlink() + assert Path(rootrev_dir, "parent").is_symlink() + assert Path(rootrev_dir, "parents").is_dir() + assert Path(rootrev_dir, "meta.json").is_symlink() + + +def test_ls_rootdir(fuse_mntdir): + expected = get_rootdir_entries() + + rootdir_path = Path(fuse_mntdir, "archive", ROOTREV_SWHID, "root") + actual = listdir(rootdir_path) + assert actual == expected