Verified Commit 03743f6a authored by Kathleen Brade's avatar Kathleen Brade Committed by Pier Angelo Vendrame
Browse files

Bug 12647: Support symlinks in the updater.

parent c10eb721
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
# update instructions which is used to remove files and directories that are no
# longer present in a complete update. The current working directory is used for
# the location to enumerate and to create the precomplete file.
# For symlinks, remove instructions are always generated.

import io
import os
@@ -45,6 +46,9 @@ def get_build_entries(root_path):
            rel_path_dir = os.path.join(parent_dir_rel_path, dir_name)
            rel_path_dir = rel_path_dir.replace("\\", "/") + "/"
            if rel_path_dir.find("distribution/") == -1:
                if os.path.islink(rel_path_dir[:-1]):
                    rel_file_path_set.add(rel_path_dir[:-1])
                else:
                    rel_dir_path_set.add(rel_path_dir)

    rel_file_path_list = list(rel_file_path_set)
+170 −33
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@
 *  updatev3.manifest
 *  -----------------
 *  method   = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
 *             "remove" | "rmdir" | "rmrfdir" | type
 *             "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type
 *
 *  'add-if-not' adds a file if it doesn't exist.
 *
@@ -493,7 +493,8 @@ static const NS_tchar* get_relative_path(const NS_tchar* fullpath) {
 *         Whether the path is a directory path. Defaults to false.
 * @return valid filesystem path or nullptr if the path checks fail.
 */
static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false,
                                bool islinktarget = false) {
  NS_tchar* path = mstrtok(kQuote, line);
  if (!path) {
    LOG(("get_valid_path: unable to determine path: " LOG_S, *line));
@@ -529,11 +530,13 @@ static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
    path[NS_tstrlen(path) - 1] = NS_T('\0');
  }

  if (!islinktarget) {
    // Don't allow relative paths that resolve to a parent directory.
    if (NS_tstrstr(path, NS_T("..")) != nullptr) {
      LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
      return nullptr;
    }
  }

  return path;
}
@@ -572,7 +575,7 @@ static void ensure_write_permissions(const NS_tchar* path) {
  (void)_wchmod(path, _S_IREAD | _S_IWRITE);
#else
  struct stat fs;
  if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) {
  if (!lstat(path, &fs) && !S_ISLNK(fs.st_mode) && !(fs.st_mode & S_IWUSR)) {
    (void)chmod(path, fs.st_mode | S_IWUSR);
  }
#endif
@@ -759,11 +762,9 @@ static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) {
    return READ_ERROR;
  }

#  ifdef XP_UNIX
  if (S_ISLNK(ss.st_mode)) {
    return ensure_copy_symlink(path, dest);
  }
#  endif

  AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
  if (!infile) {
@@ -850,11 +851,13 @@ static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest,
    return READ_ERROR;
  }

#ifdef XP_UNIX
#ifndef XP_WIN
  if (S_ISLNK(sInfo.st_mode)) {
    return ensure_copy_symlink(path, dest);
  }
#endif

#ifdef XP_UNIX
  // Ignore Unix domain sockets. See #20691.
  if (S_ISSOCK(sInfo.st_mode)) {
    return 0;
@@ -917,7 +920,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
  }

  struct NS_tstat_t spathInfo;
  rv = NS_tstat(spath, &spathInfo);
  rv = NS_tlstat(spath, &spathInfo);  // Get info about file or symlink.
  if (rv) {
    LOG(("rename_file: failed to read file status info: " LOG_S ", "
         "err: %d",
@@ -925,7 +928,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
    return READ_ERROR;
  }

  if (!S_ISREG(spathInfo.st_mode)) {
#ifdef XP_WIN
  if (!S_ISREG(spathInfo.st_mode))
#else
  if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode))
#endif
  {
    if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
      LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
           spath, errno));
@@ -934,7 +942,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
    LOG(("rename_file: proceeding to rename the directory"));
  }

  if (!NS_taccess(dpath, F_OK)) {
#ifdef XP_WIN
  if (!NS_taccess(dpath, F_OK))
#else
  if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK))
