ali@24: /* ali@24: * Copyright (C) 2014 J. Ali Harlow ali@24: * ali@24: * This program is free software; you can redistribute it and/or modify ali@24: * it under the terms of the GNU General Public License as published by ali@24: * the Free Software Foundation; either version 2 of the License, or ali@24: * (at your option) any later version. ali@24: * ali@24: * This program is distributed in the hope that it will be useful, ali@24: * but WITHOUT ANY WARRANTY; without even the implied warranty of ali@24: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ali@24: * GNU General Public License for more details. ali@24: * ali@24: * You should have received a copy of the GNU General Public License along ali@24: * with this program; if not, write to the Free Software Foundation, Inc., ali@24: * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ali@24: * ali@24: * References: ali@24: * http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/ ali@24: */ ali@24: ali@24: #include "config.h" ali@24: #ifndef WIN32 ali@24: #define _XOPEN_SOURCE 500 ali@24: #endif ali@24: #include ali@24: #include ali@24: #include ali@24: #include ali@24: #include ali@24: #include ali@24: #include ali@24: #ifdef WIN32 ali@24: #include ali@24: #include ali@24: #include ali@24: #include "resource.h" ali@24: ali@24: #ifndef FOF_NO_UI ali@24: #define FOF_NO_UI (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\ ali@24: FOF_NOCONFIRMMKDIR) ali@24: #endif ali@24: ali@24: #else /* WIN32 */ ali@24: #include ali@24: #endif /* WIN32 */ ali@24: ali@24: #ifdef WIN32 ali@24: /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */ ali@24: #undef USE_G_SPAWN ali@24: #else ali@24: #define USE_G_SPAWN ali@24: #endif ali@24: ali@24: LUALIB_API int luaopen_posix(lua_State *L); ali@24: ali@24: #ifdef WIN32 ali@24: DWORD main_thread_id; ali@24: #endif ali@24: ali@24: gchar *prefix; ali@24: ali@24: int verify_and_fix(const char *root) ali@24: { ali@24: return 0; ali@24: } ali@24: ali@24: #ifdef WIN32 ali@24: INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param, ali@24: LPARAM l_param) ali@24: { ali@24: HWND progress; ali@24: DWORD style; ali@24: switch (msg) ali@24: { ali@24: case WM_INITDIALOG: ali@24: progress=GetDlgItem(dialog,IDC_PROGRESS); ali@24: style=GetWindowLong(progress,GWL_STYLE); ali@24: SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE); ali@24: SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30); ali@24: return (INT_PTR)TRUE; ali@24: } ali@24: return (INT_PTR)FALSE; ali@24: } ali@24: #endif ali@24: ali@24: #ifdef WIN32 ali@24: __stdcall ali@24: #endif ali@24: unsigned pre_install_thread(void *data) ali@24: { ali@24: int retval; ali@95: const char *repository=data; ali@46: gchar *s,*uri; ali@24: char *install[]={"plover-gtkui",NULL}; ali@24: GError *error=NULL; ali@94: plover__uri_handler_init(); ali@24: prefix=plover_pre_install_prefix(); ali@24: s=g_strconcat(prefix,"/var/log/pre-install",NULL); ali@24: plover_log_open(s); ali@24: g_free(s); ali@24: s=g_strconcat(prefix,"/var/lib/razor",NULL); ali@46: uri=razor_path_to_uri(s); ali@24: g_free(s); ali@46: razor_set_database_uri(uri); ali@46: free(uri); ali@24: if (verify_and_fix(prefix)) ali@24: { ali@24: g_free(prefix); ali@24: return -1; ali@24: } ali@95: retval=!plover_install_uri(repository,prefix,install,&error); ali@24: if (!retval) ali@95: retval=!plover_update_uri(repository,prefix,NULL,&error); ali@24: if (error) ali@24: { ali@24: fprintf(stderr,"%s\n",error->message); ali@24: g_error_free(error); ali@24: } ali@24: #ifdef WIN32 ali@24: PostQuitMessage(retval); ali@24: PostThreadMessage(main_thread_id,WM_QUIT,retval,0); ali@24: _endthreadex(retval); ali@24: #endif ali@24: return retval; ali@24: } ali@24: ali@24: /* ali@24: * The idea is that if pre_install() fails, update/setup should fall back ali@24: * to console interfaces. ali@24: */ ali@24: #ifdef WIN32 ali@24: HANDLE ali@24: #else ali@24: void * ali@24: #endif ali@95: pre_install(const char *repository) ali@24: { ali@24: #ifdef WIN32 ali@24: HANDLE retval; ali@24: #else ali@24: void *retval; ali@24: #endif ali@38: razor_set_lua_loader("posix",(void (*)())luaopen_posix); ali@38: razor_set_lua_loader("whelk",(void (*)())luaopen_whelk); ali@24: #ifdef WIN32 ali@95: retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository, ali@95: 0,NULL); ali@24: #else ali@95: if (pre_install_thread((void *)repository)) ali@24: retval=NULL; ali@24: else ali@24: retval=(void *)1; /* Non-NULL to indicate success */ ali@24: #endif ali@24: return retval; ali@24: } ali@24: ali@24: #ifndef WIN32 ali@24: int remove_ignore(const char *fpath,const struct stat *sb,int typeflag, ali@24: struct FTW *ftwbuf) ali@24: { ali@24: (void)remove(fpath); ali@24: return 0; ali@24: } ali@24: #endif ali@24: ali@24: gboolean deltree(const char *path) ali@24: { ali@24: #ifdef WIN32 ali@24: /* Based on g_local_file_trash() */ ali@24: SHFILEOPSTRUCTW op={0}; ali@24: gboolean success; ali@24: wchar_t *wfilename; ali@24: long len; ali@24: wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL); ali@24: /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */ ali@24: wfilename=g_renew(wchar_t,wfilename,len+2); ali@24: wfilename[len+1]=0; ali@24: op.wFunc=FO_DELETE; ali@24: op.pFrom=wfilename; ali@24: op.fFlags=FOF_NO_UI; ali@24: success=!SHFileOperationW(&op); ali@24: if (success && op.fAnyOperationsAborted) ali@24: success=FALSE; ali@24: g_free(wfilename); ali@24: return success; ali@24: #else ali@24: return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS); ali@24: #endif ali@24: } ali@24: ali@24: gboolean pre_uninstall(void) ali@24: { ali@24: gboolean success; ali@46: gchar *s,*uri; ali@24: GError *error=NULL; ali@38: razor_set_lua_loader("posix",(void (*)())luaopen_posix); ali@38: razor_set_lua_loader("whelk",(void (*)())luaopen_whelk); ali@24: prefix=plover_pre_install_prefix(); ali@24: s=g_strconcat(prefix,"/var/lib/razor",NULL); ali@46: uri=razor_path_to_uri(s); ali@24: g_free(s); ali@46: razor_set_database_uri(uri); ali@46: free(uri); ali@24: success=plover_remove(NULL,&error); ali@24: if (error) ali@24: { ali@24: fprintf(stderr,"%s\n",error->message); ali@24: g_error_free(error); ali@24: } ali@24: deltree(prefix); ali@24: return success; ali@24: } ali@24: ali@24: #if defined(WIN32) && !defined(USE_G_SPAWN) ali@24: /* Based on glib's g_spawn_win32.c */ ali@24: ali@24: static gchar * ali@24: win32_cmdline_quote(const char *string) ali@24: { ali@24: const gchar *p=string; ali@24: gchar *retval,*q; ali@24: gint len=0; ali@24: gboolean need_dblquotes=FALSE; ali@24: while (*p) ali@24: { ali@24: if (*p==' ' || *p=='\t') ali@24: need_dblquotes=TRUE; ali@24: else if (*p=='"') ali@24: len++; ali@24: else if (*p=='\\') ali@24: { ali@24: const gchar *pp=p; ali@24: while (*pp && *pp=='\\') ali@24: pp++; ali@24: if (*pp=='"') ali@24: len++; ali@24: } ali@24: len++; ali@24: p++; ali@24: } ali@24: q=retval=g_malloc(len+need_dblquotes*2+1); ali@24: p=string; ali@24: if (need_dblquotes) ali@24: *q++='"'; ali@24: while (*p) ali@24: { ali@24: if (*p=='"') ali@24: *q++='\\'; ali@24: else if (*p=='\\') ali@24: { ali@24: const gchar *pp=p; ali@24: while (*pp && *pp=='\\') ali@24: pp++; ali@24: if (*pp=='"') ali@24: *q++='\\'; ali@24: } ali@24: *q++=*p; ali@24: p++; ali@24: } ali@24: if (need_dblquotes) ali@24: *q++='"'; ali@24: *q++='\0'; ali@24: return retval; ali@24: } ali@24: ali@24: /* Create a win32-style wide-character argv with suitable quoting */ ali@24: wchar_t **win32_argv_import(char **argv,GError **error) ali@24: { ali@24: int i,n; ali@24: gchar *s; ali@24: wchar_t **wargv; ali@24: GError *tmp_error=NULL; ali@24: n=g_strv_length(argv); ali@24: wargv=g_new(wchar_t *,n+1); ali@24: for(i=0;imessage); ali@24: g_error_free(tmp_error); ali@24: for(i--;i>=0;i--) ali@24: g_free(wargv[i]); ali@24: g_free(wargv); ali@24: return FALSE; ali@24: } ali@24: } ali@24: wargv[i]=NULL; ali@24: return wargv; ali@24: } ali@24: ali@24: gboolean spawn_sync(char **argv,GError **error) ali@24: { ali@24: wchar_t *wargv0,**wargv; ali@24: gintptr rc; ali@24: GError *tmp_error=NULL; ali@24: wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error); ali@24: if (!wargv0) ali@24: { ali@24: fprintf(stderr,"Conversion error in post\n"); ali@24: g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED, ali@24: "Invalid program name: %s",tmp_error->message); ali@24: g_error_free(tmp_error); ali@24: return FALSE; ali@24: } ali@24: wargv=win32_argv_import(argv,error); ali@24: if (!wargv) ali@24: { ali@24: fprintf(stderr,"Conversion error in post\n"); ali@24: g_free(wargv0); ali@24: return FALSE; ali@24: } ali@24: errno=0; ali@24: rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv); ali@24: g_free(wargv0); ali@24: g_strfreev((gchar **)wargv); ali@24: if (rc==-1 && errno!=0) ali@24: { ali@24: fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno)); ali@24: g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED, ali@24: "Failed to execute post command: %s",g_strerror(errno)); ali@24: return FALSE; ali@24: } ali@24: if (rc!=EXIT_SUCCESS) ali@24: { ali@24: fprintf(stderr,"post command failed (%ld)\n",(long)rc); ali@24: g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED, ali@24: "Post command exited with code %ld",(long)rc); ali@24: return FALSE; ali@24: } ali@24: return TRUE; ali@24: } ali@24: ali@24: #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */ ali@24: ali@24: /* ali@24: * Run a command after completing request. ali@24: * ali@24: * Command may refer to %INSTALL_PREFIX% which will be replaced by the ali@95: * (first) install prefix used, by %TEST_RESULT% which will be replaced ali@80: * by either "pass" or "fail" depending as to whether the request succeeded ali@95: * or not and/or by %REPOSITORY% which will be replaced by the URI of the ali@95: * repository used. Command may also include double quotes which will be used ali@95: * to affect how the command is split into arguments much like a shell does. ali@24: */ ali@95: gboolean run_post(int argc,char **argv,gboolean test_result, ali@95: const char *repository,GError **error) ali@24: { ali@24: int i,post_argc; ali@24: char *s; ali@24: gchar *expanded; ali@24: gchar **post_argv; ali@24: #ifdef USE_G_SPAWN ali@24: gchar *standard_output,*standard_error; ali@24: int exit_status; ali@24: #endif ali@24: GError *tmp_error=NULL; ali@87: if (argc<2) ali@24: { ali@24: g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT, ali@24: "--post: No command given"); ali@24: return FALSE; ali@24: } ali@24: printf("Running post command: %s\n",argv[1]); ali@24: if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error)) ali@24: { ali@24: g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]); ali@24: return FALSE; ali@24: } ali@24: for(i=0;i0) ali@24: { ali@24: TranslateMessage(&msg); ali@24: DispatchMessage(&msg); ali@24: } ali@24: WaitForSingleObject(thread,INFINITE); ali@24: GetExitCodeThread(thread,&retval); ali@24: CloseHandle(thread); ali@24: return retval; ali@24: } ali@24: #endif /* WIN32 */ ali@24: ali@95: gchar *pre_install_default_repository(const char *argv0) ali@94: { ali@94: size_t length; ali@94: void *contents; ali@94: gchar *path; ali@94: gchar *s,*uri; ali@94: struct razor_error *tmp_error=NULL; ali@94: /* ali@95: * The default repository is the executable itself if it's an archive ali@94: * or otherwise the directory in which the executable is stored. ali@94: */ ali@94: path=plover_get_program(argv0); ali@94: uri=razor_path_to_uri(path); ali@95: free(path); ali@94: s=g_strconcat(uri,"/repodata/comps.xml",NULL); ali@94: contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error); ali@94: g_free(s); ali@94: if (contents) ali@94: razor_uri_free_contents(contents,length); ali@94: else ali@94: { ali@94: if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR, ali@94: RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE)) ali@94: { ali@94: path=plover_get_program_directory(argv0); ali@95: uri=razor_path_to_uri(path); ali@95: free(path); ali@94: } ali@94: razor_error_free(tmp_error); ali@94: } ali@95: return uri; ali@94: } ali@94: ali@24: int main(int argc,char **argv) ali@24: { ali@80: gboolean success,uninstall=FALSE,enable_post=FALSE; ali@24: GError *error=NULL; ali@95: gchar *path=NULL,*repository=NULL; ali@80: GOptionContext *context; ali@80: GOptionEntry options[]={ ali@95: {"repository",0,0,G_OPTION_ARG_STRING,&repository, ali@95: "Repository location","uri"}, ali@80: {"path",0,0,G_OPTION_ARG_FILENAME,&path, ali@80: "Repository path","path"}, ali@80: {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall, ali@80: "Uninstall all packages",NULL}, ali@80: {"post",0,0,G_OPTION_ARG_NONE,&enable_post, ali@80: "Run command after request is processed",NULL}, ali@80: {NULL} ali@80: }; ali@24: #ifdef WIN32 ali@24: /* ali@24: * pre-inst is normally a GUI application, but rpm scripts may well ali@24: * call console applications and it looks ugly if console windows keep ali@24: * popping up. Avoid this by allocating our own console and hiding it. ali@24: * Note: ali@24: * - If pre-inst is a console application (typically for debugging), ali@24: * then skip this step. ali@24: * - Call ShowWindow twice to negate special handling on first call. ali@24: */ ali@24: if (!GetConsoleWindow()) ali@24: { ali@24: AllocConsole(); ali@24: ShowWindow(GetConsoleWindow(),SW_HIDE); ali@24: ShowWindow(GetConsoleWindow(),SW_HIDE); ali@24: } ali@24: #endif ali@31: plover_exception_handler_init(); ali@80: context=g_option_context_new("[command] - install the main installer"); ali@80: g_option_context_add_main_entries(context,options,NULL); ali@80: g_option_context_set_description(context, ali@80: "If --post is specified, then the command to run and its arguments\n" ali@80: "should be listed at the end of the command line.\n" ali@80: "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n" ali@95: "(first) install prefix used, by %TEST_RESULT% which will be\n" ali@80: "replaced by either \"pass\" or \"fail\" depending as to whether the\n" ali@95: "request succeeded or not and/or by %REPOSITORY% which will be\n" ali@95: "replaced by the URI of the repository used. Command may also include\n" ali@95: "double quotes which will be used to affect how the command is split\n" ali@95: "into arguments much like a shell does."); ali@80: g_option_context_set_strict_posix(context,TRUE); ali@80: g_option_context_set_ignore_unknown_options(context,TRUE); ali@80: if (!g_option_context_parse(context,&argc,&argv,&error)) ali@24: { ali@80: g_printerr("pre-install: %s\n",error->message); ali@80: g_printerr("Use \"%s --help\" for help\n",(*argv)[0]); ali@80: exit(1); ali@80: } ali@95: if (repository && path) ali@95: { ali@95: g_printerr("pre-install: " ali@95: "Only one of --repository and --path can be specified\n"); ali@95: exit(1); ali@95: } ali@95: if (path) ali@95: repository=razor_path_to_uri(path); ali@95: else if (!repository) ali@95: repository=pre_install_default_repository(argv[0]); ali@80: if (uninstall) ali@24: success=pre_uninstall(); ali@80: else ali@80: { ali@80: #ifdef WIN32 ali@95: success=win32_pre_install_gui(repository)==EXIT_SUCCESS; ali@80: #else ali@95: success=!!pre_install(repository); ali@80: #endif ali@24: } ali@95: if (enable_post && !run_post(argc,argv,success,repository,&error)) ali@24: { ali@24: #ifndef WIN32 ali@24: fprintf(stderr,"Error in post: %s\n",error->message); ali@24: #else ali@24: MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK); ali@24: #endif ali@24: g_error_free(error); ali@24: success=FALSE; ali@24: } ali@24: #ifdef WIN32 ali@24: return success?EXIT_SUCCESS:EXIT_FAILURE; ali@24: #else ali@24: return success?0:1; ali@24: #endif ali@24: }