pre-inst/pre-inst.c
author J. Ali Harlow <ali@juiblex.co.uk>
Mon Jun 13 12:18:42 2016 +0100 (2016-06-13)
changeset 38 a29623b68ca2
parent 31 a53fcb780468
child 46 360621bc323e
permissions -rw-r--r--
Add a testsuite and fix bugs found with it
     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;
    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     razor_set_database_path(s);
   103     g_free(s);
   104     if (verify_and_fix(prefix))
   105     {
   106 	free(path);
   107 	g_free(prefix);
   108 	return -1;
   109     }
   110     retval=!plover_install(path,prefix,install,&error);
   111     if (!retval)
   112 	retval=!plover_update(path,prefix,NULL,&error);
   113     if (error)
   114     {
   115 	fprintf(stderr,"%s\n",error->message);
   116 	g_error_free(error);
   117     }
   118     free(path);
   119 #ifdef WIN32
   120     PostQuitMessage(retval);
   121     PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
   122     _endthreadex(retval);
   123 #endif
   124     return retval;
   125 }
   126 
   127 /*
   128  * The idea is that if pre_install() fails, update/setup should fall back
   129  * to console interfaces.
   130  */
   131 #ifdef WIN32
   132 HANDLE
   133 #else
   134 void *
   135 #endif
   136 pre_install(const char *argv0)
   137 {
   138 #ifdef WIN32
   139     HANDLE retval;
   140 #else
   141     void *retval;
   142 #endif
   143     char *path;
   144     razor_set_lua_loader("posix",(void (*)())luaopen_posix);
   145     razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
   146     path=plover_get_program_directory(argv0);
   147 #ifdef WIN32
   148     retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,path,0,NULL);
   149 #else
   150     if (pre_install_thread(path))
   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;
   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     razor_set_database_path(s);
   202     g_free(s);
   203     success=plover_remove(NULL,&error);
   204     if (error)
   205     {
   206 	fprintf(stderr,"%s\n",error->message);
   207 	g_error_free(error);
   208     }
   209     deltree(prefix);
   210     return success;
   211 }
   212 
   213 #if defined(WIN32) && !defined(USE_G_SPAWN)
   214 /* Based on glib's g_spawn_win32.c */
   215 
   216 static gchar *
   217 win32_cmdline_quote(const char *string)
   218 {
   219     const gchar *p=string;
   220     gchar *retval,*q;
   221     gint len=0;
   222     gboolean need_dblquotes=FALSE;
   223     while (*p)
   224     {
   225 	if (*p==' ' || *p=='\t')
   226 	    need_dblquotes=TRUE;
   227 	else if (*p=='"')
   228 	    len++;
   229 	else if (*p=='\\')
   230 	{
   231 	    const gchar *pp=p;
   232 	    while (*pp && *pp=='\\')
   233 		pp++;
   234 	    if (*pp=='"')
   235 		len++;
   236 	}
   237 	len++;
   238 	p++;
   239     }
   240     q=retval=g_malloc(len+need_dblquotes*2+1);
   241     p=string;
   242     if (need_dblquotes)
   243 	*q++='"';
   244     while (*p)
   245     {
   246 	if (*p=='"')
   247 	    *q++='\\';
   248 	else if (*p=='\\')
   249 	{
   250 	    const gchar *pp=p;
   251 	    while (*pp && *pp=='\\')
   252 		pp++;
   253 	    if (*pp=='"')
   254 		*q++='\\';
   255 	}
   256 	*q++=*p;
   257 	p++;
   258     }
   259     if (need_dblquotes)
   260 	*q++='"';
   261     *q++='\0';
   262     return retval;
   263 }
   264 
   265 /* Create a win32-style wide-character argv with suitable quoting */
   266 wchar_t **win32_argv_import(char **argv,GError **error)
   267 {
   268     int i,n;
   269     gchar *s;
   270     wchar_t **wargv;
   271     GError *tmp_error=NULL;
   272     n=g_strv_length(argv);
   273     wargv=g_new(wchar_t *,n+1);
   274     for(i=0;i<n;i++)
   275     {
   276 	s=win32_cmdline_quote(argv[i]);
   277 	wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
   278 	g_free(s);
   279 	if (!wargv[i])
   280 	{
   281 	    g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   282 	      "Invalid argument #%d: %s",i,tmp_error->message);
   283 	    g_error_free(tmp_error);
   284 	    for(i--;i>=0;i--)
   285 		g_free(wargv[i]);
   286 	    g_free(wargv);
   287 	    return FALSE;
   288 	}
   289     }
   290     wargv[i]=NULL;
   291     return wargv;
   292 }
   293 
   294 gboolean spawn_sync(char **argv,GError **error)
   295 {
   296     wchar_t *wargv0,**wargv;
   297     gintptr rc;
   298     GError *tmp_error=NULL;
   299     wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
   300     if (!wargv0)
   301     {
   302 	fprintf(stderr,"Conversion error in post\n");
   303 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   304 	  "Invalid program name: %s",tmp_error->message);
   305 	g_error_free(tmp_error);
   306 	return FALSE;
   307     }
   308     wargv=win32_argv_import(argv,error);
   309     if (!wargv)
   310     {
   311 	fprintf(stderr,"Conversion error in post\n");
   312 	g_free(wargv0);
   313 	return FALSE;
   314     }
   315     errno=0;
   316     rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
   317     g_free(wargv0);
   318     g_strfreev((gchar **)wargv);
   319     if (rc==-1 && errno!=0)
   320     {
   321 	fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
   322 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   323 	  "Failed to execute post command: %s",g_strerror(errno));
   324 	return FALSE;
   325     }
   326     if (rc!=EXIT_SUCCESS)
   327     {
   328 	fprintf(stderr,"post command failed (%ld)\n",(long)rc);
   329 	g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
   330 	  "Post command exited with code %ld",(long)rc);
   331 	return FALSE;
   332     }
   333     return TRUE;
   334 }
   335 
   336 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
   337 
   338 /*
   339  * Run a command after completing request.
   340  *
   341  * Command may refer to %INSTALL_PREFIX% which will be replaced by the
   342  * (first) install prefix used and/or %TEST_RESULT% which will be replaced
   343  * bu either "pass" or "fail" depending as to whether the request succeeded
   344  * or not. Command may also include double quotes which will be used to
   345  * affect how the command is split into arguments much like a shell does.
   346  */
   347 gboolean run_post(int argc,char **argv,gboolean test_result,GError **error)
   348 {
   349     int i,post_argc;
   350     char *s;
   351     gchar *expanded;
   352     gchar **post_argv;
   353 #ifdef USE_G_SPAWN
   354     gchar *standard_output,*standard_error;
   355     int exit_status;
   356 #endif
   357     GError *tmp_error=NULL;
   358     if (argc<1)
   359     {
   360 	g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
   361 	  "--post: No command given");
   362 	return FALSE;
   363     }
   364     printf("Running post command: %s\n",argv[1]);
   365     if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
   366     {
   367 	g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
   368 	return FALSE;
   369     }
   370     for(i=0;i<post_argc;i++)
   371     {
   372 	s=strstr(post_argv[i],"%INSTALL_PREFIX%");
   373 	if (s)
   374 	{
   375 	    *s='\0';
   376 	    s+=strlen("%INSTALL_PREFIX%");
   377 	    expanded=g_strconcat(post_argv[i],prefix,s,NULL);
   378 	    g_free(post_argv[i]);
   379 	    post_argv[i]=expanded;
   380 	}
   381 	s=strstr(post_argv[i],"%TEST_RESULT%");
   382 	if (s)
   383 	{
   384 	    *s='\0';
   385 	    s+=strlen("%TEST_RESULT%");
   386 	    expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
   387 	    g_free(post_argv[i]);
   388 	    post_argv[i]=expanded;
   389 	}
   390     }
   391 #ifdef USE_G_SPAWN
   392     if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
   393       &standard_output,&standard_error,&exit_status,&tmp_error))
   394     {
   395 	fprintf(stderr,"Failed to start post command\n");
   396 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   397 	return FALSE;
   398     }
   399     if (standard_output && *standard_output)
   400     {
   401 	printf("Output from post command %s:\n",post_argv[0]);
   402 	fputs(standard_output,stdout);
   403     }
   404     g_free(standard_output);
   405     if (standard_error && *standard_error)
   406     {
   407 	printf("Error output from post command %s:\n",post_argv[0]);
   408 	fputs(standard_error,stdout);
   409     }
   410     g_free(standard_error);
   411     if (!g_spawn_check_exit_status(exit_status,&tmp_error))
   412     {
   413 	fprintf(stderr,"post command failed\n");
   414 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   415 	return FALSE;
   416     }
   417 #else
   418     if (!spawn_sync(post_argv,&tmp_error))
   419     {
   420 	g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
   421 	return FALSE;
   422     }
   423 #endif
   424     return TRUE;
   425 }
   426 
   427 #ifdef WIN32
   428 DWORD win32_pre_install_gui(char *argv0)
   429 {
   430     HANDLE thread;
   431     INITCOMMONCONTROLSEX icc={0,};
   432     MSG msg;
   433     DWORD retval;
   434     main_thread_id=GetCurrentThreadId();
   435     thread=(HANDLE)pre_install(argv0);
   436     if (!thread)
   437 	return EXIT_FAILURE;
   438     icc.dwSize=sizeof(icc);
   439     icc.dwICC=ICC_WIN95_CLASSES;
   440     InitCommonControlsEx(&icc);
   441     DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
   442       &ProgressDialogProc);
   443     while(GetMessage(&msg,NULL,0,0)>0)
   444     {
   445 	TranslateMessage(&msg);
   446 	DispatchMessage(&msg);
   447     }
   448     WaitForSingleObject(thread,INFINITE);
   449     GetExitCodeThread(thread,&retval);
   450     CloseHandle(thread);
   451     return retval;
   452 }
   453 #endif	/* WIN32 */
   454 
   455 int main(int argc,char **argv)
   456 {
   457     gboolean success;
   458     GError *error=NULL;
   459 #ifdef WIN32
   460     /*
   461      * pre-inst is normally a GUI application, but rpm scripts may well
   462      * call console applications and it looks ugly if console windows keep
   463      * popping up. Avoid this by allocating our own console and hiding it.
   464      * Note:
   465      *	- If pre-inst is a console application (typically for debugging),
   466      *    then skip this step.
   467      *  - Call ShowWindow twice to negate special handling on first call.
   468      */
   469     if (!GetConsoleWindow())
   470     {
   471 	AllocConsole();
   472 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   473 	ShowWindow(GetConsoleWindow(),SW_HIDE);
   474     }
   475 #endif
   476     plover_exception_handler_init();
   477     if (argc>1 && !strcmp(argv[1],"-u"))
   478     {
   479 	success=pre_uninstall();
   480 	argc--;
   481 	argv++;
   482     }
   483     else
   484 #ifdef WIN32
   485 	success=win32_pre_install_gui(argv[0])==EXIT_SUCCESS;
   486 #else
   487 	success=!!pre_install(argv[0]);
   488 #endif
   489     if (argc>1 && !strcmp(argv[1],"--post") &&
   490       !run_post(argc-1,argv+1,success,&error))
   491     {
   492 #ifndef WIN32
   493 	fprintf(stderr,"Error in post: %s\n",error->message);
   494 #else
   495 	MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
   496 #endif
   497 	g_error_free(error);
   498 	success=FALSE;
   499     }
   500 #ifdef WIN32
   501     return success?EXIT_SUCCESS:EXIT_FAILURE;
   502 #else
   503     return success?0:1;
   504 #endif
   505 }