#endif
  {
    if (ensure_remove(dpath)) {
      LOG(
          ("rename_file: destination file exists and could not be "
@@ -1053,7 +1066,19 @@ static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) {
  NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
               NS_T("%s") BACKUP_EXT, relPath);

  if (NS_taccess(backup, F_OK)) {
  bool isLink = false;
#ifndef XP_WIN
  struct stat linkInfo;
  int rv = lstat(backup, &linkInfo);
  if (rv) {
    LOG(("backup_restore: cannot get info for backup file: " LOG_S ", err: %d",
         relBackup, errno));
    return OK;
  }
  isLink = S_ISLNK(linkInfo.st_mode);
#endif

  if (!isLink && NS_taccess(backup, F_OK)) {
    LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
    return OK;
  }
@@ -1071,8 +1096,18 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
  NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
               NS_T("%s") BACKUP_EXT, relPath);

  bool isLink = false;
#ifndef XP_WIN
  struct stat linkInfo;
  int rv2 = lstat(backup, &linkInfo);
  if (rv2) {
    return OK;  // File does not exist; nothing to do.
  }
  isLink = S_ISLNK(linkInfo.st_mode);
#endif

  // Nothing to discard
  if (NS_taccess(backup, F_OK)) {
  if (!isLink && NS_taccess(backup, F_OK)) {
    return OK;
  }

@@ -1160,7 +1195,7 @@ class Action {

class RemoveFile : public Action {
 public:
  RemoveFile() : mSkip(0) {}
  RemoveFile() : mSkip(0), mIsLink(0) {}

  int Parse(NS_tchar* line) override;
  int Prepare() override;
@@ -1171,6 +1206,7 @@ class RemoveFile : public Action {
  mozilla::UniquePtr<NS_tchar[]> mFile;
  mozilla::UniquePtr<NS_tchar[]> mRelPath;
  int mSkip;
  int mIsLink;
};

int RemoveFile::Parse(NS_tchar* line) {
@@ -1193,16 +1229,26 @@ int RemoveFile::Parse(NS_tchar* line) {
}

int RemoveFile::Prepare() {
  int rv;
#ifndef XP_WIN
  struct stat linkInfo;
  rv = lstat(mFile.get(), &linkInfo);
  mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode));
#endif

  if (!mIsLink) {
    // Skip the file if it already doesn't exist.
  int rv = NS_taccess(mFile.get(), F_OK);
    rv = NS_taccess(mFile.get(), F_OK);
    if (rv) {
      mSkip = 1;
      mProgressCost = 0;
      return OK;
    }
  }

  LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));

  if (!mIsLink) {
    // Make sure that we're actually a file...
    struct NS_tstat_t fileInfo;
    rv = NS_tstat(mFile.get(), &fileInfo);
@@ -1216,6 +1262,7 @@ int RemoveFile::Prepare() {
      LOG(("path present, but not a file: " LOG_S, mFile.get()));
      return DELETE_ERROR_EXPECTED_FILE;
    }
  }

  NS_tchar* slash = (NS_tchar*)NS_tstrrchr(mFile.get(), NS_T('/'));
  if (slash) {
@@ -1966,6 +2013,92 @@ void PatchIfFile::Finish(int status) {
  PatchFile::Finish(status);
}

#ifndef XP_WIN
class AddSymlink : public Action {
 public:
  AddSymlink() : mAdded(false) {}

  virtual int Parse(NS_tchar* line);
  virtual int Prepare();
  virtual int Execute();
  virtual void Finish(int status);

 private:
  mozilla::UniquePtr<NS_tchar[]> mLinkPath;
  mozilla::UniquePtr<NS_tchar[]> mRelPath;
  mozilla::UniquePtr<NS_tchar[]> mTarget;
  bool mAdded;
};

int AddSymlink::Parse(NS_tchar* line) {
  // format "<linkname>" "target"

  NS_tchar* validPath = get_valid_path(&line);
  if (!validPath) return PARSE_ERROR;

  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
  NS_tstrcpy(mRelPath.get(), validPath);
  mLinkPath.reset(get_full_path(validPath));
  if (!mLinkPath) {
    return PARSE_ERROR;
  }

  // consume whitespace between args
  NS_tchar* q = mstrtok(kQuote, &line);
  if (!q) return PARSE_ERROR;

  validPath = get_valid_path(&line, false, true);
  if (!validPath) return PARSE_ERROR;

  mTarget = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
  NS_tstrcpy(mTarget.get(), validPath);

  return OK;
}

int AddSymlink::Prepare() {
  LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(),
       mTarget.get()));

  return OK;
}

int AddSymlink::Execute() {
  LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(),
       mTarget.get()));

  // First make sure that we can actually get rid of any existing file or link.
  struct stat linkInfo;
  int rv = lstat(mLinkPath.get(), &linkInfo);
  if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) {
    rv = NS_taccess(mLinkPath.get(), F_OK);
  }
  if (rv == 0) {
    rv = backup_create(mLinkPath.get());
    if (rv) return rv;
  } else {
    rv = ensure_parent_dir(mLinkPath.get());
    if (rv) return rv;
  }

  // Create the link.
  rv = symlink(mTarget.get(), mLinkPath.get());
  if (!rv) {
    mAdded = true;
  }

  return rv;
}

