projectaria-debug-sharing 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,208 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Reading plain files without following links, for collectors whose paths a user
17
+ or another process could swap underneath them.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import errno
23
+ import fnmatch
24
+ import logging
25
+ import os
26
+ import time
27
+ from pathlib import Path
28
+ from stat import S_ISDIR, S_ISLNK, S_ISREG
29
+ from typing import BinaryIO, Final
30
+
31
+ logger: logging.Logger = logging.getLogger(__name__)
32
+
33
+ ZIP_EARLIEST: Final[tuple[int, ...]] = (1980, 1, 1, 0, 0, 0)
34
+ ZIP_LATEST: Final[tuple[int, ...]] = (2107, 12, 31, 23, 59, 58)
35
+
36
+
37
+ def open_plain_file(path: Path) -> BinaryIO:
38
+ """
39
+ Opens `path` only if it is a plain file itself, not a link to one, and is
40
+ still the file that was opened.
41
+
42
+ Returns:
43
+ The open handle, which the caller closes.
44
+
45
+ Raises:
46
+ IsADirectoryError: `path` is a directory.
47
+ OSError: `path` is a link, FIFO or other special file, or was swapped
48
+ for another file while being opened.
49
+ """
50
+ handle = os.fdopen(os.open(path, _read_flags()), "rb")
51
+ try:
52
+ opened = os.fstat(handle.fileno())
53
+ if S_ISDIR(opened.st_mode):
54
+ raise IsADirectoryError(errno.EISDIR, "Is a directory", str(path))
55
+ if not _is_same_plain_file(opened, path.lstat()):
56
+ raise OSError(errno.EINVAL, "Refusing anything but a plain file", str(path))
57
+ except BaseException:
58
+ handle.close()
59
+ raise
60
+ return handle
61
+
62
+
63
+ def open_directory(path: Path) -> int:
64
+ """
65
+ A handle on the directory itself, so entries are opened relative to it
66
+ and a root swapped afterwards is not followed.
67
+
68
+ Returns:
69
+ The directory's file descriptor, which the caller closes.
70
+
71
+ Raises:
72
+ OSError: `path` is a symlink, or the platform has no directory handles
73
+ (Windows), where there is no race-free way to read it.
74
+ """
75
+ if os.open not in os.supports_dir_fd or not hasattr(os, "O_DIRECTORY"):
76
+ raise OSError(
77
+ errno.ENOTSUP, "No race-free directory handle on this platform", str(path)
78
+ )
79
+ try:
80
+ return os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
81
+ except NotADirectoryError:
82
+ if path.is_symlink():
83
+ raise OSError(errno.ELOOP, "Refusing a symlinked directory", str(path))
84
+ raise
85
+
86
+
87
+ def list_own_plain_files(dir_fd: int, pattern: str = "*") -> list[str]:
88
+ """
89
+ Lists the plain files directly in the directory that match `pattern` and
90
+ belong to this user. Other users' files in a shared directory are not ours
91
+ to upload. An entry that cannot be examined is skipped with a warning.
92
+
93
+ Args:
94
+ dir_fd: The directory, as from `open_directory`.
95
+ pattern: A glob the file names must match.
96
+
97
+ Returns:
98
+ The file names, newest first.
99
+
100
+ Raises:
101
+ OSError: The directory cannot be listed.
102
+ """
103
+ entries = []
104
+ with os.scandir(dir_fd) as found:
105
+ for entry in found:
106
+ try:
107
+ info = entry.stat(follow_symlinks=False)
108
+ except OSError as e:
109
+ logger.warning("Skipping %s: %s", entry.name, e.strerror or e)
110
+ continue
111
+ if (
112
+ S_ISREG(info.st_mode)
113
+ and _belongs_to_user(info)
114
+ and fnmatch.fnmatchcase(entry.name, pattern)
115
+ ):
116
+ entries.append((info.st_mtime, entry.name))
117
+ return [name for _, name in sorted(entries, reverse=True)]
118
+
119
+
120
+ def open_plain_file_at(
121
+ name: str, dir_fd: int
122
+ ) -> tuple[BinaryIO, os.stat_result] | None:
123
+ """
124
+ Opens a directory entry listed by `list_own_plain_files`, checking the
125
+ opened file is still that entry and still this user's.
126
+
127
+ Args:
128
+ name: The entry's name.
129
+ dir_fd: The directory, as from `open_directory`.
130
+
131
+ Returns:
132
+ The open handle, which the caller closes, and its stat; or None if
133
+ the entry is no longer this user's plain file.
134
+
135
+ Raises:
136
+ OSError: The entry cannot be opened or examined, as when it is a link
137
+ or has been removed.
138
+ """
139
+ handle = os.fdopen(os.open(name, _read_flags(), dir_fd=dir_fd), "rb")
140
+ try:
141
+ opened = os.fstat(handle.fileno())
142
+ current = os.stat(name, dir_fd=dir_fd, follow_symlinks=False)
143
+ except BaseException:
144
+ handle.close()
145
+ raise
146
+ if not (_is_same_plain_file(opened, current) and _belongs_to_user(opened)):
147
+ handle.close()
148
+ return None
149
+ return handle, opened
150
+
151
+
152
+ def zip_date_time(modified_time_sec: float) -> tuple[int, ...]:
153
+ """
154
+ Args:
155
+ modified_time_sec: A modification time, in seconds since the epoch.
156
+
157
+ Returns:
158
+ The local time, clamped to what a ZIP entry can store.
159
+ """
160
+ try:
161
+ local = tuple(time.localtime(modified_time_sec)[:6])
162
+ except (OverflowError, OSError, ValueError):
163
+ return ZIP_EARLIEST if modified_time_sec < 0 else ZIP_LATEST
164
+ return min(max(local, ZIP_EARLIEST), ZIP_LATEST)
165
+
166
+
167
+ def describe_entry(path: Path) -> dict[str, object]:
168
+ """
169
+ Describes a directory entry without following it if it is a link.
170
+
171
+ Args:
172
+ path: The entry.
173
+
174
+ Returns:
175
+ Its name and kind, and its size if it is a plain file.
176
+
177
+ Raises:
178
+ OSError: The entry cannot be examined.
179
+ """
180
+ info = path.lstat()
181
+ entry: dict[str, object] = {"name": path.name, "is_dir": S_ISDIR(info.st_mode)}
182
+ if S_ISLNK(info.st_mode):
183
+ entry["is_symlink"] = True
184
+ elif S_ISREG(info.st_mode):
185
+ entry["size_bytes"] = info.st_size
186
+ return entry
187
+
188
+
189
+ def _read_flags() -> int:
190
+ """O_NONBLOCK, so opening a FIFO planted at the path does not hang."""
191
+ return (
192
+ os.O_RDONLY
193
+ | getattr(os, "O_NOFOLLOW", 0)
194
+ | getattr(os, "O_NONBLOCK", 0)
195
+ | getattr(os, "O_BINARY", 0)
196
+ )
197
+
198
+
199
+ def _is_same_plain_file(opened: os.stat_result, current: os.stat_result) -> bool:
200
+ return S_ISREG(opened.st_mode) and (opened.st_dev, opened.st_ino) == (
201
+ current.st_dev,
202
+ current.st_ino,
203
+ )
204
+
205
+
206
+ def _belongs_to_user(info: os.stat_result) -> bool:
207
+ """Always True on Windows, which has no POSIX owner to compare."""
208
+ return not hasattr(os, "getuid") or info.st_uid == os.getuid()