pre-inst/pre-inst.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Jul 16 11:07:18 2016 +0100 (2016-07-16)
changeset 61 31fb35727621
parent 38 a29623b68ca2
child 80 671df13f1304
permissions -rw-r--r--
Support parallel installations. The idea is that for CAD screener, we want
to be able to install this on the same machine as a standard AVOT setup
(most notably for John's laptop). To allow for the possibility of a second
application that might have the same requirements, we add the concept of
vendor-specific distributions. Thus we can have one distribution for CAD
screener and one for The Next Big Thing. It doesn't seem trivial to have
both CAD screener and AVOT under the same vendor tag so we'll have to have
AVOT under "City Occupational" and CAD screener under "City Occupational Ltd"
or some such kludge.

Most of this is done although we are very short of test cases (in particular
we don't test that it's actually possible to install CAD screener in parallel
with AVOT or to update either of them once installed, which is fundamental).

We also have a lot of baggage left over, including an intercept of razor_set.
The problem that this was introduced to debug has been fixed but it looks
like there are a number of memory leaks which it might be useful to help
track down so it has been left in place for now.

There is still a lot of confusion in plover between path-based and URI-based
API. We should review the API, decide what we want and have a general clear up.

There is also confusion as to the purpose of RAZOR_ROOT (and meaning; path or
URI). This is not used at all in librazor (although it is used in razor.exe).
Ideally we shouldn't use it in plover or plover-gtk either although again, we
might want to support it or an equivalent in (some of) the various executables.

Work that would still to nice to do for CAD screener:

- uninstall (ideally as an installed program that hooks into Add/Remove programs
but even re-running the installer would be acceptable).
- xz support (smaller packages).
- repomd.xml and xml:base (would be needed for an Internet installer).
- graphical installer.
     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 *argv0)
   139 {
   140 #ifdef WIN32
   141     HANDLE retval;
   142 #else
   143     void *retval;
   144 #endif
   145     char *path;
   146     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   147     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   148     path=plover_get_program_directory(argv0);
   149 #ifdef WIN32
   150     retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,path,0,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  * bu 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<1)
   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 *argv0)
   433 {
   434     HANDLE thread;
   435     INITCOMMONCONTROLSEX icc={0,};
   436     MSG msg;
   437     DWORD retval;
   438     main_thread_id=GetCurrentThreadId();
   439     thread=(HANDLE)pre_install(argv0);
   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 int main(int argc,char **argv)
   460 {
   461     gboolean success;
   462     GError *error=NULL;
   463 #ifdef WIN32
   464     /*
   465      * pre-inst is normally a GUI application, but rpm scripts may well
   466      * call console applications and it looks ugly if console windows keep
   467      * popping up. Avoid this by allocating our own console and hiding it.
   468      * Note:
   469      *	- If pre-inst is a console application (typically for debugging),
   470      *    then skip this step.
   471      *  - Call ShowWindow twice to negate special handling on first call.
   472      */
   473     if (!GetConsoleWindow())
   474     {
   475 	AllocConsole();
   476 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   477 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   478     }
   479 #endif
   480     plover_exception_handler_init();
   481     if (argc>1 && !strcmp(argv[1],"-u"))
   482     {
   483 	success=pre_uninstall();
   484 	argc--;
   485 	argv++;
   486     }
   487     else
   488 #ifdef WIN32
   489 	success=win32_pre_install_gui(argv[0])==EXIT_SUCCESS;
   490 #else
   491 	success=!!pre_install(argv[0]);
   492 #endif
   493     if (argc>1 && !strcmp(argv[1],"--post") &&
   494       !run_post(argc-1,argv+1,success,&error))
   495     {
   496 #ifndef WIN32
   497 	fprintf(stderr,"Error in post: %s\n",error->message);
   498 #else
   499 	MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
   500 #endif
   501 	g_error_free(error);
   502 	success=FALSE;
   503     }
   504 #ifdef WIN32
   505     return success?EXIT_SUCCESS:EXIT_FAILURE;
   506 #else
   507     return success?0:1;
   508 #endif
   509 }