001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *      Copyright 2015 ForgeRock AS
024 */
025package org.opends.server.backends.pluggable.spi;
026
027import static org.opends.messages.BackendMessages.*;
028import static org.opends.messages.ConfigMessages.*;
029import static org.opends.server.util.StaticUtils.*;
030
031import java.io.File;
032
033import org.forgerock.i18n.LocalizableMessage;
034import org.forgerock.opendj.config.server.ConfigChangeResult;
035import org.forgerock.opendj.config.server.ConfigException;
036import org.opends.server.core.DirectoryServer;
037import org.opends.server.types.DN;
038import org.opends.server.types.FilePermission;
039
040/** Utility class for implementations of {@link Storage}. */
041public final class StorageUtils
042{
043  private StorageUtils()
044  {
045    // do not instantiate utility classes
046  }
047
048  /**
049   * Returns a database directory file from the provided parent database directory and backendId.
050   *
051   * @param parentDbDirectory the parent database directory
052   * @param backendId the backend id
053   * @return a database directory file where to store data for the provided backendId
054   */
055  public static File getDBDirectory(String parentDbDirectory, String backendId)
056  {
057    return new File(getFileForPath(parentDbDirectory), backendId);
058  }
059
060  /**
061   * Ensure backendDir exists (creating it if not) and has the specified dbDirPermissions.
062   *
063   * @param backendDir the backend directory where to set the storage files
064   * @param dbDirPermissions the permissions to set for the database directory
065   * @param configDN the backend configuration DN
066   * @throws ConfigException if configuration fails
067   */
068  public static void setupStorageFiles(File backendDir, String dbDirPermissions, DN configDN) throws ConfigException
069  {
070    ConfigChangeResult ccr = new ConfigChangeResult();
071
072    checkDBDirExistsOrCanCreate(backendDir, ccr, false);
073    if (!ccr.getMessages().isEmpty())
074    {
075      throw new ConfigException(ccr.getMessages().get(0));
076    }
077    checkDBDirPermissions(dbDirPermissions, configDN, ccr);
078    if (!ccr.getMessages().isEmpty())
079    {
080      throw new ConfigException(ccr.getMessages().get(0));
081    }
082    setDBDirPermissions(backendDir, dbDirPermissions, configDN, ccr);
083    if (!ccr.getMessages().isEmpty())
084    {
085      throw new ConfigException(ccr.getMessages().get(0));
086    }
087  }
088
089  /**
090   * Checks a directory exists or can actually be created.
091   *
092   * @param backendDir the directory to check for
093   * @param ccr the list of reasons to return upstream or null if called from setupStorage()
094   * @param cleanup true if the directory should be deleted after creation
095   */
096  public static void checkDBDirExistsOrCanCreate(File backendDir, ConfigChangeResult ccr, boolean cleanup)
097  {
098    if (!backendDir.exists())
099    {
100      if (!backendDir.mkdirs())
101      {
102        addErrorMessage(ccr, ERR_CREATE_FAIL.get(backendDir.getPath()));
103      }
104      if (cleanup)
105      {
106        backendDir.delete();
107      }
108    }
109    else if (!backendDir.isDirectory())
110    {
111      addErrorMessage(ccr, ERR_DIRECTORY_INVALID.get(backendDir.getPath()));
112    }
113  }
114
115  /**
116   * Returns false if directory permissions in the configuration are invalid.
117   * Otherwise returns the same value as it was passed in.
118   *
119   * @param dbDirPermissions the permissions to set for the database directory
120   * @param configDN the backend configuration DN
121   * @param ccr the current list of change results
122   */
123  public static void checkDBDirPermissions(String dbDirPermissions, DN configDN, ConfigChangeResult ccr)
124  {
125    try
126    {
127      FilePermission backendPermission = decodeDBDirPermissions(dbDirPermissions, configDN);
128      // Make sure the mode will allow the server itself access to the database
129      if (!backendPermission.isOwnerWritable()
130          || !backendPermission.isOwnerReadable()
131          || !backendPermission.isOwnerExecutable())
132      {
133        addErrorMessage(ccr, ERR_CONFIG_BACKEND_INSANE_MODE.get(dbDirPermissions));
134      }
135    }
136    catch (ConfigException ce)
137    {
138      addErrorMessage(ccr, ce.getMessageObject());
139    }
140  }
141
142  /**
143   * Sets files permissions on the backend directory.
144   *
145   * @param backendDir the directory to setup
146   * @param dbDirPermissions the permissions to set for the database directory
147   * @param configDN the backend configuration DN
148   * @param ccr the current list of change results
149   * @throws ConfigException if configuration fails
150   */
151  public static void setDBDirPermissions(File backendDir, String dbDirPermissions, DN configDN, ConfigChangeResult ccr)
152      throws ConfigException
153  {
154    try
155    {
156      FilePermission backendPermission = decodeDBDirPermissions(dbDirPermissions, configDN);
157      if (!FilePermission.setPermissions(backendDir, backendPermission))
158      {
159        addErrorMessage(ccr, WARN_UNABLE_SET_PERMISSIONS.get(backendPermission, backendDir));
160      }
161    }
162    catch (Exception e)
163    {
164      addErrorMessage(ccr, WARN_SET_PERMISSIONS_FAILED.get(backendDir, stackTraceToSingleLineString(e)));
165    }
166  }
167
168  private static FilePermission decodeDBDirPermissions(String dbDirPermissions, DN configDN) throws ConfigException
169  {
170    try
171    {
172      return FilePermission.decodeUNIXMode(dbDirPermissions);
173    }
174    catch (Exception e)
175    {
176      throw new ConfigException(ERR_CONFIG_BACKEND_MODE_INVALID.get(configDN));
177    }
178  }
179
180  /**
181   * Adds the provided message to the provided config change result.
182   *
183   * @param ccr the config change result
184   * @param message the message to add
185   */
186  public static void addErrorMessage(ConfigChangeResult ccr, LocalizableMessage message)
187  {
188    ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
189    ccr.addMessage(message);
190  }
191
192  /**
193   * Removes the storage files from the provided backend directory.
194   *
195   * @param backendDir
196   *          the backend directory where to remove storage files
197   */
198  public static void removeStorageFiles(File backendDir)
199  {
200    if (!backendDir.exists())
201    {
202      return;
203    }
204    if (!backendDir.isDirectory())
205    {
206      throw new StorageRuntimeException(ERR_DIRECTORY_INVALID.get(backendDir.getPath()).toString());
207    }
208
209    try
210    {
211      File[] files = backendDir.listFiles();
212      for (File f : files)
213      {
214        f.delete();
215      }
216    }
217    catch (Exception e)
218    {
219      throw new StorageRuntimeException(ERR_REMOVE_FAIL.get(e.getMessage()).toString(), e);
220    }
221  }
222
223  /**
224   * Creates a new unusable {@link StorageStatus} for the disk full threshold.
225   *
226   * @param directory the directory which reached the disk full threshold
227   * @param thresholdInBytes the threshold in bytes
228   * @param backendId the backend id
229   * @return a new unusable {@link StorageStatus}
230   */
231  public static StorageStatus statusWhenDiskSpaceFull(File directory, long thresholdInBytes, String backendId)
232  {
233    return StorageStatus.unusable(WARN_DISK_SPACE_FULL_THRESHOLD_CROSSED.get(
234        directory.getFreeSpace(), directory.getAbsolutePath(), thresholdInBytes, backendId));
235  }
236
237  /**
238   * Creates a new locked down {@link StorageStatus} for the disk low threshold.
239   *
240   * @param directory the directory which reached the disk low threshold
241   * @param thresholdInBytes the threshold in bytes
242   * @param backendId the backend id
243   * @return a new locked down {@link StorageStatus}
244   */
245  public static StorageStatus statusWhenDiskSpaceLow(File directory, long thresholdInBytes, String backendId)
246  {
247    return StorageStatus.lockedDown(WARN_DISK_SPACE_LOW_THRESHOLD_CROSSED.get(
248        directory.getFreeSpace(), directory.getAbsolutePath(), thresholdInBytes, backendId));
249  }
250}