1 /* Copyright (C) 2010 Red Hat, Inc.
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
23 #include <semaphore.h>
36 #include <microhttpd.h>
37 #include <curl/curl.h>
48 #include "state_defs.h"
50 struct hstor_client
*hstor
;
52 /***** Generic module stuff, not specific to one back end *****/
54 #define S3_IMAGE_PATTERN "^IMAGE[[:blank:]]+([^[:space:]]+)"
55 #define S3_ERROR_PATTERN "^ERROR[[:blank:]]+([^[:space:]]+)"
57 regex_t s3_success_pat
;
58 regex_t s3_failure_pat
;
66 if (regcomp(&s3_success_pat
,S3_IMAGE_PATTERN
,REG_EXTENDED
) != 0){
67 DPRINTF("could not compile S3 success pattern\n");
71 if (regcomp(&s3_failure_pat
,S3_ERROR_PATTERN
,REG_EXTENDED
) != 0){
72 DPRINTF("could not compile S3 failure pattern\n");
78 /***** Stub functions for unimplemented stuff. *****/
83 DPRINTF("*** bad call to %s\n",__func__
);
87 bad_get_child (void * ctx
)
91 DPRINTF("*** bad call to %s\n",__func__
);
96 bad_put_child (void * ctx
)
98 pipe_private
*pp
= ctx
;
99 pipe_shared
*ps
= pp
->shared
;
101 DPRINTF("*** bad call to %s\n",__func__
);
102 pipe_cons_siginit(ps
, -1);
104 return THREAD_FAILED
;
108 bad_cache_child (void * ctx
)
112 DPRINTF("*** bad call to %s\n",__func__
);
117 bad_delete (const char *bucket
, const char *key
, const char *url
)
123 DPRINTF("*** bad call to %s\n",__func__
);
124 return MHD_HTTP_BAD_REQUEST
;
128 bad_bcreate (const char *bucket
)
132 DPRINTF("*** bad call to %s\n",__func__
);
133 return MHD_HTTP_NOT_IMPLEMENTED
;
137 bad_register (my_state
*ms
, const provider_t
*prov
, const char *next
,
145 DPRINTF("*** bad call to %s\n",__func__
);
146 return MHD_HTTP_NOT_IMPLEMENTED
;
149 /***** Generic functions shared by the HTTP back ends. */
151 /* Invoked from S3/CURL/CF. */
153 http_get_prod (void *ptr
, size_t size
, size_t nmemb
, void *stream
)
155 size_t total
= size
* nmemb
;
156 pipe_shared
*ps
= stream
;
158 DPRINTF("producer posting %zu bytes as %ld\n",total
,ps
->sequence
+1);
159 pipe_prod_signal(ps
,ptr
,total
);
161 DPRINTF("producer finished chunk\n");
165 /* Invoked from S3/CURL/CF. */
167 http_put_cons (void *ptr
, size_t size
, size_t nmemb
, void *stream
)
169 size_t total
= size
* nmemb
;
170 pipe_private
*pp
= stream
;
171 pipe_shared
*ps
= pp
->shared
;
174 DPRINTF("consumer asked to read %zu\n",total
);
176 pipe_cons_siginit(ps
, 0);
178 if (!pipe_cons_wait(pp
)) {
182 DPRINTF("consumer offset %zu into %zu\n",
183 pp
->offset
, ps
->data_len
);
184 done
= ps
->data_len
- pp
->offset
;
188 memcpy(ptr
,ps
->data_ptr
+pp
->offset
,done
);
190 DPRINTF("consumer copied %zu, new offset %zu\n",
192 if (pp
->offset
== ps
->data_len
) {
193 DPRINTF("consumer finished chunk\n");
194 pipe_cons_signal(pp
, 0);
200 /***** S3-specific functions *****/
207 snprintf(svc_acc
,sizeof(svc_acc
),"%s:%u",
208 proxy_host
,proxy_port
);
209 hstor
= hstor_new(svc_acc
,proxy_host
,proxy_key
,proxy_secret
);
216 DPRINTF("could not create S3 client\n");
220 /* Start an S3 _producer_. */
222 s3_get_child (void * ctx
)
226 hstor_get(hstor
,ms
->bucket
,ms
->key
,http_get_prod
,&ms
->pipe
,0);
227 /* TBD: check return value */
229 pipe_prod_finish(&ms
->pipe
);
231 DPRINTF("producer exiting\n");
235 /* Start an S3 _consumer_. */
237 s3_put_child (void * ctx
)
239 pipe_private
*pp
= ctx
;
240 pipe_shared
*ps
= pp
->shared
;
241 my_state
*ms
= ps
->owner
;
246 clen
= MHD_lookup_connection_value(
247 ms
->conn
, MHD_HEADER_KIND
, "Content-Length");
249 llen
= strtoll(clen
,NULL
,10);
252 error (0, 0, "missing Content-Length");
253 llen
= (curl_off_t
)MHD_SIZE_UNKNOWN
;
256 rcb
= hstor_put(hstor
,ms
->bucket
,ms
->key
,http_put_cons
,llen
,pp
,NULL
);
258 DPRINTF("%s returning with error\n",__func__
);
259 pipe_cons_siginit(ps
, -1);
261 return THREAD_FAILED
;
264 DPRINTF("%s returning\n",__func__
);
270 s3_delete (const char *bucket
, const char *key
, const char *url
)
274 hstor_del(hstor
,bucket
,key
);
275 /* TBD: check return value */
281 s3_bcreate (const char *bucket
)
283 DPRINTF("creating bucket %s\n",bucket
);
285 if (!hstor_add_bucket(hstor
,bucket
)) {
286 DPRINTF(" bucket create failed\n");
287 return MHD_HTTP_INTERNAL_SERVER_ERROR
;
294 s3_init_tmpfile (char *value
)
301 path
= strdup("/tmp/iwtmp.XXXXXX");
308 error (0, errno
, "%s: failed to create file from template", path
);
315 written
= write(fd
,value
,len
);
317 if (written
!= (ssize_t
)len
) {
319 error (0, errno
, "failed to write to %s", path
);
323 "invalid write length %zd in %s",
336 s3_register (my_state
*ms
, const provider_t
*prov
, const char *next
,
339 char *kernel
= g_hash_table_lookup(args
,"kernel");
340 char *ramdisk
= g_hash_table_lookup(args
,"ramdisk");
346 const char *argv
[12];
354 int rc
= MHD_HTTP_BAD_REQUEST
;
360 return MHD_HTTP_BAD_REQUEST
;
364 DPRINTF("S3 register with next!=NULL\n");
368 DPRINTF("*** register %s/%s via %s (%s:%d)\n",
369 ms
->bucket
, ms
->key
, prov
->name
, prov
->host
, prov
->port
);
371 DPRINTF(" (using kernel %s)\n",kernel
);
374 DPRINTF(" (using ramdisk %s)\n",ramdisk
);
377 api_key
= g_hash_table_lookup(args
,"api-key");
379 api_key
= (char *)prov
->username
;
381 error (0, 0, "missing EC2 API key");
386 api_secret
= g_hash_table_lookup(args
,"api-secret");
388 api_secret
= (char *)prov
->password
;
389 if (!prov
->password
) {
390 error (0, 0, "missing EC2 API key");
395 cval
= g_hash_table_lookup(args
,"ami-cert");
397 ami_cert
= s3_init_tmpfile(cval
);
403 ami_cert
= get_provider_value(prov
->index
,"ami-cert");
405 error (0, 0, "missing EC2 AMI cert");
410 kval
= g_hash_table_lookup(args
,"ami-key");
412 ami_key
= s3_init_tmpfile(kval
);
418 ami_key
= get_provider_value(prov
->index
,"ami-key");
420 error (0, 0, "missing EC2 AMI key");
425 ami_uid
= g_hash_table_lookup(args
,"ami-uid");
427 ami_uid
= get_provider_value(prov
->index
,"ami-uid");
429 error (0, 0, "missing EC2 AMI uid");
434 ami_bkt
= g_hash_table_lookup(args
,"ami-bkt");
436 ami_bkt
= ms
->bucket
;
440 * This is the point where we go from validation to execution. If we
441 * were double-forking so this could all be asynchronous, or for that
442 * matter to return an early 100-continue, this would probably be the
443 * place to do it. Even without that, we set the ami-id here so that
444 * the caller can know things are actually in progress.
446 sprintf(ami_id_buf
,"pending %lld",(long long)time(NULL
));
447 DPRINTF("temporary ami-id = \"%s\"\n",ami_id_buf
);
448 (void)meta_set_value(ms
->bucket
,ms
->key
,"ami-id",ami_id_buf
);
449 rc
= MHD_HTTP_INTERNAL_SERVER_ERROR
;
451 const char *cmd
= "dc-register-image";
453 argv
[argc
++] = ms
->bucket
;
454 argv
[argc
++] = ms
->key
;
455 argv
[argc
++] = api_key
;
456 argv
[argc
++] = api_secret
;
457 argv
[argc
++] = ami_cert
;
458 argv
[argc
++] = ami_key
;
459 argv
[argc
++] = ami_uid
;
460 argv
[argc
++] = ami_bkt
;
461 argv
[argc
++] = kernel
? kernel
: "_default_";
462 argv
[argc
++] = ramdisk
? ramdisk
: "_default_";
465 DPRINTF("api-key = %s\n",api_key
);
466 DPRINTF("api-secret = %s\n",api_secret
);
467 DPRINTF("ami-cert = %s\n",ami_cert
);
468 DPRINTF("ami-key = %s\n",ami_key
);
469 DPRINTF("ami-uid = %s\n",ami_uid
);
470 DPRINTF("ami-bkt = %s\n",ami_bkt
);
472 if (pipe(organ
) < 0) {
473 error (0, errno
, "pipe creation failed");
479 error (0, errno
, "fork failed");
486 (void)dup2(organ
[1],STDOUT_FILENO
);
487 (void)dup2(organ
[1],STDERR_FILENO
);
488 execvp(cmd
, (char* const*)argv
);
489 error (EXIT_FAILURE
, errno
, "failed run command %s", cmd
);
492 DPRINTF("waiting for child...\n");
493 if (waitpid(pid
,NULL
,0) < 0) {
494 error (0, errno
, "waitpid failed");
496 /* TBD: check identity/status from waitpid */
497 DPRINTF("...child exited\n");
500 fp
= fdopen(organ
[0],"r");
502 DPRINTF("could not open parent pipe\n");
506 while (fgets(buf
,sizeof(buf
)-1,fp
)) {
507 buf
[sizeof(buf
)-1] = '\0';
508 if (regexec(&s3_success_pat
,buf
,2,match
,0) == 0) {
509 buf
[match
[1].rm_eo
] = '\0';
510 DPRINTF("found AMI ID: %s\n",buf
+match
[1].rm_so
);
511 sprintf(ami_id_buf
,"OK %.60s",buf
+match
[1].rm_so
);
514 else if (regexec(&s3_failure_pat
,buf
,2,match
,0) == 0) {
515 buf
[match
[1].rm_eo
] = '\0';
516 DPRINTF("found error marker: %s\n",buf
+match
[1].rm_so
);
517 sprintf(ami_id_buf
,"failed %.56s",buf
+match
[1].rm_so
);
518 rc
= MHD_HTTP_INTERNAL_SERVER_ERROR
;
521 DPRINTF("ignoring line: <%s>\n",buf
);
528 * This is a bit tricky. If we found the cert in the HTTP request and
529 * succeeded in creating a temp file, then this condition will succeed.
530 * If we failed to create the temp file, or never found a cert
531 * anywhere, there will be no ami_cert to clean up. If we got a cert
532 * from the config, then ami_cert will be set but we'll (correctly)
533 * skip cleanup because cval is null.
535 if (cval
&& ami_cert
) {
539 /* Same reasoning as above, with kval/ami_key. */
540 if (kval
&& ami_key
) {
544 (void)meta_set_value(ms
->bucket
,ms
->key
,"ami-id",ami_id_buf
);
549 /***** CURL-specific functions *****/
556 /* Start a CURL _producer_. */
558 curl_get_child (void * ctx
)
563 ms
->curl
= curl_easy_init();
565 return NULL
; /* TBD: flag error somehow */
567 ms
->cleanup
|= CLEANUP_CURL
;
568 if (ms
->from_master
) {
569 sprintf(fixed
,"http://%s:%u%s",
570 master_host
, master_port
, ms
->url
);
573 sprintf(fixed
,"http://%s:%u%s",
574 proxy_host
, proxy_port
, ms
->url
);
576 curl_easy_setopt(ms
->curl
,CURLOPT_URL
,fixed
);
577 curl_easy_setopt(ms
->curl
,CURLOPT_WRITEFUNCTION
,
579 curl_easy_setopt(ms
->curl
,CURLOPT_WRITEDATA
,&ms
->pipe
);
580 curl_easy_perform(ms
->curl
);
581 curl_easy_getinfo(ms
->curl
,CURLINFO_RESPONSE_CODE
,&ms
->rc
);
583 pipe_prod_finish(&ms
->pipe
);
585 DPRINTF("producer exiting\n");
589 /* Start a CURL _consumer_. */
591 curl_put_child (void * ctx
)
593 pipe_private
*pp
= ctx
;
594 pipe_shared
*ps
= pp
->shared
;
595 my_state
*ms
= ps
->owner
;
601 clen
= MHD_lookup_connection_value(
602 ms
->conn
, MHD_HEADER_KIND
, "Content-Length");
604 llen
= strtoll(clen
,NULL
,10);
607 error (0, 0, "missing Content-Length");
608 llen
= (curl_off_t
)MHD_SIZE_UNKNOWN
;
611 curl
= curl_easy_init();
613 pipe_cons_siginit(ps
, -1);
615 return THREAD_FAILED
;
617 sprintf(fixed
,"http://%s:%u%s",proxy_host
,proxy_port
,
619 curl_easy_setopt(curl
,CURLOPT_URL
,fixed
);
620 curl_easy_setopt(curl
,CURLOPT_UPLOAD
,1);
621 curl_easy_setopt(curl
,CURLOPT_INFILESIZE_LARGE
,llen
);
622 curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,http_put_cons
);
623 curl_easy_setopt(curl
,CURLOPT_READDATA
,pp
);
624 curl_easy_perform(curl
);
625 curl_easy_cleanup(curl
);
627 DPRINTF("%s returning\n",__func__
);
632 /* Start a CURL cache consumer. */
634 curl_cache_child (void * ctx
)
636 pipe_private
*pp
= ctx
;
637 pipe_shared
*ps
= pp
->shared
;
638 my_state
*ms
= ps
->owner
;
642 char *my_url
= strdup(ms
->url
);
645 return THREAD_FAILED
;
648 curl
= curl_easy_init();
651 return THREAD_FAILED
;
653 sprintf(fixed
,"http://%s:%u%s",proxy_host
,proxy_port
,
655 curl_easy_setopt(curl
,CURLOPT_URL
,fixed
);
656 curl_easy_setopt(curl
,CURLOPT_UPLOAD
,1);
657 curl_easy_setopt(curl
,CURLOPT_INFILESIZE_LARGE
,
658 (curl_off_t
)MHD_SIZE_UNKNOWN
);
659 curl_easy_setopt(curl
,CURLOPT_READFUNCTION
,http_put_cons
);
660 curl_easy_setopt(curl
,CURLOPT_READDATA
,pp
);
661 curl_easy_perform(curl
);
662 curl_easy_cleanup(curl
);
664 slash
= index(my_url
+1,'/');
667 meta_got_copy(my_url
+1,slash
+1,me
);
675 curl_delete (const char *bucket
, const char *key
, const char *url
)
683 curl
= curl_easy_init();
685 return MHD_HTTP_INTERNAL_SERVER_ERROR
;
688 sprintf(fixed
,"http://%s:%u%s",proxy_host
,proxy_port
,url
);
689 curl_easy_setopt(curl
,CURLOPT_URL
,fixed
);
690 curl_easy_setopt(curl
,CURLOPT_CUSTOMREQUEST
,"DELETE");
691 curl_easy_perform(curl
);
692 curl_easy_cleanup(curl
);
698 curl_bcreate (const char *bucket
)
702 DPRINTF("cannot create bucket in non-S3 mode\n");
703 /* TBD: pretend this works for testing, fix for release
704 rc = MHD_HTTP_NOT_IMPLEMENTED;
710 * We can proxy through any number of CURL/HTTP warehouses, but the chain
711 * eventually has to terminate at an S3 back end.
715 curl_register (my_state
*ms
, const provider_t
*prov
, const char *next
,
720 struct curl_httppost
*first
= NULL
;
721 struct curl_httppost
*last
= NULL
;
722 char *kernel
= g_hash_table_lookup(args
,"kernel");
723 char *ramdisk
= g_hash_table_lookup(args
,"ramdisk");
726 DPRINTF("CURL register with next==NULL\n");
727 return MHD_HTTP_BAD_REQUEST
;
730 DPRINTF("*** PROXY registration request for %s/%s to %s (%s:%d)\n",
731 ms
->bucket
, ms
->key
, prov
->name
, prov
->host
, prov
->port
);
733 curl
= curl_easy_init();
735 return MHD_HTTP_INTERNAL_SERVER_ERROR
;
737 sprintf(fixed
,"http://%s:%d/%s/%s",
738 prov
->host
,prov
->port
, ms
->bucket
, ms
->key
);
739 curl_easy_setopt(curl
,CURLOPT_URL
,fixed
);
740 curl_formadd(&first
,&last
,
741 CURLFORM_COPYNAME
, "op",
742 CURLFORM_COPYCONTENTS
, "register",
744 curl_formadd(&first
,&last
,
745 CURLFORM_COPYNAME
, "site",
746 CURLFORM_COPYCONTENTS
, next
,
749 curl_formadd(&first
,&last
,
750 CURLFORM_COPYNAME
, "kernel",
751 CURLFORM_COPYCONTENTS
, kernel
,
755 curl_formadd(&first
,&last
,
756 CURLFORM_COPYNAME
, "ramdisk",
757 CURLFORM_COPYCONTENTS
, ramdisk
,
760 curl_easy_setopt(curl
,CURLOPT_HTTPPOST
,first
);
761 curl_easy_perform(curl
);
762 curl_easy_cleanup(curl
);
767 /***** CF-specific functions (TBD) *****/
769 /***** FS-specific functions *****/
774 DPRINTF("changing directory to %s\n",local_path
);
775 if (chdir(local_path
) < 0) {
776 error(0,errno
,"chdir failed, unsafe to continue");
777 exit(!0); /* Value doesn't matter, as long as it's not zero. */
781 /* Start an FS _producer_. */
783 fs_get_child (void * ctx
)
789 char *file
= ms
->url
+1;
791 fd
= open(file
, O_RDONLY
);
793 return THREAD_FAILED
;
797 bytes
= read(fd
,buf
,sizeof(buf
));
800 error (0, errno
, "%s: read failed", file
);
804 pipe_prod_signal(&ms
->pipe
,buf
,bytes
);
808 pipe_prod_finish(&ms
->pipe
);
810 DPRINTF("producer exiting\n");
814 /* Start an FS _consumer_. */
816 fs_put_child (void * ctx
)
818 pipe_private
*pp
= ctx
;
819 pipe_shared
*ps
= pp
->shared
;
820 my_state
*ms
= ps
->owner
;
824 char *file
= ms
->url
+1;
826 fd
= open(file
,O_WRONLY
|O_CREAT
,0666);
828 pipe_cons_siginit(ps
, errno
);
830 return THREAD_FAILED
;
833 pipe_cons_siginit(ps
, 0);
835 while (pipe_cons_wait(pp
)) {
839 ps
->data_ptr
+offset
,ps
->data_len
-offset
);
842 error (0, errno
, "%s: write failed",
845 pipe_cons_signal(pp
, errno
);
849 } while (offset
< ps
->data_len
);
850 pipe_cons_signal(pp
, 0);
856 DPRINTF("%s returning\n",__func__
);
862 fs_delete (const char *bucket
, const char *key
, const char *url
)
867 if (unlink(url
+1) < 0) {
868 error (0, errno
, "%s: failed to unlink", url
+1);
869 return MHD_HTTP_NOT_FOUND
;
876 fs_bcreate (const char *bucket
)
878 DPRINTF("creating bucket %s\n",bucket
);
880 if (mkdir(bucket
,0700) < 0) {
881 error (0, errno
, "%s: failed to create directory", bucket
);
882 return MHD_HTTP_INTERNAL_SERVER_ERROR
;
889 /***** Function tables. ****/
891 backend_func_tbl bad_func_tbl
= {
902 backend_func_tbl s3_func_tbl
= {
913 backend_func_tbl curl_func_tbl
= {
924 backend_func_tbl fs_func_tbl
= {