/* * 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; char *path=data; gchar *s; char *install[]={"plover-gtkui",NULL}; GError *error=NULL; 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); razor_set_database_path(s); g_free(s); if (verify_and_fix(prefix)) { 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); } 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 *argv0) { #ifdef WIN32 HANDLE retval; #else void *retval; #endif char *path; razor_set_lua_loader("posix",(void (*)())luaopen_posix); razor_set_lua_loader("whelk",(void (*)())luaopen_whelk); path=plover_get_program_directory(argv0); #ifdef WIN32 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,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; 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); razor_set_database_path(s); g_free(s); 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 * bu 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<1) { 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 */ int main(int argc,char **argv) { gboolean success; GError *error=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(); if (argc>1 && !strcmp(argv[1],"-u")) { success=pre_uninstall(); argc--; argv++; } else #ifdef WIN32 success=win32_pre_install_gui(argv[0])==EXIT_SUCCESS; #else success=!!pre_install(argv[0]); #endif if (argc>1 && !strcmp(argv[1],"--post") && !run_post(argc-1,argv+1,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 }