1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #include <stdio.h>
  28 #include <stdlib.h>
  29 #include <unistd.h>
  30 #include <strings.h>
  31 #include <string.h>
  32 #include <dirent.h>
  33 #include <sys/types.h>
  34 #include <sys/stat.h>
  35 #include <sys/param.h>
  36 #include <sys/errno.h>
  37 #include <limits.h>
  38 #include <libnvpair.h>
  39 #include <dlfcn.h>
  40 #include <libintl.h>
  41 #include <sys/systeminfo.h>
  42 #include <sys/fs_reparse.h>
  43 #include "rp_plugin.h"
  44 
  45 #define MAXISALEN       257     /* based on sysinfo(2) man page */
  46 
  47 static rp_proto_handle_t rp_proto_handle;
  48 static rp_proto_plugin_t *rp_proto_list;
  49 
  50 int rp_plugin_init(void);
  51 static void proto_plugin_fini(void);
  52 static rp_plugin_ops_t *rp_find_protocol(const char *svctype);
  53 
  54 extern int errno;
  55 static int rp_plugin_inited = 0;
  56 
  57 /*
  58  * reparse_create()
  59  *
  60  * Create a symlink at the specified 'path' as a reparse point.
  61  * This function will fail if path refers to an existing file system
  62  * object or an object named string already exists at the given path.
  63  *
  64  * return 0 if ok else return error code.
  65  */
  66 int
  67 reparse_create(const char *path, const char *data)
  68 {
  69         int err;
  70         struct stat sbuf;
  71 
  72         if (path == NULL || data == NULL)
  73                 return (EINVAL);
  74 
  75         if ((err = reparse_validate(data)) != 0)
  76                 return (err);
  77 
  78         /* check if object exists */
  79         if (lstat(path, &sbuf) == 0)
  80                 return (EEXIST);
  81 
  82         return (symlink(data, path) ? errno : 0);
  83 }
  84 
  85 /*
  86  * reparse_unparse()
  87  *
  88  * Convert an nvlist back to a string format suitable to write
  89  * to the reparse point symlink body.  The string returned is in
  90  * allocated memory and must be freed by the caller.
  91  *
  92  * return 0 if ok else return error code.
  93  */
  94 int
  95 reparse_unparse(nvlist_t *nvl, char **stringp)
  96 {
  97         int err, buflen;
  98         char *buf, *stype, *val;
  99         nvpair_t *curr;
 100 
 101         if (nvl == NULL || stringp == NULL ||
 102             ((curr = nvlist_next_nvpair(nvl, NULL)) == NULL))
 103                 return (EINVAL);
 104 
 105         buflen = SYMLINK_MAX;
 106         if ((buf = malloc(buflen)) == NULL)
 107                 return (ENOMEM);
 108 
 109         err = 0;
 110         (void) snprintf(buf, buflen, "%s", FS_REPARSE_TAG_STR);
 111         while (curr != NULL) {
 112                 if (!(stype = nvpair_name(curr))) {
 113                         err = EINVAL;
 114                         break;
 115                 }
 116                 if ((strlcat(buf, FS_TOKEN_START_STR, buflen) >= buflen) ||
 117                     (strlcat(buf, stype, buflen) >= buflen) ||
 118                     (strlcat(buf, ":", buflen) >= buflen) ||
 119                     (nvpair_value_string(curr, &val) != 0) ||
 120                     (strlcat(buf, val, buflen) >= buflen) ||
 121                     (strlcat(buf, FS_TOKEN_END_STR, buflen) >= buflen)) {
 122                         err = E2BIG;
 123                         break;
 124                 }
 125                 curr = nvlist_next_nvpair(nvl, curr);
 126         }
 127         if (err != 0) {
 128                 free(buf);
 129                 return (err);
 130         }
 131         if (strlcat(buf, FS_REPARSE_TAG_END_STR, buflen) >= buflen) {
 132                 free(buf);
 133                 return (E2BIG);
 134         }
 135 
 136         *stringp = buf;
 137         return (0);
 138 }
 139 
 140 /*
 141  * reparse_deref()
 142  *
 143  * Accepts the service-specific item from the reparse point and returns
 144  * the service-specific data requested.  The caller specifies the size
 145  * of the buffer provided via *bufsz.
 146  *
 147  * if ok return 0 and *bufsz is updated to contain the actual length of
 148  * the returned results, else return error code. If the error code is
 149  * EOVERFLOW; results do not fit in the buffer, *bufsz will be updated
 150  * to contain the number of bytes needed to hold the results.
 151  */
 152 int
 153 reparse_deref(const char *svc_type, const char *svc_data, char *buf,
 154     size_t *bufsz)
 155 {
 156         rp_plugin_ops_t *ops;
 157 
 158         if ((svc_type == NULL) || (svc_data == NULL) || (buf == NULL) ||
 159             (bufsz == NULL))
 160                 return (EINVAL);
 161 
 162         ops = rp_find_protocol(svc_type);
 163         if ((ops != NULL) && (ops->rpo_deref != NULL))
 164                 return (ops->rpo_deref(svc_type, svc_data, buf, bufsz));
 165 
 166         /* no plugin, return error */
 167         return (ENOTSUP);
 168 }
 169 
 170 /*
 171  * reparse_delete()
 172  *
 173  * Delete a reparse point at a given pathname.  It will fail if
 174  * a reparse point does not exist at the given path or the pathname
 175  * is not a symlink.
 176  *
 177  * return 0 if ok else return error code.
 178  */
 179 int
 180 reparse_delete(const char *path)
 181 {
 182         struct stat sbuf;
 183 
 184         if (path == NULL)
 185                 return (EINVAL);
 186 
 187         /* check if object exists */
 188         if (lstat(path, &sbuf) != 0)
 189                 return (errno);
 190 
 191         if ((sbuf.st_mode & S_IFLNK) != S_IFLNK)
 192                 return (EINVAL);
 193 
 194         return (unlink(path) ? errno : 0);
 195 }
 196 
 197 /*
 198  * reparse_add()
 199  *
 200  * Add a service type entry to a nvlist with a copy of svc_data,
 201  * replacing one of the same type if already present.
 202  *
 203  * return 0 if ok else return error code.
 204  */
 205 int
 206 reparse_add(nvlist_t *nvl, const char *svc_type, const char *svc_data)
 207 {
 208         int err;
 209         char *buf;
 210         size_t bufsz;
 211         rp_plugin_ops_t *ops;
 212 
 213         if ((nvl == NULL) || (svc_type == NULL) || (svc_data == NULL))
 214                 return (EINVAL);
 215 
 216         bufsz = SYMLINK_MAX;            /* no need to mess around */
 217         if ((buf = malloc(bufsz)) == NULL)
 218                 return (ENOMEM);
 219 
 220         ops = rp_find_protocol(svc_type);
 221         if ((ops != NULL) && (ops->rpo_form != NULL))
 222                 err = ops->rpo_form(svc_type, svc_data, buf, &bufsz);
 223         else
 224                 err = ENOTSUP;          /* no plugin */
 225 
 226         if (err != 0) {
 227                 free(buf);
 228                 return (err);
 229         }
 230 
 231         err =  nvlist_add_string(nvl, svc_type, buf);
 232         free(buf);
 233         return (err);
 234 }
 235 
 236 /*
 237  * reparse_remove()
 238  *
 239  * Remove a service type entry from the nvlist, if present.
 240  *
 241  * return 0 if ok else return error code.
 242  */
 243 int
 244 reparse_remove(nvlist_t *nvl, const char *svc_type)
 245 {
 246         if ((nvl == NULL) || (svc_type == NULL))
 247                 return (EINVAL);
 248 
 249         return (nvlist_remove_all(nvl, svc_type));
 250 }
 251 
 252 /*
 253  * Returns true if name is "." or "..", otherwise returns false.
 254  */
 255 static boolean_t
 256 rp_is_dot_or_dotdot(const char *name)
 257 {
 258         if (*name != '.')
 259                 return (B_FALSE);
 260 
 261         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))
 262                 return (B_TRUE);
 263 
 264         return (B_FALSE);
 265 }
 266 
 267 static void
 268 proto_plugin_fini()
 269 {
 270         rp_proto_plugin_t *p;
 271 
 272         /*
 273          * Protocols may call this framework during _fini
 274          */
 275         for (p = rp_proto_list; p != NULL; p = p->plugin_next) {
 276                 if (p->plugin_ops->rpo_fini)
 277                         p->plugin_ops->rpo_fini();
 278         }
 279         while ((p = rp_proto_list) != NULL) {
 280                 rp_proto_list = p->plugin_next;
 281                 if (p->plugin_handle != NULL)
 282                         (void) dlclose(p->plugin_handle);
 283                 free(p);
 284         }
 285 
 286         if (rp_proto_handle.rp_ops != NULL) {
 287                 free(rp_proto_handle.rp_ops);
 288                 rp_proto_handle.rp_ops = NULL;
 289         }
 290         rp_proto_handle.rp_num_proto = 0;
 291 }
 292 
 293 /*
 294  * rp_plugin_init()
 295  *
 296  * Initialize the service type specific plugin modules.
 297  * For each reparse service type, there should be a plugin library for it.
 298  * This function walks /usr/lib/reparse directory for plugin libraries.
 299  * For each plugin library found, initialize it and add it to the internal
 300  * list of service type plugin. These are used for service type specific
 301  * operations.
 302  */
 303 int
 304 rp_plugin_init()
 305 {
 306         int err, ret = RP_OK;
 307         char isa[MAXISALEN], dirpath[MAXPATHLEN], path[MAXPATHLEN];
 308         int num_protos = 0;
 309         rp_proto_handle_t *rp_hdl;
 310         rp_proto_plugin_t *proto, *tmp;
 311         rp_plugin_ops_t *plugin_ops;
 312         struct stat st;
 313         void *dlhandle;
 314         DIR *dir;
 315         struct dirent *dent;
 316 
 317 #if defined(_LP64)
 318         if (sysinfo(SI_ARCHITECTURE_64, isa, MAXISALEN) == -1)
 319                 isa[0] = '\0';
 320 #else
 321         isa[0] = '\0';
 322 #endif
 323 
 324         (void) snprintf(dirpath, MAXPATHLEN,
 325             "%s/%s", RP_LIB_DIR, isa);
 326 
 327         if ((dir = opendir(dirpath)) == NULL)
 328                 return (RP_NO_PLUGIN_DIR);
 329 
 330         while ((dent = readdir(dir)) != NULL) {
 331                 if (rp_is_dot_or_dotdot(dent->d_name))
 332                         continue;
 333 
 334                 (void) snprintf(path, MAXPATHLEN,
 335                     "%s/%s", dirpath, dent->d_name);
 336 
 337                 /*
 338                  * If file doesn't exist, don't try to map it
 339                  */
 340                 if (stat(path, &st) < 0)
 341                         continue;
 342                 if ((dlhandle = dlopen(path, RTLD_FIRST|RTLD_LAZY)) == NULL)
 343                         continue;
 344 
 345                 plugin_ops = (rp_plugin_ops_t *)
 346                     dlsym(dlhandle, "rp_plugin_ops");
 347                 if (plugin_ops == NULL) {
 348                         (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
 349                             "Error in plugin ops for service type %s\n%s\n"),
 350                             dent->d_name, dlerror());
 351                         (void) dlclose(dlhandle);
 352                         continue;
 353                 }
 354                 proto = (rp_proto_plugin_t *)
 355                     calloc(1, sizeof (rp_proto_plugin_t));
 356                 if (proto == NULL) {
 357                         (void) dlclose(dlhandle);
 358                         (void) fprintf(stderr,
 359                             dgettext(TEXT_DOMAIN, "No memory for plugin %s\n"),
 360                             dent->d_name);
 361                         ret = RP_NO_MEMORY;
 362                         break;
 363                 }
 364 
 365                 proto->plugin_ops = plugin_ops;
 366                 proto->plugin_handle = dlhandle;
 367                 num_protos++;
 368                 proto->plugin_next = rp_proto_list;
 369                 rp_proto_list = proto;
 370         }
 371 
 372         (void) closedir(dir);
 373 
 374         if ((num_protos == 0) && (ret == 0))
 375                 ret = RP_NO_PLUGIN;
 376         /*
 377          * There was an error, so cleanup prior to return of failure.
 378          */
 379         if (ret != RP_OK) {
 380                 proto_plugin_fini();
 381                 return (ret);
 382         }
 383 
 384         rp_proto_handle.rp_ops = (rp_plugin_ops_t **)calloc(num_protos,
 385             sizeof (rp_plugin_ops_t *));
 386         if (!rp_proto_handle.rp_ops) {
 387                 proto_plugin_fini();
 388                 return (RP_NO_MEMORY);
 389         }
 390 
 391         rp_hdl = &rp_proto_handle;
 392         rp_hdl->rp_num_proto = 0;
 393         for (tmp = rp_proto_list; rp_hdl->rp_num_proto < num_protos &&
 394             tmp != NULL; tmp = tmp->plugin_next) {
 395 
 396                 err = RP_OK;
 397                 if (tmp->plugin_ops->rpo_init != NULL)
 398                         err = tmp->plugin_ops->rpo_init();
 399                 if (err != RP_OK)
 400                         continue;
 401                 rp_hdl->rp_ops[rp_hdl->rp_num_proto++] = tmp->plugin_ops;
 402         }
 403 
 404         return (rp_hdl->rp_num_proto > 0 ? RP_OK : RP_NO_PLUGIN);
 405 }
 406 
 407 
 408 /*
 409  * find_protocol()
 410  *
 411  * Search the plugin list for the specified protocol and return the
 412  * ops vector.  return NULL if protocol is not defined.
 413  */
 414 static rp_plugin_ops_t *
 415 rp_find_protocol(const char *svc_type)
 416 {
 417         int i;
 418         rp_plugin_ops_t *ops = NULL;
 419 
 420         if (svc_type == NULL)
 421                 return (NULL);
 422 
 423         if (rp_plugin_inited == 0) {
 424                 if (rp_plugin_init() == RP_OK)
 425                         rp_plugin_inited = 1;
 426                 else
 427                         return (NULL);
 428         }
 429 
 430         for (i = 0; i < rp_proto_handle.rp_num_proto; i++) {
 431                 ops = rp_proto_handle.rp_ops[i];
 432                 if (ops->rpo_supports_svc(svc_type))
 433                         return (ops);
 434 
 435         }
 436         return (NULL);
 437 }