git
/
transport-helper.c
1656 строк · 42.6 Кб
1#define USE_THE_REPOSITORY_VARIABLE2
3#include "git-compat-util.h"4#include "transport.h"5#include "quote.h"6#include "run-command.h"7#include "commit.h"8#include "environment.h"9#include "gettext.h"10#include "hex.h"11#include "object-name.h"12#include "repository.h"13#include "remote.h"14#include "string-list.h"15#include "thread-utils.h"16#include "sigchain.h"17#include "strvec.h"18#include "refs.h"19#include "refspec.h"20#include "transport-internal.h"21#include "protocol.h"22#include "packfile.h"23
24static int debug;25
26struct helper_data {27char *name;28struct child_process *helper;29FILE *out;30unsigned fetch : 1,31import : 1,32bidi_import : 1,33export : 1,34option : 1,35push : 1,36connect : 1,37stateless_connect : 1,38signed_tags : 1,39check_connectivity : 1,40no_disconnect_req : 1,41no_private_update : 1,42object_format : 1;43
44/*45* As an optimization, the transport code may invoke fetch before
46* get_refs_list. If this happens, and if the transport helper doesn't
47* support connect or stateless_connect, we need to invoke
48* get_refs_list ourselves if we haven't already done so. Keep track of
49* whether we have invoked get_refs_list.
50*/
51unsigned get_refs_list_called : 1;52
53char *export_marks;54char *import_marks;55/* These go from remote name (as in "list") to private name */56struct refspec rs;57/* Transport options for fetch-pack/send-pack (should one of58* those be invoked).
59*/
60struct git_transport_options transport_options;61};62
63static void sendline(struct helper_data *helper, struct strbuf *buffer)64{
65if (debug)66fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);67if (write_in_full(helper->helper->in, buffer->buf, buffer->len) < 0)68die_errno(_("full write to remote helper failed"));69}
70
71static int recvline_fh(FILE *helper, struct strbuf *buffer)72{
73strbuf_reset(buffer);74if (debug)75fprintf(stderr, "Debug: Remote helper: Waiting...\n");76if (strbuf_getline(buffer, helper) == EOF) {77if (debug)78fprintf(stderr, "Debug: Remote helper quit.\n");79return 1;80}81
82if (debug)83fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);84return 0;85}
86
87static int recvline(struct helper_data *helper, struct strbuf *buffer)88{
89return recvline_fh(helper->out, buffer);90}
91
92static void write_constant(int fd, const char *str)93{
94if (debug)95fprintf(stderr, "Debug: Remote helper: -> %s", str);96if (write_in_full(fd, str, strlen(str)) < 0)97die_errno(_("full write to remote helper failed"));98}
99
100static const char *remove_ext_force(const char *url)101{
102if (url) {103const char *colon = strchr(url, ':');104if (colon && colon[1] == ':')105return colon + 2;106}107return url;108}
109
110static void do_take_over(struct transport *transport)111{
112struct helper_data *data;113data = (struct helper_data *)transport->data;114transport_take_over(transport, data->helper);115fclose(data->out);116free(data->name);117free(data);118}
119
120static void standard_options(struct transport *t);121
122static struct child_process *get_helper(struct transport *transport)123{
124struct helper_data *data = transport->data;125struct strbuf buf = STRBUF_INIT;126struct child_process *helper;127int duped;128int code;129
130if (data->helper)131return data->helper;132
133helper = xmalloc(sizeof(*helper));134child_process_init(helper);135helper->in = -1;136helper->out = -1;137helper->err = 0;138strvec_pushf(&helper->args, "remote-%s", data->name);139strvec_push(&helper->args, transport->remote->name);140strvec_push(&helper->args, remove_ext_force(transport->url));141helper->git_cmd = 1;142helper->silent_exec_failure = 1;143
144if (have_git_dir())145strvec_pushf(&helper->env, "%s=%s",146GIT_DIR_ENVIRONMENT, get_git_dir());147
148helper->trace2_child_class = helper->args.v[0]; /* "remote-<name>" */149
150code = start_command(helper);151if (code < 0 && errno == ENOENT)152die(_("unable to find remote helper for '%s'"), data->name);153else if (code != 0)154exit(code);155
156data->helper = helper;157data->no_disconnect_req = 0;158refspec_init(&data->rs, REFSPEC_FETCH);159
160/*161* Open the output as FILE* so strbuf_getline_*() family of
162* functions can be used.
163* Do this with duped fd because fclose() will close the fd,
164* and stuff like taking over will require the fd to remain.
165*/
166duped = dup(helper->out);167if (duped < 0)168die_errno(_("can't dup helper output fd"));169data->out = xfdopen(duped, "r");170
171write_constant(helper->in, "capabilities\n");172
173while (1) {174const char *capname, *arg;175int mandatory = 0;176if (recvline(data, &buf))177exit(128);178
179if (!*buf.buf)180break;181
182if (*buf.buf == '*') {183capname = buf.buf + 1;184mandatory = 1;185} else186capname = buf.buf;187
188if (debug)189fprintf(stderr, "Debug: Got cap %s\n", capname);190if (!strcmp(capname, "fetch"))191data->fetch = 1;192else if (!strcmp(capname, "option"))193data->option = 1;194else if (!strcmp(capname, "push"))195data->push = 1;196else if (!strcmp(capname, "import"))197data->import = 1;198else if (!strcmp(capname, "bidi-import"))199data->bidi_import = 1;200else if (!strcmp(capname, "export"))201data->export = 1;202else if (!strcmp(capname, "check-connectivity"))203data->check_connectivity = 1;204else if (skip_prefix(capname, "refspec ", &arg)) {205refspec_append(&data->rs, arg);206} else if (!strcmp(capname, "connect")) {207data->connect = 1;208} else if (!strcmp(capname, "stateless-connect")) {209data->stateless_connect = 1;210} else if (!strcmp(capname, "signed-tags")) {211data->signed_tags = 1;212} else if (skip_prefix(capname, "export-marks ", &arg)) {213data->export_marks = xstrdup(arg);214} else if (skip_prefix(capname, "import-marks ", &arg)) {215data->import_marks = xstrdup(arg);216} else if (starts_with(capname, "no-private-update")) {217data->no_private_update = 1;218} else if (starts_with(capname, "object-format")) {219data->object_format = 1;220} else if (mandatory) {221die(_("unknown mandatory capability %s; this remote "222"helper probably needs newer version of Git"),223capname);224}225}226if (!data->rs.nr && (data->import || data->bidi_import || data->export)) {227warning(_("this remote helper should implement refspec capability"));228}229strbuf_release(&buf);230if (debug)231fprintf(stderr, "Debug: Capabilities complete.\n");232standard_options(transport);233return data->helper;234}
235
236static int disconnect_helper(struct transport *transport)237{
238struct helper_data *data = transport->data;239int res = 0;240
241if (data->helper) {242if (debug)243fprintf(stderr, "Debug: Disconnecting.\n");244if (!data->no_disconnect_req) {245/*246* Ignore write errors; there's nothing we can do,
247* since we're about to close the pipe anyway. And the
248* most likely error is EPIPE due to the helper dying
249* to report an error itself.
250*/
251sigchain_push(SIGPIPE, SIG_IGN);252xwrite(data->helper->in, "\n", 1);253sigchain_pop(SIGPIPE);254}255close(data->helper->in);256close(data->helper->out);257fclose(data->out);258res = finish_command(data->helper);259FREE_AND_NULL(data->name);260FREE_AND_NULL(data->helper);261}262return res;263}
264
265static const char *unsupported_options[] = {266TRANS_OPT_UPLOADPACK,267TRANS_OPT_RECEIVEPACK,268TRANS_OPT_THIN,269TRANS_OPT_KEEP
270};271
272static const char *boolean_options[] = {273TRANS_OPT_THIN,274TRANS_OPT_KEEP,275TRANS_OPT_FOLLOWTAGS,276TRANS_OPT_DEEPEN_RELATIVE
277};278
279static int strbuf_set_helper_option(struct helper_data *data,280struct strbuf *buf)281{
282int ret;283
284sendline(data, buf);285if (recvline(data, buf))286exit(128);287
288if (!strcmp(buf->buf, "ok"))289ret = 0;290else if (starts_with(buf->buf, "error"))291ret = -1;292else if (!strcmp(buf->buf, "unsupported"))293ret = 1;294else {295warning(_("%s unexpectedly said: '%s'"), data->name, buf->buf);296ret = 1;297}298return ret;299}
300
301static int string_list_set_helper_option(struct helper_data *data,302const char *name,303struct string_list *list)304{
305struct strbuf buf = STRBUF_INIT;306int i, ret = 0;307
308for (i = 0; i < list->nr; i++) {309strbuf_addf(&buf, "option %s ", name);310quote_c_style(list->items[i].string, &buf, NULL, 0);311strbuf_addch(&buf, '\n');312
313if ((ret = strbuf_set_helper_option(data, &buf)))314break;315strbuf_reset(&buf);316}317strbuf_release(&buf);318return ret;319}
320
321static int set_helper_option(struct transport *transport,322const char *name, const char *value)323{
324struct helper_data *data = transport->data;325struct strbuf buf = STRBUF_INIT;326int i, ret, is_bool = 0;327
328get_helper(transport);329
330if (!data->option)331return 1;332
333if (!strcmp(name, "deepen-not"))334return string_list_set_helper_option(data, name,335(struct string_list *)value);336
337for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {338if (!strcmp(name, unsupported_options[i]))339return 1;340}341
342for (i = 0; i < ARRAY_SIZE(boolean_options); i++) {343if (!strcmp(name, boolean_options[i])) {344is_bool = 1;345break;346}347}348
349strbuf_addf(&buf, "option %s ", name);350if (is_bool)351strbuf_addstr(&buf, value ? "true" : "false");352else353quote_c_style(value, &buf, NULL, 0);354strbuf_addch(&buf, '\n');355
356ret = strbuf_set_helper_option(data, &buf);357strbuf_release(&buf);358return ret;359}
360
361static void standard_options(struct transport *t)362{
363char buf[16];364int v = t->verbose;365
366set_helper_option(t, "progress", t->progress ? "true" : "false");367
368xsnprintf(buf, sizeof(buf), "%d", v + 1);369set_helper_option(t, "verbosity", buf);370
371switch (t->family) {372case TRANSPORT_FAMILY_ALL:373/*374* this is already the default,
375* do not break old remote helpers by setting "all" here
376*/
377break;378case TRANSPORT_FAMILY_IPV4:379set_helper_option(t, "family", "ipv4");380break;381case TRANSPORT_FAMILY_IPV6:382set_helper_option(t, "family", "ipv6");383break;384}385}
386
387static int release_helper(struct transport *transport)388{
389int res = 0;390struct helper_data *data = transport->data;391refspec_clear(&data->rs);392res = disconnect_helper(transport);393free(transport->data);394return res;395}
396
397static int fetch_with_fetch(struct transport *transport,398int nr_heads, struct ref **to_fetch)399{
400struct helper_data *data = transport->data;401int i;402struct strbuf buf = STRBUF_INIT;403
404for (i = 0; i < nr_heads; i++) {405const struct ref *posn = to_fetch[i];406if (posn->status & REF_STATUS_UPTODATE)407continue;408
409strbuf_addf(&buf, "fetch %s %s\n",410oid_to_hex(&posn->old_oid),411posn->symref ? posn->symref : posn->name);412}413
414strbuf_addch(&buf, '\n');415sendline(data, &buf);416
417while (1) {418const char *name;419
420if (recvline(data, &buf))421exit(128);422
423if (skip_prefix(buf.buf, "lock ", &name)) {424if (transport->pack_lockfiles.nr)425warning(_("%s also locked %s"), data->name, name);426else427string_list_append(&transport->pack_lockfiles,428name);429}430else if (data->check_connectivity &&431data->transport_options.check_self_contained_and_connected &&432!strcmp(buf.buf, "connectivity-ok"))433data->transport_options.self_contained_and_connected = 1;434else if (!buf.len)435break;436else437warning(_("%s unexpectedly said: '%s'"), data->name, buf.buf);438}439strbuf_release(&buf);440
441reprepare_packed_git(the_repository);442return 0;443}
444
445static int get_importer(struct transport *transport, struct child_process *fastimport)446{
447struct child_process *helper = get_helper(transport);448struct helper_data *data = transport->data;449int cat_blob_fd, code;450child_process_init(fastimport);451fastimport->in = xdup(helper->out);452strvec_push(&fastimport->args, "fast-import");453strvec_push(&fastimport->args, "--allow-unsafe-features");454strvec_push(&fastimport->args, debug ? "--stats" : "--quiet");455
456if (data->bidi_import) {457cat_blob_fd = xdup(helper->in);458strvec_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd);459}460fastimport->git_cmd = 1;461
462code = start_command(fastimport);463return code;464}
465
466static int get_exporter(struct transport *transport,467struct child_process *fastexport,468struct string_list *revlist_args)469{
470struct helper_data *data = transport->data;471struct child_process *helper = get_helper(transport);472int i;473
474child_process_init(fastexport);475
476/* we need to duplicate helper->in because we want to use it after477* fastexport is done with it. */
478fastexport->out = dup(helper->in);479strvec_push(&fastexport->args, "fast-export");480strvec_push(&fastexport->args, "--use-done-feature");481strvec_push(&fastexport->args, data->signed_tags ?482"--signed-tags=verbatim" : "--signed-tags=warn-strip");483if (data->export_marks)484strvec_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks);485if (data->import_marks)486strvec_pushf(&fastexport->args, "--import-marks=%s", data->import_marks);487
488for (i = 0; i < revlist_args->nr; i++)489strvec_push(&fastexport->args, revlist_args->items[i].string);490
491fastexport->git_cmd = 1;492return start_command(fastexport);493}
494
495static int fetch_with_import(struct transport *transport,496int nr_heads, struct ref **to_fetch)497{
498struct child_process fastimport;499struct helper_data *data = transport->data;500int i;501struct ref *posn;502struct strbuf buf = STRBUF_INIT;503
504get_helper(transport);505
506if (get_importer(transport, &fastimport))507die(_("couldn't run fast-import"));508
509for (i = 0; i < nr_heads; i++) {510posn = to_fetch[i];511if (posn->status & REF_STATUS_UPTODATE)512continue;513
514strbuf_addf(&buf, "import %s\n",515posn->symref ? posn->symref : posn->name);516sendline(data, &buf);517strbuf_reset(&buf);518}519
520write_constant(data->helper->in, "\n");521/*522* remote-helpers that advertise the bidi-import capability are required to
523* buffer the complete batch of import commands until this newline before
524* sending data to fast-import.
525* These helpers read back data from fast-import on their stdin, which could
526* be mixed with import commands, otherwise.
527*/
528
529if (finish_command(&fastimport))530die(_("error while running fast-import"));531
532/*533* The fast-import stream of a remote helper that advertises
534* the "refspec" capability writes to the refs named after the
535* right hand side of the first refspec matching each ref we
536* were fetching.
537*
538* (If no "refspec" capability was specified, for historical
539* reasons we default to the equivalent of *:*.)
540*
541* Store the result in to_fetch[i].old_sha1. Callers such
542* as "git fetch" can use the value to write feedback to the
543* terminal, populate FETCH_HEAD, and determine what new value
544* should be written to peer_ref if the update is a
545* fast-forward or this is a forced update.
546*/
547for (i = 0; i < nr_heads; i++) {548char *private, *name;549posn = to_fetch[i];550if (posn->status & REF_STATUS_UPTODATE)551continue;552name = posn->symref ? posn->symref : posn->name;553if (data->rs.nr)554private = apply_refspecs(&data->rs, name);555else556private = xstrdup(name);557if (private) {558if (refs_read_ref(get_main_ref_store(the_repository), private, &posn->old_oid) < 0)559die(_("could not read ref %s"), private);560free(private);561}562}563strbuf_release(&buf);564return 0;565}
566
567static int run_connect(struct transport *transport, struct strbuf *cmdbuf)568{
569struct helper_data *data = transport->data;570int ret = 0;571int duped;572FILE *input;573struct child_process *helper;574
575helper = get_helper(transport);576
577/*578* Yes, dup the pipe another time, as we need unbuffered version
579* of input pipe as FILE*. fclose() closes the underlying fd and
580* stream buffering only can be changed before first I/O operation
581* on it.
582*/
583duped = dup(helper->out);584if (duped < 0)585die_errno(_("can't dup helper output fd"));586input = xfdopen(duped, "r");587setvbuf(input, NULL, _IONBF, 0);588
589sendline(data, cmdbuf);590if (recvline_fh(input, cmdbuf))591exit(128);592
593if (!strcmp(cmdbuf->buf, "")) {594data->no_disconnect_req = 1;595if (debug)596fprintf(stderr, "Debug: Smart transport connection "597"ready.\n");598ret = 1;599} else if (!strcmp(cmdbuf->buf, "fallback")) {600if (debug)601fprintf(stderr, "Debug: Falling back to dumb "602"transport.\n");603} else {604die(_("unknown response to connect: %s"),605cmdbuf->buf);606}607
608fclose(input);609return ret;610}
611
612static int process_connect_service(struct transport *transport,613const char *name, const char *exec)614{
615struct helper_data *data = transport->data;616struct strbuf cmdbuf = STRBUF_INIT;617int ret = 0;618
619/*620* Handle --upload-pack and friends. This is fire and forget...
621* just warn if it fails.
622*/
623if (strcmp(name, exec)) {624int r = set_helper_option(transport, "servpath", exec);625if (r > 0)626warning(_("setting remote service path not supported by protocol"));627else if (r < 0)628warning(_("invalid remote service path"));629}630
631if (data->connect) {632strbuf_addf(&cmdbuf, "connect %s\n", name);633ret = run_connect(transport, &cmdbuf);634} else if (data->stateless_connect &&635(get_protocol_version_config() == protocol_v2) &&636(!strcmp("git-upload-pack", name) ||637!strcmp("git-upload-archive", name))) {638strbuf_addf(&cmdbuf, "stateless-connect %s\n", name);639ret = run_connect(transport, &cmdbuf);640if (ret)641transport->stateless_rpc = 1;642}643
644strbuf_release(&cmdbuf);645return ret;646}
647
648static int process_connect(struct transport *transport,649int for_push)650{
651struct helper_data *data = transport->data;652const char *name;653const char *exec;654int ret;655
656name = for_push ? "git-receive-pack" : "git-upload-pack";657if (for_push)658exec = data->transport_options.receivepack;659else660exec = data->transport_options.uploadpack;661
662ret = process_connect_service(transport, name, exec);663if (ret)664do_take_over(transport);665return ret;666}
667
668static int connect_helper(struct transport *transport, const char *name,669const char *exec, int fd[2])670{
671struct helper_data *data = transport->data;672
673/* Get_helper so connect is inited. */674get_helper(transport);675
676if (!process_connect_service(transport, name, exec))677die(_("can't connect to subservice %s"), name);678
679fd[0] = data->helper->out;680fd[1] = data->helper->in;681
682do_take_over(transport);683return 0;684}
685
686static struct ref *get_refs_list_using_list(struct transport *transport,687int for_push);688
689static int fetch_refs(struct transport *transport,690int nr_heads, struct ref **to_fetch)691{
692struct helper_data *data = transport->data;693int i, count;694
695get_helper(transport);696
697if (process_connect(transport, 0))698return transport->vtable->fetch_refs(transport, nr_heads, to_fetch);699
700/*701* If we reach here, then the server, the client, and/or the transport
702* helper does not support protocol v2. --negotiate-only requires
703* protocol v2.
704*/
705if (data->transport_options.acked_commits) {706warning(_("--negotiate-only requires protocol v2"));707return -1;708}709
710if (!data->get_refs_list_called)711get_refs_list_using_list(transport, 0);712
713count = 0;714for (i = 0; i < nr_heads; i++)715if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))716count++;717
718if (!count)719return 0;720
721if (data->check_connectivity &&722data->transport_options.check_self_contained_and_connected)723set_helper_option(transport, "check-connectivity", "true");724
725if (transport->cloning)726set_helper_option(transport, "cloning", "true");727
728if (data->transport_options.update_shallow)729set_helper_option(transport, "update-shallow", "true");730
731if (data->transport_options.refetch)732set_helper_option(transport, "refetch", "true");733
734if (data->transport_options.filter_options.choice) {735const char *spec = expand_list_objects_filter_spec(736&data->transport_options.filter_options);737set_helper_option(transport, "filter", spec);738}739
740if (data->transport_options.negotiation_tips)741warning("Ignoring --negotiation-tip because the protocol does not support it.");742
743if (data->fetch)744return fetch_with_fetch(transport, nr_heads, to_fetch);745
746if (data->import)747return fetch_with_import(transport, nr_heads, to_fetch);748
749return -1;750}
751
752struct push_update_ref_state {753struct ref *hint;754struct ref_push_report *report;755int new_report;756};757
758static int push_update_ref_status(struct strbuf *buf,759struct push_update_ref_state *state,760struct ref *remote_refs)761{
762char *refname, *msg;763int status, forced = 0;764
765if (starts_with(buf->buf, "option ")) {766struct object_id old_oid, new_oid;767const char *key, *val;768char *p;769
770if (!state->hint || !(state->report || state->new_report))771die(_("'option' without a matching 'ok/error' directive"));772if (state->new_report) {773if (!state->hint->report) {774CALLOC_ARRAY(state->hint->report, 1);775state->report = state->hint->report;776} else {777state->report = state->hint->report;778while (state->report->next)779state->report = state->report->next;780CALLOC_ARRAY(state->report->next, 1);781state->report = state->report->next;782}783state->new_report = 0;784}785key = buf->buf + 7;786p = strchr(key, ' ');787if (p)788*p++ = '\0';789val = p;790if (!strcmp(key, "refname"))791state->report->ref_name = xstrdup_or_null(val);792else if (!strcmp(key, "old-oid") && val &&793!parse_oid_hex(val, &old_oid, &val))794state->report->old_oid = oiddup(&old_oid);795else if (!strcmp(key, "new-oid") && val &&796!parse_oid_hex(val, &new_oid, &val))797state->report->new_oid = oiddup(&new_oid);798else if (!strcmp(key, "forced-update"))799state->report->forced_update = 1;800/* Not update remote namespace again. */801return 1;802}803
804state->report = NULL;805state->new_report = 0;806
807if (starts_with(buf->buf, "ok ")) {808status = REF_STATUS_OK;809refname = buf->buf + 3;810} else if (starts_with(buf->buf, "error ")) {811status = REF_STATUS_REMOTE_REJECT;812refname = buf->buf + 6;813} else814die(_("expected ok/error, helper said '%s'"), buf->buf);815
816msg = strchr(refname, ' ');817if (msg) {818struct strbuf msg_buf = STRBUF_INIT;819const char *end;820
821*msg++ = '\0';822if (!unquote_c_style(&msg_buf, msg, &end))823msg = strbuf_detach(&msg_buf, NULL);824else825msg = xstrdup(msg);826strbuf_release(&msg_buf);827
828if (!strcmp(msg, "no match")) {829status = REF_STATUS_NONE;830FREE_AND_NULL(msg);831}832else if (!strcmp(msg, "up to date")) {833status = REF_STATUS_UPTODATE;834FREE_AND_NULL(msg);835}836else if (!strcmp(msg, "non-fast forward")) {837status = REF_STATUS_REJECT_NONFASTFORWARD;838FREE_AND_NULL(msg);839}840else if (!strcmp(msg, "already exists")) {841status = REF_STATUS_REJECT_ALREADY_EXISTS;842FREE_AND_NULL(msg);843}844else if (!strcmp(msg, "fetch first")) {845status = REF_STATUS_REJECT_FETCH_FIRST;846FREE_AND_NULL(msg);847}848else if (!strcmp(msg, "needs force")) {849status = REF_STATUS_REJECT_NEEDS_FORCE;850FREE_AND_NULL(msg);851}852else if (!strcmp(msg, "stale info")) {853status = REF_STATUS_REJECT_STALE;854FREE_AND_NULL(msg);855}856else if (!strcmp(msg, "remote ref updated since checkout")) {857status = REF_STATUS_REJECT_REMOTE_UPDATED;858FREE_AND_NULL(msg);859}860else if (!strcmp(msg, "forced update")) {861forced = 1;862FREE_AND_NULL(msg);863}864else if (!strcmp(msg, "expecting report")) {865status = REF_STATUS_EXPECTING_REPORT;866FREE_AND_NULL(msg);867}868}869
870if (state->hint)871state->hint = find_ref_by_name(state->hint, refname);872if (!state->hint)873state->hint = find_ref_by_name(remote_refs, refname);874if (!state->hint) {875warning(_("helper reported unexpected status of %s"), refname);876return 1;877}878
879if (state->hint->status != REF_STATUS_NONE) {880/*881* Earlier, the ref was marked not to be pushed, so ignore the ref
882* status reported by the remote helper if the latter is 'no match'.
883*/
884if (status == REF_STATUS_NONE)885return 1;886}887
888if (status == REF_STATUS_OK)889state->new_report = 1;890state->hint->status = status;891state->hint->forced_update |= forced;892state->hint->remote_status = msg;893return !(status == REF_STATUS_OK);894}
895
896static int push_update_refs_status(struct helper_data *data,897struct ref *remote_refs,898int flags)899{
900struct ref *ref;901struct ref_push_report *report;902struct strbuf buf = STRBUF_INIT;903struct push_update_ref_state state = { remote_refs, NULL, 0 };904
905for (;;) {906if (recvline(data, &buf)) {907strbuf_release(&buf);908return 1;909}910if (!buf.len)911break;912push_update_ref_status(&buf, &state, remote_refs);913}914strbuf_release(&buf);915
916if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)917return 0;918
919/* propagate back the update to the remote namespace */920for (ref = remote_refs; ref; ref = ref->next) {921char *private;922
923if (ref->status != REF_STATUS_OK)924continue;925
926if (!ref->report) {927private = apply_refspecs(&data->rs, ref->name);928if (!private)929continue;930refs_update_ref(get_main_ref_store(the_repository),931"update by helper", private,932&(ref->new_oid),933NULL, 0, 0);934free(private);935} else {936for (report = ref->report; report; report = report->next) {937private = apply_refspecs(&data->rs,938report->ref_name939? report->ref_name940: ref->name);941if (!private)942continue;943refs_update_ref(get_main_ref_store(the_repository),944"update by helper", private,945report->new_oid946? report->new_oid947: &(ref->new_oid),948NULL, 0, 0);949free(private);950}951}952}953return 0;954}
955
956static void set_common_push_options(struct transport *transport,957const char *name, int flags)958{
959if (flags & TRANSPORT_PUSH_DRY_RUN) {960if (set_helper_option(transport, "dry-run", "true") != 0)961die(_("helper %s does not support dry-run"), name);962} else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) {963if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)964die(_("helper %s does not support --signed"), name);965} else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) {966if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "if-asked") != 0)967die(_("helper %s does not support --signed=if-asked"), name);968}969
970if (flags & TRANSPORT_PUSH_ATOMIC)971if (set_helper_option(transport, TRANS_OPT_ATOMIC, "true") != 0)972die(_("helper %s does not support --atomic"), name);973
974if (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES)975if (set_helper_option(transport, TRANS_OPT_FORCE_IF_INCLUDES, "true") != 0)976die(_("helper %s does not support --%s"),977name, TRANS_OPT_FORCE_IF_INCLUDES);978
979if (flags & TRANSPORT_PUSH_OPTIONS) {980struct string_list_item *item;981for_each_string_list_item(item, transport->push_options)982if (set_helper_option(transport, "push-option", item->string) != 0)983die(_("helper %s does not support 'push-option'"), name);984}985}
986
987static int push_refs_with_push(struct transport *transport,988struct ref *remote_refs, int flags)989{
990int force_all = flags & TRANSPORT_PUSH_FORCE;991int mirror = flags & TRANSPORT_PUSH_MIRROR;992int atomic = flags & TRANSPORT_PUSH_ATOMIC;993struct helper_data *data = transport->data;994struct strbuf buf = STRBUF_INIT;995struct ref *ref;996struct string_list cas_options = STRING_LIST_INIT_DUP;997struct string_list_item *cas_option;998
999get_helper(transport);1000if (!data->push)1001return 1;1002
1003for (ref = remote_refs; ref; ref = ref->next) {1004if (!ref->peer_ref && !mirror)1005continue;1006
1007/* Check for statuses set by set_ref_status_for_push() */1008switch (ref->status) {1009case REF_STATUS_REJECT_NONFASTFORWARD:1010case REF_STATUS_REJECT_STALE:1011case REF_STATUS_REJECT_ALREADY_EXISTS:1012case REF_STATUS_REJECT_REMOTE_UPDATED:1013if (atomic) {1014reject_atomic_push(remote_refs, mirror);1015string_list_clear(&cas_options, 0);1016return 0;1017} else1018continue;1019case REF_STATUS_UPTODATE:1020continue;1021default:1022; /* do nothing */1023}1024
1025if (force_all)1026ref->force = 1;1027
1028strbuf_addstr(&buf, "push ");1029if (!ref->deletion) {1030if (ref->force)1031strbuf_addch(&buf, '+');1032if (ref->peer_ref)1033strbuf_addstr(&buf, ref->peer_ref->name);1034else1035strbuf_addstr(&buf, oid_to_hex(&ref->new_oid));1036}1037strbuf_addch(&buf, ':');1038strbuf_addstr(&buf, ref->name);1039strbuf_addch(&buf, '\n');1040
1041/*1042* The "--force-with-lease" options without explicit
1043* values to expect have already been expanded into
1044* the ref->old_oid_expect[] field; we can ignore
1045* transport->smart_options->cas altogether and instead
1046* can enumerate them from the refs.
1047*/
1048if (ref->expect_old_sha1) {1049struct strbuf cas = STRBUF_INIT;1050strbuf_addf(&cas, "%s:%s",1051ref->name, oid_to_hex(&ref->old_oid_expect));1052string_list_append_nodup(&cas_options,1053strbuf_detach(&cas, NULL));1054}1055}1056if (buf.len == 0) {1057string_list_clear(&cas_options, 0);1058return 0;1059}1060
1061for_each_string_list_item(cas_option, &cas_options)1062set_helper_option(transport, "cas", cas_option->string);1063set_common_push_options(transport, data->name, flags);1064
1065strbuf_addch(&buf, '\n');1066sendline(data, &buf);1067strbuf_release(&buf);1068string_list_clear(&cas_options, 0);1069
1070return push_update_refs_status(data, remote_refs, flags);1071}
1072
1073static int push_refs_with_export(struct transport *transport,1074struct ref *remote_refs, int flags)1075{
1076struct ref *ref;1077struct child_process *helper, exporter;1078struct helper_data *data = transport->data;1079struct string_list revlist_args = STRING_LIST_INIT_DUP;1080struct strbuf buf = STRBUF_INIT;1081
1082if (!data->rs.nr)1083die(_("remote-helper doesn't support push; refspec needed"));1084
1085set_common_push_options(transport, data->name, flags);1086if (flags & TRANSPORT_PUSH_FORCE) {1087if (set_helper_option(transport, "force", "true") != 0)1088warning(_("helper %s does not support '--force'"), data->name);1089}1090
1091helper = get_helper(transport);1092
1093write_constant(helper->in, "export\n");1094
1095for (ref = remote_refs; ref; ref = ref->next) {1096char *private;1097struct object_id oid;1098
1099private = apply_refspecs(&data->rs, ref->name);1100if (private && !repo_get_oid(the_repository, private, &oid)) {1101strbuf_addf(&buf, "^%s", private);1102string_list_append_nodup(&revlist_args,1103strbuf_detach(&buf, NULL));1104oidcpy(&ref->old_oid, &oid);1105}1106free(private);1107
1108if (ref->peer_ref) {1109if (strcmp(ref->name, ref->peer_ref->name)) {1110if (!ref->deletion) {1111const char *name;1112int flag;1113
1114/* Follow symbolic refs (mainly for HEAD). */1115name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),1116ref->peer_ref->name,1117RESOLVE_REF_READING,1118&oid,1119&flag);1120if (!name || !(flag & REF_ISSYMREF))1121name = ref->peer_ref->name;1122
1123strbuf_addf(&buf, "%s:%s", name, ref->name);1124} else1125strbuf_addf(&buf, ":%s", ref->name);1126
1127string_list_append(&revlist_args, "--refspec");1128string_list_append(&revlist_args, buf.buf);1129strbuf_release(&buf);1130}1131if (!ref->deletion)1132string_list_append(&revlist_args, ref->peer_ref->name);1133}1134}1135
1136if (get_exporter(transport, &exporter, &revlist_args))1137die(_("couldn't run fast-export"));1138
1139string_list_clear(&revlist_args, 1);1140
1141if (finish_command(&exporter))1142die(_("error while running fast-export"));1143if (push_update_refs_status(data, remote_refs, flags))1144return 1;1145
1146if (data->export_marks) {1147strbuf_addf(&buf, "%s.tmp", data->export_marks);1148rename(buf.buf, data->export_marks);1149strbuf_release(&buf);1150}1151
1152return 0;1153}
1154
1155static int push_refs(struct transport *transport,1156struct ref *remote_refs, int flags)1157{
1158struct helper_data *data = transport->data;1159
1160if (process_connect(transport, 1))1161return transport->vtable->push_refs(transport, remote_refs, flags);1162
1163if (!remote_refs) {1164fprintf(stderr,1165_("No refs in common and none specified; doing nothing.\n"1166"Perhaps you should specify a branch.\n"));1167return 0;1168}1169
1170if (data->push)1171return push_refs_with_push(transport, remote_refs, flags);1172
1173if (data->export)1174return push_refs_with_export(transport, remote_refs, flags);1175
1176return -1;1177}
1178
1179
1180static int has_attribute(const char *attrs, const char *attr)1181{
1182int len;1183if (!attrs)1184return 0;1185
1186len = strlen(attr);1187for (;;) {1188const char *space = strchrnul(attrs, ' ');1189if (len == space - attrs && !strncmp(attrs, attr, len))1190return 1;1191if (!*space)1192return 0;1193attrs = space + 1;1194}1195}
1196
1197static struct ref *get_refs_list(struct transport *transport, int for_push,1198struct transport_ls_refs_options *transport_options)1199{
1200get_helper(transport);1201
1202if (process_connect(transport, for_push))1203return transport->vtable->get_refs_list(transport, for_push,1204transport_options);1205
1206return get_refs_list_using_list(transport, for_push);1207}
1208
1209static struct ref *get_refs_list_using_list(struct transport *transport,1210int for_push)1211{
1212struct helper_data *data = transport->data;1213struct child_process *helper;1214struct ref *ret = NULL;1215struct ref **tail = &ret;1216struct ref *posn;1217struct strbuf buf = STRBUF_INIT;1218
1219data->get_refs_list_called = 1;1220helper = get_helper(transport);1221
1222if (data->object_format)1223set_helper_option(transport, "object-format", "true");1224
1225if (data->push && for_push)1226write_constant(helper->in, "list for-push\n");1227else1228write_constant(helper->in, "list\n");1229
1230while (1) {1231char *eov, *eon;1232if (recvline(data, &buf))1233exit(128);1234
1235if (!*buf.buf)1236break;1237else if (buf.buf[0] == ':') {1238const char *value;1239if (skip_prefix(buf.buf, ":object-format ", &value)) {1240int algo = hash_algo_by_name(value);1241if (algo == GIT_HASH_UNKNOWN)1242die(_("unsupported object format '%s'"),1243value);1244transport->hash_algo = &hash_algos[algo];1245}1246continue;1247}1248
1249eov = strchr(buf.buf, ' ');1250if (!eov)1251die(_("malformed response in ref list: %s"), buf.buf);1252eon = strchr(eov + 1, ' ');1253*eov = '\0';1254if (eon)1255*eon = '\0';1256*tail = alloc_ref(eov + 1);1257if (buf.buf[0] == '@')1258(*tail)->symref = xstrdup(buf.buf + 1);1259else if (buf.buf[0] != '?')1260get_oid_hex_algop(buf.buf, &(*tail)->old_oid, transport->hash_algo);1261if (eon) {1262if (has_attribute(eon + 1, "unchanged")) {1263(*tail)->status |= REF_STATUS_UPTODATE;1264if (refs_read_ref(get_main_ref_store(the_repository), (*tail)->name, &(*tail)->old_oid) < 0)1265die(_("could not read ref %s"),1266(*tail)->name);1267}1268}1269tail = &((*tail)->next);1270}1271if (debug)1272fprintf(stderr, "Debug: Read ref listing.\n");1273strbuf_release(&buf);1274
1275for (posn = ret; posn; posn = posn->next)1276resolve_remote_symref(posn, ret);1277
1278return ret;1279}
1280
1281static int get_bundle_uri(struct transport *transport)1282{
1283get_helper(transport);1284
1285if (process_connect(transport, 0))1286return transport->vtable->get_bundle_uri(transport);1287
1288return -1;1289}
1290
1291static struct transport_vtable vtable = {1292.set_option = set_helper_option,1293.get_refs_list = get_refs_list,1294.get_bundle_uri = get_bundle_uri,1295.fetch_refs = fetch_refs,1296.push_refs = push_refs,1297.connect = connect_helper,1298.disconnect = release_helper1299};1300
1301int transport_helper_init(struct transport *transport, const char *name)1302{
1303struct helper_data *data = xcalloc(1, sizeof(*data));1304data->name = xstrdup(name);1305
1306transport_check_allowed(name);1307
1308if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))1309debug = 1;1310
1311list_objects_filter_init(&data->transport_options.filter_options);1312
1313transport->data = data;1314transport->vtable = &vtable;1315transport->smart_options = &(data->transport_options);1316return 0;1317}
1318
1319/*
1320* Linux pipes can buffer 65536 bytes at once (and most platforms can
1321* buffer less), so attempt reads and writes with up to that size.
1322*/
1323#define BUFFERSIZE 655361324/* This should be enough to hold debugging message. */
1325#define PBUFFERSIZE 81921326
1327/* Print bidirectional transfer loop debug message. */
1328__attribute__((format (printf, 1, 2)))1329static void transfer_debug(const char *fmt, ...)1330{
1331/*1332* NEEDSWORK: This function is sometimes used from multiple threads, and
1333* we end up using debug_enabled racily. That "should not matter" since
1334* we always write the same value, but it's still wrong. This function
1335* is listed in .tsan-suppressions for the time being.
1336*/
1337
1338va_list args;1339char msgbuf[PBUFFERSIZE];1340static int debug_enabled = -1;1341
1342if (debug_enabled < 0)1343debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0;1344if (!debug_enabled)1345return;1346
1347va_start(args, fmt);1348vsnprintf(msgbuf, PBUFFERSIZE, fmt, args);1349va_end(args);1350fprintf(stderr, "Transfer loop debugging: %s\n", msgbuf);1351}
1352
1353/* Stream state: More data may be coming in this direction. */
1354#define SSTATE_TRANSFERRING 01355/*
1356* Stream state: No more data coming in this direction, flushing rest of
1357* data.
1358*/
1359#define SSTATE_FLUSHING 11360/* Stream state: Transfer in this direction finished. */
1361#define SSTATE_FINISHED 21362
1363#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERRING)1364#define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)1365#define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)1366
1367/* Unidirectional transfer. */
1368struct unidirectional_transfer {1369/* Source */1370int src;1371/* Destination */1372int dest;1373/* Is source socket? */1374int src_is_sock;1375/* Is destination socket? */1376int dest_is_sock;1377/* Transfer state (TRANSFERRING/FLUSHING/FINISHED) */1378int state;1379/* Buffer. */1380char buf[BUFFERSIZE];1381/* Buffer used. */1382size_t bufuse;1383/* Name of source. */1384const char *src_name;1385/* Name of destination. */1386const char *dest_name;1387};1388
1389/* Closes the target (for writing) if transfer has finished. */
1390static void udt_close_if_finished(struct unidirectional_transfer *t)1391{
1392if (STATE_NEEDS_CLOSING(t->state) && !t->bufuse) {1393t->state = SSTATE_FINISHED;1394if (t->dest_is_sock)1395shutdown(t->dest, SHUT_WR);1396else1397close(t->dest);1398transfer_debug("Closed %s.", t->dest_name);1399}1400}
1401
1402/*
1403* Tries to read data from source into buffer. If buffer is full,
1404* no data is read. Returns 0 on success, -1 on error.
1405*/
1406static int udt_do_read(struct unidirectional_transfer *t)1407{
1408ssize_t bytes;1409
1410if (t->bufuse == BUFFERSIZE)1411return 0; /* No space for more. */1412
1413transfer_debug("%s is readable", t->src_name);1414bytes = xread(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);1415if (bytes < 0) {1416error_errno(_("read(%s) failed"), t->src_name);1417return -1;1418} else if (bytes == 0) {1419transfer_debug("%s EOF (with %i bytes in buffer)",1420t->src_name, (int)t->bufuse);1421t->state = SSTATE_FLUSHING;1422} else if (bytes > 0) {1423t->bufuse += bytes;1424transfer_debug("Read %i bytes from %s (buffer now at %i)",1425(int)bytes, t->src_name, (int)t->bufuse);1426}1427return 0;1428}
1429
1430/* Tries to write data from buffer into destination. If buffer is empty,
1431* no data is written. Returns 0 on success, -1 on error.
1432*/
1433static int udt_do_write(struct unidirectional_transfer *t)1434{
1435ssize_t bytes;1436
1437if (t->bufuse == 0)1438return 0; /* Nothing to write. */1439
1440transfer_debug("%s is writable", t->dest_name);1441bytes = xwrite(t->dest, t->buf, t->bufuse);1442if (bytes < 0) {1443error_errno(_("write(%s) failed"), t->dest_name);1444return -1;1445} else if (bytes > 0) {1446t->bufuse -= bytes;1447if (t->bufuse)1448memmove(t->buf, t->buf + bytes, t->bufuse);1449transfer_debug("Wrote %i bytes to %s (buffer now at %i)",1450(int)bytes, t->dest_name, (int)t->bufuse);1451}1452return 0;1453}
1454
1455
1456/* State of bidirectional transfer loop. */
1457struct bidirectional_transfer_state {1458/* Direction from program to git. */1459struct unidirectional_transfer ptg;1460/* Direction from git to program. */1461struct unidirectional_transfer gtp;1462};1463
1464static void *udt_copy_task_routine(void *udt)1465{
1466struct unidirectional_transfer *t = (struct unidirectional_transfer *)udt;1467while (t->state != SSTATE_FINISHED) {1468if (STATE_NEEDS_READING(t->state))1469if (udt_do_read(t))1470return NULL;1471if (STATE_NEEDS_WRITING(t->state))1472if (udt_do_write(t))1473return NULL;1474if (STATE_NEEDS_CLOSING(t->state))1475udt_close_if_finished(t);1476}1477return udt; /* Just some non-NULL value. */1478}
1479
1480#ifndef NO_PTHREADS1481
1482/*
1483* Join thread, with appropriate errors on failure. Name is name for the
1484* thread (for error messages). Returns 0 on success, 1 on failure.
1485*/
1486static int tloop_join(pthread_t thread, const char *name)1487{
1488int err;1489void *tret;1490err = pthread_join(thread, &tret);1491if (!tret) {1492error(_("%s thread failed"), name);1493return 1;1494}1495if (err) {1496error(_("%s thread failed to join: %s"), name, strerror(err));1497return 1;1498}1499return 0;1500}
1501
1502/*
1503* Spawn the transfer tasks and then wait for them. Returns 0 on success,
1504* -1 on failure.
1505*/
1506static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)1507{
1508pthread_t gtp_thread;1509pthread_t ptg_thread;1510int err;1511int ret = 0;1512err = pthread_create(>p_thread, NULL, udt_copy_task_routine,1513&s->gtp);1514if (err)1515die(_("can't start thread for copying data: %s"), strerror(err));1516err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine,1517&s->ptg);1518if (err)1519die(_("can't start thread for copying data: %s"), strerror(err));1520
1521ret |= tloop_join(gtp_thread, "Git to program copy");1522ret |= tloop_join(ptg_thread, "Program to git copy");1523return ret;1524}
1525#else1526
1527/* Close the source and target (for writing) for transfer. */
1528static void udt_kill_transfer(struct unidirectional_transfer *t)1529{
1530t->state = SSTATE_FINISHED;1531/*1532* Socket read end left open isn't a disaster if nobody
1533* attempts to read from it (mingw compat headers do not
1534* have SHUT_RD)...
1535*
1536* We can't fully close the socket since otherwise gtp
1537* task would first close the socket it sends data to
1538* while closing the ptg file descriptors.
1539*/
1540if (!t->src_is_sock)1541close(t->src);1542if (t->dest_is_sock)1543shutdown(t->dest, SHUT_WR);1544else1545close(t->dest);1546}
1547
1548/*
1549* Join process, with appropriate errors on failure. Name is name for the
1550* process (for error messages). Returns 0 on success, 1 on failure.
1551*/
1552static int tloop_join(pid_t pid, const char *name)1553{
1554int tret;1555if (waitpid(pid, &tret, 0) < 0) {1556error_errno(_("%s process failed to wait"), name);1557return 1;1558}1559if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {1560error(_("%s process failed"), name);1561return 1;1562}1563return 0;1564}
1565
1566/*
1567* Spawn the transfer tasks and then wait for them. Returns 0 on success,
1568* -1 on failure.
1569*/
1570static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)1571{
1572pid_t pid1, pid2;1573int ret = 0;1574
1575/* Fork thread #1: git to program. */1576pid1 = fork();1577if (pid1 < 0)1578die_errno(_("can't start thread for copying data"));1579else if (pid1 == 0) {1580udt_kill_transfer(&s->ptg);1581exit(udt_copy_task_routine(&s->gtp) ? 0 : 1);1582}1583
1584/* Fork thread #2: program to git. */1585pid2 = fork();1586if (pid2 < 0)1587die_errno(_("can't start thread for copying data"));1588else if (pid2 == 0) {1589udt_kill_transfer(&s->gtp);1590exit(udt_copy_task_routine(&s->ptg) ? 0 : 1);1591}1592
1593/*1594* Close both streams in parent as to not interfere with
1595* end of file detection and wait for both tasks to finish.
1596*/
1597udt_kill_transfer(&s->gtp);1598udt_kill_transfer(&s->ptg);1599ret |= tloop_join(pid1, "Git to program copy");1600ret |= tloop_join(pid2, "Program to git copy");1601return ret;1602}
1603#endif1604
1605/*
1606* Copies data from stdin to output and from input to stdout simultaneously.
1607* Additionally filtering through given filter. If filter is NULL, uses
1608* identity filter.
1609*/
1610int bidirectional_transfer_loop(int input, int output)1611{
1612struct bidirectional_transfer_state state;1613
1614/* Fill the state fields. */1615state.ptg.src = input;1616state.ptg.dest = 1;1617state.ptg.src_is_sock = (input == output);1618state.ptg.dest_is_sock = 0;1619state.ptg.state = SSTATE_TRANSFERRING;1620state.ptg.bufuse = 0;1621state.ptg.src_name = "remote input";1622state.ptg.dest_name = "stdout";1623
1624state.gtp.src = 0;1625state.gtp.dest = output;1626state.gtp.src_is_sock = 0;1627state.gtp.dest_is_sock = (input == output);1628state.gtp.state = SSTATE_TRANSFERRING;1629state.gtp.bufuse = 0;1630state.gtp.src_name = "stdin";1631state.gtp.dest_name = "remote output";1632
1633return tloop_spawnwait_tasks(&state);1634}
1635
1636void reject_atomic_push(struct ref *remote_refs, int mirror_mode)1637{
1638struct ref *ref;1639
1640/* Mark other refs as failed */1641for (ref = remote_refs; ref; ref = ref->next) {1642if (!ref->peer_ref && !mirror_mode)1643continue;1644
1645switch (ref->status) {1646case REF_STATUS_NONE:1647case REF_STATUS_OK:1648case REF_STATUS_EXPECTING_REPORT:1649ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;1650continue;1651default:1652break; /* do nothing */1653}1654}1655return;1656}
1657