Repository: mesonbuild/meson

Issue:
./run_tests.py prompts for my password
Commit 559286a (Support installation via polkit) causes `./run_tests.py` to prompt for my password. I press `^C`, then it doesn't run the rest of the tests. I don't want tests to prompt for user input; I do want to run the rest of the tests even if this test is failing. My machine runs OpenBSD/amd64 6.3; desktop is Xfce with OpenBSD packages xfce-4.12p9, consolekit2-1.2.1, polkit-0.113p5.

```
pehcehwahn$ ./run_tests.py | tee testlog.txt                                   
test_compiler_args_class (__main__.InternalTests) ... ok                       
test_extract_as_list (__main__.InternalTests) ... ok                           
test_listify (__main__.InternalTests) ... ok                                   
test_mode_symbolic_to_bits (__main__.InternalTests) ... ok                     
test_needs_exe_wrapper_override (__main__.InternalTests) ... ok                 
test_pkgconfig_module (__main__.InternalTests) ... ok                          
test_snippets (__main__.InternalTests) ... ok                                   
test_string_templates_substitution (__main__.InternalTests) ... ok
test_version_number (__main__.InternalTests) ... ok                            
test_absolute_prefix_libdir (__main__.AllPlatformTests) ... ok
test_all_forbidden_targets_tested (__main__.AllPlatformTests) ... ok           
test_always_prefer_c_compiler_for_asm (__main__.AllPlatformTests) ... ok       
test_array_option_bad_change (__main__.AllPlatformTests) ... ok                 
test_array_option_change (__main__.AllPlatformTests) ... ok                     
test_build_by_default (__main__.AllPlatformTests) ... ok                       
test_check_module_linking (__main__.AllPlatformTests) ... ok
test_command_line (__main__.AllPlatformTests) ... ok                           
test_compiler_detection (__main__.AllPlatformTests) ... ERROR                   
test_compiler_run_command (__main__.AllPlatformTests) ... ok                   
test_conflicting_d_dash_option (__main__.AllPlatformTests) ... ok               
test_cross_file_system_paths (__main__.AllPlatformTests) ... ok                
test_custom_target_changes_cause_rebuild (__main__.AllPlatformTests) ... ok    
test_custom_target_exe_data_deterministic (__main__.AllPlatformTests) ... ok   
test_dash_d_dedup (__main__.AllPlatformTests) ... ok                           
test_default_options_prefix (__main__.AllPlatformTests) ... ok                 
test_default_options_prefix_dependent_defaults (__main__.AllPlatformTests) ... o
k                                                                              
test_dist_git (__main__.AllPlatformTests) ... ==== AUTHENTICATING FOR org.freede
sktop.policykit.exec ===                                          
Authentication is needed to run `/usr/local/bin/python3' as the super user     
Authenticating as: kernigh                                    
password:Traceback (most recent call last):                                    
  File "./run_tests.py", line 217, in <module>                                 
    returncode += subprocess.call(mesonlib.python_command + ['run_unittests.py',
 '-v'], env=env)                                                                
  File "/usr/local/lib/python3.6/subprocess.py", line 269, in call             
    return p.wait(timeout=timeout)                          
  File "/usr/local/lib/python3.6/subprocess.py", line 1457, in wait            
    (pid, sts) = self._try_wait(0)                                              
  File "/usr/local/lib/python3.6/subprocess.py", line 1404, in _try_wait       
