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  * Copyright (c) 2012 STRATO AG. All rights reserved.
  23  */
  24 #include <sys/zfs_context.h>
  25 #include <sys/errno.h>
  26 #include <sys/stat.h>
  27 #include <sys/fits.h>
  28 #include <sys/fits_impl.h>
  29 
  30 struct fits_enum {
  31         fits_t *fe_fits;
  32         uint64_t fe_parent_ino;
  33         fits_dirent_t *fe_dirent_chain;
  34 };
  35 
  36 struct fits_file {
  37         fits_t *ff_fits;
  38         uint64_t ff_len;
  39         uint64_t ff_last_byte;
  40         uint64_t ff_ino;
  41         fits_path_t *ff_path;
  42         fits_dirent_t *ff_dirent;
  43 };
  44 
  45 static int fits_file_data_pass1(void *fits_filep, void *data, uint64_t off,
  46     uint64_t len);
  47 static int fits_dirent_add_pass1(void *fits_enump, char *name, uint64_t ino);
  48 static int fits_dirent_mod_pass1(void *fits_enump, char *name,
  49     uint64_t ino_old, uint64_t ino_new);
  50 static int fits_dir_add_pass1(fits_t *f, uint64_t ino);
  51 static int fits_mod_pass1(fits_t *f, uint64_t ino);
  52 
  53 static fits_ops_t _ops = {
  54         .fits_dir_add = fits_dir_add_pass1,
  55         .fits_dir_mod = fits_mod_pass1,
  56         .fits_dirent_add = fits_dirent_add_pass1,
  57         .fits_dirent_mod = fits_dirent_mod_pass1,
  58         .fits_file_mod = fits_mod_pass1,
  59         .fits_file_data = fits_file_data_pass1
  60 };
  61 
  62 static int fits_file_add_genchange(fits_t *f, uint64_t ino);
  63 
  64 int
  65 fits_start(fits_t *f, fits_ops_t **ops)
  66 {
  67         int ret;
  68 
  69         f->f_pass = PASS_LINK;
  70         f->f_link_add_cnt.fc_head = NULL;
  71         f->f_link_add_cnt.fc_name = "link_add_cnt";
  72         f->f_del_dir_cnt.fc_head = NULL;
  73         f->f_del_dir_cnt.fc_name = "del_dir_cnt";
  74         f->f_put_back_cnt.fc_head = NULL;
  75         f->f_put_back_cnt.fc_name = "put_back_cnt";
  76         fits_send_init(f);
  77 
  78         *ops = &_ops;
  79 
  80         ret = fits_send_start(f);
  81         if (ret) {
  82                 fits_abort(f);
  83                 return (ret);
  84         }
  85 
  86         return (0);
  87 }
  88 
  89 static int
  90 enum_dir(fits_t *f, uint64_t ino, fits_dirent_t *chain)
  91 {
  92         struct fits_enum fe = {
  93                 .fe_fits = f,
  94                 .fe_parent_ino = ino,
  95                 .fe_dirent_chain = chain
  96         };
  97 
  98         return (fits_dir_contents(f, ino, &fe));
  99 }
 100 
 101 static int
 102 fits_file_data_pass1(void *fits_filep, void *data, uint64_t off, uint64_t len)
 103 {
 104         struct fits_file *ff = fits_filep;
 105 
 106         if (off + len > ff->ff_len)
 107                 len = ff->ff_len - off;
 108 
 109         ff->ff_last_byte = off + len;
 110 
 111         return fits_send_file_data(ff->ff_fits, &ff->ff_path,
 112             ff->ff_dirent, ff->ff_ino, off, len, data);
 113 }
 114 
 115 static int
 116 dirent_add_dir(fits_t *f, fits_dirent_t *dirent, uint64_t ino, int exists)
 117 {
 118         fits_info_t si_old;
 119         fits_info_t si_new;
 120         int ret;
 121 
 122         ret = fits_get_info(f, ino, FITS_OLD, &si_old,
 123                             FI_ATTR_PARENT | FI_ATTR_GEN | FI_ATTR_MODE);
 124         if (ret && ret != ENOENT)
 125                 return (ret);
 126 
 127         if (ret == 0) {
 128                 ret = fits_get_info(f, ino, FITS_NEW, &si_new,
 129                                     FI_ATTR_GEN | FI_ATTR_MODE);
 130                 if (ret)
 131                         return (ret);
 132 
 133                 if (si_old.si_gen == si_new.si_gen ||
 134                     (S_ISDIR(si_new.si_mode) && S_ISDIR(si_old.si_mode)))
 135                         return fits_send_rename(f, dirent, ino,
 136                                                 si_old.si_parent, exists);
 137                 return (0);
 138         }
 139 
 140         if (ino > f->f_current_ino)
 141                 return (0);
 142 
 143         /* dir is new */
 144         ret = fits_send_mkdir(f, dirent, ino, exists);
 145         if (ret)
 146                 return (ret);
 147 
 148         return (enum_dir(f, ino, dirent));
 149 }
 150 
 151 int
 152 fits_dirent_add_file(fits_t *f, fits_dirent_t *dirent,
 153     uint64_t ino, uint64_t mode, int exists)
 154 {
 155         fits_info_t si_old;
 156         fits_info_t si_new;
 157         int ret;
 158         fits_path_t *fits_path;
 159         uint64_t new_count;
 160         uint64_t old_aux;
 161 
 162         ret = fits_get_info(f, ino, FITS_OLD, &si_old,
 163                             FI_ATTR_GEN | FI_ATTR_PARENT);
 164         if (ret && ret != ENOENT)
 165                 return (ret);
 166 
 167         if (ret == 0) {
 168                 ret = fits_get_info(f, ino, FITS_NEW, &si_new,
 169                                     FI_ATTR_GEN);
 170                 if (ret)
 171                         return (ret);
 172 
 173                 if (si_old.si_gen == si_new.si_gen)
 174                         return fits_send_link(f, dirent, ino, si_old.si_parent,
 175                             FITS_OLD, exists);
 176         }
 177 
 178         /* file is new */
 179         ret = fits_add_count(&f->f_link_add_cnt, ino, 1, dirent->fd_parent_ino,
 180             &new_count, &old_aux);
 181         if (ret)
 182                 return (ret);
 183 
 184         if (new_count == 1)
 185                 ret = fits_send_create_file(f, dirent, ino,
 186                                             exists, &fits_path);
 187         else
 188                 ret = fits_send_link(f, dirent, ino, old_aux, FITS_NEW, exists);
 189         if (ret)
 190                 return (ret);
 191 
 192         ret = fits_get_info(f, ino, FITS_NEW, &si_new,
 193                             FI_ATTR_SIZE | FI_ATTR_LINKS);
 194         ASSERT(ret == 0);
 195         if (new_count == 1 && S_ISREG(mode)) {
 196                 struct fits_file ff;
 197 
 198                 ff.ff_ino = ino;
 199                 ff.ff_len = si_new.si_size;
 200                 ff.ff_fits = f;
 201                 ff.ff_path = fits_path;
 202                 ff.ff_dirent = dirent;
 203                 ff.ff_last_byte = 0;
 204                 ret = fits_file_contents(f, ino, &ff);
 205                 fits_path_free(ff.ff_path);
 206                 if (ret)
 207                         return (ret);
 208                 if (ff.ff_last_byte != si_new.si_size) {
 209                         /* sparse end */
 210                         ret = fits_send_truncate(f, NULL, ino, si_new.si_size);
 211                         if (ret)
 212                                 return (ret);
 213                 }
 214         }
 215         if (new_count == si_new.si_nlinks)
 216                 fits_free_count(&f->f_link_add_cnt, ino);
 217 
 218         return (0);
 219 }
 220 
 221 static int
 222 dirent_add(fits_t *f, fits_dirent_t *dirent, uint64_t ino, int exists)
 223 {
 224         fits_info_t si;
 225         int ret;
 226 
 227         ret = fits_get_info(f, ino, FITS_NEW, &si, FI_ATTR_MODE);
 228         if (ret)
 229                 return (ret);
 230 
 231         if (S_ISDIR(si.si_mode)) {
 232                 return (dirent_add_dir(f, dirent, ino, exists));
 233         } else {
 234                 return (fits_dirent_add_file(f, dirent, ino, si.si_mode,
 235                     exists));
 236         }
 237 }
 238 
 239 static int
 240 fits_dirent_add_pass1(void *fits_enump, char *name, uint64_t ino)
 241 {
 242         struct fits_enum *fe = fits_enump;
 243         fits_dirent_t dirent = {
 244                 .fd_name = name,
 245                 .fd_parent_ino = fe->fe_parent_ino,
 246                 .fd_prev = fe->fe_dirent_chain,
 247         };
 248 
 249         return (dirent_add(fe->fe_fits, &dirent, ino, 0));
 250 }
 251 
 252 static int
 253 fits_dirent_mod_pass1(void *fits_enump, char *name,
 254     uint64_t ino_old, uint64_t ino_new)
 255 {
 256         struct fits_enum *fe = fits_enump;
 257         fits_dirent_t dirent = {
 258                 .fd_name = name,
 259                 .fd_parent_ino = fe->fe_parent_ino,
 260                 .fd_prev = fe->fe_dirent_chain,
 261         };
 262 
 263         return (dirent_add(fe->fe_fits, &dirent, ino_new, 1));
 264 }
 265 
 266 static int
 267 fits_file_add_genchange(fits_t *f, uint64_t ino)
 268 {
 269         int ret;
 270         char *name = NULL;
 271 
 272         f->f_current_ino = ino;
 273         f->f_current_path = NULL;
 274 
 275         /*
 276          * only called when generation has changed. TODO: move to own
 277          * function
 278          */
 279         fits_info_t si_old;
 280         fits_info_t si_new;
 281         int same_name = 0;
 282 
 283         ret = fits_get_info(f, ino, FITS_OLD, &si_old, FI_ATTR_MODE |
 284                             FI_ATTR_LINKS | FI_ATTR_PARENT);
 285         if (ret)
 286                 return (ret);
 287         ret = fits_get_info(f, ino, FITS_NEW, &si_new, FI_ATTR_MODE |
 288                             FI_ATTR_LINKS | FI_ATTR_PARENT);
 289         if (ret)
 290                 return (ret);
 291 
 292         if (si_old.si_nlinks > 1 && si_new.si_nlinks > 1)
 293                 return fits_add_count(&f->f_link_add_cnt, ino, 0, 0, NULL,
 294                     NULL);
 295 
 296         if (S_ISDIR(si_old.si_mode))
 297                 return (0);
 298 
 299         fits_which_t from;
 300         fits_which_t to;
 301         uint64_t new_ino;
 302         uint64_t parent = si_new.si_parent;
 303 
 304         if (si_old.si_nlinks == 1) {
 305                 from = FITS_OLD;
 306                 to = FITS_NEW;
 307                 parent = si_old.si_parent;
 308         } else if (si_old.si_nlinks > 1 && si_new.si_nlinks == 1) {
 309                 from = FITS_NEW;
 310                 to = FITS_OLD;
 311                 parent = si_new.si_parent;
 312         } else {
 313                 return (EINVAL);
 314         }
 315 
 316         ret = fits_find_entry(f, parent, ino, from, &name);
 317         if (ret)
 318                 return (ret);
 319 
 320         ret = fits_lookup_entry(f, parent, name, to, &new_ino);
 321         if (ret && ret != ENOENT)
 322                 goto out;
 323         if (ret == 0 && new_ino == ino)
 324                 same_name = 1;
 325 
 326         if ((si_old.si_nlinks == 1 || si_new.si_nlinks == 1) && !same_name) {
 327                 ret = 0;
 328                 goto out;
 329         }
 330 
 331         fits_dirent_t dirent = {
 332                 .fd_parent_ino = parent,
 333                 .fd_name = name,
 334                 .fd_prev = NULL
 335         };
 336 
 337         ret = fits_send_unlink(f, &dirent, ino);
 338         if (ret)
 339                 goto out;
 340         ret = fits_dirent_add_file(f, &dirent, ino, si_new.si_mode, 0);
 341 
 342 out:
 343         fits_free_name(name);
 344         return (ret);
 345 }
 346 
 347 static int
 348 fits_dir_add_pass1(fits_t *f, uint64_t ino)
 349 {
 350         int ret;
 351         uint64_t parent;
 352         uint64_t first_parent = FITS_NO_INO;
 353         fits_info_t si;
 354         fits_dirent_t dirent;
 355         int same_name = 0;
 356         char *name = NULL;
 357 
 358         f->f_current_ino = ino;
 359         f->f_current_path = NULL;
 360 
 361         parent = ino;
 362         while (1) {
 363                 /* the new parent must exist, otherwise the fs is wrong */
 364                 ret = fits_get_info(f, parent, FITS_NEW, &si,
 365                                     FI_ATTR_PARENT);
 366                 if (ret)
 367                         return (ret);
 368                 if (first_parent == FITS_NO_INO)
 369                         first_parent = si.si_parent;
 370 
 371                 ret = fits_get_info(f, parent, FITS_OLD, &si, 0);
 372                 if (ret && ret != ENOENT)
 373                         return (ret);
 374                 if (ret != ENOENT)
 375                         break;
 376 
 377                 /*
 378                  * this check is only needed for a full send, on all
 379                  * incrementals the parent already exists and it breaks out
 380                  * above
 381                  */
 382                 if (parent == si.si_parent) {
 383                         first_parent = FITS_NO_INO;
 384                         break;
 385                 }
 386                 parent = si.si_parent;
 387 
 388                 if (parent > ino)
 389                         return (0);
 390         }
 391 
 392         /*
 393          * check for same-name
 394          */
 395         if (first_parent != FITS_NO_INO) {
 396                 ret = fits_get_info(f, first_parent, FITS_OLD, &si,
 397                     FI_ATTR_MODE);
 398                 if (ret && ret != ENOENT)
 399                         return (ret);
 400                 if (ret == 0 && S_ISDIR(si.si_mode)) {
 401                         uint64_t old_ino;
 402 
 403                         ret = fits_find_entry(f, first_parent, ino,
 404                             FITS_NEW, &name);
 405                         if (ret)
 406                                 return (ret);
 407 
 408                         ret = fits_lookup_entry(f, first_parent, name,
 409                             FITS_OLD, &old_ino);
 410                         if (ret && ret != ENOENT) {
 411                                 goto out;
 412                         }
 413                         if (ret == 0) {
 414                                 same_name = 1;
 415                                 dirent.fd_name = name;
 416                                 dirent.fd_parent_ino = first_parent;
 417                                 dirent.fd_prev = NULL;
 418                                 if (old_ino == ino) {
 419                                         ret = fits_add_count(&f->f_link_add_cnt,
 420                                             ino, 0, 0, NULL, NULL);
 421                                         if (ret)
 422                                                 goto out;
 423                                 }
 424                         }
 425                 }
 426         }
 427         /* dir is new */
 428         ret = fits_send_mkdir(f, same_name ? &dirent : NULL, ino, same_name);
 429         if (ret)
 430                 goto out;
 431 
 432         ret = enum_dir(f, ino, NULL);
 433 
 434 out:
 435         fits_free_name(name);
 436         return (ret);
 437 }
 438 
 439 static int
 440 fits_mod_pass1(fits_t *f, uint64_t ino)
 441 {
 442         fits_info_t si_old;
 443         fits_info_t si_new;
 444         int ret;
 445 
 446         f->f_current_ino = ino;
 447         f->f_current_path = NULL;
 448 
 449         ret = fits_get_info(f, ino, FITS_NEW, &si_new, FI_ATTR_SIZE |
 450                             FI_ATTR_MODE | FI_ATTR_GEN | FI_ATTR_UID |
 451                             FI_ATTR_GID | FI_ATTR_SIZE);
 452         if (ret)
 453                 return (ret);
 454         ret = fits_get_info(f, ino, FITS_OLD, &si_old, FI_ATTR_GEN |
 455                             FI_ATTR_MODE | FI_ATTR_UID |
 456                             FI_ATTR_GID | FI_ATTR_SIZE);
 457         if (ret)
 458                 return (ret);
 459 
 460         if (!(S_ISDIR(si_old.si_mode) && S_ISDIR(si_new.si_mode)) &&
 461             si_new.si_gen != si_old.si_gen) {
 462                 if (S_ISDIR(si_new.si_mode))
 463                         return (fits_dir_add_pass1(f, ino));
 464                 else
 465                         return (fits_file_add_genchange(f, ino));
 466         }
 467 
 468         if (S_ISDIR(si_new.si_mode)) {
 469                 ret = enum_dir(f, ino, NULL);
 470                 if (ret)
 471                         return (ret);
 472         }
 473 
 474         if (S_ISREG(si_new.si_mode)) {
 475                 struct fits_file ff;
 476                 ff.ff_ino = ino;
 477                 ff.ff_len = si_new.si_size;
 478                 ff.ff_fits = f;
 479                 ff.ff_path = NULL;
 480                 ff.ff_dirent = NULL;
 481                 ff.ff_last_byte = 0;
 482 
 483                 ret = fits_file_contents(f, ino, &ff);
 484                 fits_path_free(ff.ff_path);
 485                 if (ret)
 486                         return (ret);
 487                 if (si_new.si_size < si_old.si_size ||
 488                     (si_new.si_size != si_old.si_size &&
 489                     si_new.si_size != ff.ff_last_byte)) {
 490                         ret = fits_send_truncate(f, NULL, ino, si_new.si_size);
 491                         if (ret)
 492                                 return (ret);
 493                 }
 494         }
 495 
 496         if (si_old.si_uid != si_new.si_uid || si_old.si_gid != si_new.si_gid) {
 497                 ret = fits_send_chown(f, NULL, ino, si_new.si_uid,
 498                     si_new.si_gid);
 499                 if (ret)
 500                         return (ret);
 501         }
 502         if (si_old.si_mode != si_new.si_mode) {
 503                 ret = fits_send_chmod(f, NULL, ino, si_new.si_mode);
 504                 if (ret)
 505                         return (ret);
 506         }
 507 
 508         return (ret);
 509 }