3 """Build a composefs dump from a Json config
5 See the man page of composefs-dump for details about the format:
6 https://github.com/containers/composefs/blob/main/man/composefs-dump.md
8 Ensure to check the file with the check script when you make changes to it:
10 ./check-build-composefs-dump.sh ./build-composefs_dump.py
18 from pathlib
import Path
19 from typing
import Any
21 Attrs
= dict[str, Any
]
25 """The filetype as defined by the `st_mode` stat field in octal
27 You can check the st_mode stat field of a path in Python with
28 `oct(os.stat("/path/").st_mode)`
57 path
: str |
None = None,
60 path
= attrs
["target"]
63 self
.filetype
= filetype
65 self
.uid
= attrs
["uid"]
66 self
.gid
= attrs
["gid"]
67 self
.payload
= payload
69 def write_line(self
) -> str:
73 f
"{self.filetype.value}{self.mode}",
83 return " ".join(line_list
)
86 def eprint(*args
: Any
, **kwargs
: Any
) -> None:
87 print(*args
, **kwargs
, file=sys
.stderr
)
90 def normalize_path(path
: str) -> str:
91 return str("/" + os
.path
.normpath(path
).lstrip("/"))
94 def leading_directories(path
: str) -> list[str]:
95 """Return the leading directories of path
97 Given the path "alsa/conf.d/50-pipewire.conf", for example, this function
98 returns `[ "alsa", "alsa/conf.d" ]`.
100 parents
= list(Path(path
).parents
)
102 # remove the implicit `.` from the start of a relative path or `/` from an
105 return [str(i
) for i
in parents
]
108 def add_leading_directories(
109 target
: str, attrs
: Attrs
, paths
: dict[str, ComposefsPath
]
111 """Add the leading directories of a target path to the composefs paths
113 mkcomposefs expects that all leading directories are explicitly listed in
114 the dump file. Given the path "alsa/conf.d/50-pipewire.conf", for example,
115 this function adds "alsa" and "alsa/conf.d" to the composefs paths.
117 path_components
= leading_directories(target
)
118 for component
in path_components
:
119 composefs_path
= ComposefsPath(
123 filetype
=FileType
.directory
,
127 paths
[component
] = composefs_path
131 """Build a composefs dump from a Json config
133 This config describes the files that the final composefs image is supposed
136 config_file
= sys
.argv
[1]
138 eprint("No config file was supplied.")
141 with
open(config_file
, "rb") as f
:
142 config
= json
.load(f
)
145 eprint("Config is empty.")
148 eprint("Building composefs dump...")
150 paths
: dict[str, ComposefsPath
] = {}
152 # Normalize the target path to work around issues in how targets are
153 # declared in `environment.etc`.
154 attrs
["target"] = normalize_path(attrs
["target"])
156 target
= attrs
["target"]
157 source
= attrs
["source"]
160 if "*" in source
: # Path with globbing
161 glob_sources
= glob
.glob(source
)
162 for glob_source
in glob_sources
:
163 basename
= os
.path
.basename(glob_source
)
164 glob_target
= f
"{target}/{basename}"
166 composefs_path
= ComposefsPath(
170 filetype
=FileType
.symlink
,
175 paths
[glob_target
] = composefs_path
176 add_leading_directories(glob_target
, attrs
, paths
)
177 else: # Without globbing
178 if mode
== "symlink" or mode
== "direct-symlink":
179 composefs_path
= ComposefsPath(
181 # A high approximation of the size of a symlink
183 filetype
=FileType
.symlink
,
187 elif os
.path
.isdir(source
):
188 composefs_path
= ComposefsPath(
191 filetype
=FileType
.directory
,
196 composefs_path
= ComposefsPath(
198 size
=os
.stat(source
).st_size
,
199 filetype
=FileType
.file,
201 # payload needs to be relative path in this case
202 payload
=target
.lstrip("/"),
204 paths
[target
] = composefs_path
205 add_leading_directories(target
, attrs
, paths
)
207 composefs_dump
= ["/ 4096 40755 1 0 0 0 0.0 - - -"] # Root directory
208 for key
in sorted(paths
):
209 composefs_path
= paths
[key
]
210 eprint(composefs_path
.path
)
211 composefs_dump
.append(composefs_path
.write_line())
213 print("\n".join(composefs_dump
))
216 if __name__
== "__main__":