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