+);
+
+enum {
+ PARSE_FILENAME_SUCCESS = 0,
+ PARSE_FILENAME_FAILURE = 1,
+ PARSE_FILENAME_NONE = 2,
+};
+
+/*
+ * Parses a filename in the source list file format. (See the man page for
+ * 'wimlib-imagex capture' for details on this format and the meaning.)
+ * Accepted formats for filenames are an unquoted string (whitespace-delimited),
+ * or a double or single-quoted string.
+ *
+ * @line_p: Pointer to the pointer to the line of data. Will be updated
+ * to point past the filename iff the return value is
+ * PARSE_FILENAME_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
+ * be '\0'.
+ *
+ * @len_p: @len_p initially stores the length of the line of data, which may
+ * be 0, and it will be updated to the number of bytes remaining in
+ * the line iff the return value is PARSE_FILENAME_SUCCESS.
+ *
+ * @fn_ret: Iff the return value is PARSE_FILENAME_SUCCESS, a pointer to the
+ * parsed filename will be returned here.
+ *
+ * Returns: PARSE_FILENAME_SUCCESS if a filename was successfully parsed; or
+ * PARSE_FILENAME_FAILURE if the data was invalid due to a missing
+ * closing quote; or PARSE_FILENAME_NONE if the line ended before the
+ * beginning of a filename was found.
+ */
+static int
+parse_filename(tchar **line_p, size_t *len_p, tchar **fn_ret)
+{
+ size_t len = *len_p;
+ tchar *line = *line_p;
+ tchar *fn;
+ tchar quote_char;
+
+ /* Skip leading whitespace */
+ for (;;) {
+ if (len == 0)
+ return PARSE_FILENAME_NONE;
+ if (!istspace(*line) && *line != T('\0'))
+ break;
+ line++;
+ len--;
+ }
+ quote_char = *line;
+ if (quote_char == T('"') || quote_char == T('\'')) {
+ /* Quoted filename */
+ line++;
+ len--;
+ fn = line;
+ line = tmemchr(line, quote_char, len);
+ if (!line) {
+ imagex_error(T("Missing closing quote: %"TS), fn - 1);
+ return PARSE_FILENAME_FAILURE;
+ }
+ } else {
+ /* Unquoted filename. Go until whitespace. Line is terminated
+ * by '\0', so no need to check 'len'. */
+ fn = line;
+ do {
+ line++;
+ } while (!istspace(*line) && *line != T('\0'));
+ }
+ *line = T('\0');
+ len -= line - fn;
+ *len_p = len;
+ *line_p = line;
+ *fn_ret = fn;
+ return PARSE_FILENAME_SUCCESS;
+}
+
+/* Parses a line of data (not an empty line or comment) in the source list file
+ * format. (See the man page for 'wimlib-imagex capture' for details on this
+ * format and the meaning.)
+ *
+ * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
+ * len == 0. The data in @line will be modified by this function call.
+ *
+ * @len: Length of the line of data.
+ *
+ * @source: On success, the capture source and target described by the line is
+ * written into this destination. Note that it will contain pointers
+ * to data in the @line array.
+ *
+ * Returns true if the line was valid; false otherwise. */
+static bool
+parse_source_list_line(tchar *line, size_t len,
+ struct wimlib_capture_source *source)
+{
+ /* SOURCE [DEST] */
+ int ret;
+ ret = parse_filename(&line, &len, &source->fs_source_path);
+ if (ret != PARSE_FILENAME_SUCCESS)
+ return false;
+ ret = parse_filename(&line, &len, &source->wim_target_path);
+ if (ret == PARSE_FILENAME_NONE)
+ source->wim_target_path = source->fs_source_path;
+ return ret != PARSE_FILENAME_FAILURE;
+}
+
+/* Returns %true if the given line of length @len > 0 is a comment or empty line
+ * in the source list file format. */
+static bool
+is_comment_line(const tchar *line, size_t len)
+{
+ for (;;) {
+ if (*line == T('#'))
+ return true;
+ if (!istspace(*line) && *line != T('\0'))
+ return false;
+ ++line;
+ --len;
+ if (len == 0)
+ return true;
+ }
+}
+
+/* Parses a file in the source list format. (See the man page for
+ * 'wimlib-imagex capture' for details on this format and the meaning.)
+ *
+ * @source_list_contents: Contents of the source list file. Note that this
+ * buffer will be modified to save memory allocations,
+ * and cannot be freed until the returned array of
+ * wimlib_capture_source's has also been freed.
+ *
+ * @source_list_nbytes: Number of bytes of data in the @source_list_contents
+ * buffer.
+ *
+ * @nsources_ret: On success, the length of the returned array is
+ * returned here.
+ *
+ * Returns: An array of `struct wimlib_capture_source's that can be passed to
+ * the wimlib_add_image_multisource() function to specify how a WIM image is to
+ * be created. */
+static struct wimlib_capture_source *
+parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
+ size_t *nsources_ret)
+{
+ size_t nlines;
+ tchar *p;
+ struct wimlib_capture_source *sources;
+ size_t i, j;
+ tchar *source_list_contents = *source_list_contents_p;
+
+ nlines = 0;
+ for (i = 0; i < source_list_nchars; i++)
+ if (source_list_contents[i] == T('\n'))
+ nlines++;
+
+ /* Handle last line not terminated by a newline */
+ if (source_list_nchars != 0 &&
+ source_list_contents[source_list_nchars - 1] != T('\n'))
+ {
+ source_list_contents = realloc(source_list_contents,
+ (source_list_nchars + 1) * sizeof(tchar));
+ if (!source_list_contents)
+ goto oom;
+ source_list_contents[source_list_nchars] = T('\n');
+ *source_list_contents_p = source_list_contents;
+ source_list_nchars++;
+ nlines++;
+ }
+
+ /* Always allocate at least 1 slot, just in case the implementation of
+ * calloc() returns NULL if 0 bytes are requested. */
+ sources = calloc(nlines ?: 1, sizeof(*sources));
+ if (!sources)
+ goto oom;
+ p = source_list_contents;
+ j = 0;
+ for (i = 0; i < nlines; i++) {
+ /* XXX: Could use rawmemchr() here instead, but it may not be
+ * available on all platforms. */
+ tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
+ size_t len = endp - p + 1;
+ *endp = T('\0');
+ if (!is_comment_line(p, len)) {
+ if (!parse_source_list_line(p, len, &sources[j++])) {
+ free(sources);
+ return NULL;
+ }
+ }
+ p = endp + 1;