KeyboardInterrupt                                                              
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-
8'>                                                                            
BrokenPipeError: [Errno 32] Broken pipe                                        
```
./run_tests.py prompts for my password
Commit 559286a (Support installation via polkit) causes `./run_tests.py` to prompt for my password. I press `^C`, then it doesn't run the rest of the tests. I don't want tests to prompt for user input; I do want to run the rest of the tests even if this test is failing. My machine runs OpenBSD/amd64 6.3; desktop is Xfce with OpenBSD packages xfce-4.12p9, consolekit2-1.2.1, polkit-0.113p5.

```
pehcehwahn$ ./run_tests.py | tee testlog.txt                                   
test_compiler_args_class (__main__.InternalTests) ... ok                       
test_extract_as_list (__main__.InternalTests) ... ok                           
test_listify (__main__.InternalTests) ... ok                                   
test_mode_symbolic_to_bits (__main__.InternalTests) ... ok                     
test_needs_exe_wrapper_override (__main__.InternalTests) ... ok                 
test_pkgconfig_module (__main__.InternalTests) ... ok                          
test_snippets (__main__.InternalTests) ... ok                                   
test_string_templates_substitution (__main__.InternalTests) ... ok
test_version_number (__main__.InternalTests) ... ok                            
test_absolute_prefix_libdir (__main__.AllPlatformTests) ... ok
test_all_forbidden_targets_tested (__main__.AllPlatformTests) ... ok           
test_always_prefer_c_compiler_for_asm (__main__.AllPlatformTests) ... ok       
test_array_option_bad_change (__main__.AllPlatformTests) ... ok                 
test_array_option_change (__main__.AllPlatformTests) ... ok                     
test_build_by_default (__main__.AllPlatformTests) ... ok                       
test_check_module_linking (__main__.AllPlatformTests) ... ok
test_command_line (__main__.AllPlatformTests) ... ok                           
test_compiler_detection (__main__.AllPlatformTests) ... ERROR                   
test_compiler_run_command (__main__.AllPlatformTests) ... ok                   
test_conflicting_d_dash_option (__main__.AllPlatformTests) ... ok               
test_cross_file_system_paths (__main__.AllPlatformTests) ... ok                
test_custom_target_changes_cause_rebuild (__main__.AllPlatformTests) ... ok    
test_custom_target_exe_data_deterministic (__main__.AllPlatformTests) ... ok   
test_dash_d_dedup (__main__.AllPlatformTests) ... ok                           
test_default_options_prefix (__main__.AllPlatformTests) ... ok                 
test_default_options_prefix_dependent_defaults (__main__.AllPlatformTests) ... o
k                                                                              
test_dist_git (__main__.AllPlatformTests) ... ==== AUTHENTICATING FOR org.freede
sktop.policykit.exec ===                                          
Authentication is needed to run `/usr/local/bin/python3' as the super user     
Authenticating as: kernigh                                    
password:Traceback (most recent call last):                                    
  File "./run_tests.py", line 217, in <module>                                 
    returncode += subprocess.call(mesonlib.python_command + ['run_unittests.py',
 '-v'], env=env)                                                                
  File "/usr/local/lib/python3.6/subprocess.py", line 269, in call             
    return p.wait(timeout=timeout)                          
  File "/usr/local/lib/python3.6/subprocess.py", line 1457, in wait            
    (pid, sts) = self._try_wait(0)                                              
  File "/usr/local/lib/python3.6/subprocess.py", line 1404, in _try_wait       
KeyboardInterrupt                                                              
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-
8'>                                                                            
BrokenPipeError: [Errno 32] Broken pipe                                        
```


Discussion:
Probably due to #3567; I think it's only checking the final install directory and not checking all parent directories for access. Access is "not allowed" for non-existent directories.
Another issue is that sometimes `pkexec` is available but unusable. We should probably run something like `pkexec --version` to verify that it actually works.

CCing @kirbyfan64 
Fastest way to reproduce is to run: `./run_unittests.py  -v AllPlatformTests.test_dist_git`

Simplest way to fix this is probably to try installing without privilege escalation, and only try to use pkexec if that fails with a `PermissionError` or similar.
Oof, sorry about that. I'll have a fix ready when I get to a computer in a few hours. 
Probably due to #3567; I think it's only checking the final install directory and not checking all parent directories for access. Access is "not allowed" for non-existent directories.
Another issue is that sometimes `pkexec` is available but unusable. We should probably run something like `pkexec --version` to verify that it actually works.

CCing @kirbyfan64 
Fastest way to reproduce is to run: `./run_unittests.py  -v AllPlatformTests.test_dist_git`

Simplest way to fix this is probably to try installing without privilege escalation, and only try to use pkexec if that fails with a `PermissionError` or similar.
Oof, sorry about that. I'll have a fix ready when I get to a computer in a few hours. 

Resolving patch:
diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py
--- a/mesonbuild/scripts/meson_install.py
+++ b/mesonbuild/scripts/meson_install.py
@@ -230,10 +230,6 @@ def do_install(log_dir, datafilename):
     d.destdir = os.environ.get('DESTDIR', '')
     d.fullprefix = destdir_join(d.destdir, d.prefix)
 
-    if not os.access(d.fullprefix, os.W_OK) and shutil.which('pkexec') is not None:
-        os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
-                  os.getcwd())
-
     if d.install_umask is not None:
         os.umask(d.install_umask)
 
@@ -242,15 +238,25 @@ def do_install(log_dir, datafilename):
         append_to_log('# List of files installed by Meson')
         append_to_log('# Does not contain files installed by custom scripts.')
 
-        d.dirmaker = DirMaker()
-        with d.dirmaker:
-            install_subdirs(d) # Must be first, because it needs to delete the old subtree.
-            install_targets(d)
-            install_headers(d)
-            install_man(d)
-            install_data(d)
-            restore_selinux_contexts()
-            run_install_script(d)
+        try:
+            d.dirmaker = DirMaker()
+            with d.dirmaker:
+                install_subdirs(d) # Must be first, because it needs to delete the old subtree.
+                install_targets(d)
+                install_headers(d)
+                install_man(d)
+                install_data(d)
+                restore_selinux_contexts()
+                run_install_script(d)
+        except PermissionError:
+            if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ:
+                print('Installation failed due to insufficient permissions.')
+                print('Attempting to use polkit to gain elevated privileges...')
+                os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
+                          os.getcwd())
+            else:
+                raise
+
 
 def install_subdirs(d):
     for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:


Test patch: