client.c

来自「linux subdivision ying gai ke yi le ba」· C语言 代码 · 共 1,321 行 · 第 1/4 页

C
1,321
字号
  SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "lc", &mechlist, &realm));
  if (mechlist->nelts == 0)
    return SVN_NO_ERROR;
  return do_auth(sess, mechlist, realm, pool);
}

/* --- REPORTER IMPLEMENTATION --- */

static svn_error_t *ra_svn_set_path(void *baton, const char *path,
                                    svn_revnum_t rev,
                                    svn_boolean_t start_empty,
                                    apr_pool_t *pool)
{
  ra_svn_reporter_baton_t *b = baton;

  SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "set-path", "crb", path, rev,
                               start_empty));
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
                                       apr_pool_t *pool)
{
  ra_svn_reporter_baton_t *b = baton;

  SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-path", "c", path));
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_link_path(void *baton, const char *path,
                                     const char *url,
                                     svn_revnum_t rev,
                                     svn_boolean_t start_empty,
                                     apr_pool_t *pool)
{
  ra_svn_reporter_baton_t *b = baton;

  SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "link-path", "ccrb", path, url,
                               rev, start_empty));
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_finish_report(void *baton,
                                         apr_pool_t *pool)
{
  ra_svn_reporter_baton_t *b = baton;

  SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "finish-report", ""));
  SVN_ERR(handle_auth_request(b->sess, b->pool));
  SVN_ERR(svn_ra_svn_drive_editor(b->conn, b->pool, b->editor, b->edit_baton,
                                  NULL));
  SVN_ERR(svn_ra_svn_read_cmd_response(b->conn, b->pool, ""));
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_abort_report(void *baton,
                                        apr_pool_t *pool)
{
  ra_svn_reporter_baton_t *b = baton;

  SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "abort-report", ""));
  return SVN_NO_ERROR;
}

static svn_ra_reporter_t ra_svn_reporter = {
  ra_svn_set_path,
  ra_svn_delete_path,
  ra_svn_link_path,
  ra_svn_finish_report,
  ra_svn_abort_report
};

static void ra_svn_get_reporter(ra_svn_session_baton_t *sess, apr_pool_t *pool,
                                const svn_delta_editor_t *editor,
                                void *edit_baton,
                                const svn_ra_reporter_t **reporter,
                                void **report_baton)
{
  ra_svn_reporter_baton_t *b;

  b = apr_palloc(pool, sizeof(*b));
  b->sess = sess;
  b->conn = sess->conn;
  b->pool = pool;
  b->editor = editor;
  b->edit_baton = edit_baton;

  *reporter = &ra_svn_reporter;
  *report_baton = b;
}

/* --- RA LAYER IMPLEMENTATION --- */

static svn_error_t *find_tunnel_agent(const char *tunnel, const char *user,
                                      const char *host, const char ***argv,
                                      apr_hash_t *config, apr_pool_t *pool)
{
  svn_config_t *cfg;
  const char *val, *var, *cmd;
  char **cmd_argv;
  apr_size_t len;
  apr_status_t status;
  int n;

  /* Look up the tunnel specification in config. */
  cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
                              APR_HASH_KEY_STRING) : NULL;
  svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);

  /* We have one predefined tunnel scheme, if it isn't overridden by config. */
  if (!val && strcmp(tunnel, "ssh") == 0)
    val = "$SVN_SSH ssh";

  if (!val || !*val)
    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
                             "Undefined tunnel scheme '%s'", tunnel);

  /* If the scheme definition begins with "$varname", it means there
   * is an environment variable which can override the command. */
  if (*val == '$')
    {
      val++;
      len = strcspn(val, " ");
      var = apr_pstrmemdup(pool, val, len);
      cmd = getenv(var);
      if (!cmd)
        {
          cmd = val + len;
          while (*cmd == ' ')
            cmd++;
          if (!*cmd)
            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
                                     "Tunnel scheme %s requires environment "
                                     "variable %s to be defined", tunnel, var);
        }
    }
  else
    cmd = val;

  /* Tokenize the command into a list of arguments. */
  status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
  if (status != APR_SUCCESS)
    return svn_error_wrap_apr(status, "Can't tokenize command '%s'", cmd);

  /* Append the fixed arguments to the result. */
  for (n = 0; cmd_argv[n] != NULL; n++)
    ;
  *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
  memcpy(*argv, cmd_argv, n * sizeof(char *));
  (*argv)[n++] = (user) ? apr_psprintf(pool, "%s@%s", user, host) : host;
  (*argv)[n++] = "svnserve";
  (*argv)[n++] = "-t";
  (*argv)[n] = NULL;

  return SVN_NO_ERROR;
}

/* This function handles any errors which occur in the child process
 * created for a tunnel agent.  We write the error out as a command
 * failure; the code in ra_svn_open() to read the server's greeting
 * will see the error and return it to the caller. */
static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
                                       const char *desc)
{
  svn_ra_svn_conn_t *conn;
  apr_file_t *in_file, *out_file;
  svn_error_t *err;

  apr_file_open_stdin(&in_file, pool);
  apr_file_open_stdout(&out_file, pool);
  conn = svn_ra_svn_create_conn(NULL, in_file, out_file, pool);
  err = svn_error_wrap_apr(status, NULL, "Error in child process: %s", desc);
  svn_error_clear(svn_ra_svn_write_cmd_failure(conn, pool, err));
  svn_error_clear(svn_ra_svn_flush(conn, pool));
}

