pre-inst/pre-inst.c
author J. Ali Harlow <ali@juiblex.co.uk>
Tue Apr 24 18:49:55 2018 +0100 (2018-04-24)
changeset 67 c9eb7aa21ff4
parent 38 a29623b68ca2
child 80 671df13f1304
permissions -rw-r--r--
Release 0.5.3
     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 }