Substitute or workaround for asprintf on AIX
Asked Answered
D

4

13

I'm trying to build python-kerberos on AIX. kerberospw.c uses a call to asprintf, but from what Google is telling me, asprintf does not exist on AIX.

I saw http://www.koders.com/c/fidAA9B130D588302673A28B568430A83131B7734C0.aspx?s=windows.h, which looks like I could create a stand-in asprintf, but I don't know where this would go or how I would #include it in kerberospw.c.

Is there a way I can use the koders.com example or some other code to "fake" asprintf? Can I just include the asprintf function as shown in kerberospw.c? I am not a C coder, but

asprintf (char **resultp, const char *format, ...)

doesn't look like a valid signature to me with the dots at the end. The relevant line from kerberospw.c is below

asprintf(&message, "%.*s: %.*s", (int) result_code_string.length,
(char *) result_code_string.data,
(int) result_string.length,
(char *) result_string.data);

I realize I could contact the author of python-kerberos, but a) I think it would be helpful to have a potential patch if I did so, and b) there might be other software I run across that uses asprintf, and it would be nice to have a workaround.

Doronicum answered 4/2, 2011 at 14:39 Comment(1)
Note that this man page for asprintf() says that the return value in *resultp is indeterminate in case of error. Other man pages, such as the one referenced in the question, indicate that it is explicitly set to NULL on error. If using asprintf(), do not assume that the pointer is initialized if the function fails. If implementing asprintf(), ensure that the pointer is set to null on error to give deterministic behaviour.Quietism
S
22

The asprintf is a variation of the printf family of function that allocate a buffer to hold the memory for the formatted string and return it. It is a function with a variable number of argument (hence the ... in the declaration that is valid C code). You can find a description here.

It can be reimplemented relatively easily if the vsnprintf is functioning correctly (ie, return an error if the buffer is too small to hold the formatted string).

Here is such an implementation:

#include <stdarg.h>

int asprintf(char **ret, const char *format, ...)
{
    va_list ap;

    *ret = NULL;  /* Ensure value can be passed to free() */

    va_start(ap, format);
    int count = vsnprintf(NULL, 0, format, ap);
    va_end(ap);

    if (count >= 0)
    {
        char* buffer = malloc(count + 1);
        if (buffer == NULL)
            return -1;

        va_start(ap, format);
        count = vsnprintf(buffer, count + 1, format, ap);
        va_end(ap);

        if (count < 0)
        {
            free(buffer);
            return count;
        }
        *ret = buffer;
    }

    return count;
}
Smithson answered 4/2, 2011 at 15:6 Comment(5)
You have to do a va_end(ap); after the first call to vsnprintf() and another call to va_start(ap, format); before the second call. You also need to #include <stdlib.h>, of course, and #include <stdio.h> to obtain a declaration of vsnprintf().Quietism
Ah, thank you. I always forgot to do it. I'll update my post.Smithson
Also, buffer is out of scope at the line *ret = buffer;.Quietism
Thanks Sylvain. I tested and this worked as far as getting the code to compile and for the test cases submitted with the python code to test successfully.Doronicum
For anyone looking to implement asprintf on windows, you can't use vsnprintf as it is used here, since windows treats a NULL buffer input as an error. You can replace it with the _vscprintf function, which is just like vprintf except that it doesn't print anything - only returns the character count, which is what we want.Sneer
Q
15

Building on Sylvain's answer, here is a simple implementation with both asprintf() and vasprintf() because where you need one, you usually end up needing the other too. And, given the va_copy() macro from C99, it is easy to implement asprintf() in terms of vasprintf(). Indeed, when writing varargs functions, it is very often helpful to have them in pairs, one with the ellipsis notation and one with the va_list argument in place of the ellipsis, and you trivially implement the former in terms of the latter.

This leads to the code:

int vasprintf(char **ret, const char *format, va_list args)
{
    va_list copy;
    va_copy(copy, args);

    /* Make sure it is determinate, despite manuals indicating otherwise */
    *ret = NULL;

    int count = vsnprintf(NULL, 0, format, args);
    if (count >= 0)
    {
        char *buffer = malloc(count + 1);
        if (buffer == NULL)
            count = -1;
        else if ((count = vsnprintf(buffer, count + 1, format, copy)) < 0)
            free(buffer);
        else
            *ret = buffer;
    }
    va_end(copy);  // Each va_start() or va_copy() needs a va_end()

    return count;
}

int asprintf(char **ret, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    int count = vasprintf(ret, format, args);
    va_end(args);
    return(count);
}

The tricky part of using these functions in a system where they are not provided is deciding where the functions should be declared. Ideally, they'd be in <stdio.h>, but then you wouldn't need to write them. So, you have to have some other header which includes <stdio.h> but declares these functions if they are not declared in <stdio.h>. And, ideally, the code should semi-automatically detect this. Maybe the header is "missing.h", and contains (in part):

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdarg.h>

