feature/usage
[appimagekit/gsi.git] / appimaged.c
blob78a546c380e47abd8a0f9361a1eaf01d4f91b512
1 /**************************************************************************
3 * Copyright (c) 2004-17 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 extern int notify(char *title, char *body, int timeout);
60 static gboolean verbose = FALSE;
61 static gboolean version = FALSE;
62 static gboolean install = FALSE;
63 static gboolean uninstall = FALSE;
64 gchar **remaining_args = NULL;
66 static GOptionEntry entries[] =
68 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL },
69 { "install", 'i', 0, G_OPTION_ARG_NONE, &install, "Install this appimaged instance to $HOME", NULL },
70 { "uninstall", 'u', 0, G_OPTION_ARG_NONE, &uninstall, "Uninstall an appimaged instance from $HOME", NULL },
71 { "version", 0, 0, G_OPTION_ARG_NONE, &version, "Show version number", NULL },
72 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL },
73 { NULL }
76 #define EXCLUDE_CHUNK 1024
77 #define WR_EVENTS (IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)
79 /* Run the actual work in treads;
80 * pthread allows to pass only one argument to the thread function,
81 * hence we use a struct as the argument in which the real arguments are */
82 struct arg_struct {
83 char* path;
84 gboolean verbose;
87 void *thread_appimage_register_in_system(void *arguments)
89 struct arg_struct *args = arguments;
90 appimage_register_in_system(args->path, args->verbose);
91 pthread_exit(NULL);
94 void *thread_appimage_unregister_in_system(void *arguments)
96 struct arg_struct *args = arguments;
97 appimage_unregister_in_system(args->path, args->verbose);
98 pthread_exit(NULL);
101 /* Recursively process the files in this directory and its subdirectories,
102 * http://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux
104 void initially_register(const char *name, int level)
106 DIR *dir;
107 struct dirent *entry;
109 if (!(dir = opendir(name))) {
110 if (verbose) {
111 if (errno == EACCES) {
112 g_print("_________________________\n");
113 g_print("Permission denied on dir '%s'\n", name);
115 else {
116 g_print("_________________________\n");
117 g_print("Failed to open dir '%s'\n", name);
120 closedir(dir);
121 return;
124 if (!(entry = readdir(dir))) {
125 if (verbose) {
126 g_print("_________________________\n");
127 g_print("Invalid directory stream descriptor '%s'\n", name);
129 closedir(dir);
130 return;
133 do {
134 if (entry->d_type == DT_DIR) {
135 char path[1024];
136 int len = snprintf(path, sizeof(path)-1, "%s/%s", name, entry->d_name);
137 path[len] = 0;
138 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
139 continue;
140 initially_register(path, level + 1);
142 else {
143 int ret;
144 gchar *absolute_path = g_build_path(G_DIR_SEPARATOR_S, name, entry->d_name, NULL);
145 if(g_file_test(absolute_path, G_FILE_TEST_IS_REGULAR)){
146 pthread_t some_thread;
147 struct arg_struct args;
148 args.path = absolute_path;
149 args.verbose = verbose;
150 ret = pthread_create(&some_thread, NULL, thread_appimage_register_in_system, &args);
151 if (!ret) {
152 pthread_join(some_thread, NULL);
156 } while ((entry = readdir(dir)) != NULL);
157 closedir(dir);
160 void add_dir_to_watch(char *directory)
162 if (g_file_test (directory, G_FILE_TEST_IS_DIR)){
163 if(!inotifytools_watch_recursively(directory, WR_EVENTS) ) {
164 fprintf(stderr, "%s\n", strerror(inotifytools_error()));
165 exit(1);
168 initially_register(directory, 0);
172 void handle_event(struct inotify_event *event)
174 int ret;
175 gchar *absolute_path = g_build_path(G_DIR_SEPARATOR_S, inotifytools_filename_from_wd(event->wd), event->name, NULL);
177 if((event->mask & IN_CLOSE_WRITE) | (event->mask & IN_MOVED_TO)){
178 if(g_file_test(absolute_path, G_FILE_TEST_IS_REGULAR)){
179 pthread_t some_thread;
180 struct arg_struct args;
181 args.path = absolute_path;
182 args.verbose = verbose;
183 g_print("_________________________\n");
184 ret = pthread_create(&some_thread, NULL, thread_appimage_register_in_system, &args);
185 if (!ret) {
186 pthread_join(some_thread, NULL);
191 if((event->mask & IN_MOVED_FROM) | (event->mask & IN_DELETE)){
192 pthread_t some_thread;
193 struct arg_struct args;
194 args.path = absolute_path;
195 args.verbose = verbose;
196 g_print("_________________________\n");
197 ret = pthread_create(&some_thread, NULL, thread_appimage_unregister_in_system, &args);
198 if (!ret) {
199 pthread_join(some_thread, NULL);
203 /* Too many FS events were received, some event notifications were potentially lost */
204 if (event->mask & IN_Q_OVERFLOW){
205 printf ("Warning: AN OVERFLOW EVENT OCCURRED\n");
208 if(event->mask & IN_IGNORED){
209 printf ("Warning: AN IN_IGNORED EVENT OCCURRED\n");
214 int main(int argc, char ** argv) {
216 GError *error = NULL;
217 GOptionContext *context;
219 context = g_option_context_new ("");
220 g_option_context_add_main_entries (context, entries, NULL);
221 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
222 if (!g_option_context_parse (context, &argc, &argv, &error))
224 g_print("option parsing failed: %s\n", error->message);
225 exit (1);
228 if(version){
229 fprintf(stderr,"Version: %s\n", VERSION_NUMBER);
230 exit(0);
233 if ( !inotifytools_initialize()){
234 fprintf(stderr, "inotifytools_initialize error\n");
235 exit(1);
238 gchar *user_bin_dir = g_build_filename(g_get_home_dir(), "/.local/bin", NULL);
239 gchar *installed_appimaged_location = g_build_filename(user_bin_dir, "appimaged", NULL);
240 const gchar *appimage_location = g_getenv("APPIMAGE");
241 gchar *own_desktop_file_location = g_build_filename(g_getenv("APPDIR"), "/appimaged.desktop", NULL);
242 gchar *global_autostart_file = "/etc/xdg/autostart/appimaged.desktop";
243 gchar *global_systemd_file = "/usr/lib/systemd/user/appimaged.service";
244 gchar *partial_path = g_strdup_printf("autostart/appimagekit-appimaged.desktop");
245 gchar *destination = g_build_filename(g_get_user_config_dir(), partial_path, NULL);
247 if(uninstall){
248 if(g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS))
249 fprintf(stderr, "* Please delete %s\n", installed_appimaged_location);
250 if(g_file_test (destination, G_FILE_TEST_EXISTS))
251 fprintf(stderr, "* Please delete %s\n", destination);
252 fprintf(stderr, "* To remove all AppImage desktop integration, run\n");
253 fprintf(stderr, " find ~/.local/share -name 'appimagekit_*' -exec rm {} \\;\n\n");
254 exit(0);
257 if(install){
258 if(((appimage_location != NULL)) && ((own_desktop_file_location != NULL))){
259 printf("Running from within %s\n", appimage_location);
260 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))){
261 printf ("%s is not installed, moving it to %s\n", argv[0], installed_appimaged_location);
262 g_mkdir_with_parents(user_bin_dir, 0755);
263 gchar *command = g_strdup_printf("mv \"%s\" \"%s\"", appimage_location, installed_appimaged_location);
264 system(command);
265 /* When appimaged installs itself, then to the $XDG_CONFIG_HOME/autostart/ directory, falling back to ~/.config/autostart/ */
266 fprintf(stderr, "Installing to autostart: %s\n", own_desktop_file_location);
267 g_mkdir_with_parents(g_path_get_dirname(destination), 0755);
268 gchar *command2 = g_strdup_printf("cp \"%s\" \"%s\"", own_desktop_file_location, destination);
269 system(command2);
270 if(g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS))
271 fprintf(stderr, "* Installed %s\n", installed_appimaged_location);
272 if(g_file_test (destination, G_FILE_TEST_EXISTS)){
273 gchar *command3 = g_strdup_printf("sed -i -e 's|^Exec=.*|Exec=%s|g' '%s'", installed_appimaged_location, destination);
274 if(verbose)
275 fprintf(stderr, "%s\n", command3);
276 system(command3);
277 fprintf(stderr, "* Installed %s\n", destination);
279 if(g_file_test (installed_appimaged_location, G_FILE_TEST_EXISTS))
280 fprintf(stderr, "\nTo uninstall, run %s --uninstall and follow the instructions\n\n", installed_appimaged_location);
281 char *title;
282 char *body;
283 title = g_strdup_printf("Please log out");
284 body = g_strdup_printf("and log in again to complete the installation");
285 notify(title, body, 15);
286 exit(0);
288 } else {
289 printf("Not running from within an AppImage. This binary cannot be installed in this way.\n");
290 exit(1);
294 /* When we run from inside an AppImage, then we check if we are installed
295 * in a per-user location and if not, we install ourselves there */
296 if(((appimage_location != NULL)) && ((own_desktop_file_location != NULL))){
297 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))){
298 char *title;
299 char *body;
300 title = g_strdup_printf("Not installed\n");
301 body = g_strdup_printf("Please run %s --install", argv[0]);
302 notify(title, body, 15);
303 exit(1);
307 add_dir_to_watch(user_bin_dir);
308 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/Downloads", NULL));
309 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/bin", NULL));
310 add_dir_to_watch(g_build_filename("/Applications", NULL));
311 add_dir_to_watch(g_build_filename("/isodevice/Applications", NULL)); // Ubuntu Live media
312 add_dir_to_watch(g_build_filename("/isofrom/Applications", NULL)); // openSUSE Live media
313 add_dir_to_watch(g_build_filename("/run/archiso/img_dev/Applications", NULL)); // Antergos Live media
314 add_dir_to_watch(g_build_filename("/opt", NULL));
315 add_dir_to_watch(g_build_filename("/usr/local/bin", NULL));
317 struct inotify_event * event = inotifytools_next_event(-1);
318 while (event) {
319 if(verbose){
320 inotifytools_printf(event, "%w%f %e\n");
322 fflush(stdout);
323 handle_event(event);
324 fflush(stdout);
325 event = inotifytools_next_event(-1);