0

I'm trying to use setsockopt() on both Linux and Solaris in my ftp program.

Linux uses a long for optvalue as a parameter, but Solaris uses a char instead.

#ifdef sun
    char val;
#else
    long val;
#endif

#ifdef sun
    val = 1;
    size_t len = sizeof(char);
    if(setsockopt(s_socket_fd, SOL_SOCKET, SO_REUSEADDR, &val, len) == -1) {
        perror("Fail");
        exit(1);
    }
#else
    val = 1;
    size_t len = sizeof(long);
    if(setsockopt(s_socket_fd, SOL_SOCKET, SO_REUSEADDR, &val, len) == -1) {
        perror("Fail");
        exit(1);
    }
#endif

The program works fine in Linux, but reports "Invalid argument" in Solaris when creating the socket.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Shiu Chun Ming
  • 227
  • 2
  • 13
  • Use the number `1`, not the character `'1'`. – dbush Feb 07 '18 at 17:50
  • Seems like the optvalue for solaris is a character '1'. – Shiu Chun Ming Feb 07 '18 at 17:52
  • I saw it from https://stackoverflow.com/questions/5592747/bind-error-while-recreating-socket – Shiu Chun Ming Feb 07 '18 at 17:53
  • 2
    It's incorrect. You use `char` for the type, but the value is the same. – dbush Feb 07 '18 at 17:54
  • Unfortunately, same error message happen :( – Shiu Chun Ming Feb 07 '18 at 17:59
  • 2
    BTW, the second `#ifdef` is unnecessary if you write `size_t len = sizeof val;`, to use the already-selected type. – Toby Speight Feb 07 '18 at 18:19
  • What version of Solaris? How are you compiling and linking your program? If you're linking with `-lxnet` and not `-lsocket -lnsl`, the prototype is `int setsockopt(int socket, int level, int option_name, const void* option_value, socklen_t option_len);` See https://docs.oracle.com/cd/E36784_01/html/E36875/setsockopt-3xnet.html#scrolltoc (And if you look in Solaris 11's `/usr/include/sys/socket.h`, you'd see that's the prototype anyway - because with C, you can pass a `char *` to a `void *` and an `int` to a `socklen_t` without error) – Andrew Henle Feb 07 '18 at 22:00

1 Answers1

4

There are 2 issues appearing in the question:

  • Linux uses a long for optvalue as a parameter, but Solaris uses a char instead.

TL;DR: you should use int everywhere.

in Solaris there can be slightly different definitions of setsockopt() depending on the version of Solaris and the library the program is linked against, for example Solaris 10's setsockopt (3SOCKET), setsockopt (3XNET) and Solaris 11's setsockopt (3SOCKET), setsockopt (3XNET). Linux' manpage shares the same contents as Solaris' (3SOCKET) manpages:

Most socket-level options utilize an int argument for optval. For setsockopt(), the argument should be nonzero to enable a boolean option, or zero if the option is to be disabled.

(3XNET) just doesn't state the type for bolean option. Yet-an-other-UNIX (AIX) also has old *BSD and UNIX98/XOPEN versions, on a single page, where it's an int for booleans. The confusion about using char comes from (*BSD-style) examples using a pointer cast to (char *) instead of (const void *), because the prototype is using char * for *BSD/3XNET. That doesn't mean the parameter is char. As for long, beside not being in the definition, on architectures where long is not int that would be wrong. Note that Windows is apart from *NIX there.

  • size_t len = sizeof(char);
    

Beside the fact that sizeof val should be preferred, and can be used directly in setsockopt() without even the need to use len here in the first place, len is defined as int on Solaris (3XNET) and as socklen_t on Solaris (3SOCKET) and Linux, not size_t anywhere. Link to a possible explanation on why int became socklen_t with a brief (and wrong) passage via size_t. Using other types (such as size_t on Linux) might break on some architectures when size_t is not defined as int (see the 2 previous links).

A.B
  • 376
  • 4
  • 11
  • Solaris provides *two* `setsockopt()` function calls, depending on whether or not the program links with `-lsocket -lnsl` or with `-lxnet`. See https://docs.oracle.com/cd/E36784_01/html/E36875/setsockopt-3socket.html#scrolltoc and https://docs.oracle.com/cd/E36784_01/html/E36875/setsockopt-3xnet.html#scrolltoc – Andrew Henle Feb 07 '18 at 21:50
  • This link is better: https://docs.oracle.com/cd/E18752_01/html/816-5170/setsockopt-3socket.html (that's an old Solaris 10 man page for `setsockopt.3socket`). The 3xnet entry is https://docs.oracle.com/cd/E18752_01/html/816-5170/setsockopt-3xnet.html On Solaris 11, the prototypes both use `void *` and `socklen_t`. – Andrew Henle Feb 07 '18 at 21:59
  • Ok thanks. I'll update my answer to state that Solaris 11 has the same defintions with your 2nd link. AND I'll correct a mistake I made: setsockopt() doesn't take a pointer to the size (len), only getsockopt() does – A.B Feb 07 '18 at 22:43
  • If you have access to Solaris 10, it'd be interesting to see what's in `/usr/include/sys/socket.h` there. All I have access to is OpenSolaris and Solaris 11, which all use the later, POSIX-compliant prototype. POSIX made quite a mess in this case, IMO. – Andrew Henle Feb 08 '18 at 09:17
  • Actually accès to none, I stopped being in contact with Solaris after Solaris 8. My answer required more historical dig and verifications than anything else. The Oracle documentation should be enough about the len type? It's more about the canonical way to pass a boolean for the value in (3XNET) that isn't that well defined. – A.B Feb 08 '18 at 10:03
  • 1
    I just looked into `/usr/include/sys/socket.h` on a Solaris 10 system. There are numerous variations on `#if defined(_XPG4_2)` and `#if defined(_XPG5)` in the header, changing among other things function prototypes. The OP really needs to look into how his code is compiled and linked to ensure it's all consistent. – Andrew Henle Feb 08 '18 at 14:26