pre-inst/pre-inst.c
author J. Ali Harlow <ali@juiblex.co.uk>
Fri Mar 08 12:05:41 2019 +0000 (2019-03-08)
changeset 86 a8e48c62ec03
parent 46 360621bc323e
child 87 5914de4a823f
permissions -rw-r--r--
Fix historic issues exposed by the testsuite
     1 /*
     2  * Copyright (C) 2014  J. Ali Harlow <ali@juiblex.co.uk>
     3  *
     4  * This program is free software; you can redistribute it and/or modify
     5  * it under the terms of the GNU General Public License as published by
     6  * the Free Software Foundation; either version 2 of the License, or
     7  * (at your option) any later version.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License along
    15  * with this program; if not, write to the Free Software Foundation, Inc.,
    16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    17  *
    18  * References:
    19  *	http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/
    20  */
    21 
    22 #include "config.h"
    23 #ifndef WIN32
    24 #define _XOPEN_SOURCE 500
    25 #endif
    26 #include <stdlib.h>
    27 #include <stdio.h>
    28 #include <string.h>
    29 #include <lua.h>
    30 #include <razor.h>
    31 #include <plover/plover.h>
    32 #include <whelk/whelk.h>
    33 #ifdef WIN32
    34 #include <windows.h>
    35 #include <process.h>
    36 #include <commctrl.h>
    37 #include "resource.h"
    38 
    39 #ifndef FOF_NO_UI
    40 #define FOF_NO_UI	(FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
    41 			FOF_NOCONFIRMMKDIR)
    42 #endif
    43 
    44 #else	/* WIN32 */
    45 #include <ftw.h>
    46 #endif	/* WIN32 */
    47 
    48 #ifdef WIN32
    49 /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
    50 #undef USE_G_SPAWN
    51 #else
    52 #define USE_G_SPAWN
    53 #endif
    54 
    55 LUALIB_API int luaopen_posix(lua_State *L);
    56 
    57 #ifdef WIN32
    58 DWORD main_thread_id;
    59 #endif
    60 
    61 gchar *prefix;
    62 
    63 int verify_and_fix(const char *root)
    64 {
    65     return 0;
    66 }
    67 
    68 #ifdef WIN32
    69 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
    70   LPARAM l_param)
    71 {
    72     HWND progress;
    73     DWORD style;
    74     switch (msg)
    75     {
    76 	case WM_INITDIALOG:
    77 	    progress=GetDlgItem(dialog,IDC_PROGRESS); 
    78 	    style=GetWindowLong(progress,GWL_STYLE);
    79 	    SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
    80 	    SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
    81 	    return (INT_PTR)TRUE;
    82     }
    83     return (INT_PTR)FALSE;
    84 }
    85 #endif
    86 
    87 #ifdef WIN32
    88 __stdcall
    89 #endif
    90 unsigned pre_install_thread(void *data)
    91 {
    92     int retval;
    93     char *path=data;
    94     gchar *s,*uri;
    95     char *install[]={"plover-gtkui",NULL};
    96     GError *error=NULL;
    97     prefix=plover_pre_install_prefix();
    98     s=g_strconcat(prefix,"/var/log/pre-install",NULL);
    99     plover_log_open(s);
   100     g_free(s);
   101     s=g_strconcat(prefix,"/var/lib/razor",NULL);
   102     uri=razor_path_to_uri(s);
   103     g_free(s);
   104     razor_set_database_uri(uri);
   105     free(uri);
   106     if (verify_and_fix(prefix))
   107     {
   108 	free(path);
   109 	g_free(prefix);
   110 	return -1;
   111     }
   112     retval=!plover_install(path,prefix,install,&error);
   113     if (!retval)
   114 	retval=!plover_update(path,prefix,NULL,&error);
   115     if (error)
   116     {
   117 	fprintf(stderr,"%s\n",error->message);
   118 	g_error_free(error);
   119     }
   120     free(path);
   121 #ifdef WIN32
   122     PostQuitMessage(retval);
   123     PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
   124     _endthreadex(retval);
   125 #endif
   126     return retval;
   127 }
   128 
   129 /*
   130  * The idea is that if pre_install() fails, update/setup should fall back
   131  * to console interfaces.
   132  */
   133 #ifdef WIN32
   134 HANDLE
   135 #else
   136 void *
   137 #endif
   138 pre_install(const char *path)
   139 {
   140 #ifdef WIN32
   141     HANDLE retval;
   142 #else
   143     void *retval;
   144 #endif
   145     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   146     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   147 #ifdef WIN32
   148     retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)path,0,
   149       NULL);
   150 #else
   151     if (pre_install_thread(path))
   152 	retval=NULL;
   153     else
   154 	retval=(void *)1;		/* Non-NULL to indicate success */
   155 #endif
   156     return retval;
   157 }
   158 
   159 #ifndef WIN32
   160 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
   161   struct FTW *ftwbuf)
   162 {
   163     (void)remove(fpath);
   164     return 0;
   165 }
   166 #endif
   167 
   168 gboolean deltree(const char *path)
   169 {
   170 #ifdef WIN32
   171     /* Based on g_local_file_trash() */
   172     SHFILEOPSTRUCTW op={0};
   173     gboolean success;
   174     wchar_t *wfilename;
   175     long len;
   176     wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
   177     /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
   178     wfilename=g_renew(wchar_t,wfilename,len+2);
   179     wfilename[len+1]=0;
   180     op.wFunc=FO_DELETE;
   181     op.pFrom=wfilename;
   182     op.fFlags=FOF_NO_UI;
   183     success=!SHFileOperationW(&op);
   184     if (success && op.fAnyOperationsAborted)
   185 	success=FALSE;
   186     g_free(wfilename);
   187     return success;
   188 #else
   189     return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
   190 #endif
   191 }
   192 
   193 gboolean pre_uninstall(void)
   194 {
   195     gboolean success;
   196     gchar *s,*uri;
   197     GError *error=NULL;
   198     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   199     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   200     prefix=plover_pre_install_prefix();
   201     s=g_strconcat(prefix,"/var/lib/razor",NULL);
   202     uri=razor_path_to_uri(s);
   203     g_free(s);
   204     razor_set_database_uri(uri);
   205     free(uri);
   206     success=plover_remove(NULL,&error);
   207     if (error)
   208     {
   209 	fprintf(stderr,"%s\n",error->message);
   210 	g_error_free(error);
   211     }
   212     deltree(prefix);
   213     return success;
   214 }
   215 
   216 #if defined(WIN32) && !defined(USE_G_SPAWN)
   217 /* Based on glib's g_spawn_win32.c */
   218 
   219 static gchar *
   220 win32_cmdline_quote(const char *string)
   221 {
   222     const gchar *p=string;
   223     gchar *retval,*q;
   224     gint len=0;
   225     gboolean need_dblquotes=FALSE;
   226     while (*p)
   227     {
   228 	if (*p==' ' || *p=='\t')
   229 	    need_dblquotes=TRUE;
   230 	else if (*p=='"')
   231 	    len++;
   232 	else if (*p=='\\')
   233 	{
   234 	    const gchar *pp=p;
   235 	    while (*pp && *pp=='\\')
   236 		pp++;
   237 	    if (*pp=='"')
   238 		len++;
   239 	}
   240 	len++;
   241 	p++;
   242     }
   243     q=retval=g_malloc(len+need_dblquotes*2+1);
   244     p=string;
   245     if (need_dblquotes)
   246 	*q++='"';
   247     while (*p)
   248     {
   249 	if (*p=='"')
   250 	    *q++='\\';
   251 	else if (*p=='\\')
   252 	{
   253 	    const gchar *pp=p;
   254 	    while (*pp && *pp=='\\')
   255 		pp++;
   256 	    if (*pp=='"')
   257 		*q++='\\';
   258 	}
   259 	*q++=*p;
   260 	p++;
   261     }
   262     if (need_dblquotes)
   263 	*q++='"';
   264     *q++='\0';
   265     return retval;
   266 }
   267 
   268 /* Create a win32-style wide-character argv with suitable quoting */
   269 wchar_t **win32_argv_import(char **argv,GError **error)
   270 {
   271     int i,n;
   272     gchar *s;
   273     wchar_t **wargv;
   274     GError *tmp_error=NULL;
   275     n=g_strv_length(argv);
   276     wargv=g_new(wchar_t *,n+1);
   277     for(i=0;i<n;i++)
   278     {
   279 	s=win32_cmdline_quote(argv[i]);
   280 	wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
   281 	g_free(s);
   282 	if (!wargv[i])
   283 	{
   284 	    g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   285 	      "Invalid argument #%d: %s",i,tmp_error->message);
   286 	    g_error_free(tmp_error);
   287 	    for(i--;i>=0;i--)
   288 		g_free(wargv[i]);
   289 	    g_free(wargv);
   290 	    return FALSE;
   291 	}
   292     }
   293     wargv[i]=NULL;
   294     return wargv;
   295 }
   296 
   297 gboolean spawn_sync(char **argv,GError **error)
   298 {
   299     wchar_t *wargv0,**wargv;
   300     gintptr rc;
   301     GError *tmp_error=NULL;
   302     wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
   303     if (!wargv0)
   304     {
   305 	fprintf(stderr,"Conversion error in post\n");
   306 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   307 	  "Invalid program name: %s",tmp_error->message);
   308 	g_error_free(tmp_error);
   309 	return FALSE;
   310     }
   311     wargv=win32_argv_import(argv,error);
   312     if (!wargv)
   313     {
   314 	fprintf(stderr,"Conversion error in post\n");
   315 	g_free(wargv0);
   316 	return FALSE;
   317     }
   318     errno=0;
   319     rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
   320     g_free(wargv0);
   321     g_strfreev((gchar **)wargv);
   322     if (rc==-1 && errno!=0)
   323     {
   324 	fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
   325 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   326 	  "Failed to execute post command: %s",g_strerror(errno));
   327 	return FALSE;
   328     }
   329     if (rc!=EXIT_SUCCESS)
   330     {
   331 	fprintf(stderr,"post command failed (%ld)\n",(long)rc);
   332 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   333 	  "Post command exited with code %ld",(long)rc);
   334 	return FALSE;
   335     }
   336     return TRUE;
   337 }
   338 
   339 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
   340 
   341 /*
   342  * Run a command after completing request.
   343  *
   344  * Command may refer to %INSTALL_PREFIX% which will be replaced by the
   345  * (first) install prefix used and/or %TEST_RESULT% which will be replaced
   346  * by either "pass" or "fail" depending as to whether the request succeeded
   347  * or not. Command may also include double quotes which will be used to
   348  * affect how the command is split into arguments much like a shell does.
   349  */
   350 gboolean run_post(int argc,char **argv,gboolean test_result,GError **error)
   351 {
   352     int i,post_argc;
   353     char *s;
   354     gchar *expanded;
   355     gchar **post_argv;
   356 #ifdef USE_G_SPAWN
   357     gchar *standard_output,*standard_error;
   358     int exit_status;
   359 #endif
   360     GError *tmp_error=NULL;
   361     if (argc<1)
   362     {
   363 	g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
   364 	  "--post: No command given");
   365 	return FALSE;
   366     }
   367     printf("Running post command: %s\n",argv[1]);
   368     if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
   369     {
   370 	g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
   371 	return FALSE;
   372     }
   373     for(i=0;i<post_argc;i++)
   374     {
   375 	s=strstr(post_argv[i],"%INSTALL_PREFIX%");
   376 	if (s)
   377 	{
   378 	    *s='\0';
   379 	    s+=strlen("%INSTALL_PREFIX%");
   380 	    expanded=g_strconcat(post_argv[i],prefix,s,NULL);
   381 	    g_free(post_argv[i]);
   382 	    post_argv[i]=expanded;
   383 	}
   384 	s=strstr(post_argv[i],"%TEST_RESULT%");
   385 	if (s)
   386 	{
   387 	    *s='\0';
   388 	    s+=strlen("%TEST_RESULT%");
   389 	    expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
   390 	    g_free(post_argv[i]);
   391 	    post_argv[i]=expanded;
   392 	}
   393     }
   394 #ifdef USE_G_SPAWN
   395     if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
   396       &standard_output,&standard_error,&exit_status,&tmp_error))
   397     {
   398 	fprintf(stderr,"Failed to start post command\n");
   399 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   400 	return FALSE;
   401     }
   402     if (standard_output && *standard_output)
   403     {
   404 	printf("Output from post command %s:\n",post_argv[0]);
   405 	fputs(standard_output,stdout);
   406     }
   407     g_free(standard_output);
   408     if (standard_error && *standard_error)
   409     {
   410 	printf("Error output from post command %s:\n",post_argv[0]);
   411 	fputs(standard_error,stdout);
   412     }
   413     g_free(standard_error);
   414     if (!g_spawn_check_exit_status(exit_status,&tmp_error))
   415     {
   416 	fprintf(stderr,"post command failed\n");
   417 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   418 	return FALSE;
   419     }
   420 #else
   421     if (!spawn_sync(post_argv,&tmp_error))
   422     {
   423 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   424 	return FALSE;
   425     }
   426 #endif
   427     return TRUE;
   428 }
   429 
   430 #ifdef WIN32
   431 DWORD win32_pre_install_gui(char *path)
   432 {
   433     HANDLE thread;
   434     INITCOMMONCONTROLSEX icc={0,};
   435     MSG msg;
   436     DWORD retval;
   437     main_thread_id=GetCurrentThreadId();
   438     thread=(HANDLE)pre_install(path);
   439     if (!thread)
   440 	return EXIT_FAILURE;
   441     icc.dwSize=sizeof(icc);
   442     icc.dwICC=ICC_WIN95_CLASSES;
   443     InitCommonControlsEx(&icc);
   444     DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
   445       &ProgressDialogProc);
   446     while(GetMessage(&msg,NULL,0,0)>0)
   447     {
   448 	TranslateMessage(&msg);
   449 	DispatchMessage(&msg);
   450     }
   451     WaitForSingleObject(thread,INFINITE);
   452     GetExitCodeThread(thread,&retval);
   453     CloseHandle(thread);
   454     return retval;
   455 }
   456 #endif	/* WIN32 */
   457 
   458 int main(int argc,char **argv)
   459 {
   460     gboolean success,uninstall=FALSE,enable_post=FALSE;
   461     GError *error=NULL;
   462     gchar *path=NULL;
   463     GOptionContext *context;
   464     GOptionEntry options[]={
   465 	{"path",0,0,G_OPTION_ARG_FILENAME,&path,
   466 	  "Repository path","path"},
   467 	{"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
   468 	  "Uninstall all packages",NULL},
   469 	{"post",0,0,G_OPTION_ARG_NONE,&enable_post,
   470 	  "Run command after request is processed",NULL},
   471 	{NULL}
   472     };
   473 #ifdef WIN32
   474     /*
   475      * pre-inst is normally a GUI application, but rpm scripts may well
   476      * call console applications and it looks ugly if console windows keep
   477      * popping up. Avoid this by allocating our own console and hiding it.
   478      * Note:
   479      *	- If pre-inst is a console application (typically for debugging),
   480      *    then skip this step.
   481      *  - Call ShowWindow twice to negate special handling on first call.
   482      */
   483     if (!GetConsoleWindow())
   484     {
   485 	AllocConsole();
   486 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   487 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   488     }
   489 #endif
   490     plover_exception_handler_init();
   491     context=g_option_context_new("[command] - install the main installer");
   492     g_option_context_add_main_entries(context,options,NULL);
   493     g_option_context_set_description(context,
   494       "If --post is specified, then the command to run and its arguments\n"
   495       "should be listed at the end of the command line.\n"
   496       "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
   497       "(first) install prefix used and/or %TEST_RESULT% which will be\n"
   498       "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
   499       "request succeeded or not. Command may also include double quotes which\n"
   500       "will be used to affect how the command is split into arguments much\n"
   501       "like a shell does.");
   502     g_option_context_set_strict_posix(context,TRUE);
   503     g_option_context_set_ignore_unknown_options(context,TRUE);
   504     if (!g_option_context_parse(context,&argc,&argv,&error))
   505     {
   506 	g_printerr("pre-install: %s\n",error->message);
   507 	g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
   508 	exit(1);
   509     }
   510     if (uninstall)
   511 	success=pre_uninstall();
   512     else
   513     {
   514 	if (!path)
   515 	    path=plover_get_program_directory(argv[0]);
   516 #ifdef WIN32
   517 	success=win32_pre_install_gui(path)==EXIT_SUCCESS;
   518 #else
   519 	success=!!pre_install(path);
   520 #endif
   521     }
   522     if (enable_post && !run_post(argc-1,argv+1,success,&error))
   523     {
   524 #ifndef WIN32
   525 	fprintf(stderr,"Error in post: %s\n",error->message);
   526 #else
   527 	MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
   528 #endif
   529 	g_error_free(error);
   530 	success=FALSE;
   531     }
   532 #ifdef WIN32
   533     return success?EXIT_SUCCESS:EXIT_FAILURE;
   534 #else
   535     return success?0:1;
   536 #endif
   537 }