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