/* * Copyright (C) 2014, 2020 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 #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 */ #include "post.h" #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) { gboolean changed; int retval; const char *repository=data; gchar *s,*uri; char *install[]={"plover-gtkui",NULL}; char **pkgs=install; struct comps *comps; struct comps_group *group; struct comps_requirement *pkg; struct plover_vector *packages=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(prefix); return -1; } s=g_strconcat(repository,"/repodata/comps.xml",NULL); comps=plover_comps_new_from_uri(s,&error); retval=!comps; g_free(s); if (!retval) { group=plover_comps_lookup_group(comps,"installer"); if (group) { packages=plover_vector_new(); do { changed=FALSE; for(pkg=group->packages;pkg;pkg=pkg->next) { if (plover_vector_contains(packages,pkg->name)) continue; if (pkg->type==COMPS_REQUIREMENT_DEFAULT || pkg->type==COMPS_REQUIREMENT_MANDATORY || pkg->type==COMPS_REQUIREMENT_CONDITIONAL && plover_vector_contains(packages,pkg->requires)) { changed=TRUE; plover_vector_append(packages,pkg->name); } } } while(changed); pkgs=packages->strings; } } if (!retval) retval=!plover_install_uri(repository,prefix,pkgs,&error); if (!retval) retval=!plover_update_uri(repository,prefix,NULL,&error); if (packages) plover_vector_free(packages); if (comps) plover_comps_free(comps); if (error) { fprintf(stderr,"%s\n",error->message); g_error_free(error); } #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 *repository) { #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 *)repository, 0,NULL); #else if (pre_install_thread((void *)repository)) 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) */ gboolean pre_install_spawn_sync(char **argv,GError **error) { GError *tmp_error=NULL; #ifdef USE_G_SPAWN gchar *standard_output,*standard_error; int exit_status; if (!g_spawn_sync(NULL,argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL, &standard_output,&standard_error,&exit_status,&tmp_error)) { fprintf(stderr,"Failed to start post command\n"); g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]); return FALSE; } if (standard_output && *standard_output) { printf("Output from post command %s:\n",argv[0]); fputs(standard_output,stdout); } g_free(standard_output); if (standard_error && *standard_error) { printf("Error output from post command %s:\n",argv[0]); fputs(standard_error,stdout); } g_free(standard_error); if (!g_spawn_check_exit_status(exit_status,&tmp_error)) { fprintf(stderr,"post command failed\n"); g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]); return FALSE; } #else if (!spawn_sync(argv,&tmp_error)) { g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]); return FALSE; } #endif return TRUE; } /* * Run a command after completing request. * * Command may refer to %INSTALL_PREFIX% which will be replaced by the * (first) install prefix used, by %TEST_RESULT% which will be replaced * by either "pass" or "fail" depending as to whether the request succeeded * or not and/or by %REPOSITORY% which will be replaced by the URI of the * repository used. 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, const char *repository,GError **error) { int i,post_argc; char *s; gchar *expanded; gchar **post_argv; 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_repository(const char *argv0) { size_t length; void *contents; gchar *path; gchar *s,*uri; struct razor_error *tmp_error=NULL; /* * The default repository 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); free(path); s=g_strconcat(uri,"/repodata/comps.xml",NULL); 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)) { path=plover_get_program_directory(argv0); uri=razor_path_to_uri(path); free(path); } razor_error_free(tmp_error); } return uri; } gboolean pre_install_post(int argc,char **argv,gboolean success, const char *repository) { gchar *s,*uri; struct post *post=NULL; GError *error=NULL; if (!argv && !success) { /* * If no --post command has been given, then we want to run the * program specified in %INSTALL_PREFIX%/etc/pre-inst.post * However, if the pre-install failed, then that file isn't likely * to exist (and it's requirements even less so) so we issue an error * instead. Unfortunately, it's hard to include the error message * here. It will have been logged in the plover log, but that's not * much help to users. */ #ifndef WIN32 fprintf(stderr, "Installation failed: Failed to unpack the main installer\n"); #else MessageBox(NULL,"Failed to unpack the main installer", "Installation failed",MB_ICONERROR|MB_OK); #endif return FALSE; } if (!argv) { post=pre_install_post_new(repository,prefix); uri=razor_path_to_uri(prefix); s=g_strconcat(uri,"/etc/pre-inst.post",NULL); g_free(uri); pre_install_post_load_uri(post,s,&error); if (error) g_debug("Failed to load post configuration from pre-inst.post: %s", error->message); g_free(s); if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT)) { /* * If no post configuration file exists, that's not an error; * there simply is no post action configured. */ g_clear_error(&error); } } if (post && post->argc>0) pre_install_spawn_sync(post->argv,&error); else if (argv) run_post(argc,argv,success,repository,&error); if (post) pre_install_post_free(post); if (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; } return success; } int main(int argc,char **argv) { gboolean success,uninstall=FALSE,enable_post=FALSE; GError *error=NULL; gchar *path=NULL,*repository=NULL; GOptionContext *context; GOptionEntry options[]={ {"repository",0,0,G_OPTION_ARG_STRING,&repository, "Repository location","uri"}, {"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, by %TEST_RESULT% which will be\n" "replaced by either \"pass\" or \"fail\" depending as to whether the\n" "request succeeded or not and/or by %REPOSITORY% which will be\n" "replaced by the URI of the repository used. Command may also include\n" "double quotes which will be used to affect how the command is split\n" "into arguments much 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 (repository && path) { g_printerr("pre-install: " "Only one of --repository and --path can be specified\n"); exit(1); } if (path) repository=razor_path_to_uri(path); else if (!repository) repository=pre_install_default_repository(argv[0]); if (uninstall) success=pre_uninstall(); else { #ifdef WIN32 success=win32_pre_install_gui(repository)==EXIT_SUCCESS; #else success=!!pre_install(repository); #endif } if (enable_post) success=pre_install_post(argc,argv,success,repository); else if (!uninstall) success=pre_install_post(0,NULL,success,repository); #ifdef WIN32 return success?EXIT_SUCCESS:EXIT_FAILURE; #else return success?0:1; #endif }