Runtime: Allow recursive extraction of partial sections of the AppImage
[appimagekit/gsi.git] / src / appimagetoolnoglib.c
blobf900e76f6c1326bbe9a68e0bf06f170613af8a6a
1 #include <stdio.h>
2 #include <argp.h>
4 #include <stdlib.h>
5 #include <fcntl.h>
6 #include "squashfuse.h"
8 #include <sys/types.h>
9 #include <sys/stat.h>
11 #include "binreloc.h"
12 #ifndef NULL
13 #define NULL ((void *) 0)
14 #endif
16 #include <libgen.h>
18 #include <unistd.h>
20 #include <stdio.h>
22 extern char runtime[];
23 extern unsigned int runtime_len;
26 const char *argp_program_version =
27 "appimagetool 0.1";
29 const char *argp_program_bug_address =
30 "<probono@puredarwin.org>";
32 static char doc[] =
33 "appimagetool -- Generate, extract, and inspect AppImages";
35 /* This structure is used by main to communicate with parse_opt. */
36 struct arguments
38 char *args[2]; /* SOURCE and DESTINATION */
39 int verbose; /* The -v flag */
40 int list; /* The -l flag */
41 char *dumpfile; /* Argument for -d */
45 static struct argp_option options[] =
47 {"verbose", 'v', 0, 0, "Produce verbose output"},
48 {"list", 'l', 0, 0,
49 "List files in SOURCE AppImage"},
50 {"dump", 'd', "FILE", 0,
51 "Dump FILE from SOURCE AppImage to stdout"},
52 {0}
56 static error_t
57 parse_opt (int key, char *arg, struct argp_state *state)
59 struct arguments *arguments = state->input;
61 switch (key)
63 case 'v':
64 arguments->verbose = 1;
65 break;
66 case 'l':
67 arguments->list = 1;
68 break;
69 case 'd':
70 arguments->dumpfile = arg;
71 break;
72 case ARGP_KEY_ARG:
73 if (state->arg_num >= 3)
75 argp_usage(state);
77 arguments->args[state->arg_num] = arg;
78 break;
79 case ARGP_KEY_END:
80 if (state->arg_num < 1)
82 argp_usage (state);
84 break;
85 default:
86 return ARGP_ERR_UNKNOWN;
88 return 0;
92 static char args_doc[] = "SOURCE {DESTINATION}";
95 static struct argp argp = {options, parse_opt, args_doc, doc};
98 // #####################################################################
101 static void die(const char *msg) {
102 fprintf(stderr, "%s\n", msg);
103 exit(1);
106 int is_directory(const char *path) {
107 struct stat statbuf;
108 if (stat(path, &statbuf) != 0)
109 return 0;
110 return S_ISDIR(statbuf.st_mode);
113 int is_regular_file(const char *path)
115 struct stat path_stat;
116 stat(path, &path_stat);
117 return S_ISREG(path_stat.st_mode);
120 /* Function that prints the contents of a squashfs file
121 * using libsquashfuse (#include "squashfuse.h") */
122 int sfs_ls(char* image) {
123 sqfs_err err = SQFS_OK;
124 sqfs_traverse trv;
125 sqfs fs;
127 if ((err = sqfs_open_image(&fs, image, 0)))
128 die("sqfs_open_image error");
130 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
131 die("sqfs_traverse_open error");
132 while (sqfs_traverse_next(&trv, &err)) {
133 if (!trv.dir_end) {
134 printf("%s\n", trv.path);
137 if (err)
138 die("sqfs_traverse_next error");
139 sqfs_traverse_close(&trv);
141 sqfs_fd_close(fs.fd);
142 return 0;
145 /* Generate a squashfs filesystem using mksquashfs on the $PATH */
146 int sfs_mksquashfs(char *source, char *destination) {
147 pid_t parent = getpid();
148 pid_t pid = fork();
150 if (pid == -1) {
151 // error, failed to fork()
152 return(-1);
153 } else if (pid > 0) {
154 int status;
155 waitpid(pid, &status, 0);
156 } else {
157 // we are the child
158 execlp("mksquashfs", "mksquashfs", source, destination, "-root-owned", "-noappend", (char *)0);
159 perror("execlp"); // execvp() returns only on error
160 return(-1); // exec never returns
162 return(0);
165 /* Generate a squashfs filesystem
166 * The following would work if we link to mksquashfs.o after we renamed
167 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
168 * this because squashfs-tools is not under a permissive license
169 int sfs_mksquashfs(char *source, char *destination) {
170 char *child_argv[5];
171 child_argv[0] = NULL;
172 child_argv[1] = source;
173 child_argv[2] = destination;
174 child_argv[3] = "-root-owned";
175 child_argv[4] = "-noappend";
176 mksquashfs_main(5, child_argv);
180 // #####################################################################
182 int main (int argc, char **argv)
185 /* Initialize binreloc so that we always know where we live */
186 BrInitError error;
187 if (br_init (&error) == 0) {
188 printf ("Warning: binreloc failed to initialize (error code %d)\n", error);
190 printf ("This tool is located at %s\n", br_find_exe_dir(NULL));
192 struct arguments arguments;
194 /* Set argument defaults */
195 arguments.list = 0;
196 arguments.verbose = 0;
197 arguments.dumpfile = NULL;
199 /* Where the magic happens */
200 argp_parse (&argp, argc, argv, 0, 0, &arguments);
202 /* If in list mode */
203 if (arguments.list){
204 sfs_ls(arguments.args[0]);
205 exit(0);
208 /* If in dumpfile mode */
209 if (arguments.dumpfile){
210 fprintf (stdout, "%s from the AppImage %s should be dumped to stdout\n", arguments.dumpfile, arguments.args[0]);
211 die("To be implemented");
214 /* Print argument values */
215 if (arguments.verbose)
216 fprintf (stdout, "Original SOURCE = %s\nOriginal DESTINATION = %s\n",
217 arguments.args[0],
218 arguments.args[1]);
220 /* If the first argument is a directory, then we assume that we should package it */
221 if(is_directory(arguments.args[0])){
222 char *destination;
223 char source[PATH_MAX];
224 realpath(arguments.args[0], source);
225 if (arguments.args[1]) {
226 destination = arguments.args[1];
227 } else {
228 /* No destination has been specified, to let's construct one
229 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
230 destination = basename(br_strcat(source, ".AppImage"));
231 fprintf (stdout, "DESTINATION not specified, so assuming %s\n", destination);
233 fprintf (stdout, "%s should be packaged as %s\n", arguments.args[0], destination);
235 /* mksquashfs can currently not start writing at an offset,
236 * so we need a tempfile. https://github.com/plougher/squashfs-tools/pull/13
237 * should hopefully change that. */
238 char *tempfile;
239 fprintf (stderr, "Generating squashfs...\n");
240 tempfile = br_strcat(destination, ".temp");
241 int result = sfs_mksquashfs(source, tempfile);
242 if(result != 0)
243 die("sfs_mksquashfs error");
245 fprintf (stderr, "Generating AppImage...\n");
246 FILE *fpsrc = fopen(tempfile, "rb");
247 if (fpsrc == NULL) {
248 die("Not able to open the tempfile for reading, aborting");
250 FILE *fpdst = fopen(destination, "w");
251 if (fpdst == NULL) {
252 die("Not able to open the destination file for writing, aborting");
255 /* runtime is embedded into this executable
256 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
257 int size = runtime_len;
258 char *data = runtime;
259 if (arguments.verbose)
260 printf("Size of the embedded runtime: %d bytes\n", size);
261 /* Where to store updateinformation. Fixed offset preferred for easy manipulation
262 * after the fact. Proposal: 4 KB at the end of the 128 KB padding.
263 * Hence, offset 126976, max. 4096 bytes long.
264 * Possibly we might want to store additional information in the future.
265 * Assuming 4 blocks of 4096 bytes each.
267 if(size > 128*1024-4*4096-2){
268 die("Size of the embedded runtime is too large, aborting");
270 // printf("%s", data);
271 fwrite(data, size, 1, fpdst);
273 if(ftruncate(fileno(fpdst), 128*1024) != 0) {
274 die("Not able to write padding to destination file, aborting");
277 fseek (fpdst, 0, SEEK_END);
278 char byte;
280 while (!feof(fpsrc))
282 fread(&byte, sizeof(char), 1, fpsrc);
283 fwrite(&byte, sizeof(char), 1, fpdst);
286 fclose(fpsrc);
287 fclose(fpdst);
289 fprintf (stderr, "Marking the AppImage as executable...\n");
290 if (chmod (destination, 0755) < 0) {
291 printf("Could not set executable bit, aborting\n");
292 exit(1);
294 if(unlink(tempfile) != 0) {
295 die("Could not delete the tempfile, aborting");
297 fprintf (stderr, "Success\n");
300 /* If the first argument is a regular file, then we assume that we should unpack it */
301 if(is_regular_file(arguments.args[0])){
302 fprintf (stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", arguments.args[0]);
303 die("To be implemented");
306 return 0;