pre-inst/pre-inst.c
author J. Ali Harlow <ali@juiblex.co.uk>
Tue Jul 14 13:17:21 2020 +0100 (2020-07-14)
changeset 95 212150407fcc
parent 94 0e1a57225c62
child 96 d2d88f14283e
permissions -rw-r--r--
Add support to pre-inst for %REPOSITORY%
     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     const char *repository=data;
    94     gchar *s,*uri;
    95     char *install[]={"plover-gtkui",NULL};
    96     GError *error=NULL;
    97     plover__uri_handler_init();
    98     prefix=plover_pre_install_prefix();
    99     s=g_strconcat(prefix,"/var/log/pre-install",NULL);
   100     plover_log_open(s);
   101     g_free(s);
   102     s=g_strconcat(prefix,"/var/lib/razor",NULL);
   103     uri=razor_path_to_uri(s);
   104     g_free(s);
   105     razor_set_database_uri(uri);
   106     free(uri);
   107     if (verify_and_fix(prefix))
   108     {
   109 	g_free(prefix);
   110 	return -1;
   111     }
   112     retval=!plover_install_uri(repository,prefix,install,&error);
   113     if (!retval)
   114 	retval=!plover_update_uri(repository,prefix,NULL,&error);
   115     if (error)
   116     {
   117 	fprintf(stderr,"%s\n",error->message);
   118 	g_error_free(error);
   119     }
   120 #ifdef WIN32
   121     PostQuitMessage(retval);
   122     PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
   123     _endthreadex(retval);
   124 #endif
   125     return retval;
   126 }
   127 
   128 /*
   129  * The idea is that if pre_install() fails, update/setup should fall back
   130  * to console interfaces.
   131  */
   132 #ifdef WIN32
   133 HANDLE
   134 #else
   135 void *
   136 #endif
   137 pre_install(const char *repository)
   138 {
   139 #ifdef WIN32
   140     HANDLE retval;
   141 #else
   142     void *retval;
   143 #endif
   144     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   145     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   146 #ifdef WIN32
   147     retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository,
   148       0,NULL);
   149 #else
   150     if (pre_install_thread((void *)repository))
   151 	retval=NULL;
   152     else
   153 	retval=(void *)1;		/* Non-NULL to indicate success */
   154 #endif
   155     return retval;
   156 }
   157 
   158 #ifndef WIN32
   159 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
   160   struct FTW *ftwbuf)
   161 {
   162     (void)remove(fpath);
   163     return 0;
   164 }
   165 #endif
   166 
   167 gboolean deltree(const char *path)
   168 {
   169 #ifdef WIN32
   170     /* Based on g_local_file_trash() */
   171     SHFILEOPSTRUCTW op={0};
   172     gboolean success;
   173     wchar_t *wfilename;
   174     long len;
   175     wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
   176     /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
   177     wfilename=g_renew(wchar_t,wfilename,len+2);
   178     wfilename[len+1]=0;
   179     op.wFunc=FO_DELETE;
   180     op.pFrom=wfilename;
   181     op.fFlags=FOF_NO_UI;
   182     success=!SHFileOperationW(&op);
   183     if (success && op.fAnyOperationsAborted)
   184 	success=FALSE;
   185     g_free(wfilename);
   186     return success;
   187 #else
   188     return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
   189 #endif
   190 }
   191 
   192 gboolean pre_uninstall(void)
   193 {
   194     gboolean success;
   195     gchar *s,*uri;
   196     GError *error=NULL;
   197     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   198     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   199     prefix=plover_pre_install_prefix();
   200     s=g_strconcat(prefix,"/var/lib/razor",NULL);
   201     uri=razor_path_to_uri(s);
   202     g_free(s);
   203     razor_set_database_uri(uri);
   204     free(uri);
   205     success=plover_remove(NULL,&error);
   206     if (error)
   207     {
   208 	fprintf(stderr,"%s\n",error->message);
   209 	g_error_free(error);
   210     }
   211     deltree(prefix);
   212     return success;
   213 }
   214 
   215 #if defined(WIN32) && !defined(USE_G_SPAWN)
   216 /* Based on glib's g_spawn_win32.c */
   217 
   218 static gchar *
   219 win32_cmdline_quote(const char *string)
   220 {
   221     const gchar *p=string;
   222     gchar *retval,*q;
   223     gint len=0;
   224     gboolean need_dblquotes=FALSE;
   225     while (*p)
   226     {
   227 	if (*p==' ' || *p=='\t')
   228 	    need_dblquotes=TRUE;
   229 	else if (*p=='"')
   230 	    len++;
   231 	else if (*p=='\\')
   232 	{
   233 	    const gchar *pp=p;
   234 	    while (*pp && *pp=='\\')
   235 		pp++;
   236 	    if (*pp=='"')
   237 		len++;
   238 	}
   239 	len++;
   240 	p++;
   241     }
   242     q=retval=g_malloc(len+need_dblquotes*2+1);
   243     p=string;
   244     if (need_dblquotes)
   245 	*q++='"';
   246     while (*p)
   247     {
   248 	if (*p=='"')
   249 	    *q++='\\';
   250 	else if (*p=='\\')
   251 	{
   252 	    const gchar *pp=p;
   253 	    while (*pp && *pp=='\\')
   254 		pp++;
   255 	    if (*pp=='"')
   256 		*q++='\\';
   257 	}
   258 	*q++=*p;
   259 	p++;
   260     }
   261     if (need_dblquotes)
   262 	*q++='"';
   263     *q++='\0';
   264     return retval;
   265 }
   266 
   267 /* Create a win32-style wide-character argv with suitable quoting */
   268 wchar_t **win32_argv_import(char **argv,GError **error)
   269 {
   270     int i,n;
   271     gchar *s;
   272     wchar_t **wargv;
   273     GError *tmp_error=NULL;
   274     n=g_strv_length(argv);
   275     wargv=g_new(wchar_t *,n+1);
   276     for(i=0;i<n;i++)
   277     {
   278 	s=win32_cmdline_quote(argv[i]);
   279 	wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
   280 	g_free(s);
   281 	if (!wargv[i])
   282 	{
   283 	    g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   284 	      "Invalid argument #%d: %s",i,tmp_error->message);
   285 	    g_error_free(tmp_error);
   286 	    for(i--;i>=0;i--)
   287 		g_free(wargv[i]);
   288 	    g_free(wargv);
   289 	    return FALSE;
   290 	}
   291     }
   292     wargv[i]=NULL;
   293     return wargv;
   294 }
   295 
   296 gboolean spawn_sync(char **argv,GError **error)
   297 {
   298     wchar_t *wargv0,**wargv;
   299     gintptr rc;
   300     GError *tmp_error=NULL;
   301     wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
   302     if (!wargv0)
   303     {
   304 	fprintf(stderr,"Conversion error in post\n");
   305 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   306 	  "Invalid program name: %s",tmp_error->message);
   307 	g_error_free(tmp_error);
   308 	return FALSE;
   309     }
   310     wargv=win32_argv_import(argv,error);
   311     if (!wargv)
   312     {
   313 	fprintf(stderr,"Conversion error in post\n");
   314 	g_free(wargv0);
   315 	return FALSE;
   316     }
   317     errno=0;
   318     rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
   319     g_free(wargv0);
   320     g_strfreev((gchar **)wargv);
   321     if (rc==-1 && errno!=0)
   322     {
   323 	fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
   324 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   325 	  "Failed to execute post command: %s",g_strerror(errno));
   326 	return FALSE;
   327     }
   328     if (rc!=EXIT_SUCCESS)
   329     {
   330 	fprintf(stderr,"post command failed (%ld)\n",(long)rc);
   331 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   332 	  "Post command exited with code %ld",(long)rc);
   333 	return FALSE;
   334     }
   335     return TRUE;
   336 }
   337 
   338 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
   339 
   340 /*
   341  * Run a command after completing request.
   342  *
   343  * Command may refer to %INSTALL_PREFIX% which will be replaced by the
   344  * (first) install prefix used, by %TEST_RESULT% which will be replaced
   345  * by either "pass" or "fail" depending as to whether the request succeeded
   346  * or not and/or by %REPOSITORY% which will be replaced by the URI of the
   347  * repository used. Command may also include double quotes which will be used
   348  * to 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,
   351   const char *repository,GError **error)
   352 {
   353     int i,post_argc;
   354     char *s;
   355     gchar *expanded;
   356     gchar **post_argv;
   357 #ifdef USE_G_SPAWN
   358     gchar *standard_output,*standard_error;
   359     int exit_status;
   360 #endif
   361     GError *tmp_error=NULL;
   362     if (argc<2)
   363     {
   364 	g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
   365 	  "--post: No command given");
   366 	return FALSE;
   367     }
   368     printf("Running post command: %s\n",argv[1]);
   369     if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
   370     {
   371 	g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
   372 	return FALSE;
   373     }
   374     for(i=0;i<post_argc;i++)
   375     {
   376 	s=strstr(post_argv[i],"%INSTALL_PREFIX%");
   377 	if (s)
   378 	{
   379 	    *s='\0';
   380 	    s+=strlen("%INSTALL_PREFIX%");
   381 	    expanded=g_strconcat(post_argv[i],prefix,s,NULL);
   382 	    g_free(post_argv[i]);
   383 	    post_argv[i]=expanded;
   384 	}
   385 	s=strstr(post_argv[i],"%TEST_RESULT%");
   386 	if (s)
   387 	{
   388 	    *s='\0';
   389 	    s+=strlen("%TEST_RESULT%");
   390 	    expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
   391 	    g_free(post_argv[i]);
   392 	    post_argv[i]=expanded;
   393 	}
   394 	s=strstr(post_argv[i],"%REPOSITORY%");
   395 	if (s)
   396 	{
   397 	    *s='\0';
   398 	    s+=strlen("%REPOSITORY%");
   399 	    expanded=g_strconcat(post_argv[i],repository,s,NULL);
   400 	    g_free(post_argv[i]);
   401 	    post_argv[i]=expanded;
   402 	}
   403     }
   404 #ifdef USE_G_SPAWN
   405     if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
   406       &standard_output,&standard_error,&exit_status,&tmp_error))
   407     {
   408 	fprintf(stderr,"Failed to start post command\n");
   409 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   410 	return FALSE;
   411     }
   412     if (standard_output && *standard_output)
   413     {
   414 	printf("Output from post command %s:\n",post_argv[0]);
   415 	fputs(standard_output,stdout);
   416     }
   417     g_free(standard_output);
   418     if (standard_error && *standard_error)
   419     {
   420 	printf("Error output from post command %s:\n",post_argv[0]);
   421 	fputs(standard_error,stdout);
   422     }
   423     g_free(standard_error);
   424     if (!g_spawn_check_exit_status(exit_status,&tmp_error))
   425     {
   426 	fprintf(stderr,"post command failed\n");
   427 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   428 	return FALSE;
   429     }
   430 #else
   431     if (!spawn_sync(post_argv,&tmp_error))
   432     {
   433 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   434 	return FALSE;
   435     }
   436 #endif
   437     return TRUE;
   438 }
   439 
   440 #ifdef WIN32
   441 DWORD win32_pre_install_gui(char *repository)
   442 {
   443     HANDLE thread;
   444     INITCOMMONCONTROLSEX icc={0,};
   445     MSG msg;
   446     DWORD retval;
   447     main_thread_id=GetCurrentThreadId();
   448     thread=(HANDLE)pre_install(repository);
   449     if (!thread)
   450 	return EXIT_FAILURE;
   451     icc.dwSize=sizeof(icc);
   452     icc.dwICC=ICC_WIN95_CLASSES;
   453     InitCommonControlsEx(&icc);
   454     DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
   455       &ProgressDialogProc);
   456     while(GetMessage(&msg,NULL,0,0)>0)
   457     {
   458 	TranslateMessage(&msg);
   459 	DispatchMessage(&msg);
   460     }
   461     WaitForSingleObject(thread,INFINITE);
   462     GetExitCodeThread(thread,&retval);
   463     CloseHandle(thread);
   464     return retval;
   465 }
   466 #endif	/* WIN32 */
   467 
   468 gchar *pre_install_default_repository(const char *argv0)
   469 {
   470     size_t length;
   471     void *contents;
   472     gchar *path;
   473     gchar *s,*uri;
   474     struct razor_error *tmp_error=NULL;
   475     /*
   476      * The default repository is the executable itself if it's an archive
   477      * or otherwise the directory in which the executable is stored.
   478      */
   479     path=plover_get_program(argv0);
   480     uri=razor_path_to_uri(path);
   481     free(path);
   482     s=g_strconcat(uri,"/repodata/comps.xml",NULL);
   483     contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
   484     g_free(s);
   485     if (contents)
   486 	razor_uri_free_contents(contents,length);
   487     else
   488     {
   489 	if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
   490 	  RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
   491 	{
   492 	    path=plover_get_program_directory(argv0);
   493 	    uri=razor_path_to_uri(path);
   494 	    free(path);
   495 	}
   496 	razor_error_free(tmp_error);
   497     }
   498     return uri;
   499 }
   500 
   501 int main(int argc,char **argv)
   502 {
   503     gboolean success,uninstall=FALSE,enable_post=FALSE;
   504     GError *error=NULL;
   505     gchar *path=NULL,*repository=NULL;
   506     GOptionContext *context;
   507     GOptionEntry options[]={
   508 	{"repository",0,0,G_OPTION_ARG_STRING,&repository,
   509 	  "Repository location","uri"},
   510 	{"path",0,0,G_OPTION_ARG_FILENAME,&path,
   511 	  "Repository path","path"},
   512 	{"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
   513 	  "Uninstall all packages",NULL},
   514 	{"post",0,0,G_OPTION_ARG_NONE,&enable_post,
   515 	  "Run command after request is processed",NULL},
   516 	{NULL}
   517     };
   518 #ifdef WIN32
   519     /*
   520      * pre-inst is normally a GUI application, but rpm scripts may well
   521      * call console applications and it looks ugly if console windows keep
   522      * popping up. Avoid this by allocating our own console and hiding it.
   523      * Note:
   524      *	- If pre-inst is a console application (typically for debugging),
   525      *    then skip this step.
   526      *  - Call ShowWindow twice to negate special handling on first call.
   527      */
   528     if (!GetConsoleWindow())
   529     {
   530 	AllocConsole();
   531 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   532 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   533     }
   534 #endif
   535     plover_exception_handler_init();
   536     context=g_option_context_new("[command] - install the main installer");
   537     g_option_context_add_main_entries(context,options,NULL);
   538     g_option_context_set_description(context,
   539       "If --post is specified, then the command to run and its arguments\n"
   540       "should be listed at the end of the command line.\n"
   541       "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
   542       "(first) install prefix used, by %TEST_RESULT% which will be\n"
   543       "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
   544       "request succeeded or not and/or by %REPOSITORY% which will be\n"
   545       "replaced by the URI of the repository used. Command may also include\n"
   546       "double quotes which will be used to affect how the command is split\n"
   547       "into arguments much like a shell does.");
   548     g_option_context_set_strict_posix(context,TRUE);
   549     g_option_context_set_ignore_unknown_options(context,TRUE);
   550     if (!g_option_context_parse(context,&argc,&argv,&error))
   551     {
   552 	g_printerr("pre-install: %s\n",error->message);
   553 	g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
   554 	exit(1);
   555     }
   556     if (repository && path)
   557     {
   558 	g_printerr("pre-install: "
   559 	  "Only one of --repository and --path can be specified\n");
   560 	exit(1);
   561     }
   562     if (path)
   563 	repository=razor_path_to_uri(path);
   564     else if (!repository)
   565 	repository=pre_install_default_repository(argv[0]);
   566     if (uninstall)
   567 	success=pre_uninstall();
   568     else
   569     {
   570 #ifdef WIN32
   571 	success=win32_pre_install_gui(repository)==EXIT_SUCCESS;
   572 #else
   573 	success=!!pre_install(repository);
   574 #endif
   575     }
   576     if (enable_post && !run_post(argc,argv,success,repository,&error))
   577     {
   578 #ifndef WIN32
   579 	fprintf(stderr,"Error in post: %s\n",error->message);
   580 #else
   581 	MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
   582 #endif
   583 	g_error_free(error);
   584 	success=FALSE;
   585     }
   586 #ifdef WIN32
   587     return success?EXIT_SUCCESS:EXIT_FAILURE;
   588 #else
   589     return success?0:1;
   590 #endif
   591 }