Add cli_posix_readlink() and a torture test for it.
[ira/wip.git] / source3 / libsmb / clifile.c
index e48656b698e6cec0b9fd5569a1c2f0e8fbdcad0f..187fcdf62519b55200cb0dca10b9ebded250b061 100644 (file)
@@ -106,7 +106,6 @@ static uint8_t *trans2_bytes_push_str(uint8_t *buf, bool ucs2,
                        false, pconverted_size);
 }
 
-
 /****************************************************************************
  Hard/Symlink a file (UNIX extensions).
  Creates new name (sym)linked to oldname.
@@ -272,6 +271,189 @@ NTSTATUS cli_posix_symlink(struct cli_state *cli,
        return status;
 }
 
+/****************************************************************************
+ Read a POSIX symlink.
+****************************************************************************/
+
+struct readlink_state {
+       uint16_t setup;
+       uint8_t *param;
+       uint8_t *data;
+       uint32_t num_data;
+};
+
+static void cli_posix_readlink_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+                               subreq, struct tevent_req);
+       struct readlink_state *state = tevent_req_data(req, struct readlink_state);
+       NTSTATUS status;
+
+       status = cli_trans_recv(subreq, state, NULL, NULL, NULL, NULL,
+                       &state->data, &state->num_data);
+       TALLOC_FREE(subreq);
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return;
+       }
+       if (state->num_data == 0) {
+               tevent_req_nterror(req, NT_STATUS_DATA_ERROR);
+               return;
+       }
+       if (state->data[state->num_data-1] != '\0') {
+               tevent_req_nterror(req, NT_STATUS_DATA_ERROR);
+               return;
+       }
+       tevent_req_done(req);
+}
+
+struct tevent_req *cli_posix_readlink_send(TALLOC_CTX *mem_ctx,
+                                       struct event_context *ev,
+                                       struct cli_state *cli,
+                                       const char *fname,
+                                       size_t len)
+{
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct readlink_state *state = NULL;
+       uint32_t maxbytelen = (uint32_t)(cli_ucs2(cli) ? len*3 : len);
+
+       if (maxbytelen < len) {
+               return NULL;
+       }
+
+       req = tevent_req_create(mem_ctx, &state, struct readlink_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       /* Setup setup word. */
+       SSVAL(&state->setup, 0, TRANSACT2_QPATHINFO);
+
+       /* Setup param array. */
+       state->param = talloc_array(state, uint8_t, 6);
+       if (tevent_req_nomem(state->param, req)) {
+               return tevent_req_post(req, ev);
+       }
+       memset(state->param, '\0', 6);
+       SSVAL(state->param,0,SMB_QUERY_FILE_UNIX_LINK);
+
+       state->param = trans2_bytes_push_str(state->param, cli_ucs2(cli), fname,
+                                  strlen(fname)+1, NULL);
+
+       if (tevent_req_nomem(state->param, req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       subreq = cli_trans_send(state,                  /* mem ctx. */
+                               ev,                     /* event ctx. */
+                               cli,                    /* cli_state. */
+                               SMBtrans2,              /* cmd. */
+                               NULL,                   /* pipe name. */
+                               -1,                     /* fid. */
+                               0,                      /* function. */
+                               0,                      /* flags. */
+                               &state->setup,          /* setup. */
+                               1,                      /* num setup uint16_t words. */
+                               0,                      /* max returned setup. */
+                               state->param,           /* param. */
+                               talloc_get_size(state->param),  /* num param. */
+                               2,                      /* max returned param. */
+                               NULL,                   /* data. */
+                               0,                      /* num data. */
+                               maxbytelen);            /* max returned data. */
+
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_posix_readlink_done, req);
+       return req;
+}
+
+NTSTATUS cli_posix_readlink_recv(struct tevent_req *req, struct cli_state *cli,
+                               char *retpath, size_t len)
+{
+       NTSTATUS status;
+       char *converted = NULL;
+       size_t converted_size = 0;
+       struct readlink_state *state = tevent_req_data(req, struct readlink_state);
+
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       /* The returned data is a pushed string, not raw data. */
+       if (!convert_string_talloc(state,
+                               cli_ucs2(cli) ? CH_UTF16LE : CH_DOS, 
+                               CH_UNIX,
+                               state->data,
+                               state->num_data,
+                               &converted,
+                               &converted_size,
+                               true)) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       len = MIN(len,converted_size);
+       if (len == 0) {
+               return NT_STATUS_DATA_ERROR;
+       }
+       memcpy(retpath, converted, len);
+       return NT_STATUS_OK;
+}
+
+NTSTATUS cli_posix_readlink(struct cli_state *cli, const char *fname,
+                               char *linkpath, size_t len)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct event_context *ev = NULL;
+       struct tevent_req *req = NULL;
+       NTSTATUS status = NT_STATUS_OK;
+
+       if (cli_has_async_calls(cli)) {
+               /*
+                * Can't use sync call while an async call is in flight
+                */
+               status = NT_STATUS_INVALID_PARAMETER;
+               goto fail;
+       }
+
+       ev = event_context_init(frame);
+       if (ev == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto fail;
+       }
+
+       /* Len is in bytes, we need it in UCS2 units. */
+       if (2*len < len) {
+               status = NT_STATUS_INVALID_PARAMETER;
+               goto fail;
+       }
+
+       req = cli_posix_readlink_send(frame,
+                               ev,
+                               cli,
+                               fname,
+                               len);
+       if (req == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto fail;
+       }
+
+       if (!tevent_req_poll(req, ev)) {
+               status = map_nt_error_from_unix(errno);
+               goto fail;
+       }
+
+       status = cli_posix_readlink_recv(req, cli, linkpath, len);
+
+ fail:
+       TALLOC_FREE(frame);
+       if (!NT_STATUS_IS_OK(status)) {
+               cli_set_error(cli, status);
+       }
+       return status;
+}
+
+
 /****************************************************************************
  Hard link a file (UNIX extensions).
 ****************************************************************************/