#ifndef HAVE_ASPRINTF
extern int asprintf(char **ret, const char *format, ...);
extern int vasprintf(char **ret, const char *format, va_list args);
#endif /* HAVE_ASPRINTF */

Also, note that this man page for asprintf() says that the return value in the pointer is indeterminate in case of error. Other man pages, including the one referenced in the question, indicate that it is explicitly set to NULL on error. The C Standard committee document (n1337.pdf) does not specify the error behaviour on lack of memory.

  • If using asprintf(), do not assume that the pointer is initialized if the function fails.
  • If implementing asprintf(), ensure that the pointer is set to null on error to give deterministic behaviour.
Quietism answered 4/2, 2011 at 17:14 Comment(9)
Thanks Jonathan. You are right that AIX doesn't have vasprint on a fresh load either. I tested this as well and it seemed to compile fine. I would upvote both this and Sylvain's answers, but I don't actually have enough rep because I'm not a very good SO participant.Doronicum
@bobwood: you should accept - and did accept - Sylvain's answer; in my view, that is completely correct. He did the donkey work (with a little assistance); my answer is strictly derivative. AFAIK, you can always upvote answers - and upvote more than one answer if you feel that more than one answer is helpful. The accepted answer is for the 'most helpful' answer and gives the answerer (and you, as the asker) an extra bonus. (One draft version of my answer started with "Do not upvote this". I got greedy and removed it.)Quietism
Reputation gained and upvotes awarded on the answers to this question.Doronicum
On allocation error, you return the length of the formatted string and the pointer ret points to is not set to NULL. This means that the allocation failure case is indistinguishable from the successful case if you reference an uninitialised pointer (which is quite common on asprintf() calls.Reardon
Minor question: why use *ret = 0; and buffer != NULL rather than 0 or NULL for both?Steen
Sorry, I didn't see the *ret = 0 (may be with NULL I would have seen it ;-) as chux suggested) so there is a distinct behavior in case of failed allocation. This doesn't change the bug though, as all documentations of asprintf() I've seen (and no exception with your 2 links), state that -1 must be returned in that case and that the pointer value is undefined.Reardon
@chux: it demonstrates that (a) I'm human and (b) I didn't have anyone off SO to review the code. Also, given that the answer is 3.5 years old, it isn't a crucial issue. I note that the C committee version of the specification (N1337) does not mention the error behaviour. However, it is easy (trivial) to fix the code to match the 'return -1 on memory allocation error' in the man pages.Quietism
@tristopia: thanks for pointing out the bug; I've fixed it in the code.Quietism
@Jonathan Leffler My comment about NULL/0 is coincidental with tristopia and has nothing to do with the -1 return value issue. I agree my comment is not a crucial issue, hence my prefix of "minor question". I respect your many fine answers and was simply curious about the difference. It was likely either a trivial inconsistency or maybe some insightful aspect your code was trying to express.Steen
C
1

I came here looking for a quick implementation for Windows and Linux which set the return pointer to NULL on failure.

Jonathan Leffler's answer looked to be the better one, but then I noticed it doesn't set -1 when malloc fails.

I did more searching and came across this discussion of implementing asprintf, which then enlightened me that Jonathan and Sylvain both did not handle overflow correctly either.

I now recommend this solution provided with the aforementioned discussion, which seems to be cover all the important platforms and apparently handles every failure scenario correctly.

Concave answered 3/7, 2014 at 13:21 Comment(1)
Although I agree with these points, this answer is a comment on the various answers. It is not an answer. At best it is a link-only answer.Steen
R
1

Here an implementation that doesn't call snprintf() twice in most of the cases. I omitted the includes and defines as shown in other responses.

As it should be, define the asprintf() as a call to vasprintf()

int asprintf(char **dst, const char * pcFormat, ...)
{
va_list ap;

  va_start(ap, pcFormat);
  int len = vasprintf(dst, pcFormat, ap);
  va_end(ap);
  return len;
}

We preallocate a buffer to an predefined appropriate size and only in case of overflow call vsnprintf() a second time. The rationale being that s*printf() function are considered very heavy and overallocating memory being acceptable.

int vasprintf(char **dst, const char * pcFormat, va_list ap)
{
  int len = 512;      /* Worked quite well on our project */
  int allocated = 0;
  va_list ap_copy;
  char *buff = NULL;

  while(len >= allocated) {
    free(buff);
    buff = malloc(len+1);
    if(buff) {
      allocated = len+1;
      va_copy(ap_copy, ap);
      len = vsnprintf(buff, len+1, pcFormat, ap_copy);
      va_end(ap_copy);
    }
    else   /* malloc() failed */
      return -1;
  }
  *dst = buff;
  return len;
}

EDIT: I replaced the realloc() call by a simple malloc() as it is cheaper. In the case of overflow a free()/malloc() pair costs less than realloc() because of its internal hidden memcpy(). As we overwrite the whole buffer anyway with the subsequent call to vsnprintf() there is no point in that copy.

Region answered 30/9, 2014 at 17:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.