Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_irqbalance_client.h"
3 : #include <errno.h>
4 : #include <dirent.h>
5 : #include <sys/socket.h>
6 : #include <sys/types.h>
7 : #include <sys/un.h>
8 : #include <unistd.h>
9 :
10 : #define IRQBALANCE_RUN_DIR "/run/irqbalance"
11 :
12 : static int
13 0 : fd_irqbalance_connect1( char const * path ) {
14 0 : int sock = socket( AF_UNIX, SOCK_STREAM, 0 );
15 0 : if( FD_UNLIKELY( sock<0 ) ) FD_LOG_ERR(( "socket(AF_UNIX,SOCK_STREAM) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
16 :
17 0 : int err;
18 :
19 0 : if( FD_UNLIKELY( setsockopt( sock, SOL_SOCKET, SO_PASSCRED, &(int){1}, sizeof(int) )<0 ) ) {
20 0 : FD_LOG_ERR(( "setsockopt(SO_PASSCRED) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
21 0 : }
22 :
23 0 : struct sockaddr_un addr;
24 0 : fd_memset( &addr, 0, sizeof(addr) );
25 0 : addr.sun_family = AF_UNIX;
26 0 : if( FD_UNLIKELY( strlen( path ) >= sizeof(addr.sun_path) ) ) {
27 0 : err = ENOENT;
28 0 : goto cleanup;
29 0 : }
30 0 : strcpy( addr.sun_path, path );
31 :
32 0 : if( FD_UNLIKELY( connect( sock, fd_type_pun( &addr ), sizeof(addr) )<0 ) ) {
33 0 : err = errno;
34 0 : if( FD_UNLIKELY( err!=ENOENT && err!=ECONNREFUSED && err!=EACCES ) ) {
35 0 : FD_LOG_ERR(( "connect(`%s`) failed (%i-%s)", path, err, fd_io_strerror( err ) ));
36 0 : }
37 0 : goto cleanup;
38 0 : }
39 :
40 0 : return sock;
41 :
42 0 : cleanup:
43 0 : if( FD_UNLIKELY( 0!=close( sock ) ) ) {
44 0 : FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
45 0 : }
46 0 : errno = err;
47 0 : return -1;
48 0 : }
49 :
50 : static int
51 0 : fd_irqbalance_connect( void ) {
52 : /* Unfortunately the irqbalance daemon does not use a fixed file path */
53 0 : char path[ PATH_MAX ];
54 0 : DIR * dir = opendir( "/run/irqbalance" );
55 0 : if( FD_UNLIKELY( !dir ) ) {
56 0 : if( FD_UNLIKELY( errno!=ENOENT && errno!=EACCES ) ) {
57 0 : FD_LOG_ERR(( "opendir(`%s`) failed (%i-%s)", IRQBALANCE_RUN_DIR, errno, fd_io_strerror( errno ) ));
58 0 : }
59 0 : return -1;
60 0 : }
61 0 : struct dirent * entry;
62 0 : int sock = -1;
63 0 : int err = ENOENT;
64 0 : while( (entry = readdir( dir )) ) {
65 0 : if( FD_UNLIKELY( entry->d_type!=DT_SOCK ) ) continue;
66 0 : if( FD_UNLIKELY( strncmp( entry->d_name, "irqbalance", 10 ) ) ) continue;
67 0 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "%s/%s", IRQBALANCE_RUN_DIR, entry->d_name ) );
68 0 : sock = fd_irqbalance_connect1( path );
69 0 : if( FD_UNLIKELY( sock<0 ) ) {
70 0 : err = errno;
71 0 : if( FD_UNLIKELY( errno==EACCES ) ) break;
72 0 : continue;
73 0 : }
74 0 : break;
75 0 : }
76 0 : if( FD_UNLIKELY( closedir( dir ) ) ) {
77 0 : FD_LOG_ERR(( "closedir() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
78 0 : }
79 0 : if( FD_UNLIKELY( sock<0 ) ) errno = err;
80 0 : return sock;
81 0 : }
82 :
83 : static int
84 : fd_irqbalance_send( int fd,
85 : void const * req,
86 0 : ulong req_len ) {
87 0 : struct iovec iov = {
88 0 : .iov_base = (void *)req,
89 0 : .iov_len = req_len
90 0 : };
91 :
92 0 : union {
93 0 : struct cmsghdr hdr;
94 0 : uchar buf[ CMSG_SPACE( sizeof(struct ucred) ) ];
95 0 : } ctrl;
96 0 : fd_memset( &ctrl, 0, sizeof(ctrl) );
97 :
98 0 : struct msghdr msg;
99 0 : fd_memset( &msg, 0, sizeof(msg) );
100 0 : msg.msg_iov = &iov;
101 0 : msg.msg_iovlen = 1U;
102 0 : msg.msg_control = ctrl.buf;
103 0 : msg.msg_controllen = sizeof(ctrl.buf);
104 :
105 0 : struct cmsghdr * cmsg = CMSG_FIRSTHDR( &msg );
106 0 : cmsg->cmsg_level = SOL_SOCKET;
107 0 : cmsg->cmsg_type = SCM_CREDENTIALS;
108 0 : cmsg->cmsg_len = CMSG_LEN( sizeof(struct ucred) );
109 :
110 0 : struct ucred cred = {
111 0 : .pid = getpid(),
112 0 : .uid = geteuid(),
113 0 : .gid = getegid()
114 0 : };
115 0 : fd_memcpy( CMSG_DATA( cmsg ), &cred, sizeof(cred) );
116 :
117 0 : return sendmsg( fd, &msg, MSG_NOSIGNAL )==(long)req_len;
118 0 : }
119 :
120 : static int
121 : fd_irqbalance_request( void const * req,
122 : ulong req_len,
123 : char * resp,
124 0 : ulong resp_sz ) {
125 :
126 0 : if( FD_LIKELY( resp && resp_sz ) ) resp[0] = '\0';
127 :
128 0 : int fd = fd_irqbalance_connect();
129 0 : if( FD_UNLIKELY( fd<0 ) ) return -1;
130 :
131 0 : if( FD_UNLIKELY( !fd_irqbalance_send( fd, req, req_len ) ) ) {
132 0 : FD_LOG_ERR(( "failed to send command to irqbalance (%i-%s)", errno, fd_io_strerror( errno ) ));
133 0 : }
134 0 : if( FD_UNLIKELY( 0!=shutdown( fd, SHUT_WR ) ) ) {
135 0 : FD_LOG_ERR(( "shutdown(irqbalance_sock_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
136 0 : }
137 0 : if( FD_LIKELY( resp ) ) {
138 0 : ulong off = 0UL;
139 0 : for(;;) {
140 0 : long n = recv( fd, resp+off, resp_sz-off-1UL, 0 );
141 0 : if( FD_UNLIKELY( n<0 ) ) {
142 0 : if( FD_UNLIKELY( errno==EINTR ) ) continue;
143 0 : FD_LOG_ERR(( "failed to recv response from irqbalance (%i-%s)", errno, fd_io_strerror( errno ) ));
144 0 : }
145 0 : if( !n ) break; /* EOF */
146 0 : off += (ulong)n;
147 0 : if( FD_UNLIKELY( off>=resp_sz-1UL ) ) {
148 0 : FD_LOG_ERR(( "response from irqbalance is too large for the receive buffer (>=%lu)", resp_sz ));
149 0 : }
150 0 : }
151 0 : if( FD_UNLIKELY( !off ) ) {
152 : /* The daemon accepted the connection but closed it without replying.
153 : It does this when it rejects our SCM_CREDENTIALS (non-root), so
154 : treat an empty response as the expected permission error. */
155 0 : if( FD_UNLIKELY( 0!=close( fd ) ) ) {
156 0 : FD_LOG_ERR(( "close(irqbalance_sock_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
157 0 : }
158 0 : errno = EACCES;
159 0 : return -1;
160 0 : }
161 0 : resp[ off ] = '\0';
162 0 : }
163 :
164 0 : if( FD_UNLIKELY( 0!=close( fd ) ) ) {
165 0 : FD_LOG_ERR(( "close(irqbalance_sock_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
166 0 : }
167 0 : return 0;
168 0 : }
169 :
170 : int
171 0 : fd_irqbalance_ban_cpus_set( fd_cpuset_t const * cpuset ) {
172 : /* 1024x (FD_TILE_MAX) comma-separated 4 char strings is approx 5kB */
173 0 : char req[ 16384 ];
174 0 : char * p = fd_cstr_init( req );
175 0 : p = fd_cstr_append_cstr( p, "settings cpus " );
176 0 : int first = 1;
177 0 : for( ulong iter = fd_cpuset_const_iter_init( cpuset );
178 0 : !fd_cpuset_const_iter_done( iter );
179 0 : iter = fd_cpuset_const_iter_next( cpuset, iter ) ) {
180 0 : if( FD_LIKELY( !first ) ) p = fd_cstr_append_char( p, ',' );
181 0 : else first = 0;
182 0 : if( FD_UNLIKELY( p-req+32 >= (long)sizeof(req) ) ) break; /* unreachable */
183 0 : p = fd_cstr_append_ulong_as_text( p, 0, 0, iter, fd_ulong_base10_dig_cnt( iter ) );
184 0 : }
185 0 : if( FD_UNLIKELY( first ) ) p = fd_cstr_append_cstr( p, "NULL" );
186 0 : ulong len = (ulong)( p - req );
187 0 : fd_cstr_fini( p );
188 :
189 0 : return fd_irqbalance_request( req, len, NULL, 0UL );
190 0 : }
191 :
192 : static int
193 0 : fd_irqbalance_hex_digit( char c ) {
194 0 : if( FD_LIKELY( (c>='0') & (c<='9') ) ) return c-'0';
195 0 : if( FD_LIKELY( (c>='a') & (c<='f') ) ) return 10+c-'a';
196 0 : if( FD_LIKELY( (c>='A') & (c<='F') ) ) return 10+c-'A';
197 0 : return -1;
198 0 : }
199 :
200 : int
201 0 : fd_irqbalance_ban_cpus_get( fd_cpuset_t * cpuset ) {
202 0 : char resp[ 16384 ];
203 0 : fd_cpuset_new( cpuset );
204 :
205 0 : int err = fd_irqbalance_request( "setup", 5UL, resp, sizeof(resp) );
206 0 : if( FD_UNLIKELY( -1==err ) ) return -1;
207 :
208 0 : char * banned = strstr( resp, "BANNED " );
209 0 : if( FD_UNLIKELY( !banned ) ) {
210 0 : FD_LOG_ERR(( "failed to parse irqbalance setup response: missing BANNED token" ));
211 0 : }
212 :
213 0 : banned += 7UL;
214 0 : char * end = banned;
215 0 : while( FD_LIKELY( *end && (*end!=' ') ) ) end++;
216 :
217 0 : ulong cpu = 0UL;
218 0 : for( char * p=end; p>banned; ) {
219 0 : p--;
220 0 : if( FD_UNLIKELY( *p==',' ) ) continue;
221 :
222 0 : int digit = fd_irqbalance_hex_digit( *p );
223 0 : if( FD_UNLIKELY( digit<0 ) ) {
224 0 : FD_LOG_ERR(( "failed to parse irqbalance banned CPU mask" ));
225 0 : }
226 :
227 0 : for( ulong bit=0UL; bit<4UL; bit++ ) {
228 0 : if( FD_UNLIKELY( cpu>=FD_TILE_MAX ) ) {
229 0 : FD_LOG_ERR(( "irqbalance banned CPU mask exceeds FD_TILE_MAX (%lu) CPUs", (ulong)FD_TILE_MAX ));
230 0 : }
231 0 : if( FD_UNLIKELY( digit & (1<<bit) ) ) fd_cpuset_insert( cpuset, cpu );
232 0 : cpu++;
233 0 : }
234 0 : }
235 :
236 0 : return 0;
237 0 : }
|