parse_options(const char *data, struct parsed_mount_info *parsed_info)
{
	char *value = NULL, *equals = NULL;
	char *next_keyword = NULL;
	char *out = parsed_info->options;
	unsigned long *filesys_flags = &parsed_info->flags;
	int out_len = 0;
	int word_len;
	int rc = 0;
	int got_uid = 0, got_gid = 0;
	char user[32];
	char group[32];

	/* make sure we're starting from beginning */
	out[0] = '\0';

	/* BB fixme check for separator override BB */
	if (getuid()) {
		got_uid = 1;
		snprintf(user, sizeof(user), "%u", getuid());
		got_gid = 1;
		snprintf(group, sizeof(group), "%u", getgid());
	}

	if (!data)
		return EX_USAGE;

	/*
	 * format is keyword,keyword2=value2,keyword3=value3... 
	 * data  = next keyword
	 * value = next value ie stuff after equal sign
	 */
	while (data && *data) {
		next_keyword = strchr(data, ',');	/* BB handle sep= */

		/* temporarily null terminate end of keyword=value pair */
		if (next_keyword)
			*next_keyword++ = 0;

		/* temporarily null terminate keyword if there's a value */
		value = NULL;
		if ((equals = strchr(data, '=')) != NULL) {
			*equals = '\0';
			value = equals + 1;
		}

		/* FIXME: turn into a token parser? */
		if (strncmp(data, "users", 5) == 0) {
			if (!value || !*value) {
				*filesys_flags |= MS_USERS;
				goto nocopy;
			}
		} else if (strncmp(data, "user_xattr", 10) == 0) {
			/* do nothing - need to skip so not parsed as user name */
		} else if (strncmp(data, "user", 4) == 0) {
			if (!value || !*value) {
				if (data[4] == '\0') {
					*filesys_flags |= MS_USER;
					goto nocopy;
				} else {
					fprintf(stderr,
						"username specified with no parameter\n");
					return EX_USAGE;
				}
			} else {
				if (strnlen(value, 260) >= 260) {
					fprintf(stderr, "username too long\n");
					return EX_USAGE;
				}
				rc = parse_username(value, parsed_info);
				if (rc) {
					fprintf(stderr,
						"problem parsing username\n");
					return rc;
				}
				goto nocopy;
			}
		} else if (strncmp(data, "pass", 4) == 0) {
			if (parsed_info->got_password) {
				fprintf(stderr,
					"password specified twice, ignoring second\n");
				goto nocopy;
			}
			if (!value || !*value) {
				parsed_info->got_password = 1;
				goto nocopy;
			}
			rc = set_password(parsed_info, value);
			if (rc)
				return rc;
			goto nocopy;
		} else if (strncmp(data, "sec", 3) == 0) {
			if (value) {
				if (!strncmp(value, "none", 4) ||
				    !strncmp(value, "krb5", 4))
					parsed_info->got_password = 1;
			}
		} else if (strncmp(data, "ip", 2) == 0) {
			if (!value || !*value) {
				fprintf(stderr,
					"target ip address argument missing");
			} else if (strnlen(value, MAX_ADDRESS_LEN) <=
				   MAX_ADDRESS_LEN) {
				if (parsed_info->verboseflag)
					fprintf(stderr,
						"ip address %s override specified\n",
						value);
			} else {
				fprintf(stderr, "ip address too long\n");
				return EX_USAGE;
			}
		} else if ((strncmp(data, "unc", 3) == 0)
			   || (strncmp(data, "target", 6) == 0)
			   || (strncmp(data, "path", 4) == 0)) {
			if (!value || !*value) {
				fprintf(stderr,
					"invalid path to network resource\n");
				return EX_USAGE;	/* needs_arg; */
			}
			rc = parse_unc(value, parsed_info);
			if (rc)
				return rc;
		} else if ((strncmp(data, "dom" /* domain */ , 3) == 0)
			   || (strncmp(data, "workg", 5) == 0)) {
			/* note this allows for synonyms of "domain"
			   such as "DOM" and "dom" and "workgroup"
			   and "WORKGRP" etc. */
			if (!value || !*value) {
				fprintf(stderr, "CIFS: invalid domain name\n");
				return EX_USAGE;
			}
			if (strnlen(value, sizeof(parsed_info->domain)) >=
			    sizeof(parsed_info->domain)) {
				fprintf(stderr, "domain name too long\n");
				return EX_USAGE;
			}
			strlcpy(parsed_info->domain, value,
				sizeof(parsed_info->domain));
			goto nocopy;
		} else if (strncmp(data, "cred", 4) == 0) {
			if (value && *value) {
				rc = open_cred_file(value, parsed_info);
				if (rc) {
					fprintf(stderr,
						"error %d (%s) opening credential file %s\n",
						rc, strerror(rc), value);
					return rc;
				}
			} else {
				fprintf(stderr,
					"invalid credential file name specified\n");
				return EX_USAGE;
			}
		} else if (strncmp(data, "uid", 3) == 0) {
			if (value && *value) {
				got_uid = 1;
				if (!isdigit(*value)) {
					struct passwd *pw;

					if (!(pw = getpwnam(value))) {
						fprintf(stderr,
							"bad user name \"%s\"\n",
							value);
						return EX_USAGE;
					}
					snprintf(user, sizeof(user), "%u",
						 pw->pw_uid);
				} else {
					strlcpy(user, value, sizeof(user));
				}
			}
			goto nocopy;
		} else if (strncmp(data, "gid", 3) == 0) {
			if (value && *value) {
				got_gid = 1;
				if (!isdigit(*value)) {
					struct group *gr;

					if (!(gr = getgrnam(value))) {
						fprintf(stderr,
							"bad group name \"%s\"\n",
							value);
						return EX_USAGE;
					}
					snprintf(group, sizeof(group), "%u",
						 gr->gr_gid);
				} else {
					strlcpy(group, value, sizeof(group));
				}
			}
			goto nocopy;
			/* fmask and dmask synonyms for people used to smbfs syntax */
		} else if (strcmp(data, "file_mode") == 0
			   || strcmp(data, "fmask") == 0) {
			if (!value || !*value) {
				fprintf(stderr,
					"Option '%s' requires a numerical argument\n",
					data);
				return EX_USAGE;
			}

			if (value[0] != '0') {
				fprintf(stderr,
					"WARNING: '%s' not expressed in octal.\n",
					data);
			}

			if (strcmp(data, "fmask") == 0) {
				fprintf(stderr,
					"WARNING: CIFS mount option 'fmask' is deprecated. Use 'file_mode' instead.\n");
				data = "file_mode";	/* BB fix this */
			}
		} else if (strcmp(data, "dir_mode") == 0
			   || strcmp(data, "dmask") == 0) {
			if (!value || !*value) {
				fprintf(stderr,
					"Option '%s' requires a numerical argument\n",
					data);
				return EX_USAGE;
			}

			if (value[0] != '0') {
				fprintf(stderr,
					"WARNING: '%s' not expressed in octal.\n",
					data);
			}

			if (strcmp(data, "dmask") == 0) {
				fprintf(stderr,
					"WARNING: CIFS mount option 'dmask' is deprecated. Use 'dir_mode' instead.\n");
				data = "dir_mode";
			}
			/* the following eight mount options should be
			   stripped out from what is passed into the kernel
			   since these eight options are best passed as the
			   mount flags rather than redundantly to the kernel 
			   and could generate spurious warnings depending on the
			   level of the corresponding cifs vfs kernel code */
		} else if (strncmp(data, "nosuid", 6) == 0) {
			*filesys_flags |= MS_NOSUID;
		} else if (strncmp(data, "suid", 4) == 0) {
			*filesys_flags &= ~MS_NOSUID;
		} else if (strncmp(data, "nodev", 5) == 0) {
			*filesys_flags |= MS_NODEV;
		} else if ((strncmp(data, "nobrl", 5) == 0) ||
			   (strncmp(data, "nolock", 6) == 0)) {
			*filesys_flags &= ~MS_MANDLOCK;
		} else if (strncmp(data, "dev", 3) == 0) {
			*filesys_flags &= ~MS_NODEV;
		} else if (strncmp(data, "noexec", 6) == 0) {
			*filesys_flags |= MS_NOEXEC;
		} else if (strncmp(data, "exec", 4) == 0) {
			*filesys_flags &= ~MS_NOEXEC;
		} else if (strncmp(data, "guest", 5) == 0) {
			parsed_info->got_user = 1;
			parsed_info->got_password = 1;
		} else if (strncmp(data, "ro", 2) == 0) {
			*filesys_flags |= MS_RDONLY;
			goto nocopy;
		} else if (strncmp(data, "rw", 2) == 0) {
			*filesys_flags &= ~MS_RDONLY;
			goto nocopy;
		} else if (strncmp(data, "remount", 7) == 0) {
			*filesys_flags |= MS_REMOUNT;
		}

		/* check size before copying option to buffer */
		word_len = strlen(data);
		if (value)
			word_len += 1 + strlen(value);

		/* need 2 extra bytes for comma and null byte */
		if (out_len + word_len + 2 > MAX_OPTIONS_LEN) {
			fprintf(stderr, "Options string too long\n");
			return EX_USAGE;
		}

		/* put back equals sign, if any */
		if (equals)
			*equals = '=';

		/* go ahead and copy */
		if (out_len)
			strlcat(out, ",", MAX_OPTIONS_LEN);

		strlcat(out, data, MAX_OPTIONS_LEN);
		out_len = strlen(out);
nocopy:
		data = next_keyword;
	}

	/* special-case the uid and gid */
	if (got_uid) {
		word_len = strlen(user);

		if (out_len + word_len + 6 > MAX_OPTIONS_LEN) {
			fprintf(stderr, "Options string too long\n");
			return EX_USAGE;
		}

		if (out_len) {
			strlcat(out, ",", out_len + word_len + 6);
			out_len++;
		}
		snprintf(out + out_len, word_len + 5, "uid=%s", user);
		out_len = strlen(out);
	}
	if (got_gid) {
		word_len = strlen(group);

		if (out_len + 1 + word_len + 6 > MAX_OPTIONS_LEN) {
			fprintf(stderr, "Options string too long\n");
			return EX_USAGE;
		}

		if (out_len) {
			strlcat(out, ",", out_len + word_len + 6);
			out_len++;
		}
		snprintf(out + out_len, word_len + 5, "gid=%s", group);
		out_len = strlen(out);
	}

	return 0;
}