1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/pre-inst/pre-inst.c Sun Nov 01 21:44:52 2015 +0000
1.3 @@ -0,0 +1,504 @@
1.4 +/*
1.5 + * Copyright (C) 2014 J. Ali Harlow <ali@juiblex.co.uk>
1.6 + *
1.7 + * This program is free software; you can redistribute it and/or modify
1.8 + * it under the terms of the GNU General Public License as published by
1.9 + * the Free Software Foundation; either version 2 of the License, or
1.10 + * (at your option) any later version.
1.11 + *
1.12 + * This program is distributed in the hope that it will be useful,
1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.15 + * GNU General Public License for more details.
1.16 + *
1.17 + * You should have received a copy of the GNU General Public License along
1.18 + * with this program; if not, write to the Free Software Foundation, Inc.,
1.19 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1.20 + *
1.21 + * References:
1.22 + * http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/
1.23 + */
1.24 +
1.25 +#include "config.h"
1.26 +#ifndef WIN32
1.27 +#define _XOPEN_SOURCE 500
1.28 +#endif
1.29 +#include <stdlib.h>
1.30 +#include <stdio.h>
1.31 +#include <string.h>
1.32 +#include <lua.h>
1.33 +#include <razor.h>
1.34 +#include <plover/plover.h>
1.35 +#include <whelk/whelk.h>
1.36 +#ifdef WIN32
1.37 +#include <windows.h>
1.38 +#include <process.h>
1.39 +#include <commctrl.h>
1.40 +#include "resource.h"
1.41 +
1.42 +#ifndef FOF_NO_UI
1.43 +#define FOF_NO_UI (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
1.44 + FOF_NOCONFIRMMKDIR)
1.45 +#endif
1.46 +
1.47 +#else /* WIN32 */
1.48 +#include <ftw.h>
1.49 +#endif /* WIN32 */
1.50 +
1.51 +#ifdef WIN32
1.52 +/* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
1.53 +#undef USE_G_SPAWN
1.54 +#else
1.55 +#define USE_G_SPAWN
1.56 +#endif
1.57 +
1.58 +LUALIB_API int luaopen_posix(lua_State *L);
1.59 +
1.60 +#ifdef WIN32
1.61 +DWORD main_thread_id;
1.62 +#endif
1.63 +
1.64 +gchar *prefix;
1.65 +
1.66 +int verify_and_fix(const char *root)
1.67 +{
1.68 + return 0;
1.69 +}
1.70 +
1.71 +#ifdef WIN32
1.72 +INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
1.73 + LPARAM l_param)
1.74 +{
1.75 + HWND progress;
1.76 + DWORD style;
1.77 + switch (msg)
1.78 + {
1.79 + case WM_INITDIALOG:
1.80 + progress=GetDlgItem(dialog,IDC_PROGRESS);
1.81 + style=GetWindowLong(progress,GWL_STYLE);
1.82 + SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
1.83 + SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
1.84 + return (INT_PTR)TRUE;
1.85 + }
1.86 + return (INT_PTR)FALSE;
1.87 +}
1.88 +#endif
1.89 +
1.90 +#ifdef WIN32
1.91 +__stdcall
1.92 +#endif
1.93 +unsigned pre_install_thread(void *data)
1.94 +{
1.95 + int retval;
1.96 + char *path=data;
1.97 + gchar *s;
1.98 + char *install[]={"plover-gtkui",NULL};
1.99 + GError *error=NULL;
1.100 + prefix=plover_pre_install_prefix();
1.101 + s=g_strconcat(prefix,"/var/log/pre-install",NULL);
1.102 + plover_log_open(s);
1.103 + g_free(s);
1.104 + s=g_strconcat(prefix,"/var/lib/razor",NULL);
1.105 + razor_set_database_path(s);
1.106 + g_free(s);
1.107 + if (verify_and_fix(prefix))
1.108 + {
1.109 + free(path);
1.110 + g_free(prefix);
1.111 + return -1;
1.112 + }
1.113 + retval=!plover_install(path,prefix,install,&error);
1.114 + if (!retval)
1.115 + retval=!plover_update(path,prefix,NULL,&error);
1.116 + if (error)
1.117 + {
1.118 + fprintf(stderr,"%s\n",error->message);
1.119 + g_error_free(error);
1.120 + }
1.121 + free(path);
1.122 +#ifdef WIN32
1.123 + PostQuitMessage(retval);
1.124 + PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
1.125 + _endthreadex(retval);
1.126 +#endif
1.127 + return retval;
1.128 +}
1.129 +
1.130 +/*
1.131 + * The idea is that if pre_install() fails, update/setup should fall back
1.132 + * to console interfaces.
1.133 + */
1.134 +#ifdef WIN32
1.135 +HANDLE
1.136 +#else
1.137 +void *
1.138 +#endif
1.139 +pre_install(const char *argv0)
1.140 +{
1.141 +#ifdef WIN32
1.142 + HANDLE retval;
1.143 +#else
1.144 + void *retval;
1.145 +#endif
1.146 + char *path;
1.147 + razor_set_lua_loader("posix",luaopen_posix);
1.148 + razor_set_lua_loader("whelk",luaopen_whelk);
1.149 + path=plover_get_program_directory(argv0);
1.150 +#ifdef WIN32
1.151 + retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,path,0,NULL);
1.152 +#else
1.153 + if (pre_install_thread(path))
1.154 + retval=NULL;
1.155 + else
1.156 + retval=(void *)1; /* Non-NULL to indicate success */
1.157 +#endif
1.158 + return retval;
1.159 +}
1.160 +
1.161 +#ifndef WIN32
1.162 +int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
1.163 + struct FTW *ftwbuf)
1.164 +{
1.165 + (void)remove(fpath);
1.166 + return 0;
1.167 +}
1.168 +#endif
1.169 +
1.170 +gboolean deltree(const char *path)
1.171 +{
1.172 +#ifdef WIN32
1.173 + /* Based on g_local_file_trash() */
1.174 + SHFILEOPSTRUCTW op={0};
1.175 + gboolean success;
1.176 + wchar_t *wfilename;
1.177 + long len;
1.178 + wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
1.179 + /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
1.180 + wfilename=g_renew(wchar_t,wfilename,len+2);
1.181 + wfilename[len+1]=0;
1.182 + op.wFunc=FO_DELETE;
1.183 + op.pFrom=wfilename;
1.184 + op.fFlags=FOF_NO_UI;
1.185 + success=!SHFileOperationW(&op);
1.186 + if (success && op.fAnyOperationsAborted)
1.187 + success=FALSE;
1.188 + g_free(wfilename);
1.189 + return success;
1.190 +#else
1.191 + return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
1.192 +#endif
1.193 +}
1.194 +
1.195 +gboolean pre_uninstall(void)
1.196 +{
1.197 + gboolean success;
1.198 + gchar *s;
1.199 + GError *error=NULL;
1.200 + razor_set_lua_loader("posix",luaopen_posix);
1.201 + razor_set_lua_loader("whelk",luaopen_whelk);
1.202 + prefix=plover_pre_install_prefix();
1.203 + s=g_strconcat(prefix,"/var/lib/razor",NULL);
1.204 + razor_set_database_path(s);
1.205 + g_free(s);
1.206 + success=plover_remove(NULL,&error);
1.207 + if (error)
1.208 + {
1.209 + fprintf(stderr,"%s\n",error->message);
1.210 + g_error_free(error);
1.211 + }
1.212 + deltree(prefix);
1.213 + return success;
1.214 +}
1.215 +
1.216 +#if defined(WIN32) && !defined(USE_G_SPAWN)
1.217 +/* Based on glib's g_spawn_win32.c */
1.218 +
1.219 +static gchar *
1.220 +win32_cmdline_quote(const char *string)
1.221 +{
1.222 + const gchar *p=string;
1.223 + gchar *retval,*q;
1.224 + gint len=0;
1.225 + gboolean need_dblquotes=FALSE;
1.226 + while (*p)
1.227 + {
1.228 + if (*p==' ' || *p=='\t')
1.229 + need_dblquotes=TRUE;
1.230 + else if (*p=='"')
1.231 + len++;
1.232 + else if (*p=='\\')
1.233 + {
1.234 + const gchar *pp=p;
1.235 + while (*pp && *pp=='\\')
1.236 + pp++;
1.237 + if (*pp=='"')
1.238 + len++;
1.239 + }
1.240 + len++;
1.241 + p++;
1.242 + }
1.243 + q=retval=g_malloc(len+need_dblquotes*2+1);
1.244 + p=string;
1.245 + if (need_dblquotes)
1.246 + *q++='"';
1.247 + while (*p)
1.248 + {
1.249 + if (*p=='"')
1.250 + *q++='\\';
1.251 + else if (*p=='\\')
1.252 + {
1.253 + const gchar *pp=p;
1.254 + while (*pp && *pp=='\\')
1.255 + pp++;
1.256 + if (*pp=='"')
1.257 + *q++='\\';
1.258 + }
1.259 + *q++=*p;
1.260 + p++;
1.261 + }
1.262 + if (need_dblquotes)
1.263 + *q++='"';
1.264 + *q++='\0';
1.265 + return retval;
1.266 +}
1.267 +
1.268 +/* Create a win32-style wide-character argv with suitable quoting */
1.269 +wchar_t **win32_argv_import(char **argv,GError **error)
1.270 +{
1.271 + int i,n;
1.272 + gchar *s;
1.273 + wchar_t **wargv;
1.274 + GError *tmp_error=NULL;
1.275 + n=g_strv_length(argv);
1.276 + wargv=g_new(wchar_t *,n+1);
1.277 + for(i=0;i<n;i++)
1.278 + {
1.279 + s=win32_cmdline_quote(argv[i]);
1.280 + wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
1.281 + g_free(s);
1.282 + if (!wargv[i])
1.283 + {
1.284 + g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
1.285 + "Invalid argument #%d: %s",i,tmp_error->message);
1.286 + g_error_free(tmp_error);
1.287 + for(i--;i>=0;i--)
1.288 + g_free(wargv[i]);
1.289 + g_free(wargv);
1.290 + return FALSE;
1.291 + }
1.292 + }
1.293 + wargv[i]=NULL;
1.294 + return wargv;
1.295 +}
1.296 +
1.297 +gboolean spawn_sync(char **argv,GError **error)
1.298 +{
1.299 + wchar_t *wargv0,**wargv;
1.300 + gintptr rc;
1.301 + GError *tmp_error=NULL;
1.302 + wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
1.303 + if (!wargv0)
1.304 + {
1.305 + fprintf(stderr,"Conversion error in post\n");
1.306 + g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
1.307 + "Invalid program name: %s",tmp_error->message);
1.308 + g_error_free(tmp_error);
1.309 + return FALSE;
1.310 + }
1.311 + wargv=win32_argv_import(argv,error);
1.312 + if (!wargv)
1.313 + {
1.314 + fprintf(stderr,"Conversion error in post\n");
1.315 + g_free(wargv0);
1.316 + return FALSE;
1.317 + }
1.318 + errno=0;
1.319 + rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
1.320 + g_free(wargv0);
1.321 + g_strfreev((gchar **)wargv);
1.322 + if (rc==-1 && errno!=0)
1.323 + {
1.324 + fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
1.325 + g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
1.326 + "Failed to execute post command: %s",g_strerror(errno));
1.327 + return FALSE;
1.328 + }
1.329 + if (rc!=EXIT_SUCCESS)
1.330 + {
1.331 + fprintf(stderr,"post command failed (%ld)\n",(long)rc);
1.332 + g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
1.333 + "Post command exited with code %ld",(long)rc);
1.334 + return FALSE;
1.335 + }
1.336 + return TRUE;
1.337 +}
1.338 +
1.339 +#endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
1.340 +
1.341 +/*
1.342 + * Run a command after completing request.
1.343 + *
1.344 + * Command may refer to %INSTALL_PREFIX% which will be replaced by the
1.345 + * (first) install prefix used and/or %TEST_RESULT% which will be replaced
1.346 + * bu either "pass" or "fail" depending as to whether the request succeeded
1.347 + * or not. Command may also include double quotes which will be used to
1.348 + * affect how the command is split into arguments much like a shell does.
1.349 + */
1.350 +gboolean run_post(int argc,char **argv,gboolean test_result,GError **error)
1.351 +{
1.352 + int i,post_argc;
1.353 + char *s;
1.354 + gchar *expanded;
1.355 + gchar **post_argv;
1.356 +#ifdef USE_G_SPAWN
1.357 + gchar *standard_output,*standard_error;
1.358 + int exit_status;
1.359 +#endif
1.360 + GError *tmp_error=NULL;
1.361 + if (argc<1)
1.362 + {
1.363 + g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
1.364 + "--post: No command given");
1.365 + return FALSE;
1.366 + }
1.367 + printf("Running post command: %s\n",argv[1]);
1.368 + if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
1.369 + {
1.370 + g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
1.371 + return FALSE;
1.372 + }
1.373 + for(i=0;i<post_argc;i++)
1.374 + {
1.375 + s=strstr(post_argv[i],"%INSTALL_PREFIX%");
1.376 + if (s)
1.377 + {
1.378 + *s='\0';
1.379 + s+=strlen("%INSTALL_PREFIX%");
1.380 + expanded=g_strconcat(post_argv[i],prefix,s,NULL);
1.381 + g_free(post_argv[i]);
1.382 + post_argv[i]=expanded;
1.383 + }
1.384 + s=strstr(post_argv[i],"%TEST_RESULT%");
1.385 + if (s)
1.386 + {
1.387 + *s='\0';
1.388 + s+=strlen("%TEST_RESULT%");
1.389 + expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
1.390 + g_free(post_argv[i]);
1.391 + post_argv[i]=expanded;
1.392 + }
1.393 + }
1.394 +#ifdef USE_G_SPAWN
1.395 + if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
1.396 + &standard_output,&standard_error,&exit_status,&tmp_error))
1.397 + {
1.398 + fprintf(stderr,"Failed to start post command\n");
1.399 + g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
1.400 + return FALSE;
1.401 + }
1.402 + if (standard_output && *standard_output)
1.403 + {
1.404 + printf("Output from post command %s:\n",post_argv[0]);
1.405 + fputs(standard_output,stdout);
1.406 + }
1.407 + g_free(standard_output);
1.408 + if (standard_error && *standard_error)
1.409 + {
1.410 + printf("Error output from post command %s:\n",post_argv[0]);
1.411 + fputs(standard_error,stdout);
1.412 + }
1.413 + g_free(standard_error);
1.414 + if (!g_spawn_check_exit_status(exit_status,&tmp_error))
1.415 + {
1.416 + fprintf(stderr,"post command failed\n");
1.417 + g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
1.418 + return FALSE;
1.419 + }
1.420 +#else
1.421 + if (!spawn_sync(post_argv,&tmp_error))
1.422 + {
1.423 + g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
1.424 + return FALSE;
1.425 + }
1.426 +#endif
1.427 + return TRUE;
1.428 +}
1.429 +
1.430 +#ifdef WIN32
1.431 +DWORD win32_pre_install_gui(char *argv0)
1.432 +{
1.433 + HANDLE thread;
1.434 + INITCOMMONCONTROLSEX icc={0,};
1.435 + MSG msg;
1.436 + DWORD retval;
1.437 + main_thread_id=GetCurrentThreadId();
1.438 + thread=(HANDLE)pre_install(argv0);
1.439 + if (!thread)
1.440 + return EXIT_FAILURE;
1.441 + icc.dwSize=sizeof(icc);
1.442 + icc.dwICC=ICC_WIN95_CLASSES;
1.443 + InitCommonControlsEx(&icc);
1.444 + DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
1.445 + &ProgressDialogProc);
1.446 + while(GetMessage(&msg,NULL,0,0)>0)
1.447 + {
1.448 + TranslateMessage(&msg);
1.449 + DispatchMessage(&msg);
1.450 + }
1.451 + WaitForSingleObject(thread,INFINITE);
1.452 + GetExitCodeThread(thread,&retval);
1.453 + CloseHandle(thread);
1.454 + return retval;
1.455 +}
1.456 +#endif /* WIN32 */
1.457 +
1.458 +int main(int argc,char **argv)
1.459 +{
1.460 + gboolean success;
1.461 + GError *error=NULL;
1.462 +#ifdef WIN32
1.463 + /*
1.464 + * pre-inst is normally a GUI application, but rpm scripts may well
1.465 + * call console applications and it looks ugly if console windows keep
1.466 + * popping up. Avoid this by allocating our own console and hiding it.
1.467 + * Note:
1.468 + * - If pre-inst is a console application (typically for debugging),
1.469 + * then skip this step.
1.470 + * - Call ShowWindow twice to negate special handling on first call.
1.471 + */
1.472 + if (!GetConsoleWindow())
1.473 + {
1.474 + AllocConsole();
1.475 + ShowWindow(GetConsoleWindow(),SW_HIDE);
1.476 + ShowWindow(GetConsoleWindow(),SW_HIDE);
1.477 + }
1.478 +#endif
1.479 + if (argc>1 && !strcmp(argv[1],"-u"))
1.480 + {
1.481 + success=pre_uninstall();
1.482 + argc--;
1.483 + argv++;
1.484 + }
1.485 + else
1.486 +#ifdef WIN32
1.487 + success=win32_pre_install_gui(argv[0])==EXIT_SUCCESS;
1.488 +#else
1.489 + success=!!pre_install(argv[0]);
1.490 +#endif
1.491 + if (argc>1 && !strcmp(argv[1],"--post") &&
1.492 + !run_post(argc-1,argv+1,success,&error))
1.493 + {
1.494 +#ifndef WIN32
1.495 + fprintf(stderr,"Error in post: %s\n",error->message);
1.496 +#else
1.497 + MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
1.498 +#endif
1.499 + g_error_free(error);
1.500 + success=FALSE;
1.501 + }
1.502 +#ifdef WIN32
1.503 + return success?EXIT_SUCCESS:EXIT_FAILURE;
1.504 +#else
1.505 + return success?0:1;
1.506 +#endif
1.507 +}