github.com/singularityware/singularity@v3.1.1+incompatible/docs/2.x-slurm/singularity.c (about) 1 /* 2 * Copyright (c) 2017-2018, SyLabs, Inc. All rights reserved. 3 * Copyright (c) 2017, SingularityWare, LLC. All rights reserved. 4 * 5 * Copyright (c) 2015-2017, Gregory M. Kurtzer. All rights reserved. 6 * 7 * Copyright (c) 2016-2017, The Regents of the University of California, 8 * through Lawrence Berkeley National Laboratory (subject to receipt of any 9 * required approvals from the U.S. Dept. of Energy). All rights reserved. 10 * 11 * This software is licensed under a customized 3-clause BSD license. Please 12 * consult LICENSE file distributed with the sources of this project regarding 13 * your rights to use or distribute this software. 14 * 15 * NOTICE. This Software was developed under funding from the U.S. Department of 16 * Energy and the U.S. Government consequently retains certain rights. As such, 17 * the U.S. Government has been granted for itself and others acting on its 18 * behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software 19 * to reproduce, distribute copies to the public, prepare derivative works, and 20 * perform publicly and display publicly, and to permit other to do so. 21 * 22 */ 23 24 #define _GNU_SOURCE 1 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <unistd.h> 29 #include <sys/types.h> 30 #include <errno.h> 31 #include <string.h> 32 #include <fcntl.h> 33 34 #include "util/util.h" 35 #include "util/file.h" 36 #include "util/registry.h" 37 #include "lib/image/image.h" 38 #include "util/suid.h" 39 #include "util/sessiondir.h" 40 #include "util/cleanupd.h" 41 #include "lib/runtime/runtime.h" 42 #include "util/config_parser.h" 43 #include "util/privilege.h" 44 45 #include "action-lib/include.h" 46 47 #include "slurm/spank.h" 48 49 SPANK_PLUGIN(singularity, 1); 50 51 #ifndef SYSCONFDIR 52 #define SYSCONFDIR "/etc" 53 #endif 54 55 #define INT_MAX_STRING_SIZE 30 56 57 // These should only be set post-fork and before exec; 58 // this means reading / writing to these should be thread-safe. 59 static char job_uid_str[INT_MAX_STRING_SIZE]; //Flawfinder: ignore 60 static char job_gid_str[INT_MAX_STRING_SIZE]; //Flawfinder: ignore 61 static char *job_image = NULL; 62 static char *job_bindpath = NULL; 63 64 static int setup_container_environment(spank_t spank) 65 { 66 67 uid_t job_uid = -1; 68 gid_t job_gid = -1; 69 char *job_cwd = NULL; 70 char *bindpath = NULL; 71 int argc; 72 char **argv = NULL; 73 int i; 74 75 setenv("SINGULARITY_MESSAGELEVEL", "1", 0); //Don't overwrite if exists 76 77 if (ESPANK_SUCCESS != spank_get_item(spank, S_JOB_UID, &job_uid)) { 78 slurm_error("spank/%s: Failed to get job's target UID", plugin_name); 79 return -1; 80 } 81 if (INT_MAX_STRING_SIZE <= 82 snprintf(job_uid_str, INT_MAX_STRING_SIZE, "%u", job_uid)) { // Flawfinder: ignore 83 slurm_error("spank/%s: Failed to serialize job's UID to string", 84 plugin_name); 85 return -1; 86 } 87 if (setenv("SINGULARITY_TARGET_UID", job_uid_str, 1) < 0) { 88 slurm_error("spank/%s: Failed to setenv(\"SINGULARITY_TARGET_UID\")", 89 plugin_name); 90 return -1; 91 } 92 93 if (ESPANK_SUCCESS != spank_get_item(spank, S_JOB_GID, &job_gid)) { 94 slurm_error("spank/%s: Failed to get job's target GID", plugin_name); 95 return -1; 96 } 97 if (INT_MAX_STRING_SIZE <= 98 snprintf(job_gid_str, INT_MAX_STRING_SIZE, "%u", job_gid)) { // Flawfinder: ignore 99 slurm_error("spank/%s: Failed to serialize job's GID to string", 100 plugin_name); 101 return -1; 102 } 103 if (setenv("SINGULARITY_TARGET_GID", job_gid_str, 1) < 0) { 104 slurm_error("spank/%s: Failed to setenv(\"SINGULARITY_TARGET_GID\")", 105 plugin_name); 106 return -1; 107 } 108 109 job_cwd = get_current_dir_name(); 110 if (!job_cwd) { 111 slurm_error("spank/%s: Failed to determine job's correct PWD: %s", 112 plugin_name, strerror(errno)); 113 return -1; 114 } 115 if (setenv("SINGULARITY_TARGET_PWD", job_cwd, 1) < 0) { 116 slurm_error("spank/%s: Failed to setenv(\"SINGULARITY_TARGET_PWD\")", 117 plugin_name); 118 return -1; 119 } 120 /* setenv() makes a copy */ 121 free(job_cwd); 122 123 if (!job_image) { 124 slurm_error("spank/%s: Unable to determine job's image file.", 125 plugin_name); 126 return -1; 127 } 128 if (setenv("SINGULARITY_IMAGE", job_image, 1) < 0) { 129 slurm_error("spank/%s: Failed to setenv(\"SINGULARITY_IMAGE\")", 130 plugin_name); 131 return -1; 132 } 133 134 if ((job_bindpath) && 135 (setenv("SINGULARITY_BINDPATH", job_bindpath, 1) < 0)) { 136 slurm_error("spank/%s: Failed to setenv(\"SINGULARITY_BINDPATH\")", 137 plugin_name); 138 return -1; 139 } 140 141 return 0; 142 } 143 144 static int setup_container_cwd() { 145 singularity_message(DEBUG, "Trying to change directory to where we started\n"); 146 char *target_pwd = singularity_registry_get("TARGET_PWD"); 147 148 if (!target_pwd || (chdir(target_pwd) < 0)) { 149 singularity_message(ERROR, "Failed to change into correct directory " 150 "(%s) inside container.", 151 target_pwd ? target_pwd : "UNKNOWN"); 152 return -1; 153 } 154 free(target_pwd); 155 return 0; 156 } 157 158 static int setup_container(spank_t spank) 159 { 160 int rc; 161 struct image_object image; 162 char *command = NULL; 163 164 if ((rc = setup_container_environment(spank)) != 0) { return rc; } 165 166 /* 167 * Ugg, singularity_* calls tend to call ABORT(255), which translates to 168 * exit(255), all over the place. The slurm SPANK hook API may not 169 * expect such sudden death of the pending slurm task. I've left 170 * a bunch of following "return rc;" commented out, as the failure 171 * conditions from singularity_* calls isn't clear to me. 172 */ 173 174 // Before we do anything, check privileges and drop permission 175 singularity_priv_init(); 176 singularity_priv_drop(); 177 178 singularity_message(VERBOSE, "Running Slurm/Singularity integration " 179 "plugin\n"); 180 181 if ((rc = singularity_config_init()) != 0) { 182 return rc; 183 } 184 185 186 singularity_priv_init(); 187 //TODO singularity_suid_init(argv); 188 189 singularity_registry_init(); 190 singularity_priv_userns(); 191 singularity_priv_drop(); 192 193 singularity_cleanupd(); 194 195 singularity_runtime_ns(SR_NS_ALL); 196 197 singularity_sessiondir(); 198 199 image = singularity_image_init(singularity_registry_get("IMAGE")); 200 201 if ( singularity_registry_get("WRITABLE") == NULL ) { 202 singularity_image_open(&image, O_RDONLY); 203 } else { 204 singularity_image_open(&image, O_RDWR); 205 } 206 207 singularity_image_check(&image); 208 singularity_image_bind(&image); 209 singularity_image_mount(&image, singularity_runtime_rootfs(NULL)); 210 211 action_ready(singularity_runtime_rootfs(NULL)); 212 213 singularity_runtime_overlayfs(); 214 singularity_runtime_mounts(); 215 singularity_runtime_files(); 216 singularity_runtime_enter(); 217 218 singularity_runtime_environment(); 219 220 singularity_priv_drop_perm(); 221 222 223 if ((rc = setup_container_cwd()) < 0) { 224 singularity_message(ERROR, "Could not obtain current directory.\n"); 225 return rc; 226 } 227 228 envar_set("SINGULARITY_CONTAINER", singularity_image_name(&image), 1); // Legacy PS1 support 229 envar_set("SINGULARITY_NAME", singularity_image_name(&image), 1); 230 envar_set("SINGULARITY_SHELL", singularity_registry_get("SHELL"), 1); 231 232 command = singularity_registry_get("COMMAND"); 233 singularity_message(LOG, "USER=%s, IMAGE='%s', COMMAND='%s'\n", singularity_priv_getuser(), singularity_image_name(&image), singularity_registry_get("COMMAND")); 234 235 // At this point, the current process is in the runtime container environment. 236 // Return control flow back to Slurm: when execv is invoked, it'll be done from 237 // within the container. 238 239 return 0; 240 } 241 242 // TODO: When run on the submit host, we should evaluate the URL and create/cache docker images as necessary. 243 static int determine_image(int val, const char *optarg, int remote) 244 { 245 if (val) {} // Suppresses unused error... 246 // TODO: could do some basic path validation here in order to prevent an ABORT() later. 247 job_image = strdup(optarg); 248 249 return job_image == NULL; 250 } 251 252 static int determine_bind(int val, const char *optarg, int remote) 253 { 254 if (!job_bindpath) { 255 job_bindpath = strdup(optarg); 256 } 257 return job_bindpath == NULL; 258 } 259 260 /// SPANK plugin functions. 261 262 int slurm_spank_init(spank_t spank, int ac, char **av) 263 { 264 int i; 265 struct spank_option image_opt, 266 bind_opt; 267 268 memset(&image_opt, '\0', sizeof(image_opt)); 269 image_opt.name = "singularity-image"; 270 image_opt.arginfo = "[path]"; 271 image_opt.usage = "Specify a path to a Singularity image, directory tree, " 272 "or Docker image"; 273 image_opt.has_arg = 1; 274 image_opt.val = 0; 275 image_opt.cb = determine_image; 276 if (ESPANK_SUCCESS != spank_option_register(spank, &image_opt)) { 277 slurm_error("spank/%s: Unable to register a new option.", 278 plugin_name); 279 return -1; 280 } 281 282 memset(&bind_opt, '\0', sizeof(bind_opt)); 283 bind_opt.name = "singularity-bind"; 284 bind_opt.arginfo = "[path || src:dest],..."; 285 bind_opt.usage = "Specify a user-bind path specification. Can either be " 286 "a path or a src:dest pair, specifying the bind mount to " 287 "perform"; 288 bind_opt.has_arg = 1; 289 bind_opt.val = 0; 290 bind_opt.cb = determine_bind; 291 if (ESPANK_SUCCESS != spank_option_register(spank, &bind_opt)) { 292 slurm_error("spank/%s: Unable to register a new option.", 293 plugin_name); 294 return -1; 295 } 296 297 // Make this a no-op except when starting the task. 298 if (spank_context() == S_CTX_ALLOCATOR || (spank_remote(spank) != 1)) { 299 return 0; 300 } 301 302 for (i = 0; i < ac; i++) { 303 if (strncmp ("default_image=", av[i], 14) == 0) { 304 const char *optarg = av[i] + 14; 305 job_image = strdup(optarg); 306 } else { 307 slurm_error ("spank/%s: Invalid option: %s", av[i], plugin_name); 308 } 309 } 310 311 return 0; 312 } 313 314 int slurm_spank_task_init_privileged(spank_t spank, int ac, char *argv[]) 315 { 316 if (job_image) { 317 return setup_container(spank); 318 } 319 return 0; 320 }