Conversation
|
Thanks for your pull request, @epi! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Some tips to help speed things up:
Bear in mind that large or tricky changes may require multiple rounds of review and revision. Please see CONTRIBUTING.md for more information. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. |
CyberShadow
left a comment
There was a problem hiding this comment.
Good idea, however the implementation seems to be somewhat confused.
It seems like the change seems to go halfway to making UnixAddress just store the address as a char[] with a header, which also seems to align with the actual representation and usage of UnixAddress. This is not how typical C programs deal with UNIX addresses (and other types of addresses), as there the address types are forced to be self-contained value types, which is not the case for std.socket.
So, why not go all the way and make UnixAddress just manage a header of sockaddr_un.sun_path.offsetof bytes plus an arbitrarily long (and containing arbitrary bytes) path? Something very similar was already done for SocketSet, which is not limited to FD_SETSIZE like the C equivalent.
std/socket.d
Outdated
| sun.sun_path.ptr[0 .. path.length] = (cast(byte[]) path)[]; | ||
| sun.sun_path.ptr[path.length] = 0; | ||
| _nameLen = cast(socklen_t) (sockaddr_un.init.sun_path.offsetof + | ||
| path.length + (sun.sun_path.ptr[0] ? 1 : 0)); |
There was a problem hiding this comment.
Wouldn't the null byte be already included in path, and thus, path.length?
There was a problem hiding this comment.
For pathname addresses, the terminating null is of course not included in path, and the manual recommends following these rules:
- The pathname in sun_path should be null-terminated.
- The length of the pathname, including the terminating null byte, should not exceed the size of sun_path.
- The addrlen argument that describes the enclosing sockaddr_un structure should have a value of at least:
offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path)+1
or, more simply, addrlen can be specified as sizeof(struct sockaddr_un).
For abstract addresses, the null byte is the initial one and it is included in the path. The length of the address in this case is sockaddr_un.init.sun_path.offsetof + path.length.
There was a problem hiding this comment.
Alright, so I understand that unlike regular UNIX sockets, abstract sockets' paths do not need to be null-terminated (and in fact doing so would change the address).
Please add a short comment describing the length calculation done here, as in your message above. A DDoc comment on the constructor mentioning how to create an abstract UNIX socket address would also be nice.
| return (cast(const(char)*) sun.sun_path.ptr)[0 .. | ||
| _nameLen - sockaddr_un.init.sun_path.offsetof - | ||
| (sun.sun_path.ptr[0] ? 1 : 0)].idup; | ||
| } |
There was a problem hiding this comment.
Why exclude the null byte from the result? It was present in the constructor, so excluding it here would be inconsistent. Also, without it, it seems like it's no longer possible to know whether a particular UnixAddress represents an abstract path or not.
There was a problem hiding this comment.
The excluded byte is the null byte terminating a pathname address (added in the constructor to make it a C string).
For abstract address (sun.sun_path.ptr[0] == 0), nothing is excluded.
There was a problem hiding this comment.
I see, thanks for the explanation. This should be added as a comment. I think it would be better to split the length calculation in multiple statements, so it's possible to explain each step.
A DDoc comment mentioning that filesystem paths are returned without the implicit null terminator but Linux abstract socket paths are returned as-is would be nice.
| if (len > this.nameLen) | ||
| throw new SocketParameterException("Not enough socket address storage"); | ||
| } | ||
|
|
There was a problem hiding this comment.
I don't understand the point of this method. The base Address class does not own the storage for the address data, so where the address itself is stored or how it deals with its storage/length is up to the descendant classes.
Setting the length for an address of fixed length (e.g. IP addresses) is nonsensical, so I don't see why this should be part of the abstract Address interface.
Furthermore, it seems to break the expectation that invoking a property's setter with some value should either throw or cause invoking the getter right afterwards to return the same value.
There was a problem hiding this comment.
The reasoning behind adding this setter was:
There are methods in Socket (remoteAddress, localAddress, receiveFrom), which do the following:
- call
Socket.createAddress, getting an instance of one ofAddress' descendants, created with argument-less constructor. - call
Address.nameandAddress.nameLengetters on that instance to get the address and capacity of the buffer for the next call - call one of
getsockname,getpeername,recvfrom, to set the name in the buffer just obtained. - read
Address.nameLenagain and compare with result of previous call to see if the buffer was large enough (receiveFromdoesn't do this step)
While the above procedure works well for fixed-length addresses, it doesn't provide a way to feed the actual length of the address back to the concrete Address. I had to add a way that doesn't change the public interface nor the behavior of the above methods for fixed-length addresses, and does not require adding a special case for UnixAddress in all of them.
The nameLen setter is protected and deliberately not documented with DDoc. The default implementation in base Address is intended to be sufficient for the typical case: fixed-length addresses.
Yes, there is one case where it breaks the expectation you mentioned: where the len argument passed is less than the fixed-length. I should add a check for this.
Another solution I had in mind was to remove the argument-less constructors, get the address from getsockname, getpeername and receiveFrom to a temporary buffer on the stack, and refactor createAddress to create concrete Addresses by passing that temporary buffer to the appropriate constructor.
There was a problem hiding this comment.
Alright. So, abstract UNIX socket addresses require that the address length is not greater than the length of the actual abstract "path", because they allow embedded null bytes and there is no way to infer the address length like the other address types we support so far. For this, I think a regular method would be more appropriate (for the reasons I mentioned above).
Also, it would be great if you could summarize the above into comments, as all this may not be obvious to the next person looking at this code.
Gotcha. If you look at the commit list on this PR, it says that the latest commit is 6f247e6. However, if you try to fetch the PR branch ( It looks like 6f247e6 has the changelog entry, so it seems GitHub's caching is broken and is serving the wrong commit on the pull/ refs. |
|
Sent a message to support. CC @wilzbach :) |
|
As for the fail in DAutoTest, the reason could be that I added the changelog entry, committed with --amend, and then pushed with --force. As for storing arbitrarily long arrays of bytes in [1] http://lxr.linux.no/linux+v4.10.1/include/uapi/linux/socket.h#L7 |
That is a great argument against that idea.
|
That's quite a typical action and we are seeing this error message on other PRs , so I don't think this is related to anything you did (or did not) ;-) |
|
I'll just note that my first try was with [1] http://golang-examples.tumblr.com/post/92025745979/pitfall-of-abstract-unix-domain-socket-address-in |
CyberShadow
left a comment
There was a problem hiding this comment.
Thanks for the explanations; this looks pretty great, just needs some clarifications in the code itself. An example for creating abstract UNIX socket addresses in the documentation would also be nice.
| if (len > this.nameLen) | ||
| throw new SocketParameterException("Not enough socket address storage"); | ||
| } | ||
|
|
There was a problem hiding this comment.
Alright. So, abstract UNIX socket addresses require that the address length is not greater than the length of the actual abstract "path", because they allow embedded null bytes and there is no way to infer the address length like the other address types we support so far. For this, I think a regular method would be more appropriate (for the reasons I mentioned above).
Also, it would be great if you could summarize the above into comments, as all this may not be obvious to the next person looking at this code.
std/socket.d
Outdated
| sun.sun_path.ptr[0 .. path.length] = (cast(byte[]) path)[]; | ||
| sun.sun_path.ptr[path.length] = 0; | ||
| _nameLen = cast(socklen_t) (sockaddr_un.init.sun_path.offsetof + | ||
| path.length + (sun.sun_path.ptr[0] ? 1 : 0)); |
There was a problem hiding this comment.
Alright, so I understand that unlike regular UNIX sockets, abstract sockets' paths do not need to be null-terminated (and in fact doing so would change the address).
Please add a short comment describing the length calculation done here, as in your message above. A DDoc comment on the constructor mentioning how to create an abstract UNIX socket address would also be nice.
| return (cast(const(char)*) sun.sun_path.ptr)[0 .. | ||
| _nameLen - sockaddr_un.init.sun_path.offsetof - | ||
| (sun.sun_path.ptr[0] ? 1 : 0)].idup; | ||
| } |
There was a problem hiding this comment.
I see, thanks for the explanation. This should be added as a comment. I think it would be better to split the length calculation in multiple statements, so it's possible to explain each step.
A DDoc comment mentioning that filesystem paths are returned without the implicit null terminator but Linux abstract socket paths are returned as-is would be nice.
|
Thanks for the detailed review, I'll follow up with the requested changes shortly. |
|
What's wrong with std.concurrency tests? |
Looks like a random failure.
Works for me. You may need to |
That shouldn't be necessary - please let me know if the nuke helped. |
Okay it was necessary. Sorry. Fix -> dlang/dlang.org#1837 |
|
Thanks, I can now see my changelog entry. |
|
Is there anything else I should improve in this PR? |
|
I don't think so. Thanks for your great work on this PR. Please send more! :) |
Lack of support for abstract UNIX domain sockets was discussed in forums [1], but I couldn't find a related issue in bugzilla.
According to [2]: "abstract: an abstract socket address is distinguished (from a pathname socket) by the fact that sun_path[0] is a null byte ('\0'). The socket's address in this namespace is given by the additional bytes in sun_path that are covered by the specified length of the address structure. (Null bytes in the name have no special significance.)"
For this reason, the previous implementation using strlen() was not sufficient, and additional class field containing the length of the address was required.
[1] http://forum.dlang.org/post/mmk50s$1qm$1@digitalmars.com
[2] UNIX(7)