fs_config: Add fs_config_generate

fs_config_generate_$(TARGET_DEVICE) is built based off the content
of $(TARGET_ANDROID_FILESYSTEM_CONFIG_H). We also add the rules
fs_config_dirs and fs_config_file to utilize this command
for target contents:

fs_config_generate_$(TARGET_DEVICE) -D -o system/etc/fs_config_dir
fs_config_generate_$(TARGET_DEVICE) -F -o system/etc/fs_config_file

In order to use this feature, one must have the fs_config_dirs and
fs_config_files in the $(PRODUCT_PACKAGES) list defined in the
device make files in $(TARGET_DEVICE_DIR). And either an
android_filesystem_config.h file in that directory, or define a
path in TARGET_ANDROID_FILESYSTEM_CONFIG_H to point to one.

Bug: 19908228
Change-Id: Iee1543d99169f874e0915ae07962a7750ecb6342
diff --git a/tools/fs_config/Android.mk b/tools/fs_config/Android.mk
index 3e16962..34a3522 100644
--- a/tools/fs_config/Android.mk
+++ b/tools/fs_config/Android.mk
@@ -22,3 +22,64 @@
 LOCAL_CFLAGS := -Werror
 
 include $(BUILD_HOST_EXECUTABLE)
+
+# To Build the custom target binary for the host to generate the fs_config
+# override files. The executable is hard coded to include the
+# $(TARGET_ANDROID_FILESYSTEM_CONFIG_H) file if it exists.
+# Expectations:
+#    device/<vendor>/<device>/android_filesystem_config.h
+#        fills in struct fs_path_config android_device_dirs[] and
+#                 struct fs_path_config android_device_files[]
+#    device/<vendor>/<device>/device.mk
+#        PRODUCT_PACKAGES += fs_config_dirs fs_config_files
+
+# If not specified, check if default one to be found
+ANDROID_FS_CONFIG_H := android_filesystem_config.h
+
+ifneq ($(TARGET_ANDROID_FILESYSTEM_CONFIG_H),)
+ifeq ($(filter %/$(ANDROID_FS_CONFIG_H),$(TARGET_ANDROID_FILESYSTEM_CONFIG_H)),)
+$(error TARGET_ANDROID_FILESYSTEM_CONFIG_H file name must be $(ANDROID_FS_CONFIG_H), \
+ see "$(notdir $(TARGET_ANDROID_FILESYSTEM_CONFIG_H))".)
+endif
+
+my_fs_config_h := $(TARGET_ANDROID_FILESYSTEM_CONFIG_H)
+else ifneq ($(wildcard $(TARGET_DEVICE_DIR)/$(ANDROID_FS_CONFIG_H)),)
+my_fs_config_h := $(TARGET_DEVICE_DIR)/$(ANDROID_FS_CONFIG_H)
+else
+my_fs_config_h := $(LOCAL_PATH)/default/$(ANDROID_FS_CONFIG_H)
+endif
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := fs_config_generate.c
+LOCAL_MODULE := fs_config_generate_$(TARGET_DEVICE)
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_CFLAGS := -Werror -Wno-error=\#warnings
+LOCAL_C_INCLUDES := $(dir $(my_fs_config_h))
+include $(BUILD_HOST_EXECUTABLE)
+fs_config_generate_bin := $(LOCAL_INSTALLED_MODULE)
+
+# Generate the system/etc/fs_config_dirs binary file for the target
+# Add fs_config_dirs to PRODUCT_PACKAGES in the device make file to enable
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := fs_config_dirs
+LOCAL_MODULE_CLASS := ETC
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(fs_config_generate_bin)
+	@mkdir -p $(dir $@)
+	$< -D -o $@
+
+# Generate the system/etc/fs_config_files binary file for the target
+# Add fs_config_files to PRODUCT_PACKAGES in the device make file to enable
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := fs_config_files
+LOCAL_MODULE_CLASS := ETC
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(fs_config_generate_bin)
+	@mkdir -p $(dir $@)
+	$< -F -o $@
+
+ANDROID_FS_CONFIG_H :=
+my_fs_config_h :=
+fs_config_generate_bin :=
diff --git a/tools/fs_config/default/android_filesystem_config.h b/tools/fs_config/default/android_filesystem_config.h
new file mode 100644
index 0000000..820b04a
--- /dev/null
+++ b/tools/fs_config/default/android_filesystem_config.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This file is used to enhance the properties of the filesystem
+** images generated by build tools (mkbootfs and mkyaffs2image) and
+** by the device side of adb.
+*/
+
+/*
+** Resorting to the default file means someone requested fs_config_dirs or
+** fs_config_files in their device configuration without providing an
+** associated header.
+*/
+#warning No device-supplied android_filesystem_config.h, using empty default.
+
+/* Rules for directories.
+** These rules are applied based on "first match", so they
+** should start with the most specific path and work their
+** way up to the root.
+*/
+
+#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS 1 /* opt out of specifying */
+
+/* Rules for files.
+** These rules are applied based on "first match", so they
+** should start with the most specific path and work their
+** way up to the root. Prefixes ending in * denotes wildcard
+** and will allow partial matches.
+*/
+
+#define NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES 1 /* opt out of specifying */
diff --git a/tools/fs_config/fs_config_generate.c b/tools/fs_config/fs_config_generate.c
new file mode 100644
index 0000000..87769c7
--- /dev/null
+++ b/tools/fs_config/fs_config_generate.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <private/android_filesystem_config.h>
+
+/*
+ * This program expects android_device_dirs and android_device_files
+ * to be defined in the supplied android_filesystem_config.h file in
+ * the device/<vendor>/<product> $(TARGET_DEVICE_DIR). Then generates
+ * the binary format used in the /system/etc/fs_config_dirs and
+ * the /system/etc/fs_config_files to be used by the runtimes.
+ */
+#include "android_filesystem_config.h"
+
+#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
+  static const struct fs_path_config android_device_dirs[] = {
+};
+#endif
+
+#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_FILES
+static const struct fs_path_config android_device_files[] = {
+#ifdef NO_ANDROID_FILESYSTEM_CONFIG_DEVICE_DIRS
+    { 0, AID_ROOT, AID_ROOT, 0, "system/etc/fs_config_dirs" },
+#endif
+    { 0, AID_ROOT, AID_ROOT, 0, "system/etc/fs_config_files" },
+};
+#endif
+
+static void usage() {
+  fprintf(stderr,
+    "Generate binary content for fs_config_dirs (-D) and fs_config_files (-F)\n"
+    "from device-specific android_filesystem_config.h override\n\n"
+    "Usage: fs_config_generate -D|-F [-o output-file]\n");
+}
+
+int main(int argc, char** argv) {
+  const struct fs_path_config *pc;
+  const struct fs_path_config *end;
+  bool dir = false, file = false;
+  FILE *fp = stdout;
+  int opt;
+
+  while((opt = getopt(argc, argv, "DFho:")) != -1) {
+    switch(opt) {
+    case 'D':
+      if (file) {
+        fprintf(stderr, "Must specify only -D or -F\n");
+        usage();
+        exit(EXIT_FAILURE);
+      }
+      dir = true;
+      break;
+    case 'F':
+      if (dir) {
+        fprintf(stderr, "Must specify only -F or -D\n");
+        usage();
+        exit(EXIT_FAILURE);
+      }
+      file = true;
+      break;
+    case 'o':
+      if (fp != stdout) {
+        fprintf(stderr, "Specify only one output file\n");
+        usage();
+        exit(EXIT_FAILURE);
+      }
+      fp = fopen(optarg, "w");
+      if (fp == NULL) {
+        fprintf(stderr, "Can not open \"%s\"\n", optarg);
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'h':
+      usage();
+      exit(EXIT_SUCCESS);
+    default:
+      usage();
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (!file && !dir) {
+    fprintf(stderr, "Must specify either -F or -D\n");
+    usage();
+    exit(EXIT_FAILURE);
+  }
+
+  if (dir) {
+    pc = android_device_dirs;
+    end = &android_device_dirs[sizeof(android_device_dirs) / sizeof(android_device_dirs[0])];
+  } else {
+    pc = android_device_files;
+    end = &android_device_files[sizeof(android_device_files) / sizeof(android_device_files[0])];
+  }
+  for(; (pc < end) && pc->prefix; pc++) {
+    char buffer[512];
+    ssize_t len = fs_config_generate(buffer, sizeof(buffer), pc);
+    if (len < 0) {
+      fprintf(stderr, "Entry too large\n");
+      exit(EXIT_FAILURE);
+    }
+    if (fwrite(buffer, 1, len, fp) != (size_t)len) {
+      fprintf(stderr, "Write failure\n");
+      exit(EXIT_FAILURE);
+    }
+  }
+  fclose(fp);
+
+  return 0;
+}