static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
                                apr_pool_t *pool)
{
  apr_status_t status;
  apr_proc_t *proc;
  apr_procattr_t *attr;

  status = apr_procattr_create(&attr, pool);
  if (status == APR_SUCCESS)
    status = apr_procattr_io_set(attr, 1, 1, 0);
  if (status == APR_SUCCESS)
    status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
  if (status == APR_SUCCESS)
    status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
  proc = apr_palloc(pool, sizeof(*proc));
  if (status == APR_SUCCESS)
    status = apr_proc_create(proc, *args, args, NULL, attr, pool);
  if (status != APR_SUCCESS)
    return svn_error_wrap_apr(status, NULL, "Can't create tunnel");

  /* Arrange for the tunnel agent to get a SIGKILL on pool
   * cleanup.  This is a little extreme, but the alternatives
   * weren't working out:
   *   - Closing the pipes and waiting for the process to die
   *     was prone to mysterious hangs which are difficult to
   *     diagnose (e.g. svnserve dumps core due to unrelated bug;
   *     sshd goes into zombie state; ssh connection is never
   *     closed; ssh never terminates).
   *   - Killing the tunnel agent with SIGTERM leads to unsightly
   *     stderr output from ssh.
   */
  apr_pool_note_subprocess(pool, proc, APR_KILL_ALWAYS);

  /* APR pipe objects inherit by default.  But we don't want the
   * tunnel agent's pipes held open by future child processes
   * (such as other ra_svn sessions), so turn that off. */
  apr_file_inherit_unset(proc->in);
  apr_file_inherit_unset(proc->out);

  /* Guard against dotfile output to stdout on the server. */
  *conn = svn_ra_svn_create_conn(NULL, proc->out, proc->in, pool);
  (*conn)->proc = proc;
  SVN_ERR(svn_ra_svn_skip_leading_garbage(*conn, pool));
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_open(void **baton, const char *url,
                                const svn_ra_callbacks_t *callbacks,
                                void *callback_baton,
                                apr_hash_t *config,
                                apr_pool_t *pool)
{
  ra_svn_session_baton_t *sess;
  svn_ra_svn_conn_t *conn;
  apr_socket_t *sock;
  const char *hostname, *user, *tunnel, **args;
  unsigned short port;
  apr_uint64_t minver, maxver;
  apr_array_header_t *mechlist, *caplist;

  if (parse_url(url, &tunnel, &user, &port, &hostname, pool) != 0)
    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
                             "Illegal svn repository URL '%s'", url);

  if (tunnel)
    {
      SVN_ERR(find_tunnel_agent(tunnel, user, hostname, &args, config, pool));
      SVN_ERR(make_tunnel(args, &conn, pool));
    }
  else
    {
      SVN_ERR(make_connection(hostname, port, &sock, pool));
      conn = svn_ra_svn_create_conn(sock, NULL, NULL, pool);
    }

  /* Read server's greeting. */
  SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "nnll", &minver, &maxver,
                                       &mechlist, &caplist));
  /* We support protocol versions 1 and 2. */
  if (minver > 2)
    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
                             "Server requires minimum version %d",
                             (int) minver);
  SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));

  sess = apr_palloc(pool, sizeof(*sess));
  sess->conn = conn;
  sess->protocol_version = (maxver > 2) ? 2 : maxver;
  sess->is_tunneled = (tunnel != NULL);
  sess->auth_baton = callbacks->auth_baton;
  sess->user = user;
  sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", hostname, port);

  /* In protocol version 2, we send back our protocol version, our
   * capability list, and the URL, and subsequently there is an auth
   * request.  In version 1, we send back the protocol version, auth
   * mechanism, mechanism initial response, and capability list, and;
   * then send the URL after authentication.  do_auth temporarily has
   * support for the mixed-style response. */
  /* When we punt support for protocol version 1, we should:
   * - Eliminate this conditional and the similar one below
   * - Remove v1 support from auth_response and inline it into do_auth
   * - Remove the (realm == NULL) support from do_auth
   * - Inline do_auth into handle_auth_request
   * - Remove the protocol version check from handle_auth_request */
  if (sess->protocol_version == 1)
    {
      SVN_ERR(do_auth(sess, mechlist, NULL, pool));
      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c", url));
    }
  else
    {
      SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(w)c", (apr_uint64_t) 2,
                                     SVN_RA_SVN_CAP_EDIT_PIPELINE, url));
      SVN_ERR(handle_auth_request(sess, pool));
    }

  /* This is where the security layer would go into effect if we
   * supported security layers, which is a ways off. */

  /* Read the repository's uuid. */
  SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "c?c", &conn->uuid,
                                       &conn->repos_root));
  if (conn->repos_root)
    {
      conn->repos_root = svn_path_canonicalize(conn->repos_root, pool);
      /* We should check that the returned string is a prefix of url, since
         that's the API guarantee, but this isn't true for 1.0 servers.
         Checking the length prevents client crashes. */
      if (strlen(conn->repos_root) > strlen(url))
        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
                                _("Impossibly long repository root from "
                                  "server"));
    }

  *baton = sess;
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_get_latest_rev(void *baton, svn_revnum_t *rev,
                                          apr_pool_t *pool)
{
  ra_svn_session_baton_t *sess = baton;
  svn_ra_svn_conn_t *conn = sess->conn;

  SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-latest-rev", ""));
  SVN_ERR(handle_auth_request(sess, pool));
  SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
  return SVN_NO_ERROR;
}

static svn_error_t *ra_svn_get_dated_rev(void *baton, svn_revnum_t *rev,
                                         apr_time_t tm, apr_pool_t *pool)
{
  ra_svn_session_baton_t *sess = baton;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?