From 34f231cc66bc746228fe367712447058d76757b3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 9 Jan 2013 20:39:06 -0500 Subject: MAINT-1481: remove linux-updater; move logic to Linux update_install Now that the viewer's own background updater logic is responsible for downloading a new installer, the only functionality we still use in linux-updater that couldn't be expressed more simply in bash is the UI. But since most Linux distros capable of running SL at all have zenity, and all will have xmessage, we can handle even the UI part. Add xmenity wrapper script so update_install doesn't have to care which is present, and make the bash script that used to launch linux-updater do the real work. --- indra/CMakeLists.txt | 3 +- indra/linux_updater/CMakeLists.txt | 51 -- indra/linux_updater/linux_updater.cpp | 926 --------------------- indra/newview/CMakeLists.txt | 1 - indra/newview/viewer_manifest.py | 17 +- .../updater/scripts/linux/update_install | 151 +++- .../updater/scripts/linux/xmenity | 55 ++ 7 files changed, 210 insertions(+), 994 deletions(-) delete mode 100644 indra/linux_updater/CMakeLists.txt delete mode 100644 indra/linux_updater/linux_updater.cpp create mode 100755 indra/viewer_components/updater/scripts/linux/xmenity diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 24c98bfada..001bb4b935 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -82,8 +82,7 @@ if (VIEWER) if (LINUX) add_subdirectory(${VIEWER_PREFIX}linux_crash_logger) - add_subdirectory(${VIEWER_PREFIX}linux_updater) - add_dependencies(viewer linux-crash-logger-strip-target linux-updater) + add_dependencies(viewer linux-crash-logger-strip-target) elseif (DARWIN) add_subdirectory(${VIEWER_PREFIX}mac_crash_logger) add_subdirectory(${VIEWER_PREFIX}mac_updater) diff --git a/indra/linux_updater/CMakeLists.txt b/indra/linux_updater/CMakeLists.txt deleted file mode 100644 index 4377a6333c..0000000000 --- a/indra/linux_updater/CMakeLists.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -*- cmake -*- - -project(linux_updater) - -include(00-Common) -include(CURL) -include(CARes) -include(OpenSSL) -include(UI) -include(LLCommon) -include(LLVFS) -include(LLXML) -include(LLUI) -include(Linking) - -include_directories( - ${LLCOMMON_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} - ${LLXML_INCLUDE_DIRS} - ${LLUI_INCLUDE_DIRS} - ${CURL_INCLUDE_DIRS} - ${CARES_INCLUDE_DIRS} - ${OPENSSL_INCLUDE_DIRS} - ${UI_INCLUDE_DIRS} - ) - -set(linux_updater_SOURCE_FILES linux_updater.cpp) - -set(linux_updater_HEADER_FILES CMakeLists.txt) - -set_source_files_properties(${linux_updater_HEADER_FILES} - PROPERTIES HEADER_FILES_ONLY TRUE) - -list(APPEND linux_updater_SOURCE_FILES ${linux_updater_HEADER_FILES}) - -add_executable(linux-updater ${linux_updater_SOURCE_FILES}) - -target_link_libraries(linux-updater - ${CURL_LIBRARIES} - ${CARES_LIBRARIES} - ${OPENSSL_LIBRARIES} - ${CRYPTO_LIBRARIES} - ${UI_LIBRARIES} - ${LLXML_LIBRARIES} - ${LLUI_LIBRARIES} - ${LLVFS_LIBRARIES} - ${LLCOMMON_LIBRARIES} - ) - -add_custom_target(linux-updater-target ALL - DEPENDS linux-updater) diff --git a/indra/linux_updater/linux_updater.cpp b/indra/linux_updater/linux_updater.cpp deleted file mode 100644 index 991dfd9dce..0000000000 --- a/indra/linux_updater/linux_updater.cpp +++ /dev/null @@ -1,926 +0,0 @@ -/** - * @file linux_updater.cpp - * @author Kyle Ambroff , Tofu Linden - * @brief Viewer update program for unix platforms that support GTK+ - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include -#include -#include - -#include "linden_common.h" -#include "llerrorcontrol.h" -#include "llfile.h" -#include "lldir.h" -#include "lldiriterator.h" - -/*==========================================================================*| -// IQA-490: Use of LLTrans -- by this program at least -- appears to be buggy. -// With it, the 3.3.2 beta 1 linux-updater.bin crashes; without it seems stable. -#include "llxmlnode.h" -#include "lltrans.h" -|*==========================================================================*/ - -static class LLTrans -{ -public: - LLTrans(); - static std::string getString(const std::string& key); - -private: - std::string _getString(const std::string& key) const; - - typedef std::map MessageMap; - MessageMap mMessages; -} sLLTransInstance; - -#include -#include -#include - -extern "C" { -#include -} - -const guint UPDATE_PROGRESS_TIMEOUT = 100; -const guint UPDATE_PROGRESS_TEXT_TIMEOUT = 1000; -const guint ROTATE_IMAGE_TIMEOUT = 8000; - -typedef struct _updater_app_state { - std::string app_name; - std::string url; - std::string file; - std::string image_dir; - std::string dest_dir; - std::string strings_dirs; - std::string strings_file; - - LLDirIterator *image_dir_iter; - - GtkWidget *window; - GtkWidget *progress_bar; - GtkWidget *image; - - double progress_value; - bool activity_mode; - - guint image_rotation_timeout_id; - guint progress_update_timeout_id; - guint update_progress_text_timeout_id; - - bool failure; -} UpdaterAppState; - -// List of entries from strings.xml to always replace -static std::set default_trans_args; -void init_default_trans_args() -{ - default_trans_args.insert("SECOND_LIFE"); // World - default_trans_args.insert("APP_NAME"); - default_trans_args.insert("SECOND_LIFE_GRID"); - default_trans_args.insert("SUPPORT_SITE"); -} - -bool translate_init(std::string comma_delim_path_list, - std::string base_xml_name) -{ - return true; -/*==========================================================================*| - init_default_trans_args(); - - // extract paths string vector from comma-delimited flat string - std::vector paths; - LLStringUtil::getTokens(comma_delim_path_list, paths, ","); // split over ',' - - for(std::vector::iterator it = paths.begin(), end_it = paths.end(); - it != end_it; - ++it) - { - (*it) = gDirUtilp->findSkinnedFilename(*it, base_xml_name); - } - - // suck the translation xml files into memory - LLXMLNodePtr root; - bool success = LLXMLNode::getLayeredXMLNode(root, paths); - if (!success) - { - // couldn't load string table XML - return false; - } - else - { - // get those strings out of the XML - LLTrans::parseStrings(root, default_trans_args); - return true; - } -|*==========================================================================*/ -} - - -void updater_app_ui_init(void); -void updater_app_quit(UpdaterAppState *app_state); -void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state); -std::string next_image_filename(std::string& image_path, LLDirIterator& iter); -void display_error(GtkWidget *parent, std::string title, std::string message); -BOOL install_package(std::string package_file, std::string destination); -BOOL spawn_viewer(UpdaterAppState *app_state); - -extern "C" { - void on_window_closed(GtkWidget *sender, GdkEvent *event, gpointer state); - gpointer worker_thread_cb(gpointer *data); - int download_progress_cb(gpointer data, double t, double d, double utotal, double ulnow); - gboolean rotate_image_cb(gpointer data); - gboolean progress_update_timeout(gpointer data); - gboolean update_progress_text_timeout(gpointer data); -} - -void updater_app_ui_init(UpdaterAppState *app_state) -{ - GtkWidget *vbox; - GtkWidget *summary_label; - GtkWidget *description_label; - GtkWidget *frame; - - llassert(app_state != NULL); - - // set up window and main container - std::string window_title = LLTrans::getString("UpdaterWindowTitle"); - app_state->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(app_state->window), - window_title.c_str()); - gtk_window_set_resizable(GTK_WINDOW(app_state->window), FALSE); - gtk_window_set_position(GTK_WINDOW(app_state->window), - GTK_WIN_POS_CENTER_ALWAYS); - - gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12); - g_signal_connect(G_OBJECT(app_state->window), "delete-event", - G_CALLBACK(on_window_closed), app_state); - - vbox = gtk_vbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(app_state->window), vbox); - - // set top label - std::ostringstream label_ostr; - label_ostr << "" - << LLTrans::getString("UpdaterNowUpdating") - << ""; - - summary_label = gtk_label_new(NULL); - gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE); - gtk_label_set_markup(GTK_LABEL(summary_label), - label_ostr.str().c_str()); - gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5); - gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0); - - // create the description label - description_label = gtk_label_new(LLTrans::getString("UpdaterUpdatingDescriptive").c_str()); - gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE); - gtk_misc_set_alignment(GTK_MISC(description_label), 0, 0.5); - gtk_box_pack_start(GTK_BOX(vbox), description_label, FALSE, FALSE, 0); - - // If an image path has been set, load the background images - if (!app_state->image_dir.empty()) { - frame = gtk_frame_new(NULL); - gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - - // load the first image - app_state->image = gtk_image_new_from_file - (next_image_filename(app_state->image_dir, *app_state->image_dir_iter).c_str()); - gtk_widget_set_size_request(app_state->image, 340, 310); - gtk_container_add(GTK_CONTAINER(frame), app_state->image); - - // rotate the images every 5 seconds - app_state->image_rotation_timeout_id = g_timeout_add - (ROTATE_IMAGE_TIMEOUT, rotate_image_cb, app_state); - } - - // set up progress bar, and update it roughly every 1/10 of a second - app_state->progress_bar = gtk_progress_bar_new(); - gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar), - LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str()); - gtk_box_pack_start(GTK_BOX(vbox), - app_state->progress_bar, FALSE, TRUE, 0); - app_state->progress_update_timeout_id = g_timeout_add - (UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state); - app_state->update_progress_text_timeout_id = g_timeout_add - (UPDATE_PROGRESS_TEXT_TIMEOUT, update_progress_text_timeout, app_state); - - gtk_widget_show_all(app_state->window); -} - -gboolean rotate_image_cb(gpointer data) -{ - UpdaterAppState *app_state; - std::string filename; - - llassert(data != NULL); - app_state = (UpdaterAppState *) data; - - filename = next_image_filename(app_state->image_dir, *app_state->image_dir_iter); - - gdk_threads_enter(); - gtk_image_set_from_file(GTK_IMAGE(app_state->image), filename.c_str()); - gdk_threads_leave(); - - return TRUE; -} - -std::string next_image_filename(std::string& image_path, LLDirIterator& iter) -{ - std::string image_filename; - iter.next(image_filename); - return gDirUtilp->add(image_path, image_filename); -} - -void on_window_closed(GtkWidget *sender, GdkEvent* event, gpointer data) -{ - UpdaterAppState *app_state; - - llassert(data != NULL); - app_state = (UpdaterAppState *) data; - - updater_app_quit(app_state); -} - -void updater_app_quit(UpdaterAppState *app_state) -{ - if (app_state != NULL) - { - g_source_remove(app_state->progress_update_timeout_id); - - if (!app_state->image_dir.empty()) - { - g_source_remove(app_state->image_rotation_timeout_id); - } - } - - gtk_main_quit(); -} - -void display_error(GtkWidget *parent, std::string title, std::string message) -{ - GtkWidget *dialog; - - dialog = gtk_message_dialog_new(GTK_WINDOW(parent), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - "%s", message.c_str()); - gtk_window_set_title(GTK_WINDOW(dialog), title.c_str()); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); -} - -gpointer worker_thread_cb(gpointer data) -{ - UpdaterAppState *app_state; - CURL *curl; - CURLcode result; - FILE *package_file; - GError *error = NULL; - int fd; - - //g_return_val_if_fail (data != NULL, NULL); - app_state = (UpdaterAppState *) data; - - try { - - if(!app_state->url.empty()) - { - char* tmp_local_filename = NULL; - // create temporary file to store the package. - fd = g_file_open_tmp - ("secondlife-update-XXXXXX", &tmp_local_filename, &error); - if (error != NULL) - { - llerrs << "Unable to create temporary file: " - << error->message - << llendl; - - g_error_free(error); - throw 0; - } - - if(tmp_local_filename != NULL) - { - app_state->file = tmp_local_filename; - g_free(tmp_local_filename); - } - - package_file = fdopen(fd, "wb"); - if (package_file == NULL) - { - llerrs << "Failed to create temporary file: " - << app_state->file.c_str() - << llendl; - - gdk_threads_enter(); - display_error(app_state->window, - LLTrans::getString("UpdaterFailDownloadTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); - gdk_threads_leave(); - throw 0; - } - - // initialize curl and start downloading the package - llinfos << "Downloading package: " << app_state->url << llendl; - - curl = curl_easy_init(); - if (curl == NULL) - { - llerrs << "Failed to initialize libcurl" << llendl; - - gdk_threads_enter(); - display_error(app_state->window, - LLTrans::getString("UpdaterFailDownloadTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); - gdk_threads_leave(); - throw 0; - } - - curl_easy_setopt(curl, CURLOPT_URL, app_state->url.c_str()); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, TRUE); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE); - curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, - &download_progress_cb); - curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state); - - result = curl_easy_perform(curl); - fclose(package_file); - curl_easy_cleanup(curl); - - if (result) - { - llerrs << "Failed to download update: " - << app_state->url - << llendl; - - gdk_threads_enter(); - display_error(app_state->window, - LLTrans::getString("UpdaterFailDownloadTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); - gdk_threads_leave(); - - throw 0; - } - } - - // now pulse the progres bar back and forth while the package is - // being unpacked - gdk_threads_enter(); - std::string installing_msg = LLTrans::getString("UpdaterNowInstalling"); - gtk_progress_bar_set_text( - GTK_PROGRESS_BAR(app_state->progress_bar), - installing_msg.c_str()); - app_state->activity_mode = TRUE; - gdk_threads_leave(); - - // *TODO: if the destination is not writable, terminate this - // thread and show file chooser? - if (!install_package(app_state->file.c_str(), app_state->dest_dir)) - { - llwarns << "Failed to install package to destination: " - << app_state->dest_dir - << llendl; - - gdk_threads_enter(); - display_error(app_state->window, - LLTrans::getString("UpdaterFailInstallTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); - //"Failed to update " + app_state->app_name, - gdk_threads_leave(); - throw 0; - } - - // try to spawn the new viewer - if (!spawn_viewer(app_state)) - { - llwarns << "Viewer was not installed properly in : " - << app_state->dest_dir - << llendl; - - gdk_threads_enter(); - display_error(app_state->window, - LLTrans::getString("UpdaterFailStartTitle"), - LLTrans::getString("UpdaterFailUpdateDescriptive")); - gdk_threads_leave(); - throw 0; - } - } - catch (...) - { - app_state->failure = TRUE; - } - - gdk_threads_enter(); - updater_app_quit(app_state); - gdk_threads_leave(); - - return NULL; -} - - -gboolean less_anal_gspawnsync(gchar **argv, - gchar **stderr_output, - gint *child_exit_status, - GError **spawn_error) -{ - // store current SIGCHLD handler if there is one, replace with default - // handler to make glib happy - struct sigaction sigchld_backup; - struct sigaction sigchld_appease_glib; - sigchld_appease_glib.sa_handler = SIG_DFL; - sigemptyset(&sigchld_appease_glib.sa_mask); - sigchld_appease_glib.sa_flags = 0; - sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup); - - gboolean rtn = g_spawn_sync(NULL, - argv, - NULL, - (GSpawnFlags) (G_SPAWN_STDOUT_TO_DEV_NULL), - NULL, - NULL, - NULL, - stderr_output, - child_exit_status, - spawn_error); - - // restore SIGCHLD handler - sigaction(SIGCHLD, &sigchld_backup, NULL); - - return rtn; -} - - -// perform a rename, or perform a (prompted) root rename if that fails -int -rename_with_sudo_fallback(const std::string& filename, const std::string& newname) -{ - int rtncode = ::rename(filename.c_str(), newname.c_str()); - lldebugs << "rename result is: " << rtncode << " / " << errno << llendl; - if (rtncode && (EACCES == errno || EPERM == errno || EXDEV == errno)) - { - llinfos << "Permission problem in rename, or moving between different mount points. Retrying as a mv under a sudo." << llendl; - // failed due to permissions, try again as a gksudo or kdesu mv wrapper hack - char *sudo_cmd = NULL; - sudo_cmd = g_find_program_in_path("gksudo"); - if (!sudo_cmd) - { - sudo_cmd = g_find_program_in_path("kdesu"); - } - if (sudo_cmd) - { - char *mv_cmd = NULL; - mv_cmd = g_find_program_in_path("mv"); - if (mv_cmd) - { - char *src_string_copy = g_strdup(filename.c_str()); - char *dst_string_copy = g_strdup(newname.c_str()); - char* argv[] = - { - sudo_cmd, - mv_cmd, - src_string_copy, - dst_string_copy, - NULL - }; - - gchar *stderr_output = NULL; - gint child_exit_status = 0; - GError *spawn_error = NULL; - if (!less_anal_gspawnsync(argv, &stderr_output, - &child_exit_status, &spawn_error)) - { - llwarns << "Failed to spawn child process: " - << spawn_error->message - << llendl; - } - else if (child_exit_status) - { - llwarns << "mv command failed: " - << (stderr_output ? stderr_output : "(no reason given)") - << llendl; - } - else - { - // everything looks good, clear the error code - rtncode = 0; - } - - g_free(src_string_copy); - g_free(dst_string_copy); - if (spawn_error) g_error_free(spawn_error); - } - } - } - return rtncode; -} - -gboolean install_package(std::string package_file, std::string destination) -{ - char *tar_cmd = NULL; - std::ostringstream command; - - // Find the absolute path to the 'tar' command. - tar_cmd = g_find_program_in_path("tar"); - if (!tar_cmd) - { - llerrs << "`tar' was not found in $PATH" << llendl; - return FALSE; - } - llinfos << "Found tar command: " << tar_cmd << llendl; - - // Unpack the tarball in a temporary place first, then move it to - // its final destination - std::string tmp_dest_dir = gDirUtilp->getTempFilename(); - if (LLFile::mkdir(tmp_dest_dir, 0744)) - { - llerrs << "Failed to create directory: " - << destination - << llendl; - - return FALSE; - } - - char *package_file_string_copy = g_strdup(package_file.c_str()); - char *tmp_dest_dir_string_copy = g_strdup(tmp_dest_dir.c_str()); - gchar *argv[8] = { - tar_cmd, - const_cast("--strip"), const_cast("1"), - const_cast("-xjf"), - package_file_string_copy, - const_cast("-C"), tmp_dest_dir_string_copy, - NULL, - }; - - llinfos << "Untarring package: " << package_file << llendl; - - // store current SIGCHLD handler if there is one, replace with default - // handler to make glib happy - struct sigaction sigchld_backup; - struct sigaction sigchld_appease_glib; - sigchld_appease_glib.sa_handler = SIG_DFL; - sigemptyset(&sigchld_appease_glib.sa_mask); - sigchld_appease_glib.sa_flags = 0; - sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup); - - gchar *stderr_output = NULL; - gint child_exit_status = 0; - GError *untar_error = NULL; - if (!less_anal_gspawnsync(argv, &stderr_output, - &child_exit_status, &untar_error)) - { - llwarns << "Failed to spawn child process: " - << untar_error->message - << llendl; - return FALSE; - } - - if (child_exit_status) - { - llwarns << "Untar command failed: " - << (stderr_output ? stderr_output : "(no reason given)") - << llendl; - return FALSE; - } - - g_free(tar_cmd); - g_free(package_file_string_copy); - g_free(tmp_dest_dir_string_copy); - g_free(stderr_output); - if (untar_error) g_error_free(untar_error); - - // move the existing package out of the way if it exists - if (gDirUtilp->fileExists(destination)) - { - std::string backup_dir = destination + ".backup"; - int oldcounter = 1; - while (gDirUtilp->fileExists(backup_dir)) - { - // find a foo.backup.N folder name that isn't taken yet - backup_dir = destination + ".backup." + llformat("%d", oldcounter); - ++oldcounter; - } - - if (rename_with_sudo_fallback(destination, backup_dir)) - { - llwarns << "Failed to move directory: '" - << destination << "' -> '" << backup_dir - << llendl; - return FALSE; - } - } - - // The package has been unpacked in a staging directory, now we just - // need to move it to its destination. - if (rename_with_sudo_fallback(tmp_dest_dir, destination)) - { - llwarns << "Failed to move installation to the destination: " - << destination - << llendl; - return FALSE; - } - - // \0/ Success! - return TRUE; -} - -gboolean progress_update_timeout(gpointer data) -{ - UpdaterAppState *app_state; - - llassert(data != NULL); - - app_state = (UpdaterAppState *) data; - - gdk_threads_enter(); - if (app_state->activity_mode) - { - gtk_progress_bar_pulse - (GTK_PROGRESS_BAR(app_state->progress_bar)); - } - else - { - gtk_progress_set_value(GTK_PROGRESS(app_state->progress_bar), - app_state->progress_value); - } - gdk_threads_leave(); - - return TRUE; -} - -gboolean update_progress_text_timeout(gpointer data) -{ - UpdaterAppState *app_state; - - llassert(data != NULL); - app_state = (UpdaterAppState *) data; - - if (app_state->activity_mode == TRUE) - { - // We no longer need this timeout, it will be removed. - return FALSE; - } - - if (!app_state->progress_value) - { - return TRUE; - } - - std::string progress_text = llformat((LLTrans::getString("UpdaterProgressBarText")+" (%.0f%%)").c_str(), app_state->progress_value); - - gdk_threads_enter(); - gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar), - progress_text.c_str()); - gdk_threads_leave(); - - return TRUE; -} - -int download_progress_cb(gpointer data, - double t, - double d, - double utotal, - double ulnow) -{ - UpdaterAppState *app_state; - - llassert(data != NULL); - app_state = (UpdaterAppState *) data; - - if (t <= 0.0) - { - app_state->progress_value = 0; - } - else - { - app_state->progress_value = d * 100.0 / t; - } - return 0; -} - -BOOL spawn_viewer(UpdaterAppState *app_state) -{ - llassert(app_state != NULL); - - std::string cmd = app_state->dest_dir + "/secondlife"; - GError *error = NULL; - - // We want to spawn the Viewer on the same display as the updater app - gboolean success = gdk_spawn_command_line_on_screen - (gtk_widget_get_screen(app_state->window), cmd.c_str(), &error); - - if (!success) - { - llwarns << "Failed to launch viewer: " << error->message - << llendl; - } - - if (error) g_error_free(error); - - return success; -} - -void show_usage_and_exit() -{ - std::cout << "Usage: linux-updater <--url URL | --file FILE> --name NAME --dest PATH --stringsdir PATH1,PATH2 --stringsfile FILE" - << "[--image-dir PATH]" - << std::endl; - exit(1); -} - -void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state) -{ - int i; - - for (i = 1; i < argc; i++) - { - if ((!strcmp(argv[i], "--url")) && (++i < argc)) - { - app_state->url = argv[i]; - } - else if ((!strcmp(argv[i], "--file")) && (++i < argc)) - { - app_state->file = argv[i]; - } - else if ((!strcmp(argv[i], "--name")) && (++i < argc)) - { - app_state->app_name = argv[i]; - } - else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc)) - { - app_state->image_dir = argv[i]; - app_state->image_dir_iter = new LLDirIterator(argv[i], "*.jpg"); - } - else if ((!strcmp(argv[i], "--dest")) && (++i < argc)) - { - app_state->dest_dir = argv[i]; - } - else if ((!strcmp(argv[i], "--stringsdir")) && (++i < argc)) - { - app_state->strings_dirs = argv[i]; - } - else if ((!strcmp(argv[i], "--stringsfile")) && (++i < argc)) - { - app_state->strings_file = argv[i]; - } - else - { - // show usage, an invalid option was given. - show_usage_and_exit(); - } - } - - if (app_state->app_name.empty() - || (app_state->url.empty() && app_state->file.empty()) - || app_state->dest_dir.empty()) - { - show_usage_and_exit(); - } - - app_state->progress_value = 0.0; - app_state->activity_mode = FALSE; - app_state->failure = FALSE; - - translate_init(app_state->strings_dirs, app_state->strings_file); -} - -int main(int argc, char **argv) -{ - UpdaterAppState* app_state = new UpdaterAppState; - GThread *worker_thread; - - parse_args_and_init(argc, argv, app_state); - - // Initialize logger, and rename old log file - gDirUtilp->initAppDirs("SecondLife"); - LLError::initForApplication - (gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); - std::string old_log_file = gDirUtilp->getExpandedFilename - (LL_PATH_LOGS, "updater.log.old"); - std::string log_file = - gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log"); - LLFile::rename(log_file, old_log_file); - LLError::logToFile(log_file); - - // initialize gthreads and gtk+ - if (!g_thread_supported()) - { - g_thread_init(NULL); - gdk_threads_init(); - } - - gtk_init(&argc, &argv); - - // create UI - updater_app_ui_init(app_state); - - //llinfos << "SAMPLE TRANSLATION IS: " << LLTrans::getString("LoginInProgress") << llendl; - - // create download thread - worker_thread = g_thread_create - (GThreadFunc(worker_thread_cb), app_state, FALSE, NULL); - - gdk_threads_enter(); - gtk_main(); - gdk_threads_leave(); - - // Delete the file only if created from url download. - if(!app_state->url.empty() && !app_state->file.empty()) - { - if (gDirUtilp->fileExists(app_state->file)) - { - LLFile::remove(app_state->file); - } - } - - bool success = !app_state->failure; - delete app_state->image_dir_iter; - delete app_state; - return success ? 0 : 1; -} - -/***************************************************************************** -* Dummy LLTrans implementation (IQA-490) -*****************************************************************************/ -static LLTrans sStaticStrings; - -// lookup -std::string LLTrans::_getString(const std::string& key) const -{ - MessageMap::const_iterator found = mMessages.find(key); - if (found != mMessages.end()) - { - return found->second; - } - LL_WARNS("linux_updater") << "No message for key '" << key - << "' -- add to LLTrans::LLTrans() in linux_updater.cpp" - << LL_ENDL; - return key; -} - -// static lookup -std::string LLTrans::getString(const std::string& key) -{ - return sLLTransInstance._getString(key); -} - -// initialization -LLTrans::LLTrans() -{ - typedef std::pair Pair; - static const Pair data[] = - { - Pair("UpdaterFailDownloadTitle", - "Failed to download update"), - Pair("UpdaterFailInstallTitle", - "Failed to install update"), - Pair("UpdaterFailStartTitle", - "Failed to start viewer"), - Pair("UpdaterFailUpdateDescriptive", - "An error occurred while updating Second Life. " - "Please download the latest version from www.secondlife.com."), - Pair("UpdaterNowInstalling", - "Installing Second Life..."), - Pair("UpdaterNowUpdating", - "Now updating Second Life..."), - Pair("UpdaterProgressBarText", - "Downloading update"), - Pair("UpdaterProgressBarTextWithEllipses", - "Downloading update..."), - Pair("UpdaterUpdatingDescriptive", - "Your Second Life Viewer is being updated to the latest release. " - "This may take some time, so please be patient."), - Pair("UpdaterWindowTitle", - "Second Life Update") - }; - - BOOST_FOREACH(Pair pair, data) - { - mMessages[pair.first] = pair.second; - } -} diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 0197ac794f..22960a2d0d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1865,7 +1865,6 @@ if (LINUX) set(COPY_INPUT_DEPENDENCIES ${VIEWER_BINARY_NAME} linux-crash-logger - linux-updater SLPlugin media_plugin_webkit media_plugin_gstreamer010 diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index ea75d4f4f6..bc623aee00 100644 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -968,9 +968,9 @@ class LinuxManifest(ViewerManifest): if self.prefix(src="", dst="bin"): self.path("secondlife-bin","do-not-directly-run-secondlife-bin") self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin") - self.path("../linux_updater/linux-updater", "linux-updater.bin") self.path2basename("../llplugin/slplugin", "SLPlugin") self.path2basename("../viewer_components/updater/scripts/linux", "update_install") + self.path2basename("../viewer_components/updater/scripts/linux", "xmenity") self.end_prefix("bin") if self.prefix("res-sdl"): @@ -1001,7 +1001,7 @@ class LinuxManifest(ViewerManifest): def copy_finish(self): # Force executable permissions to be set for scripts # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802 - for script in 'secondlife', 'bin/update_install': + for script in 'secondlife', 'bin/update_install', 'bin/xmenity': self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script)) def package_finish(self): @@ -1017,9 +1017,7 @@ class LinuxManifest(ViewerManifest): else: installer_name += '_' + self.channel_oneword().upper() - if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): - print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build" - self.run_command("find %(d)r/bin %(d)r/lib -type f \\! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure + self.strip_binaries() # Fix access permissions self.run_command(""" @@ -1054,6 +1052,11 @@ class LinuxManifest(ViewerManifest): 'dst': self.get_dst_prefix(), 'inst': self.build_path_of(installer_name)}) + def strip_binaries(self): + if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): + print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build" + self.run_command(r"find %(d)r/bin %(d)r/lib -type f \! \( -name update_install -o -name xmenity \) | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure + class Linux_i686Manifest(LinuxManifest): def construct(self): super(Linux_i686Manifest, self).construct() @@ -1139,9 +1142,7 @@ class Linux_i686Manifest(LinuxManifest): self.path("libvivoxplatform.so") self.end_prefix("lib") - if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): - print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build" - self.run_command("find %(d)r/bin %(d)r/lib -type f \\! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure + self.strip_binaries() class Linux_x86_64Manifest(LinuxManifest): diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install index e0505a9f72..7c08966830 100644 --- a/indra/viewer_components/updater/scripts/linux/update_install +++ b/indra/viewer_components/updater/scripts/linux/update_install @@ -1,10 +1,149 @@ #! /bin/bash -INSTALL_DIR=$(cd "$(dirname "$0")/.." ; pwd) -export LD_LIBRARY_PATH="$INSTALL_DIR/lib" -bin/linux-updater.bin --file "$1" --dest "$INSTALL_DIR" --name "Second Life Viewer" --stringsdir "$INSTALL_DIR/skins/default/xui/en" --stringsfile "strings.xml" -if [ $? -ne 0 ] - then echo $3 >> "$2" +# @file update_install +# @author Nat Goodspeed +# @date 2013-01-09 +# @brief Update the containing Second Life application bundle to the version in +# the specified tarball. +# +# This bash implementation is derived from the previous linux-updater.bin +# application. +# +# $LicenseInfo:firstyear=2013&license=viewerlgpl$ +# Copyright (c) 2013, Linden Research, Inc. +# $/LicenseInfo$ + +tarball="$1" # the file to install +markerfile="$2" # create this file on failure +mandatory="$3" # what to write to markerfile on failure + +function log { + # our log file will be open as stderr -- but until we set up that + # redirection, logging to stderr is better than nothing + echo "$*" 1>&2 +} + +function status { + log "$@" + # Prefix with '#' so xmenity will recognize it as a status message + echo "#$*" +} + +function fail { + # Log the message + log "$@" + # tell subsequent viewer things went south + echo "$mandatory" > "$markerfile" + # add boilerplate + local msg="An error occurred while updating Second Life: +$* +Please download the latest viewer from www.secondlife.com." + # Restate test from xmenity to detect whether we can use zenity or must + # fall back to xmessage + zenpath="$(which zenity)" + if [ -n "$zenpath" -a -x "$zenpath" ] + then "$zenpath" --error --title "Second Life Viewer Updater" \ + --width=320 --height=120 --text="$msg" + else xmessage -buttons -OK:2 -center "$msg" + fi + exit 1 +} + +function sudo_mv { + # If we have write permission to both parent directories, shouldn't need + # sudo. + if [ -w "$(dirname "$1")" -a -w "$(dirname "$2")" ] + then mv "$1" "$2" || fail "Couldn't move $1 to $2" + else # use one of the likely sudo programs + sudo="$(which gksudo)" + if [ -z "$sudo" ] + then sudo="$(which kdesu)" + fi + if [ -z "$sudo" ] + then # couldn't find either one, just try it anyway + mv "$1" "$2" || fail "Couldn't move $1 to $2" + else # even with sudo, could fail, e.g. different filesystems + "$sudo" mv "$1" "$2" || fail "Couldn't move $1 to $2" + fi + fi +} + +# empty array +cleanups=() + +function cleanup { + # wacky bash syntax for appending to array + cleanups[${#cleanups[*]}]="$*" +} + +function onexit { + for action in "${cleanups[@]}" + do # don't quote, support actions consisting of multiple words + $action + done +} + +trap 'onexit' EXIT + +mydir="$(dirname "$0")" +# We happen to know that the viewer specifies a marker-file pathname within +# the logs directory. +logsdir="$(dirname "$markerfile")" +logname="$logsdir/updater.log" + +# move aside old updater.log; we're about to create a new one +[ -f "$logname" ] && mv "$logname" "$logname.old" + +# Set up redirections for this script such that stderr is logged, while +# special stdout messages drive our UI, as described in xmenity. +exec 2> "$logname" | "$mydir/xmenity" +# Piping to xmenity requires that we end with a line consisting of the string +# "100" to terminate zenity progress bar. +cleanup echo 100 + +# Rather than setting up a special pipeline to timestamp every line of stderr, +# produce header lines into log file indicating timestamp and the arguments +# with which we were invoked. +date 1>&2 +log "$0 $*" + +# Log every command we execute, along with any stderr it might produce +set -x + +status 'Installing Second Life...' + +# Creating tempdir under /tmp means it's possible that tempdir is on a +# different filesystem than INSTALL_DIR. One is tempted to create tempdir on a +# path derived from `dirname INSTALL_DIR`, but then we might need to add +# another sudo prompt to create it. +tempdir="/tmp/$(basename "$0").$$" +tempinstall="$tempdir/install" +mkdir -p "$tempinstall" || fail "Couldn't create $tempinstall" +cleanup rm -rf "$tempdir" + +# If we already knew the name of the tarball's top-level directory, we could +# just move that when all was said and done. Since we don't, untarring to the +# 'install' subdir with --strip 1 effectively renames that top-level +# directory. +tar --strip 1 -xjf "$tarball" -C "$tempinstall" || fail "Untar command failed" + +INSTALL_DIR="$(cd "$mydir/.." ; pwd)" + +# Considering we're launched from a subdirectory of INSTALL_DIR, would be +# surprising if it did NOT already exist... +if [ -f "$INSTALL_DIR" ] +then backup="$INSTALL_DIR.backup" + backupn=1 + while [ -f "$backup" ] + do backup="$INSTALL_DIR.backup.$backupn" + ((backupn += 1)) + done + sudo_mv "$INSTALL_DIR" "$backup" fi +# We unpacked the tarball into tempinstall. Move that. +sudo_mv "$tempinstall" "$INSTALL_DIR" + +rm -f "$tarball" -rm -f "$1" +# launch the updated viewer +"$INSTALL_DIR/secondlife" & diff --git a/indra/viewer_components/updater/scripts/linux/xmenity b/indra/viewer_components/updater/scripts/linux/xmenity new file mode 100755 index 0000000000..c0c033904c --- /dev/null +++ b/indra/viewer_components/updater/scripts/linux/xmenity @@ -0,0 +1,55 @@ +#!/bin/bash + +# @file xmenity +# @author Nat Goodspeed +# @date 2013-01-09 +# @brief Provide progress UI for bash scripts (e.g. update_install) using +# zenity if available, xmessage if not. +# +# $LicenseInfo:firstyear=2013&license=viewerlgpl$ +# Copyright (c) 2013, Linden Research, Inc. +# $/LicenseInfo$ + +# This script invokes either zenity --progress or, if zenity is unavailable, +# wraps xmessage in a zenity-like interface. That is its mutant power. +# Pass $1 as the title for your zenity box. It is ignored by xmessage. +# Send updates on stdin: +# A line containing only a decimal integer from 0 - 100 sets that progress. +# End with 100 to tell zenity to terminate. +# A line starting with '#' replaces the progress text. +# All other stdin lines are ignored. + +zenpath="$(which zenity)" +if [ -n "$zenpath" -a -x "$zenpath" ] +then # if executable zenity is on PATH, run that instead of this. + exec "$zenpath" --progress --title="$1" --auto-close --width=320 --height=120 +fi + +# Arriving here means we don't have zenity available. The remainder of this +# script is the xmessage wrapper. + +# We operate by leaving one background xmessage process running. This is the +# pid of that process. +xmpid="" + +function clear_message { + [ -n "$xmpid" ] && kill $xmpid + xmpid="" +} + +# Cancel any pending xmessage, regardless of how we exit. +trap 'clear_message' EXIT + +while read line +do # terminate like zenity --progress + [ "$line" == "100" ] && break + # ignore everything but replacement text + nohash="${line#'#'}" + # if stripping leading hash doesn't change line, it doesn't have one + [ "$nohash" == "$line" ] && continue + # clear any previous message + clear_message + # put up a new xmessage and capture its pid + xmessage -buttons OK:2 -center "$nohash" & + xmpid=$! +done -- cgit v1.2.3 From 6e9782f79f6d3cac2bfeb72c6cd43b409020c76e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 10 Jan 2013 09:40:20 -0500 Subject: MAINT-1481: minor bug fix plus incomplete UI tweaks. Test for existence of target name using -e rather than -f. (-d would work too, but in this case we must respond to any name collision, whether file or directory.) Instead of terminating on failure, make sudo_mv return rc of the [sudo] mv command to its caller. If the attempt to move new install to actual viewer directory fails, restore previous viewer before failing. When redirecting the script's stderr to updater.log, first save existing stderr to another file descriptor, and restore it when we launch viewer. Otherwise updater.log ends up collecting the viewer's duplicate stderr log output! The construct 'exec ... | program' doesn't work. In fact it causes any other redirections on that command to fail too. Remove it -- real fix pending. --- .../updater/scripts/linux/update_install | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install index 7c08966830..167e2b7881 100644 --- a/indra/viewer_components/updater/scripts/linux/update_install +++ b/indra/viewer_components/updater/scripts/linux/update_install @@ -53,7 +53,7 @@ function sudo_mv { # If we have write permission to both parent directories, shouldn't need # sudo. if [ -w "$(dirname "$1")" -a -w "$(dirname "$2")" ] - then mv "$1" "$2" || fail "Couldn't move $1 to $2" + then mv "$1" "$2" else # use one of the likely sudo programs sudo="$(which gksudo)" if [ -z "$sudo" ] @@ -61,9 +61,9 @@ function sudo_mv { fi if [ -z "$sudo" ] then # couldn't find either one, just try it anyway - mv "$1" "$2" || fail "Couldn't move $1 to $2" + mv "$1" "$2" else # even with sudo, could fail, e.g. different filesystems - "$sudo" mv "$1" "$2" || fail "Couldn't move $1 to $2" + "$sudo" mv "$1" "$2" fi fi } @@ -94,9 +94,9 @@ logname="$logsdir/updater.log" # move aside old updater.log; we're about to create a new one [ -f "$logname" ] && mv "$logname" "$logname.old" -# Set up redirections for this script such that stderr is logged, while -# special stdout messages drive our UI, as described in xmenity. -exec 2> "$logname" | "$mydir/xmenity" +# Set up redirections for this script such that stderr is logged. (But first +# move the previous stderr to file descriptor 3.) +exec 3>&2- 2> "$logname" # Piping to xmenity requires that we end with a line consisting of the string # "100" to terminate zenity progress bar. cleanup echo 100 @@ -131,19 +131,25 @@ INSTALL_DIR="$(cd "$mydir/.." ; pwd)" # Considering we're launched from a subdirectory of INSTALL_DIR, would be # surprising if it did NOT already exist... -if [ -f "$INSTALL_DIR" ] +if [ -e "$INSTALL_DIR" ] then backup="$INSTALL_DIR.backup" backupn=1 - while [ -f "$backup" ] + while [ -e "$backup" ] do backup="$INSTALL_DIR.backup.$backupn" ((backupn += 1)) done - sudo_mv "$INSTALL_DIR" "$backup" + sudo_mv "$INSTALL_DIR" "$backup" || fail "Couldn't move $INSTALL_DIR to $backup" fi # We unpacked the tarball into tempinstall. Move that. -sudo_mv "$tempinstall" "$INSTALL_DIR" +if ! sudo_mv "$tempinstall" "$INSTALL_DIR" +then # If we failed to move the temp install to INSTALL_DIR, try to restore + # INSTALL_DIR from backup + sudo_mv "$backup" "$INSTALL_DIR" + fail "Couldn't move $1 to $2" +fi rm -f "$tarball" -# launch the updated viewer -"$INSTALL_DIR/secondlife" & +# Launch the updated viewer. Restore original stderr from file descriptor 3, +# though -- otherwise updater.log gets cluttered with the viewer log! +"$INSTALL_DIR/secondlife" 2>&3- & -- cgit v1.2.3 From 83f625445b87b8c5cb53c1a152f03402c0606dee Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 11 Jan 2013 12:24:44 -0500 Subject: MAINT-1481: Remove xmenity script and viewer_manifest.py references. --- indra/newview/viewer_manifest.py | 5 +- .../updater/scripts/linux/xmenity | 55 ---------------------- 2 files changed, 2 insertions(+), 58 deletions(-) delete mode 100755 indra/viewer_components/updater/scripts/linux/xmenity diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index bc623aee00..e7108141ee 100644 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -970,7 +970,6 @@ class LinuxManifest(ViewerManifest): self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin") self.path2basename("../llplugin/slplugin", "SLPlugin") self.path2basename("../viewer_components/updater/scripts/linux", "update_install") - self.path2basename("../viewer_components/updater/scripts/linux", "xmenity") self.end_prefix("bin") if self.prefix("res-sdl"): @@ -1001,7 +1000,7 @@ class LinuxManifest(ViewerManifest): def copy_finish(self): # Force executable permissions to be set for scripts # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802 - for script in 'secondlife', 'bin/update_install', 'bin/xmenity': + for script in 'secondlife', 'bin/update_install': self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script)) def package_finish(self): @@ -1055,7 +1054,7 @@ class LinuxManifest(ViewerManifest): def strip_binaries(self): if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build" - self.run_command(r"find %(d)r/bin %(d)r/lib -type f \! \( -name update_install -o -name xmenity \) | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure + self.run_command(r"find %(d)r/bin %(d)r/lib -type f \! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure class Linux_i686Manifest(LinuxManifest): def construct(self): diff --git a/indra/viewer_components/updater/scripts/linux/xmenity b/indra/viewer_components/updater/scripts/linux/xmenity deleted file mode 100755 index c0c033904c..0000000000 --- a/indra/viewer_components/updater/scripts/linux/xmenity +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -# @file xmenity -# @author Nat Goodspeed -# @date 2013-01-09 -# @brief Provide progress UI for bash scripts (e.g. update_install) using -# zenity if available, xmessage if not. -# -# $LicenseInfo:firstyear=2013&license=viewerlgpl$ -# Copyright (c) 2013, Linden Research, Inc. -# $/LicenseInfo$ - -# This script invokes either zenity --progress or, if zenity is unavailable, -# wraps xmessage in a zenity-like interface. That is its mutant power. -# Pass $1 as the title for your zenity box. It is ignored by xmessage. -# Send updates on stdin: -# A line containing only a decimal integer from 0 - 100 sets that progress. -# End with 100 to tell zenity to terminate. -# A line starting with '#' replaces the progress text. -# All other stdin lines are ignored. - -zenpath="$(which zenity)" -if [ -n "$zenpath" -a -x "$zenpath" ] -then # if executable zenity is on PATH, run that instead of this. - exec "$zenpath" --progress --title="$1" --auto-close --width=320 --height=120 -fi - -# Arriving here means we don't have zenity available. The remainder of this -# script is the xmessage wrapper. - -# We operate by leaving one background xmessage process running. This is the -# pid of that process. -xmpid="" - -function clear_message { - [ -n "$xmpid" ] && kill $xmpid - xmpid="" -} - -# Cancel any pending xmessage, regardless of how we exit. -trap 'clear_message' EXIT - -while read line -do # terminate like zenity --progress - [ "$line" == "100" ] && break - # ignore everything but replacement text - nohash="${line#'#'}" - # if stripping leading hash doesn't change line, it doesn't have one - [ "$nohash" == "$line" ] && continue - # clear any previous message - clear_message - # put up a new xmessage and capture its pid - xmessage -buttons OK:2 -center "$nohash" & - xmpid=$! -done -- cgit v1.2.3 From 9e755ee98d6851154874a735c559c80509d7501d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 11 Jan 2013 12:36:03 -0500 Subject: MAINT-1481: Clean up update_install UI, including error output. Capture actual error output from mkdir and mv; display it to user. Introduce mysudo function used by sudo_mv function for graphical sudo command. Since update_install actually only displays a single status message, just use zenity --info instead of a zenity progress box: need not update its message. Borrow semantics for clear_message and status functions from xmenity script. Introduce errorbox function so we only have to make zenity/xmessage test once. Move cleanup, onexit to top so we can use for clear_message. --- .../updater/scripts/linux/update_install | 164 ++++++++++++++------- 1 file changed, 113 insertions(+), 51 deletions(-) diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install index 167e2b7881..0f624c4dee 100644 --- a/indra/viewer_components/updater/scripts/linux/update_install +++ b/indra/viewer_components/updater/scripts/linux/update_install @@ -13,78 +13,136 @@ # Copyright (c) 2013, Linden Research, Inc. # $/LicenseInfo$ +# **************************************************************************** +# script parameters +# **************************************************************************** tarball="$1" # the file to install markerfile="$2" # create this file on failure mandatory="$3" # what to write to markerfile on failure +# **************************************************************************** +# helper functions +# **************************************************************************** +# empty array +cleanups=() + +# add a cleanup action to execute on exit +function cleanup { + # wacky bash syntax for appending to array + cleanups[${#cleanups[*]}]="$*" +} + +# called implicitly on exit +function onexit { + for action in "${cleanups[@]}" + do # don't quote, support actions consisting of multiple words + $action + done +} +trap 'onexit' EXIT + +# write to log file function log { # our log file will be open as stderr -- but until we set up that # redirection, logging to stderr is better than nothing echo "$*" 1>&2 } -function status { - log "$@" - # Prefix with '#' so xmenity will recognize it as a status message - echo "#$*" +# We display status by leaving one background xmessage process running. This +# is the pid of that process. +statuspid="" + +function clear_message { + [ -n "$statuspid" ] && kill $statuspid + statuspid="" } +# make sure we remove any message box we might have put up +cleanup clear_message + +# can we use zenity, or must we fall back to xmessage? +zenpath="$(which zenity)" +if [ -n "$zenpath" ] +then # zenity on PATH and is executable + # display a message box and continue + function status { + # clear any previous message + clear_message + # put up a new zenity box and capture its pid + "$zenpath" --info --title "Second Life Viewer Updater" \ + --width=320 --height=120 --text="$*" & + statuspid=$! + } + + # display an error box and wait for user + function errorbox { + "$zenpath" --error --title "Second Life Viewer Updater" \ + --width=320 --height=120 --text="$*" + } + +else # no zenity, use xmessage instead + # display a message box and continue + function status { + # clear any previous message + clear_message + # put up a new xmessage and capture its pid + xmessage -buttons OK:2 -center "$*" & + statuspid=$! + } + + # display an error box and wait for user + function errorbox { + xmessage -buttons OK:2 -center "$*" + } +fi + +# display an error box and terminate function fail { # Log the message log "$@" # tell subsequent viewer things went south echo "$mandatory" > "$markerfile" # add boilerplate - local msg="An error occurred while updating Second Life: + errorbox "An error occurred while updating Second Life: $* Please download the latest viewer from www.secondlife.com." - # Restate test from xmenity to detect whether we can use zenity or must - # fall back to xmessage - zenpath="$(which zenity)" - if [ -n "$zenpath" -a -x "$zenpath" ] - then "$zenpath" --error --title "Second Life Viewer Updater" \ - --width=320 --height=120 --text="$msg" - else xmessage -buttons -OK:2 -center "$msg" - fi exit 1 } +# Find a graphical sudo program and define mysudo function. On error, $? is +# nonzero; output is in $err instead of being written to stdout/stderr. +gksudo="$(which gksudo)" +kdesu="$(which kdesu)" +if [ -n "$gksudo" ] +then function mysudo { + # gksudo allows you to specify description + err="$("$gksudo" --description "Second Life Viewer Updater" "$@" 2>&1)" + } +elif [ -n "$kdesu" ] +then function mysudo { + err="$("$kdesu" "$@" 2>&1)" + } +else # couldn't find either one, just try it anyway + function mysudo { + err="$("$@" 2>&1)" + } +fi + +# Move directories, using mysudo if we think it necessary. On error, $? is +# nonzero; output is in $err instead of being written to stdout/stderr. function sudo_mv { # If we have write permission to both parent directories, shouldn't need # sudo. if [ -w "$(dirname "$1")" -a -w "$(dirname "$2")" ] - then mv "$1" "$2" - else # use one of the likely sudo programs - sudo="$(which gksudo)" - if [ -z "$sudo" ] - then sudo="$(which kdesu)" - fi - if [ -z "$sudo" ] - then # couldn't find either one, just try it anyway - mv "$1" "$2" - else # even with sudo, could fail, e.g. different filesystems - "$sudo" mv "$1" "$2" - fi + then err="$(mv "$@" 2>&1)" + else # use available sudo program; mysudo sets $? and $err + mysudo mv "$@" fi } -# empty array -cleanups=() - -function cleanup { - # wacky bash syntax for appending to array - cleanups[${#cleanups[*]}]="$*" -} - -function onexit { - for action in "${cleanups[@]}" - do # don't quote, support actions consisting of multiple words - $action - done -} - -trap 'onexit' EXIT - +# **************************************************************************** +# main script logic +# **************************************************************************** mydir="$(dirname "$0")" # We happen to know that the viewer specifies a marker-file pathname within # the logs directory. @@ -97,9 +155,6 @@ logname="$logsdir/updater.log" # Set up redirections for this script such that stderr is logged. (But first # move the previous stderr to file descriptor 3.) exec 3>&2- 2> "$logname" -# Piping to xmenity requires that we end with a line consisting of the string -# "100" to terminate zenity progress bar. -cleanup echo 100 # Rather than setting up a special pipeline to timestamp every line of stderr, # produce header lines into log file indicating timestamp and the arguments @@ -114,17 +169,19 @@ status 'Installing Second Life...' # Creating tempdir under /tmp means it's possible that tempdir is on a # different filesystem than INSTALL_DIR. One is tempted to create tempdir on a -# path derived from `dirname INSTALL_DIR`, but then we might need to add -# another sudo prompt to create it. +# path derived from `dirname INSTALL_DIR` -- but it seems modern 'mv' can +# handle moving across filesystems?? tempdir="/tmp/$(basename "$0").$$" tempinstall="$tempdir/install" -mkdir -p "$tempinstall" || fail "Couldn't create $tempinstall" +# capture the actual error message, if any +err="$(mkdir -p "$tempinstall" 2>&1)" || fail "$err" cleanup rm -rf "$tempdir" # If we already knew the name of the tarball's top-level directory, we could # just move that when all was said and done. Since we don't, untarring to the # 'install' subdir with --strip 1 effectively renames that top-level # directory. +# untar failures tend to be voluminous -- don't even try to capture, just log tar --strip 1 -xjf "$tarball" -C "$tempinstall" || fail "Untar command failed" INSTALL_DIR="$(cd "$mydir/.." ; pwd)" @@ -138,16 +195,21 @@ then backup="$INSTALL_DIR.backup" do backup="$INSTALL_DIR.backup.$backupn" ((backupn += 1)) done - sudo_mv "$INSTALL_DIR" "$backup" || fail "Couldn't move $INSTALL_DIR to $backup" + # on error, fail with actual error message from sudo_mv: permissions, + # cross-filesystem mv, ...? + sudo_mv "$INSTALL_DIR" "$backup" || fail "$err" fi # We unpacked the tarball into tempinstall. Move that. if ! sudo_mv "$tempinstall" "$INSTALL_DIR" then # If we failed to move the temp install to INSTALL_DIR, try to restore - # INSTALL_DIR from backup + # INSTALL_DIR from backup. Save $err because next sudo_mv will trash it! + realerr="$err" sudo_mv "$backup" "$INSTALL_DIR" - fail "Couldn't move $1 to $2" + fail "$realerr" fi +# Removing the tarball here, rather than with a 'cleanup' action, means we +# only remove it if we succeeded. rm -f "$tarball" # Launch the updated viewer. Restore original stderr from file descriptor 3, -- cgit v1.2.3 From 22db60ed0dfd606ab8f8d49141446442a4a72a48 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 11 Jan 2013 15:58:50 -0500 Subject: MAINT-1481: use 'mktemp -d' to generate tempdir. Responding to Lex's code-review comments. --- indra/viewer_components/updater/scripts/linux/update_install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install index 0f624c4dee..a9df9042fd 100644 --- a/indra/viewer_components/updater/scripts/linux/update_install +++ b/indra/viewer_components/updater/scripts/linux/update_install @@ -171,7 +171,7 @@ status 'Installing Second Life...' # different filesystem than INSTALL_DIR. One is tempted to create tempdir on a # path derived from `dirname INSTALL_DIR` -- but it seems modern 'mv' can # handle moving across filesystems?? -tempdir="/tmp/$(basename "$0").$$" +tempdir="$(mktemp -d)" tempinstall="$tempdir/install" # capture the actual error message, if any err="$(mkdir -p "$tempinstall" 2>&1)" || fail "$err" -- cgit v1.2.3