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