diff options
author | Weston Andros Adamson <dros@netapp.com> | 2011-05-31 18:48:56 -0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2011-07-12 13:40:26 -0400 |
commit | c9895cb69b07a4b17d8fdae26667f9a9fba5183b (patch) | |
tree | 743aab596a936684948fbdb550f5bdbb96381b9c /fs/nfs/nfs4filelayoutdev.c | |
parent | 82c2c8b8616fa9e77264c53f0df483f74ac54613 (diff) |
NFS: pnfs IPv6 support
Handle ipv6 remote addresses from GETDEVICEINFO
- supports netid "tcp" for ipv4 and "tcp6" for ipv6 as rfc 5665 specifies
- added ds_remotestr to avoid having to handle different AFs in every dprintk
- tested against pynfs 4.1 server, submitting ipv6 support patch to pynfs
- tested with IPv6 disabled, it compiles cleanly and relies on rpc_pton to
refuse to accept IPv6 addresses
Signed-off-by: Weston Andros Adamson <dros@netapp.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs/nfs4filelayoutdev.c')
-rw-r--r-- | fs/nfs/nfs4filelayoutdev.c | 255 |
1 files changed, 185 insertions, 70 deletions
diff --git a/fs/nfs/nfs4filelayoutdev.c b/fs/nfs/nfs4filelayoutdev.c index 3b7bf1377264..1a8308b90108 100644 --- a/fs/nfs/nfs4filelayoutdev.c +++ b/fs/nfs/nfs4filelayoutdev.c | |||
@@ -56,28 +56,56 @@ print_ds(struct nfs4_pnfs_ds *ds) | |||
56 | printk("%s NULL device\n", __func__); | 56 | printk("%s NULL device\n", __func__); |
57 | return; | 57 | return; |
58 | } | 58 | } |
59 | printk(" ip_addr %x port %hu\n" | 59 | printk(" ds %s\n" |
60 | " ref count %d\n" | 60 | " ref count %d\n" |
61 | " client %p\n" | 61 | " client %p\n" |
62 | " cl_exchange_flags %x\n", | 62 | " cl_exchange_flags %x\n", |
63 | ntohl(ds->ds_ip_addr), ntohs(ds->ds_port), | 63 | ds->ds_remotestr, |
64 | atomic_read(&ds->ds_count), ds->ds_clp, | 64 | atomic_read(&ds->ds_count), ds->ds_clp, |
65 | ds->ds_clp ? ds->ds_clp->cl_exchange_flags : 0); | 65 | ds->ds_clp ? ds->ds_clp->cl_exchange_flags : 0); |
66 | } | 66 | } |
67 | 67 | ||
68 | /* nfs4_ds_cache_lock is held */ | 68 | /* nfs4_ds_cache_lock is held */ |
69 | static struct nfs4_pnfs_ds * | 69 | static struct nfs4_pnfs_ds * |
70 | _data_server_lookup_locked(u32 ip_addr, u32 port) | 70 | _data_server_lookup_locked(struct sockaddr *addr, size_t addrlen) |
71 | { | 71 | { |
72 | struct nfs4_pnfs_ds *ds; | 72 | struct nfs4_pnfs_ds *ds; |
73 | 73 | struct sockaddr_in *a, *b; | |
74 | dprintk("_data_server_lookup: ip_addr=%x port=%hu\n", | 74 | struct sockaddr_in6 *a6, *b6; |
75 | ntohl(ip_addr), ntohs(port)); | ||
76 | 75 | ||
77 | list_for_each_entry(ds, &nfs4_data_server_cache, ds_node) { | 76 | list_for_each_entry(ds, &nfs4_data_server_cache, ds_node) { |
78 | if (ds->ds_ip_addr == ip_addr && | 77 | if (addr->sa_family != ds->ds_addr.ss_family) |
79 | ds->ds_port == port) { | 78 | continue; |
80 | return ds; | 79 | |
80 | switch (addr->sa_family) { | ||
81 | case AF_INET: | ||
82 | a = (struct sockaddr_in *)addr; | ||
83 | b = (struct sockaddr_in *)&ds->ds_addr; | ||
84 | |||
85 | if (a->sin_addr.s_addr == b->sin_addr.s_addr && | ||
86 | a->sin_port == b->sin_port) | ||
87 | return ds; | ||
88 | break; | ||
89 | |||
90 | case AF_INET6: | ||
91 | a6 = (struct sockaddr_in6 *)addr; | ||
92 | b6 = (struct sockaddr_in6 *)&ds->ds_addr; | ||
93 | |||
94 | /* LINKLOCAL addresses must have matching scope_id */ | ||
95 | if (ipv6_addr_scope(&a6->sin6_addr) == | ||
96 | IPV6_ADDR_SCOPE_LINKLOCAL && | ||
97 | a6->sin6_scope_id != b6->sin6_scope_id) | ||
98 | continue; | ||
99 | |||
100 | if (ipv6_addr_equal(&a6->sin6_addr, &b6->sin6_addr) && | ||
101 | a6->sin6_port == b6->sin6_port) | ||
102 | return ds; | ||
103 | break; | ||
104 | |||
105 | default: | ||
106 | dprintk("%s: unhandled address family: %u\n", | ||
107 | __func__, addr->sa_family); | ||
108 | return NULL; | ||
81 | } | 109 | } |
82 | } | 110 | } |
83 | return NULL; | 111 | return NULL; |
@@ -91,19 +119,14 @@ static int | |||
91 | nfs4_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds) | 119 | nfs4_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds) |
92 | { | 120 | { |
93 | struct nfs_client *clp; | 121 | struct nfs_client *clp; |
94 | struct sockaddr_in sin; | ||
95 | int status = 0; | 122 | int status = 0; |
96 | 123 | ||
97 | dprintk("--> %s ip:port %x:%hu au_flavor %d\n", __func__, | 124 | dprintk("--> %s addr %s au_flavor %d\n", __func__, ds->ds_remotestr, |
98 | ntohl(ds->ds_ip_addr), ntohs(ds->ds_port), | ||
99 | mds_srv->nfs_client->cl_rpcclient->cl_auth->au_flavor); | 125 | mds_srv->nfs_client->cl_rpcclient->cl_auth->au_flavor); |
100 | 126 | ||
101 | sin.sin_family = AF_INET; | 127 | clp = nfs4_set_ds_client(mds_srv->nfs_client, |
102 | sin.sin_addr.s_addr = ds->ds_ip_addr; | 128 | (struct sockaddr *)&ds->ds_addr, |
103 | sin.sin_port = ds->ds_port; | 129 | ds->ds_addrlen, IPPROTO_TCP); |
104 | |||
105 | clp = nfs4_set_ds_client(mds_srv->nfs_client, (struct sockaddr *)&sin, | ||
106 | sizeof(sin), IPPROTO_TCP); | ||
107 | if (IS_ERR(clp)) { | 130 | if (IS_ERR(clp)) { |
108 | status = PTR_ERR(clp); | 131 | status = PTR_ERR(clp); |
109 | goto out; | 132 | goto out; |
@@ -115,8 +138,8 @@ nfs4_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds) | |||
115 | goto out_put; | 138 | goto out_put; |
116 | } | 139 | } |
117 | ds->ds_clp = clp; | 140 | ds->ds_clp = clp; |
118 | dprintk("%s [existing] ip=%x, port=%hu\n", __func__, | 141 | dprintk("%s [existing] server=%s\n", __func__, |
119 | ntohl(ds->ds_ip_addr), ntohs(ds->ds_port)); | 142 | ds->ds_remotestr); |
120 | goto out; | 143 | goto out; |
121 | } | 144 | } |
122 | 145 | ||
@@ -135,8 +158,7 @@ nfs4_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds) | |||
135 | goto out_put; | 158 | goto out_put; |
136 | 159 | ||
137 | ds->ds_clp = clp; | 160 | ds->ds_clp = clp; |
138 | dprintk("%s [new] ip=%x, port=%hu\n", __func__, ntohl(ds->ds_ip_addr), | 161 | dprintk("%s [new] addr: %s\n", __func__, ds->ds_remotestr); |
139 | ntohs(ds->ds_port)); | ||
140 | out: | 162 | out: |
141 | return status; | 163 | return status; |
142 | out_put: | 164 | out_put: |
@@ -153,6 +175,7 @@ destroy_ds(struct nfs4_pnfs_ds *ds) | |||
153 | 175 | ||
154 | if (ds->ds_clp) | 176 | if (ds->ds_clp) |
155 | nfs_put_client(ds->ds_clp); | 177 | nfs_put_client(ds->ds_clp); |
178 | kfree(ds->ds_remotestr); | ||
156 | kfree(ds); | 179 | kfree(ds); |
157 | } | 180 | } |
158 | 181 | ||
@@ -179,31 +202,85 @@ nfs4_fl_free_deviceid(struct nfs4_file_layout_dsaddr *dsaddr) | |||
179 | kfree(dsaddr); | 202 | kfree(dsaddr); |
180 | } | 203 | } |
181 | 204 | ||
205 | /* | ||
206 | * Create a string with a human readable address and port to avoid | ||
207 | * complicated setup around many dprinks. | ||
208 | */ | ||
209 | static char * | ||
210 | nfs4_pnfs_remotestr(struct sockaddr *ds_addr, gfp_t gfp_flags) | ||
211 | { | ||
212 | char buf[INET6_ADDRSTRLEN + IPV6_SCOPE_ID_LEN]; | ||
213 | char *remotestr; | ||
214 | char *startsep = ""; | ||
215 | char *endsep = ""; | ||
216 | size_t len; | ||
217 | uint16_t port; | ||
218 | |||
219 | switch (ds_addr->sa_family) { | ||
220 | case AF_INET: | ||
221 | port = ((struct sockaddr_in *)ds_addr)->sin_port; | ||
222 | break; | ||
223 | case AF_INET6: | ||
224 | startsep = "["; | ||
225 | endsep = "]"; | ||
226 | port = ((struct sockaddr_in6 *)ds_addr)->sin6_port; | ||
227 | break; | ||
228 | default: | ||
229 | dprintk("%s: Unknown address family %u\n", | ||
230 | __func__, ds_addr->sa_family); | ||
231 | return NULL; | ||
232 | } | ||
233 | |||
234 | if (!rpc_ntop((struct sockaddr *)ds_addr, buf, sizeof(buf))) { | ||
235 | dprintk("%s: error printing addr\n", __func__); | ||
236 | return NULL; | ||
237 | } | ||
238 | |||
239 | len = strlen(buf) + strlen(startsep) + strlen(endsep) + 1 + 5 + 1; | ||
240 | remotestr = kzalloc(len, gfp_flags); | ||
241 | |||
242 | if (unlikely(!remotestr)) { | ||
243 | dprintk("%s: couldn't alloc remotestr\n", __func__); | ||
244 | return NULL; | ||
245 | } | ||
246 | |||
247 | snprintf(remotestr, len, "%s%s%s:%u", | ||
248 | startsep, buf, endsep, ntohs(port)); | ||
249 | |||
250 | return remotestr; | ||
251 | } | ||
252 | |||
182 | static struct nfs4_pnfs_ds * | 253 | static struct nfs4_pnfs_ds * |
183 | nfs4_pnfs_ds_add(struct inode *inode, u32 ip_addr, u32 port, gfp_t gfp_flags) | 254 | nfs4_pnfs_ds_add(struct sockaddr *addr, size_t addrlen, gfp_t gfp_flags) |
184 | { | 255 | { |
185 | struct nfs4_pnfs_ds *tmp_ds, *ds; | 256 | struct nfs4_pnfs_ds *tmp_ds, *ds = NULL; |
257 | char *remotestr; | ||
186 | 258 | ||
187 | ds = kzalloc(sizeof(*tmp_ds), gfp_flags); | 259 | ds = kzalloc(sizeof(*tmp_ds), gfp_flags); |
188 | if (!ds) | 260 | if (!ds) |
189 | goto out; | 261 | goto out; |
190 | 262 | ||
263 | /* this is only used for debugging, so it's ok if its NULL */ | ||
264 | remotestr = nfs4_pnfs_remotestr(addr, gfp_flags); | ||
265 | |||
191 | spin_lock(&nfs4_ds_cache_lock); | 266 | spin_lock(&nfs4_ds_cache_lock); |
192 | tmp_ds = _data_server_lookup_locked(ip_addr, port); | 267 | tmp_ds = _data_server_lookup_locked(addr, addrlen); |
193 | if (tmp_ds == NULL) { | 268 | if (tmp_ds == NULL) { |
194 | ds->ds_ip_addr = ip_addr; | 269 | memcpy(&ds->ds_addr, addr, addrlen); |
195 | ds->ds_port = port; | 270 | ds->ds_addrlen = addrlen; |
271 | ds->ds_remotestr = remotestr; | ||
196 | atomic_set(&ds->ds_count, 1); | 272 | atomic_set(&ds->ds_count, 1); |
197 | INIT_LIST_HEAD(&ds->ds_node); | 273 | INIT_LIST_HEAD(&ds->ds_node); |
198 | ds->ds_clp = NULL; | 274 | ds->ds_clp = NULL; |
199 | list_add(&ds->ds_node, &nfs4_data_server_cache); | 275 | list_add(&ds->ds_node, &nfs4_data_server_cache); |
200 | dprintk("%s add new data server ip 0x%x\n", __func__, | 276 | dprintk("%s add new data server %s\n", __func__, |
201 | ds->ds_ip_addr); | 277 | ds->ds_remotestr); |
202 | } else { | 278 | } else { |
279 | kfree(remotestr); | ||
203 | kfree(ds); | 280 | kfree(ds); |
204 | atomic_inc(&tmp_ds->ds_count); | 281 | atomic_inc(&tmp_ds->ds_count); |
205 | dprintk("%s data server found ip 0x%x, inc'ed ds_count to %d\n", | 282 | dprintk("%s data server %s found, inc'ed ds_count to %d\n", |
206 | __func__, tmp_ds->ds_ip_addr, | 283 | __func__, tmp_ds->ds_remotestr, |
207 | atomic_read(&tmp_ds->ds_count)); | 284 | atomic_read(&tmp_ds->ds_count)); |
208 | ds = tmp_ds; | 285 | ds = tmp_ds; |
209 | } | 286 | } |
@@ -213,18 +290,21 @@ out: | |||
213 | } | 290 | } |
214 | 291 | ||
215 | /* | 292 | /* |
216 | * Currently only support ipv4, and one multi-path address. | 293 | * Currently only supports ipv4, ipv6 and one multi-path address. |
217 | */ | 294 | */ |
218 | static struct nfs4_pnfs_ds * | 295 | static struct nfs4_pnfs_ds * |
219 | decode_and_add_ds(struct xdr_stream *streamp, struct inode *inode, gfp_t gfp_flags) | 296 | decode_and_add_ds(struct xdr_stream *streamp, struct inode *inode, gfp_t gfp_flags) |
220 | { | 297 | { |
221 | struct nfs4_pnfs_ds *ds = NULL; | 298 | struct nfs4_pnfs_ds *ds = NULL; |
222 | char *buf; | 299 | char *buf, *portstr; |
223 | const char *ipend, *pstr; | 300 | struct sockaddr_storage ss; |
224 | u32 ip_addr, port; | 301 | size_t sslen; |
225 | int nlen, rlen, i; | 302 | u32 port; |
303 | int nlen, rlen; | ||
226 | int tmp[2]; | 304 | int tmp[2]; |
227 | __be32 *p; | 305 | __be32 *p; |
306 | char *netid, *match_netid; | ||
307 | size_t match_netid_len; | ||
228 | 308 | ||
229 | /* r_netid */ | 309 | /* r_netid */ |
230 | p = xdr_inline_decode(streamp, 4); | 310 | p = xdr_inline_decode(streamp, 4); |
@@ -236,62 +316,97 @@ decode_and_add_ds(struct xdr_stream *streamp, struct inode *inode, gfp_t gfp_fla | |||
236 | if (unlikely(!p)) | 316 | if (unlikely(!p)) |
237 | goto out_err; | 317 | goto out_err; |
238 | 318 | ||
239 | /* Check that netid is "tcp" */ | 319 | netid = kmalloc(nlen+1, gfp_flags); |
240 | if (nlen != 3 || memcmp((char *)p, "tcp", 3)) { | 320 | if (unlikely(!netid)) |
241 | dprintk("%s: ERROR: non ipv4 TCP r_netid\n", __func__); | ||
242 | goto out_err; | 321 | goto out_err; |
243 | } | ||
244 | 322 | ||
245 | /* r_addr */ | 323 | netid[nlen] = '\0'; |
324 | memcpy(netid, p, nlen); | ||
325 | |||
326 | /* r_addr: ip/ip6addr with port in dec octets - see RFC 5665 */ | ||
246 | p = xdr_inline_decode(streamp, 4); | 327 | p = xdr_inline_decode(streamp, 4); |
247 | if (unlikely(!p)) | 328 | if (unlikely(!p)) |
248 | goto out_err; | 329 | goto out_free_netid; |
249 | rlen = be32_to_cpup(p); | 330 | rlen = be32_to_cpup(p); |
250 | 331 | ||
251 | p = xdr_inline_decode(streamp, rlen); | 332 | p = xdr_inline_decode(streamp, rlen); |
252 | if (unlikely(!p)) | 333 | if (unlikely(!p)) |
253 | goto out_err; | 334 | goto out_free_netid; |
254 | 335 | ||
255 | /* ipv6 length plus port is legal */ | 336 | /* port is ".ABC.DEF", 8 chars max */ |
256 | if (rlen > INET6_ADDRSTRLEN + 8) { | 337 | if (rlen > INET6_ADDRSTRLEN + IPV6_SCOPE_ID_LEN + 8) { |
257 | dprintk("%s: Invalid address, length %d\n", __func__, | 338 | dprintk("%s: Invalid address, length %d\n", __func__, |
258 | rlen); | 339 | rlen); |
259 | goto out_err; | 340 | goto out_free_netid; |
260 | } | 341 | } |
261 | buf = kmalloc(rlen + 1, gfp_flags); | 342 | buf = kmalloc(rlen + 1, gfp_flags); |
262 | if (!buf) { | 343 | if (!buf) { |
263 | dprintk("%s: Not enough memory\n", __func__); | 344 | dprintk("%s: Not enough memory\n", __func__); |
264 | goto out_err; | 345 | goto out_free_netid; |
265 | } | 346 | } |
266 | buf[rlen] = '\0'; | 347 | buf[rlen] = '\0'; |
267 | memcpy(buf, p, rlen); | 348 | memcpy(buf, p, rlen); |
268 | 349 | ||
269 | /* replace the port dots with dashes for the in4_pton() delimiter*/ | 350 | /* replace port '.' with '-' */ |
270 | for (i = 0; i < 2; i++) { | 351 | portstr = strrchr(buf, '.'); |
271 | char *res = strrchr(buf, '.'); | 352 | if (!portstr) { |
272 | if (!res) { | 353 | dprintk("%s: Failed finding expected dot in port\n", |
273 | dprintk("%s: Failed finding expected dots in port\n", | 354 | __func__); |
274 | __func__); | 355 | goto out_free_buf; |
275 | goto out_free; | 356 | } |
276 | } | 357 | *portstr = '-'; |
277 | *res = '-'; | 358 | |
359 | /* find '.' between address and port */ | ||
360 | portstr = strrchr(buf, '.'); | ||
361 | if (!portstr) { | ||
362 | dprintk("%s: Failed finding expected dot between address and " | ||
363 | "port\n", __func__); | ||
364 | goto out_free_buf; | ||
278 | } | 365 | } |
366 | *portstr = '\0'; | ||
279 | 367 | ||
280 | /* Currently only support ipv4 address */ | 368 | if (!rpc_pton(buf, portstr-buf, (struct sockaddr *)&ss, sizeof(ss))) { |
281 | if (in4_pton(buf, rlen, (u8 *)&ip_addr, '-', &ipend) == 0) { | 369 | dprintk("%s: Error parsing address %s\n", __func__, buf); |
282 | dprintk("%s: Only ipv4 addresses supported\n", __func__); | 370 | goto out_free_buf; |
283 | goto out_free; | ||
284 | } | 371 | } |
285 | 372 | ||
286 | /* port */ | 373 | portstr++; |
287 | pstr = ipend; | 374 | sscanf(portstr, "%d-%d", &tmp[0], &tmp[1]); |
288 | sscanf(pstr, "-%d-%d", &tmp[0], &tmp[1]); | ||
289 | port = htons((tmp[0] << 8) | (tmp[1])); | 375 | port = htons((tmp[0] << 8) | (tmp[1])); |
290 | 376 | ||
291 | ds = nfs4_pnfs_ds_add(inode, ip_addr, port, gfp_flags); | 377 | switch (ss.ss_family) { |
292 | dprintk("%s: Decoded address and port %s\n", __func__, buf); | 378 | case AF_INET: |
293 | out_free: | 379 | ((struct sockaddr_in *)&ss)->sin_port = port; |
380 | sslen = sizeof(struct sockaddr_in); | ||
381 | match_netid = "tcp"; | ||
382 | match_netid_len = 3; | ||
383 | break; | ||
384 | |||
385 | case AF_INET6: | ||
386 | ((struct sockaddr_in6 *)&ss)->sin6_port = port; | ||
387 | sslen = sizeof(struct sockaddr_in6); | ||
388 | match_netid = "tcp6"; | ||
389 | match_netid_len = 4; | ||
390 | break; | ||
391 | |||
392 | default: | ||
393 | dprintk("%s: unsupported address family: %u\n", | ||
394 | __func__, ss.ss_family); | ||
395 | goto out_free_buf; | ||
396 | } | ||
397 | |||
398 | if (nlen != match_netid_len || strncmp(netid, match_netid, nlen)) { | ||
399 | dprintk("%s: ERROR: r_netid \"%s\" != \"%s\"\n", | ||
400 | __func__, netid, match_netid); | ||
401 | goto out_free_buf; | ||
402 | } | ||
403 | |||
404 | ds = nfs4_pnfs_ds_add((struct sockaddr *)&ss, sslen, gfp_flags); | ||
405 | dprintk("%s: Added DS %s\n", __func__, ds->ds_remotestr); | ||
406 | out_free_buf: | ||
294 | kfree(buf); | 407 | kfree(buf); |
408 | out_free_netid: | ||
409 | kfree(netid); | ||
295 | out_err: | 410 | out_err: |
296 | return ds; | 411 | return ds; |
297 | } | 412 | } |
@@ -591,13 +706,13 @@ nfs4_fl_select_ds_fh(struct pnfs_layout_segment *lseg, u32 j) | |||
591 | 706 | ||
592 | static void | 707 | static void |
593 | filelayout_mark_devid_negative(struct nfs4_file_layout_dsaddr *dsaddr, | 708 | filelayout_mark_devid_negative(struct nfs4_file_layout_dsaddr *dsaddr, |
594 | int err, u32 ds_addr) | 709 | int err, const char *ds_remotestr) |
595 | { | 710 | { |
596 | u32 *p = (u32 *)&dsaddr->id_node.deviceid; | 711 | u32 *p = (u32 *)&dsaddr->id_node.deviceid; |
597 | 712 | ||
598 | printk(KERN_ERR "NFS: data server %x connection error %d." | 713 | printk(KERN_ERR "NFS: data server %s connection error %d." |
599 | " Deviceid [%x%x%x%x] marked out of use.\n", | 714 | " Deviceid [%x%x%x%x] marked out of use.\n", |
600 | ds_addr, err, p[0], p[1], p[2], p[3]); | 715 | ds_remotestr, err, p[0], p[1], p[2], p[3]); |
601 | 716 | ||
602 | spin_lock(&nfs4_ds_cache_lock); | 717 | spin_lock(&nfs4_ds_cache_lock); |
603 | dsaddr->flags |= NFS4_DEVICE_ID_NEG_ENTRY; | 718 | dsaddr->flags |= NFS4_DEVICE_ID_NEG_ENTRY; |
@@ -628,7 +743,7 @@ nfs4_fl_prepare_ds(struct pnfs_layout_segment *lseg, u32 ds_idx) | |||
628 | err = nfs4_ds_connect(s, ds); | 743 | err = nfs4_ds_connect(s, ds); |
629 | if (err) { | 744 | if (err) { |
630 | filelayout_mark_devid_negative(dsaddr, err, | 745 | filelayout_mark_devid_negative(dsaddr, err, |
631 | ntohl(ds->ds_ip_addr)); | 746 | ds->ds_remotestr); |
632 | return NULL; | 747 | return NULL; |
633 | } | 748 | } |
634 | } | 749 | } |