diff -r 000000000000 -r 9816b7a56c23 pre-inst/pre-inst.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pre-inst/pre-inst.c Mon Nov 17 11:30:24 2014 +0000 @@ -0,0 +1,504 @@ +/* + * 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",luaopen_posix); + razor_set_lua_loader("whelk",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",luaopen_posix); + razor_set_lua_loader("whelk",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 + 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 +}