+/* Check if a certain feature is supported by e2fsprogs.
+ * Firstly we try to use "debugfs supported_features" command to check if
+ * the feature is supported. If this fails we try to set this feature with
+ * mke2fs to check for its support. */
+static int is_e2fsprogs_feature_supp(const char *feature)
+{
+ static char supp_features[4096] = "";
+ FILE *fp;
+ char cmd[PATH_MAX];
+ char imgname[] = "/tmp/test-img-XXXXXX";
+ int fd = -1;
+ int ret = 1;
+
+ if (supp_features[0] == '\0') {
+ snprintf(cmd, sizeof(cmd), "%s -c -R supported_features 2>&1",
+ DEBUGFS);
+
+ /* Using popen() instead of run_command() since debugfs does
+ * not return proper error code if command is not supported */
+ fp = popen(cmd, "r");
+ if (!fp) {
+ fprintf(stderr, "%s: %s\n", progname, strerror(errno));
+ return 0;
+ }
+ ret = fread(supp_features, 1, sizeof(supp_features), fp);
+ fclose(fp);
+ }
+ if (ret > 0 && strstr(supp_features,
+ strncmp(feature, "-O ", 3) ? feature : feature+3))
+ return 0;
+
+ if ((fd = mkstemp(imgname)) < 0)
+ return -1;
+ else
+ close(fd);
+
+ snprintf(cmd, sizeof(cmd), "%s -F %s %s 100 >/dev/null 2>&1",
+ MKE2FS, feature, imgname);
+ /* run_command() displays the output of mke2fs when it fails for
+ * some feature, so use system() directly */
+ ret = system(cmd);
+ unlink(imgname);
+
+ return ret;
+}
+
+/**
+ * append_unique: append @key or @key=@val pair to @buf only if @key does not
+ * exists
+ * @buf: buffer to hold @key or @key=@val
+ * @prefix: prefix string before @key
+ * @key: key string
+ * @val: value string if it's a @key=@val pair
+ */
+static void append_unique(char *buf, char *prefix, char *key, char *val,
+ size_t maxbuflen)
+{
+ char *anchor, *end;
+ int len;
+
+ if (key == NULL)
+ return;
+
+ anchor = end = strstr(buf, key);
+ /* try to find exact match string in @buf */
+ while (end && *end != '\0' && *end != ',' && *end != ' ' && *end != '=')
+ ++end;
+ len = end - anchor;
+ if (anchor == NULL || strlen(key) != len ||
+ strncmp(anchor, key, len) != 0) {
+ if (prefix != NULL)
+ strscat(buf, prefix, maxbuflen);
+
+ strscat(buf, key, maxbuflen);
+ if (val != NULL) {
+ strscat(buf, "=", maxbuflen);
+ strscat(buf, val, maxbuflen);
+ }
+ }
+}
+
+static void enable_default_ext4_features(struct mkfs_opts *mop, char *anchor,
+ size_t maxbuflen, int user_spec)
+{
+ if (IS_OST(&mop->mo_ldd)) {
+ append_unique(anchor, user_spec ? "," : " -O ",
+ "extents", NULL, sizeof(mop->mo_mkfsopts));
+ append_unique(anchor, ",", "uninit_bg", NULL, maxbuflen);
+ } else if (IS_MDT(&mop->mo_ldd)) {
+ append_unique(anchor, user_spec ? "," : " -O ",
+ "dirdata", NULL, maxbuflen);
+ append_unique(anchor, ",", "uninit_bg", NULL, maxbuflen);
+ append_unique(anchor, ",", "^extents", NULL, maxbuflen);
+ } else {
+ append_unique(anchor, user_spec ? "," : " -O ",
+ "uninit_bg", NULL, maxbuflen);
+ }
+
+ /* Multiple mount protection enabled only if failover node specified */
+ if (failover) {
+ if (is_e2fsprogs_feature_supp("-O mmp") == 0)
+ append_unique(anchor, ",", "mmp", NULL, maxbuflen);
+ else
+ disp_old_e2fsprogs_msg("mmp", 1);
+ }
+
+ /* Allow more than 65000 subdirectories */
+ if (is_e2fsprogs_feature_supp("-O dir_nlink") == 0)
+ append_unique(anchor, ",", "dir_nlink", NULL, maxbuflen);
+
+#ifdef HAVE_EXT4_LDISKFS
+ /* The following options are only valid for ext4-based ldiskfs.
+ * If --backfstype=ext3 is specified, do not enable them. */
+ if (mop->mo_ldd.ldd_mount_type == LDD_MT_EXT3)
+ return;
+
+ /* Allow files larger than 2TB. Also needs LU-16, but not harmful. */
+ if (is_e2fsprogs_feature_supp("-O huge_file") == 0)
+ append_unique(anchor, ",", "huge_file", NULL, maxbuflen);
+
+ /* Enable large block addresses if the LUN is over 2^32 blocks. */
+ if (mop->mo_device_sz / (L_BLOCK_SIZE >> 10) >= 0x100002000ULL &&
+ is_e2fsprogs_feature_supp("-O 64bit") == 0)
+ append_unique(anchor, ",", "64bit", NULL, maxbuflen);
+
+ /* Cluster inode/block bitmaps and inode table for more efficient IO.
+ * Align the flex groups on a 1MB boundary for better performance. */
+ /* This -O feature needs to go last, since it adds the "-G" option. */
+ if (is_e2fsprogs_feature_supp("-O flex_bg") == 0) {
+ char tmp_buf[64];
+
+ append_unique(anchor, ",", "flex_bg", NULL, maxbuflen);
+
+ if (IS_OST(&mop->mo_ldd)) {
+ snprintf(tmp_buf, sizeof(tmp_buf), " -G %u",
+ (1 << 20) / L_BLOCK_SIZE);
+ strscat(anchor, tmp_buf, maxbuflen);
+ }
+ }
+ /* Don't add any more "-O" options here, see last comment above */
+#endif
+}
+
+/**
+ * moveopts_to_end: find the option string, move remaining strings to
+ * where option string starts, and append the option
+ * string at the end
+ * @start: where the option string starts before the move
+ * RETURN: where the option string starts after the move
+ */
+static char *moveopts_to_end(char *start)
+{
+ char save[512];
+ char *end, *idx;
+
+ /* skip whitespace before options */
+ end = start + 2;
+ while (*end == ' ')
+ ++end;
+
+ /* find end of option characters */
+ while (*end != ' ' && *end != '\0')
+ ++end;
+
+ /* save options */
+ strncpy(save, start, end - start);
+ save[end - start] = '\0';
+
+ /* move remaining options up front */
+ if (*end)
+ memmove(start, end, strlen(end));
+ *(start + strlen(end)) = '\0';
+
+ /* append the specified options */
+ if (*(start + strlen(start) - 1) != ' ')
+ strcat(start, " ");
+ idx = start + strlen(start);
+ strcat(start, save);
+
+ return idx;
+}
+