diff --git a/common/dirhelpers.c b/common/dirhelpers.c index f5f14f74..b5e01b2b 100644 --- a/common/dirhelpers.c +++ b/common/dirhelpers.c @@ -25,12 +25,14 @@ */ #include "../common.h" +#include "catdup.h" #include #include #include #include #include +#include bool check_dir_exists(const char * dirname) { @@ -240,3 +242,160 @@ bool ensure_dir_exist_varg(int mode, ...) return ret; } + +bool ladish_rmdir_recursive(const char * dirpath) +{ + DIR * dir; + struct dirent * dentry_ptr; + char * entry_fullpath; + struct stat st; + bool success; + + success = false; + + dir = opendir(dirpath); + if (dir == NULL) + { + log_error("Cannot open directory '%s': %d (%s)", dirpath, errno, strerror(errno)); + goto exit; + } + + while ((dentry_ptr = readdir(dir)) != NULL) + { + if (strcmp(dentry_ptr->d_name, ".") == 0 || + strcmp(dentry_ptr->d_name, "..") == 0) + { + continue; + } + + entry_fullpath = catdup3(dirpath, "/", dentry_ptr->d_name); + if (entry_fullpath == NULL) + { + log_error("catdup() failed"); + goto close; + } + + if (stat(entry_fullpath, &st) != 0) + { + log_error("failed to stat '%s': %d (%s)", entry_fullpath, errno, strerror(errno)); + } + else + { + if (S_ISDIR(st.st_mode)) + { + if (!ladish_rmdir_recursive(entry_fullpath)) + { + goto free; + } + } + else + { + if (unlink(entry_fullpath) < 0) + { + log_error("unlink('%s') failed. errno = %d (%s)", dirpath, errno, strerror(errno)); + goto free; + } + } + } + + free(entry_fullpath); + } + + if (rmdir(dirpath) < 0) + { + log_error("rmdir('%s') failed. errno = %d (%s)", dirpath, errno, strerror(errno)); + } + else + { + success = true; + } + + goto close; + +free: + free(entry_fullpath); +close: + closedir(dir); +exit: + return success; +} + +bool ladish_rotate(const char * src, const char * dst, unsigned int max_backups) +{ + size_t len = strlen(dst) + 100; + char paths[2][len]; + struct stat st; + char * path; + const char * older_path; + bool oldest_found; + unsigned int backup; + + oldest_found = false; + older_path = NULL; + backup = max_backups; + while (backup > 0) + { + path = paths[backup % 2]; + snprintf(path, len, "%s.%u", dst, backup); + + if (stat(path, &st) != 0) + { + if (!oldest_found && errno == ENOENT) + { + log_info("\"%s\" does not exist", path); + goto next; + } + + log_error("Failed to stat \"%s\": %d (%s)", path, errno, strerror(errno)); + return false; + } + + if (!S_ISDIR(st.st_mode)) + { + log_error("\"%s\" exists but is not directory.", path); + return false; + } + + oldest_found = true; + + if (backup < max_backups) + { + ASSERT(older_path != NULL); + log_info("rename '%s' -> '%s'", path, older_path); + if (rename(path, older_path) != 0) + { + log_error("rename('%s' -> '%s') failed. errno = %d (%s)", path, older_path, errno, strerror(errno)); + return false; + } + } + else + { + /* try to remove dst.max_backups */ + log_info("rmdir '%s'", path); + if (!ladish_rmdir_recursive(path)) + { + return false; + } + } + + next: + older_path = path; + backup--; + } + + log_info("rename '%s' -> '%s'", dst, path); + if (rename(dst, path) != 0 && errno != ENOENT) + { + log_error("rename('%s' -> '%s') failed. errno = %d (%s)", dst, path, errno, strerror(errno)); + return false; + } + + log_info("rename '%s' -> '%s'", src, dst); + if (rename(src, dst) != 0) + { + log_error("rename('%s' -> '%s') failed. errno = %d (%s)", src, dst, errno, strerror(errno)); + return false; + } + + return true; +} diff --git a/common/dirhelpers.h b/common/dirhelpers.h index 2274ccde..539a0849 100644 --- a/common/dirhelpers.h +++ b/common/dirhelpers.h @@ -30,5 +30,7 @@ bool check_dir_exists(const char * dirname); bool ensure_dir_exist(const char * dirname, int mode); bool ensure_dir_exist_varg(int mode, ...); +bool ladish_rmdir_recursive(const char * dirname); +bool ladish_rotate(const char * src, const char * dst, unsigned int max_backups); #endif /* #ifndef DIRHELPERS_H__805193D2_2662_40FA_8814_AF8A4E08F4B0__INCLUDED */ diff --git a/daemon/app_supervisor.c b/daemon/app_supervisor.c index f9eb6a8e..94726891 100644 --- a/daemon/app_supervisor.c +++ b/daemon/app_supervisor.c @@ -40,6 +40,7 @@ #include "../proxies/lash_client_proxy.h" #include "../common/catdup.h" #include "../common/dirhelpers.h" +#include "jack_session.h" struct ladish_app { @@ -48,6 +49,7 @@ struct ladish_app uuid_t uuid; char * name; char * commandline; + char * js_commandline; bool terminal; char level[MAX_LEVEL_CHARCOUNT]; pid_t pid; @@ -67,6 +69,13 @@ struct ladish_app_supervisor char * name; char * opath; char * dir; + + char * js_dir; + char * js_temp_dir; + unsigned int pending_js_saves; + void * save_callback_context; + ladish_save_complete_callback save_callback; + char * project_name; uint64_t version; uint64_t next_id; @@ -85,8 +94,9 @@ bool ladish_check_app_level_validity(const char * level, size_t * len_ptr) } if (strcmp(level, LADISH_APP_LEVEL_0) != 0 && - strcmp(level, LADISH_APP_LEVEL_1) != 0&& - strcmp(level, LADISH_APP_LEVEL_LASH) != 0) + strcmp(level, LADISH_APP_LEVEL_1) != 0 && + strcmp(level, LADISH_APP_LEVEL_LASH) != 0 && + strcmp(level, LADISH_APP_LEVEL_JACKSESSION) != 0) { return false; } @@ -154,6 +164,13 @@ ladish_app_supervisor_create( } supervisor_ptr->dir = NULL; + + supervisor_ptr->js_temp_dir = NULL; + supervisor_ptr->js_dir = NULL; + supervisor_ptr->pending_js_saves = 0; + supervisor_ptr->save_callback_context = NULL; + supervisor_ptr->save_callback = NULL; + supervisor_ptr->project_name = NULL; supervisor_ptr->version = 0; @@ -206,6 +223,7 @@ void remove_app_internal(struct ladish_app_supervisor * supervisor_ptr, struct l free(app_ptr->dbus_name); free(app_ptr->name); free(app_ptr->commandline); + free(app_ptr->js_commandline); free(app_ptr); } @@ -250,6 +268,72 @@ void emit_app_state_changed(struct ladish_app_supervisor * supervisor_ptr, struc &level_str); } +static void ladish_js_save_complete(struct ladish_app_supervisor * supervisor_ptr) +{ + struct list_head * node_ptr; + struct ladish_app * app_ptr; + bool success; + + ASSERT(supervisor_ptr->js_temp_dir != NULL); + ASSERT(supervisor_ptr->js_dir != NULL); + ASSERT(supervisor_ptr->pending_js_saves == 0); + + /* find whether all strdup() calls for new commandlines succeeded */ + list_for_each(node_ptr, &supervisor_ptr->applist) + { + app_ptr = list_entry(node_ptr, struct ladish_app, siblings); + if (app_ptr->state == LADISH_APP_STATE_STARTED && + strcmp(app_ptr->level, LADISH_APP_LEVEL_JACKSESSION) == 0 && + app_ptr->js_commandline == NULL) + { /* a strdup() call has failed, free js commandline buffers allocated by succeeded strdup() calls */ + list_for_each(node_ptr, &supervisor_ptr->applist) + { + app_ptr = list_entry(node_ptr, struct ladish_app, siblings); + free(app_ptr->js_commandline); + app_ptr->js_commandline = NULL; + } + goto fail_rm_temp_dir; + } + } + + /* move js_dir to js_dir.1; move js_temp_dir to js_dir */ + success = ladish_rotate(supervisor_ptr->js_temp_dir, supervisor_ptr->js_dir, 10); + if (!success) + { + goto fail_rm_temp_dir; + } + + list_for_each(node_ptr, &supervisor_ptr->applist) + { + app_ptr = list_entry(node_ptr, struct ladish_app, siblings); + if (app_ptr->state == LADISH_APP_STATE_STARTED && + strcmp(app_ptr->level, LADISH_APP_LEVEL_JACKSESSION) == 0) + { + ASSERT(app_ptr->commandline != NULL); + ASSERT(app_ptr->js_commandline != NULL); + free(app_ptr->commandline); + app_ptr->commandline = app_ptr->js_commandline; + app_ptr->js_commandline = NULL; + } + } + + goto done; + +fail_rm_temp_dir: + if (!ladish_rmdir_recursive(supervisor_ptr->js_temp_dir)) + { + log_error("Cannot remove JS temp dir '%s'", supervisor_ptr->js_temp_dir); + } + +done: + free(supervisor_ptr->js_temp_dir); + supervisor_ptr->js_temp_dir = NULL; + + supervisor_ptr->save_callback(supervisor_ptr->save_callback_context, success); + supervisor_ptr->save_callback = NULL; + supervisor_ptr->save_callback_context = NULL; +} + #define supervisor_ptr ((struct ladish_app_supervisor *)supervisor_handle) const char * ladish_app_supervisor_get_opath(ladish_app_supervisor_handle supervisor_handle) @@ -333,33 +417,39 @@ ladish_app_supervisor_add( if (!ladish_check_app_level_validity(level, &len)) { log_error("invalid level '%s'", level); - return NULL; + goto fail; + } + + if (strcmp(level, LADISH_APP_LEVEL_JACKSESSION) == 0 && + supervisor_ptr->dir == NULL) + { + log_error("jack session apps need directory"); + goto fail; } app_ptr = malloc(sizeof(struct ladish_app)); if (app_ptr == NULL) { log_error("malloc of struct ladish_app failed"); - return NULL; + goto fail; } app_ptr->name = strdup(name); if (app_ptr->name == NULL) { log_error("strdup() failed for app name"); - free(app_ptr); - return NULL; + goto free_app; } app_ptr->commandline = strdup(command); if (app_ptr->commandline == NULL) { log_error("strdup() failed for app commandline"); - free(app_ptr->name); - free(app_ptr); - return NULL; + goto free_name; } + app_ptr->js_commandline = NULL; + app_ptr->dbus_name = NULL; app_ptr->terminal = terminal; @@ -418,6 +508,13 @@ ladish_app_supervisor_add( &level); return (ladish_app_handle)app_ptr; + +free_name: + free(app_ptr->name); +free_app: + free(app_ptr); +fail: + return NULL; } static void ladish_app_send_signal(struct ladish_app * app_ptr, int sig, bool prefer_firstborn) @@ -622,6 +719,36 @@ exit: return; } +#define app_ptr ((struct ladish_app *)context) + +static void ladish_js_app_save_complete(void * context, const char * commandline) +{ + log_info("JS app saved, commandline '%s'", commandline); + ASSERT(app_ptr->supervisor->js_temp_dir != NULL); + + ASSERT(app_ptr->js_commandline == NULL); + app_ptr->js_commandline = strdup(commandline); + if (app_ptr->js_commandline == NULL) + { + log_error("strdup() failed for JS app '%s' commandline '%s'", app_ptr->name, commandline); + } + + if (app_ptr->supervisor->pending_js_saves != 1) + { + ASSERT(app_ptr->supervisor->pending_js_saves > 1); + app_ptr->supervisor->pending_js_saves--; + log_info("%u more JS apps are still saving", app_ptr->supervisor->pending_js_saves); + return; + } + + log_info("Last JS app saved"); + app_ptr->supervisor->pending_js_saves = 0; + + ladish_js_save_complete(app_ptr->supervisor); +} + +#undef app_ptr + static inline void ladish_app_initiate_save(struct ladish_app * app_ptr) { if (strcmp(app_ptr->level, LADISH_APP_LEVEL_LASH) == 0 && @@ -629,6 +756,11 @@ static inline void ladish_app_initiate_save(struct ladish_app * app_ptr) { ladish_app_initiate_lash_save(app_ptr, app_ptr->supervisor->dir != NULL ? app_ptr->supervisor->dir : g_base_dir); } + else if (strcmp(app_ptr->level, LADISH_APP_LEVEL_JACKSESSION) == 0) + { + log_info("Initiating JACK session save for '%s'", app_ptr->name); + ladish_js_save_app(app_ptr->uuid, app_ptr->supervisor->js_temp_dir, app_ptr, ladish_js_app_save_complete); + } else if (strcmp(app_ptr->level, LADISH_APP_LEVEL_1) == 0) { ladish_app_send_signal(app_ptr, SIGUSR1, true); @@ -658,6 +790,10 @@ bool ladish_app_supervisor_clear(ladish_app_supervisor_handle supervisor_handle) struct ladish_app * app_ptr; bool lifeless; + free(supervisor_ptr->js_temp_dir); + supervisor_ptr->js_temp_dir = NULL; + free(supervisor_ptr->js_dir); + supervisor_ptr->js_dir = NULL; free(supervisor_ptr->dir); supervisor_ptr->dir = NULL; @@ -700,6 +836,9 @@ ladish_app_supervisor_set_directory( const char * dir) { char * dup; + char * js_dir; + + ASSERT(supervisor_ptr->pending_js_saves == 0); dup = strdup(dir); if (dup == NULL) @@ -708,13 +847,20 @@ ladish_app_supervisor_set_directory( return false; } - if (supervisor_ptr->dir != NULL) + js_dir = catdup(dir, "/js_apps"); + if (js_dir == NULL) { - free(supervisor_ptr->dir); + log_error("catdup() failed to compose supervisor js dir path"); + free(dup); + return false; } + free(supervisor_ptr->dir); supervisor_ptr->dir = dup; + free(supervisor_ptr->js_dir); + supervisor_ptr->js_dir = js_dir; + return true; } @@ -811,18 +957,42 @@ ladish_app_supervisor_enum( bool ladish_app_supervisor_start_app(ladish_app_supervisor_handle supervisor_handle, ladish_app_handle app_handle) { + char uuid_str[37]; + char * js_dir; + bool ret; + app_ptr->zombie = false; ASSERT(app_ptr->pid == 0); - if (!loader_execute( - supervisor_ptr->name, - supervisor_ptr->project_name, - app_ptr->name, - supervisor_ptr->dir != NULL ? supervisor_ptr->dir : "/", - app_ptr->terminal, - app_ptr->commandline, - &app_ptr->pid)) + if (strcmp(app_ptr->level, LADISH_APP_LEVEL_JACKSESSION) == 0) + { + uuid_unparse(app_ptr->uuid, uuid_str); + js_dir = catdup4(supervisor_ptr->js_dir, "/", uuid_str, "/"); + if (js_dir == NULL) + { + log_error("catdup4() failed to compose app jack session dir"); + return false; + } + } + else + { + js_dir = NULL; + } + + ret = loader_execute( + supervisor_ptr->name, + supervisor_ptr->project_name, + app_ptr->name, + supervisor_ptr->dir != NULL ? supervisor_ptr->dir : "/", + js_dir, + app_ptr->terminal, + app_ptr->commandline, + &app_ptr->pid); + + free(js_dir); + + if (!ret) { return false; } @@ -870,11 +1040,6 @@ void ladish_app_kill(ladish_app_handle app_handle) app_ptr->state = LADISH_APP_STATE_KILL; } -void ladish_app_save(ladish_app_handle app_handle) -{ - ladish_app_initiate_save(app_ptr); -} - void ladish_app_restore(ladish_app_handle app_handle) { if (strcmp(app_ptr->level, LADISH_APP_LEVEL_LASH) == 0 && @@ -1006,6 +1171,47 @@ ladish_app_supervisor_save( { struct list_head * node_ptr; struct ladish_app * app_ptr; + bool success; + + ASSERT(supervisor_ptr->js_temp_dir == NULL); + ASSERT(supervisor_ptr->pending_js_saves == 0); + list_for_each(node_ptr, &supervisor_ptr->applist) + { + app_ptr = list_entry(node_ptr, struct ladish_app, siblings); + if (app_ptr->state == LADISH_APP_STATE_STARTED && + strcmp(app_ptr->level, LADISH_APP_LEVEL_JACKSESSION) == 0) + { + ASSERT(app_ptr->js_commandline == NULL); + supervisor_ptr->pending_js_saves++; + } + } + + if (supervisor_ptr->pending_js_saves > 0) + { + ASSERT(supervisor_ptr->dir != NULL); + supervisor_ptr->js_temp_dir = catdup(supervisor_ptr->dir, "/js_apps.tmpXXXXXX"); + if (supervisor_ptr->js_temp_dir == NULL) + { + log_error("catdup() failed to compose supervisor js temp dir path template"); + goto reset_js_pending_saves; + } + + if (mkdtemp(supervisor_ptr->js_temp_dir) == NULL) + { + log_error("mkdtemp('%s') failed. errno = %d (%s)", supervisor_ptr->js_temp_dir, errno, strerror(errno)); + goto free_js_temp_dir; + } + + log_info("saving %u JACK session apps to '%s'", supervisor_ptr->pending_js_saves, supervisor_ptr->js_temp_dir); + + supervisor_ptr->save_callback_context = context; + supervisor_ptr->save_callback = callback; + } + else + { + ASSERT(supervisor_ptr->save_callback_context == NULL); + ASSERT(supervisor_ptr->save_callback == NULL); + } list_for_each(node_ptr, &supervisor_ptr->applist) { @@ -1024,7 +1230,22 @@ ladish_app_supervisor_save( ladish_app_initiate_save(app_ptr); } - callback(context, true); + success = true; + goto exit; + +free_js_temp_dir: + free(supervisor_ptr->js_temp_dir); + supervisor_ptr->js_temp_dir = NULL; +reset_js_pending_saves: + supervisor_ptr->pending_js_saves = 0; + success = false; +exit: + if (supervisor_ptr->pending_js_saves == 0) + { + ASSERT(supervisor_ptr->save_callback == NULL); + ASSERT(callback != NULL); + callback(context, success); + } } const char * ladish_app_supervisor_get_name(ladish_app_supervisor_handle supervisor_handle) diff --git a/daemon/app_supervisor.h b/daemon/app_supervisor.h index 057cbe3c..62ffadd4 100644 --- a/daemon/app_supervisor.h +++ b/daemon/app_supervisor.h @@ -35,7 +35,7 @@ #define LADISH_APP_STATE_STOPPING 2 /**< @brief app is stopping */ #define LADISH_APP_STATE_KILL 3 /**< @brief app is being force killed */ -#define MAX_LEVEL_CHARCOUNT 10 /* includes terminating nul char */ +#define MAX_LEVEL_CHARCOUNT 12 /* includes terminating nul char */ /** * App supervisor object handle (pointer to opaque data) @@ -425,13 +425,6 @@ void ladish_app_stop(ladish_app_handle app_handle); */ void ladish_app_kill(ladish_app_handle app_handle); -/** - * Initiate save of app internal state. The app must be in started state. - * - * @param[in] app_handle Handle of app - */ -void ladish_app_save(ladish_app_handle app_handle); - /** * Initiate restore of app internal state. The app must be in started state. * diff --git a/daemon/loader.c b/daemon/loader.c index f7e11831..c0ee708f 100644 --- a/daemon/loader.c +++ b/daemon/loader.c @@ -263,6 +263,7 @@ void loader_exec_program( const char * commandline, const char * working_dir, + const char * session_dir, bool run_in_terminal, const char * vgraph_name, const char * project_name, @@ -296,6 +297,11 @@ loader_exec_program( setenv("LADISH_PROJECT_NAME", project_name, true); } + if (session_dir != NULL) + { + setenv("SESSION_DIR", session_dir, true); + } + i = 0; if (run_in_terminal) @@ -521,6 +527,7 @@ loader_execute( const char * project_name, const char * app_name, const char * working_dir, + const char * session_dir, bool run_in_terminal, const char * commandline, pid_t * pid_ptr) @@ -634,7 +641,7 @@ loader_execute( set_ldpreload(); - loader_exec_program(commandline, working_dir, run_in_terminal, vgraph_name, project_name, app_name); + loader_exec_program(commandline, working_dir, session_dir, run_in_terminal, vgraph_name, project_name, app_name); return false; /* We should never get here */ } diff --git a/daemon/loader.h b/daemon/loader.h index 6485f948..f2ee99f6 100644 --- a/daemon/loader.h +++ b/daemon/loader.h @@ -2,7 +2,7 @@ /* * LADI Session Handler (ladish) * - * Copyright (C) 2009, 2010 Nedko Arnaudov + * Copyright (C) 2009, 2010, 2011 Nedko Arnaudov * ************************************************************************** * This file contains interface to the code that starts programs @@ -35,6 +35,7 @@ loader_execute( const char * project_name, const char * app_name, const char * working_dir, + const char * session_dir, bool run_in_terminal, const char * commandline, pid_t * pid_ptr); diff --git a/daemon/room_load.c b/daemon/room_load.c index 464e6987..0f3203e0 100644 --- a/daemon/room_load.c +++ b/daemon/room_load.c @@ -871,6 +871,12 @@ bool ladish_room_load_project(ladish_room_handle room_handle, const char * proje XML_SetCharacterDataHandler(parser, callback_chrdata); XML_SetUserData(parser, &parse_context); + ladish_app_supervisor_set_directory(room_ptr->app_supervisor, project_dir); + if (!ladish_app_supervisor_set_project_name(room_ptr->app_supervisor, room_ptr->project_name)) + { + ladish_app_supervisor_set_project_name(room_ptr->app_supervisor, NULL); + } + xmls = XML_ParseBuffer(parser, bytes_read, XML_TRUE); if (xmls == XML_STATUS_ERROR && !parse_context.error) { @@ -886,12 +892,6 @@ bool ladish_room_load_project(ladish_room_handle room_handle, const char * proje ladish_graph_dump(room_ptr->graph); ladish_app_supervisor_dump(room_ptr->app_supervisor); - ladish_app_supervisor_set_directory(room_ptr->app_supervisor, project_dir); - if (!ladish_app_supervisor_set_project_name(room_ptr->app_supervisor, room_ptr->project_name)) - { - ladish_app_supervisor_set_project_name(room_ptr->app_supervisor, NULL); - } - ladish_graph_trick_dicts(room_ptr->graph); ladish_try_connect_hidden_connections(room_ptr->graph); ladish_app_supervisor_autorun(room_ptr->app_supervisor); diff --git a/gui/dialogs.c b/gui/dialogs.c index 3b36f07e..39b1d437 100644 --- a/gui/dialogs.c +++ b/gui/dialogs.c @@ -55,6 +55,7 @@ void run_custom_command_dialog(void) gtk_widget_set_sensitive(GTK_WIDGET(level0_button), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(level1_button), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(level2lash_button), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(level2js_button), is_room_view(view)); gtk_window_set_focus(GTK_WINDOW(g_app_dialog), GTK_WIDGET(command_entry)); gtk_window_set_title(GTK_WINDOW(g_app_dialog), _("New application")); diff --git a/gui/gladish.ui b/gui/gladish.ui index 83316eb0..8e97a1a9 100644 --- a/gui/gladish.ui +++ b/gui/gladish.ui @@ -1258,7 +1258,7 @@ along with LADI Session Handler; if not, write to the Free Software Foundation, True app_level0 False - False + True True diff --git a/gui/world_tree.c b/gui/world_tree.c index 17bf4a0f..f60059c4 100644 --- a/gui/world_tree.c +++ b/gui/world_tree.c @@ -267,6 +267,7 @@ void on_popup_menu_action_app_properties(GtkWidget * menuitem, gpointer userdata gtk_widget_set_sensitive(GTK_WIDGET(level0_button), !running); gtk_widget_set_sensitive(GTK_WIDGET(level1_button), !running); gtk_widget_set_sensitive(GTK_WIDGET(level2lash_button), !running); + gtk_widget_set_sensitive(GTK_WIDGET(level2js_button), !running && is_room_view(view)); /* comparing pointers here is ok, because ladish_map_app_level_constant() was called by ladish_app_supervisor_get_app_properties() */ if (level == LADISH_APP_LEVEL_0)