/lib/live/mount/findiso/Applications for Manjaro
[appimagekit/gsi.git] / src / appimaged.c
blobbcebd35a3b22e6d8cd50601c20dd80f8f8ec60ff
1 /**************************************************************************
3 * Copyright (c) 2004-18 Simon Peter
5 * All Rights Reserved.
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
25 **************************************************************************/
27 #ident "AppImage by Simon Peter, http://appimage.org/"
30 * Optional daempon to watch directories for AppImages
31 * and register/unregister them with the system
33 * TODO (feel free to send pull requests):
34 * - Switch to https://developer.gnome.org/gio/stable/GFileMonitor.html (but with subdirectories)
35 * which would drop the dependency on libinotifytools.so.0
36 * - Add and remove subdirectories on the fly at runtime -
37 * see https://github.com/paragone/configure-via-inotify/blob/master/inotify/src/inotifywatch.c
40 #include <stdio.h>
41 #include <errno.h>
42 #include <string.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45 #include <sys/types.h>
46 #include <dirent.h>
48 #include <inotifytools/inotifytools.h>
49 #include <inotifytools/inotify.h>
51 #include <glib.h>
52 #include <glib/gprintf.h>
54 #include "shared.c"
56 #include <pthread.h>
58 #ifndef RELEASE_NAME
59 #define RELEASE_NAME "continuous build"
60 #endif
62 extern int notify(char *title, char *body, int timeout);
64 static gboolean verbose = FALSE;
65 static gboolean showVersionOnly = FALSE;
66 static gboolean install = FALSE;
67 static gboolean uninstall = FALSE;
68 static gboolean no_install = FALSE;
69 gchar **remaining_args = NULL;
71 static GOptionEntry entries[] =
73 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL },
74 { "install", 'i', 0, G_OPTION_ARG_NONE, &install, "Install this appimaged instance to $HOME", NULL },
75 { "uninstall", 'u', 0, G_OPTION_ARG_NONE, &uninstall, "Uninstall an appimaged instance from $HOME", NULL },
76 { "no-install", 'n', 0, G_OPTION_ARG_NONE, &no_install, "Force run without installation", NULL },
77 { "version", 0, 0, G_OPTION_ARG_NONE, &showVersionOnly, "Show version number", NULL },
78 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL },
79 { NULL }
82 #define EXCLUDE_CHUNK 1024
83 #define WR_EVENTS (IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)
85 /* Run the actual work in treads;
86 * pthread allows to pass only one argument to the thread function,
87 * hence we use a struct as the argument in which the real arguments are */
88 struct arg_struct {
89 char* path;
90 gboolean verbose;
93 void *thread_appimage_register_in_system(void *arguments)
95 struct arg_struct *args = arguments;
96 appimage_register_in_system(args->path, args->verbose);
97 pthread_exit(NULL);
100 void *thread_appimage_unregister_in_system(void *arguments)
102 struct arg_struct *args = arguments;
103 appimage_unregister_in_system(args->path, args->verbose);
104 pthread_exit(NULL);
107 /* Recursively process the files in this directory and its subdirectories,
108 * http://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux
110 void initially_register(const char *name, int level)
112 DIR *dir;
113 struct dirent *entry;
115 if (!(dir = opendir(name))) {
116 if (verbose) {
117 if (errno == EACCES) {
118 g_print("_________________________\n");
119 g_print("Permission denied on dir '%s'\n", name);
121 else {
122 g_print("_________________________\n");
123 g_print("Failed to open dir '%s'\n", name);
126 closedir(dir);
127 return;
130 if (!(entry = readdir(dir))) {
131 if (verbose) {
132 g_print("_________________________\n");
133 g_print("Invalid directory stream descriptor '%s'\n", name);
135 closedir(dir);
136 return;
139 do {
140 if (entry->d_type == DT_DIR) {
141 char path[1024];
142 int len = snprintf(path, sizeof(path)-1, "%s/%s", name, entry->d_name);
143 path[len] = 0;
144 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
145 continue;
146 initially_register(path, level + 1);
148 else {
149 int ret;
150 gchar *absolute_path = g_build_path(G_DIR_SEPARATOR_S, name, entry->d_name, NULL);
151 if(g_file_test(absolute_path, G_FILE_TEST_IS_REGULAR)){
152 pthread_t some_thread;
153 struct arg_struct args;
154 args.path = absolute_path;
155 args.verbose = verbose;
156 ret = pthread_create(&some_thread, NULL, thread_appimage_register_in_system, &args);
157 if (!ret) {
158 pthread_join(some_thread, NULL);
161 g_free(absolute_path);
163 } while ((entry = readdir(dir)) != NULL);
164 closedir(dir);
167 void add_dir_to_watch(char *directory)
169 if (g_file_test (directory, G_FILE_TEST_IS_DIR)){
170 if(!inotifytools_watch_recursively(directory, WR_EVENTS) ) {
171 fprintf(stderr, "%s\n", strerror(inotifytools_error()));
172 exit(1);
175 initially_register(directory, 0);
179 void handle_event(struct inotify_event *event)
181 int ret;
182 gchar *absolute_path = g_build_path(G_DIR_SEPARATOR_S, inotifytools_filename_from_wd(event->wd), event->name, NULL);
184 if((event->mask & IN_CLOSE_WRITE) | (event->mask & IN_MOVED_TO)){
185 if(g_file_test(absolute_path, G_FILE_TEST_IS_REGULAR)){
186 pthread_t some_thread;
187 struct arg_struct args;
188 args.path = absolute_path;
189 args.verbose = verbose;
190 g_print("_________________________\n");
191 ret = pthread_create(&some_thread, NULL, thread_appimage_register_in_system, &args);
192 if (!ret) {
193 pthread_join(some_thread, NULL);
198 if((event->mask & IN_MOVED_FROM) | (event->mask & IN_DELETE)){
199 pthread_t some_thread;
200 struct arg_struct args;
201 args.path = absolute_path;
202 args.verbose = verbose;
203 g_print("_________________________\n");
204 ret = pthread_create(&some_thread, NULL, thread_appimage_unregister_in_system, &args);
205 if (!ret) {
206 pthread_join(some_thread, NULL);
210 g_free(absolute_path);
212 /* Too many FS events were received, some event notifications were potentially lost */
213 if (event->mask & IN_Q_OVERFLOW){
214 printf ("Warning: AN OVERFLOW EVENT OCCURRED\n");
217 if(event->mask & IN_IGNORED){
218 printf ("Warning: AN IN_IGNORED EVENT OCCURRED\n");
223 int main(int argc, char ** argv) {
225 GError *error = NULL;
226 GOptionContext *context;
228 context = g_option_context_new ("");
229 g_option_context_add_main_entries (context, entries, NULL);
230 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
231 if (!g_option_context_parse (context, &argc, &argv, &error))
233 g_print("option parsing failed: %s\n", error->message);
234 exit (1);
237 // always show version, but exit immediately if only the version number was requested
238 fprintf(
239 stderr,
240 "appimaged, %s (commit %s), build %s built on %s\n",
241 RELEASE_NAME, GIT_COMMIT, BUILD_NUMBER, BUILD_DATE
244 if(showVersionOnly)
245 exit(0);
247 if (!inotifytools_initialize()) {
248 fprintf(stderr, "inotifytools_initialize error\n");
249 exit(1);
252 gchar *user_bin_dir = g_build_filename(g_get_home_dir(), "/.local/bin", NULL);
253 gchar *installed_appimaged_location = g_build_filename(user_bin_dir, "appimaged", NULL);
254 const gchar *appimage_location = g_getenv("APPIMAGE");
255 gchar *own_desktop_file_location = g_build_filename(g_getenv("APPDIR"), "/appimaged.desktop", NULL);
256 gchar *global_autostart_file = "/etc/xdg/autostart/appimaged.desktop";
257 gchar *global_systemd_file = "/usr/lib/systemd/user/appimaged.service";
258 gchar *partial_path = g_strdup_printf("autostart/appimagekit-appimaged.desktop");
259 gchar *destination = g_build_filename(g_get_user_config_dir(), partial_path, NULL);
261 if(uninstall){
262 if(g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS))
263 fprintf(stderr, "* Please delete %s\n", installed_appimaged_location);
264 if(g_file_test (destination, G_FILE_TEST_EXISTS))
265 fprintf(stderr, "* Please delete %s\n", destination);
266 fprintf(stderr, "* To remove all AppImage desktop integration, run\n");
267 fprintf(stderr, " find ~/.local/share -name 'appimagekit_*' -exec rm {} \\;\n\n");
268 exit(0);
271 if(install){
272 if(((appimage_location != NULL)) && ((own_desktop_file_location != NULL))){
273 printf("Running from within %s\n", appimage_location);
274 if ( (! g_file_test ("/usr/bin/appimaged", G_FILE_TEST_EXISTS)) && (! g_file_test (global_autostart_file, G_FILE_TEST_EXISTS)) && (! g_file_test (global_systemd_file, G_FILE_TEST_EXISTS))){
275 printf ("%s is not installed, moving it to %s\n", argv[0], installed_appimaged_location);
276 g_mkdir_with_parents(user_bin_dir, 0755);
277 gchar *command = g_strdup_printf("mv \"%s\" \"%s\"", appimage_location, installed_appimaged_location);
278 system(command);
279 /* When appimaged installs itself, then to the $XDG_CONFIG_HOME/autostart/ directory, falling back to ~/.config/autostart/ */
280 fprintf(stderr, "Installing to autostart: %s\n", own_desktop_file_location);
281 g_mkdir_with_parents(g_path_get_dirname(destination), 0755);
282 gchar *command2 = g_strdup_printf("cp \"%s\" \"%s\"", own_desktop_file_location, destination);
283 system(command2);
284 if(g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS))
285 fprintf(stderr, "* Installed %s\n", installed_appimaged_location);
286 if(g_file_test (destination, G_FILE_TEST_EXISTS)){
287 gchar *command3 = g_strdup_printf("sed -i -e 's|^Exec=.*|Exec=%s|g' '%s'", installed_appimaged_location, destination);
288 if(verbose)
289 fprintf(stderr, "%s\n", command3);
290 system(command3);
291 fprintf(stderr, "* Installed %s\n", destination);
293 if(g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS))
294 fprintf(stderr, "\nTo uninstall, run %s --uninstall and follow the instructions\n\n", installed_appimaged_location);
295 char *title;
296 char *body;
297 title = g_strdup_printf("Please log out");
298 body = g_strdup_printf("and log in again to complete the installation");
299 notify(title, body, 15);
300 exit(0);
302 } else {
303 printf("Not running from within an AppImage. This binary cannot be installed in this way.\n");
304 exit(1);
308 /* When we run from inside an AppImage, then we check if we are installed
309 * in a per-user location and if not, we install ourselves there */
310 if(!no_install && (appimage_location != NULL && own_desktop_file_location != NULL)) {
311 if ( (! g_file_test ("/usr/bin/appimaged", G_FILE_TEST_EXISTS)) && ((! g_file_test (global_autostart_file, G_FILE_TEST_EXISTS)) || (! g_file_test (destination, G_FILE_TEST_EXISTS))) && (! g_file_test (global_systemd_file, G_FILE_TEST_EXISTS)) && (! g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS)) && (g_file_test (own_desktop_file_location, G_FILE_TEST_IS_REGULAR))){
312 char *title;
313 char *body;
314 title = g_strdup_printf("Not installed\n");
315 body = g_strdup_printf("Please run %s --install", argv[0]);
316 notify(title, body, 15);
317 exit(1);
321 add_dir_to_watch(user_bin_dir);
322 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/Downloads", NULL));
323 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/bin", NULL));
324 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/.bin", NULL));
325 add_dir_to_watch(g_build_filename("/Applications", NULL));
326 // Perhaps we should determine the following dynamically using something like
327 // mount | grep -i iso | head -n 1 | cut -d ' ' -f 3
328 add_dir_to_watch(g_build_filename("/isodevice/Applications", NULL)); // Ubuntu Live media
329 add_dir_to_watch(g_build_filename("/isofrom/Applications", NULL)); // openSUSE Live media
330 add_dir_to_watch(g_build_filename("/run/archiso/img_dev/Applications", NULL)); // Antergos Live media
331 add_dir_to_watch(g_build_filename("/lib/live/mount/findiso/Applications", NULL)); // Manjaro Live media
332 add_dir_to_watch(g_build_filename("/opt", NULL));
333 add_dir_to_watch(g_build_filename("/usr/local/bin", NULL));
335 struct inotify_event * event = inotifytools_next_event(-1);
336 while (event) {
337 if(verbose){
338 inotifytools_printf(event, "%w%f %e\n");
340 fflush(stdout);
341 handle_event(event);
342 fflush(stdout);
343 event = inotifytools_next_event(-1);