1 /* Android asset directory tool.
2
3 Copyright (C) 2023 Free Software Foundation, Inc.
4
5 This file is part of GNU Emacs.
6
7 GNU Emacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or (at
10 your option) any later version.
11
12 GNU Emacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <byteswap.h>
26 #include <stdlib.h>
27 #include <dirent.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include <sys/stat.h>
32
33 /* This program takes a directory as input, and generates a
34 ``directory-tree'' file suitable for inclusion in an Android
35 application package.
36
37 Such a file records the layout of the `assets' directory in the
38 package. Emacs records this information itself and uses it in the
39 Android emulation of readdir, because the system asset manager APIs
40 are routinely buggy, and are often unable to locate directories or
41 files.
42
43 The file is packed, with no data alignment guarantees made. The
44 file starts with the bytes "EMACS", following which is the name of
45 the first file or directory, a NULL byte and an unsigned int
46 indicating the offset from the start of the file to the start of
47 the next sibling. Following that is a list of subdirectories or
48 files in the same format. The long is stored LSB first.
49
50 Directories can be distinguished from ordinary files through the
51 last bytes of their file names (immediately previous to their
52 terminating NULL bytes), which are set to the directory separator
53 character `/'. */
54
55
56
57 struct directory_tree
58 {
59 /* The offset to the next sibling. */
60 size_t offset;
61
62 /* The name of this directory or file. */
63 char *name;
64
65 /* Subdirectories and files inside this directory. */
66 struct directory_tree *children, *next;
67 };
68
69 /* Exit with EXIT_FAILURE, after printing a description of a failing
70 function WHAT along with the details of the error. */
71
72 static _Noreturn void
73 croak (const char *what)
74 {
75 perror (what);
76 exit (EXIT_FAILURE);
77 }
78
79 /* Like malloc, but aborts on failure. */
80
81 static void *
82 xmalloc (size_t size)
83 {
84 void *ptr;
85
86 ptr = malloc (size);
87
88 if (!ptr)
89 croak ("malloc");
90
91 return ptr;
92 }
93
94 /* Recursively build a struct directory_tree structure for each
95 subdirectory or file in DIR, in preparation for writing it out to
96 disk. PARENT should be the directory tree associated with the
97 parent directory, or else PARENT->offset must be initialized to
98 5. */
99
100 static void
101 main_1 (DIR *dir, struct directory_tree *parent)
102 {
103 struct dirent *dirent;
104 int dir_fd, fd;
105 struct stat statb;
106 struct directory_tree *this, **last;
107 size_t length;
108 DIR *otherdir;
109
110 dir_fd = dirfd (dir);
111 last = &parent->children;
112
113 while ((dirent = readdir (dir)))
114 {
115 /* Determine what kind of file DIRENT is. */
116
117 if (fstatat (dir_fd, dirent->d_name, &statb,
118 AT_SYMLINK_NOFOLLOW) == -1)
119 croak ("fstatat");
120
121 /* Ignore . and ... */
122
123 if (!strcmp (dirent->d_name, ".")
124 || !strcmp (dirent->d_name, ".."))
125 continue;
126
127 length = strlen (dirent->d_name);
128
129 if (statb.st_mode & S_IFDIR)
130 {
131 /* This is a directory. Write its name followed by a
132 trailing slash, then a NULL byte, and the offset to the
133 next sibling. */
134 this = xmalloc (sizeof *this);
135 this->children = NULL;
136 this->next = NULL;
137 *last = this;
138 last = &this->next;
139 this->name = xmalloc (length + 2);
140 strcpy (this->name, dirent->d_name);
141
142 /* Now record the offset to the end of this directory. This
143 is length + 1, for the file name, and 5 more bytes for
144 the trailing NULL and long. */
145 this->offset = parent->offset + length + 6;
146
147 /* Terminate that with a slash and trailing NULL byte. */
148 this->name[length] = '/';
149 this->name[length + 1] = '\0';
150
151 /* Open and build that directory recursively. */
152
153 fd = openat (dir_fd, dirent->d_name, O_DIRECTORY,
154 O_RDONLY);
155 if (fd < 0)
156 croak ("openat");
157 otherdir = fdopendir (fd);
158 if (!otherdir)
159 croak ("fdopendir");
160
161 main_1 (otherdir, this);
162
163 /* Close this directory. */
164 closedir (otherdir);
165
166 /* Finally, set parent->offset to this->offset as well. */
167 parent->offset = this->offset;
168 }
169 else if (statb.st_mode & S_IFREG)
170 {
171 /* This is a regular file. */
172 this = xmalloc (sizeof *this);
173 this->children = NULL;
174 this->next = NULL;
175 *last = this;
176 last = &this->next;
177 this->name = xmalloc (length + 1);
178 strcpy (this->name, dirent->d_name);
179
180 /* This is one byte shorter because there is no trailing
181 slash. */
182 this->offset = parent->offset + length + 5;
183 parent->offset = this->offset;
184 }
185 }
186 }
187
188 /* Write the struct directory_tree TREE and all of is children to the
189 file descriptor FD. OFFSET is the offset of TREE and may be
190 modified; it is only used for checking purposes. */
191
192 static void
193 main_2 (int fd, struct directory_tree *tree, size_t *offset)
194 {
195 ssize_t size;
196 struct directory_tree *child;
197 unsigned int output;
198
199 /* Write tree->name with the trailing NULL byte. */
200 size = strlen (tree->name) + 1;
201 if (write (fd, tree->name, size) < size)
202 croak ("write");
203
204 /* Write the offset. */
205 #ifdef WORDS_BIGENDIAN
206 output = bswap_32 (tree->offset);
207 #else
208 output = tree->offset;
209 #endif
210 if (write (fd, &output, 4) < 1)
211 croak ("write");
212 size += 4;
213
214 /* Now update offset. */
215 *offset += size;
216
217 /* Write out each child. */
218 for (child = tree->children; child; child = child->next)
219 main_2 (fd, child, offset);
220
221 /* Verify the offset is correct. */
222 if (tree->offset != *offset)
223 {
224 fprintf (stderr,
225 "asset-directory-tool: invalid offset: expected %tu, "
226 "got %tu.\n"
227 "Please report this bug to bug-gnu-emacs@gnu.org, along\n"
228 "with an archive containing the contents of the java/inst"
229 "all_temp directory.\n",
230 tree->offset, *offset);
231 abort ();
232 }
233 }
234
235 int
236 main (int argc, char **argv)
237 {
238 int fd;
239 DIR *indir;
240 struct directory_tree tree;
241 size_t offset;
242
243 if (argc != 3)
244 {
245 fprintf (stderr, "usage: %s directory output-file\n",
246 argv[0]);
247 return EXIT_FAILURE;
248 }
249
250 fd = open (argv[2], O_CREAT | O_TRUNC | O_RDWR,
251 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
252
253 if (fd < 0)
254 {
255 perror ("open");
256 return EXIT_FAILURE;
257 }
258
259 indir = opendir (argv[1]);
260
261 if (!indir)
262 {
263 perror ("opendir");
264 return EXIT_FAILURE;
265 }
266
267 /* Write the first 5 byte header to FD. */
268
269 if (write (fd, "EMACS", 5) < 5)
270 {
271 perror ("write");
272 return EXIT_FAILURE;
273 }
274
275 /* Now iterate through children of INDIR, building the directory
276 tree. */
277 tree.offset = 5;
278 tree.children = NULL;
279
280 main_1 (indir, &tree);
281 closedir (indir);
282
283 /* Finally, write the directory tree to the output file. */
284 offset = 5;
285 for (; tree.children; tree.children = tree.children->next)
286 main_2 (fd, tree.children, &offset);
287
288 return 0;
289 }