void AddSymlink::Finish(int status) {
  LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), mTarget.get()));
  // When there is an update failure and a link has been added it is removed
  // here since there might not be a backup to replace it.
  if (status && mAdded) NS_tremove(mLinkPath.get());
  backup_finish(mLinkPath.get(), mRelPath.get(), status);
}
#endif

//-----------------------------------------------------------------------------

#ifdef XP_WIN
@@ -4760,6 +4893,10 @@ int DoUpdate() {
      action = new AddIfNotFile();
    } else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) {  // Patch if exists
      action = new PatchIfFile();
#ifndef XP_WIN
    } else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) {
      action = new AddSymlink();
#endif
    } else {
      LOG(("DoUpdate: unknown token: " LOG_S, token));
      free(buf);
+25 −0
Original line number Diff line number Diff line
@@ -126,6 +126,15 @@ make_add_if_instruction() {
  echo "add-if \"$f\" \"$f\"" >> "$filev3"
}

make_addsymlink_instruction() {
  link="$1"
  target="$2"
  filev3="$3"

  verbose_notice "        addsymlink: $link -> $target"
  echo "addsymlink \"$link\" \"$target\"" >> "$filev3"
}

make_patch_instruction() {
  f="$1"
  filev3="$2"
@@ -216,3 +225,19 @@ list_dirs() {
  done < "${temp_dirlist}"
  rm "${temp_dirlist}"
}

# List all symbolic links in the current directory, stripping leading "./"
list_symlinks() {
  count=0

  find . -type l \
    | sed 's/\.\/\(.*\)/\1/' \
    | sort -r > "temp-symlinklist"
  while read symlink; do
    target=$(readlink "$symlink")
    eval "${1}[$count]=\"$symlink\""
    eval "${2}[$count]=\"$target\""
    (( count++ ))
  done < "temp-symlinklist"
  rm "temp-symlinklist"
}
+10 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ if [ ! -f "precomplete" ]; then
fi

list_files files
list_symlinks symlinks symlink_targets

popd

@@ -120,6 +121,15 @@ for ((i=0; $i<$num_files; i=$i+1)); do
  targetfiles="$targetfiles \"$f\""
done

notice ""
notice "Adding symlink add instructions to update manifests"
num_symlinks=${#symlinks[*]}
for ((i=0; $i<$num_symlinks; i=$i+1)); do
  link="${symlinks[$i]}"
  target="${symlink_targets[$i]}"
  make_addsymlink_instruction "$link" "$target" "$updatemanifestv3"
done

# Append remove instructions for any dead files.
notice ""
notice "Adding file and directory remove instructions from file 'removed-files'"
+19 −0
Original line number Diff line number Diff line
@@ -148,6 +148,7 @@ fi

list_files oldfiles
list_dirs olddirs
list_symlinks oldsymlinks oldsymlink_targets

popd

@@ -165,6 +166,7 @@ fi

list_dirs newdirs
list_files newfiles
list_symlinks newsymlinks newsymlink_targets

popd

@@ -293,6 +295,23 @@ for ((i=0; $i<$num_oldfiles; i=$i+1)); do
  fi
done

# Remove and re-add symlinks
notice ""
notice "Adding symlink remove/add instructions to update manifests"
num_oldsymlinks=${#oldsymlinks[*]}
for ((i=0; $i<$num_oldsymlinks; i=$i+1)); do
  link="${oldsymlinks[$i]}"
  verbose_notice "        remove: $link"
  echo "remove \"$link\"" >> "$updatemanifestv3"
done

num_newsymlinks=${#newsymlinks[*]}
for ((i=0; $i<$num_newsymlinks; i=$i+1)); do
  link="${newsymlinks[$i]}"
  target="${newsymlink_targets[$i]}"
  make_addsymlink_instruction "$link" "$target" "$updatemanifestv3"
done

# Newly added files
notice ""
notice "Adding file add instructions to update manifests"