<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Soul Of Free Loop &#187; WebDAV</title>
	<atom:link href="https://zohead.com/archives/tag/webdav/feed/" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>解决Linux下使用davfs2挂载坚果云的问题</title>
		<link>https://zohead.com/archives/davfs2-nutstore/</link>
		<comments>https://zohead.com/archives/davfs2-nutstore/#comments</comments>
		<pubDate>Wed, 28 Oct 2015 16:53:51 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[davfs2]]></category>
		<category><![CDATA[WebDAV]]></category>
		<category><![CDATA[云存储]]></category>
		<category><![CDATA[坚果云]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=1068</guid>
		<description><![CDATA[坚果云算是现在国内云存储提供商里比较另类的一个了，没有提供巨大的容量空间，也没有非常多的人使用他的分享功能，但好在基本功能非常全面，桌面同步客户端做的比较清爽，各种类型的客户端基本都能支持，特别像多个用户协作修改文件的同步、文件版本历史这些我比较看重的功能做的还不错。坚果云也有一定的付费用户，稍微坑一点的就是坚果云免费用户的初始容量只有 1GB，之后每个月免费用户可以再上传 1GB 的数据（免费用户同时限制每个月下行流量 3GB）。 坚果云虽然没有提供 API 功能，但好在其算是国内唯一提供 WebDAV 方式访问的网盘（国外的类似网盘也比较少，免费的像 Box.com 就算一个了），这样基本 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><span style="line-height: 1.5em;">坚果云算是现在国内</span>云存储提供商里比较另类的一个了，没有提供巨大的容量空间，也没有非常多的人使用他的分享功能，但好在基本功能非常全面，桌面同步客户端做的比较清爽，各种类型的客户端基本都能支持，特别像多个用户协作修改文件的同步、文件版本历史这些我比较看重的功能做的还不错。坚果云也有一定的付费用户，稍微坑一点的就是坚果云免费用户的初始容量只有 1GB，之后每个月免费用户可以再上传 1GB 的数据（免费用户同时限制每个月下行流量 3GB）。</p>
<p>坚果云虽然没有提供 API 功能，但好在其算是国内唯一提供 WebDAV 方式访问的网盘（国外的类似网盘也比较少，免费的像 Box.com 就算一个了），这样基本就可以在各种不同类型的客户端中不依靠其同步客户端就能读写云中的数据。之前我在 Windows 上使用自带的资源管理器以 WebDAV 方式访问坚果云看起来没什么问题，但到 Linux 下使用最流行的 davfs2 软件挂载坚果云 WebDAV 的时候却直接报错无法访问（WebDAV 功能需要在坚果云账户设置中的 “第三方应用管理” 里开启）。</p>
<p>Linux 终端下 davfs2 挂载坚果云的报错信息如下：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)root@localhost:~# mount -t davfs https://dav.jianguoyun.com/dav /mnt
Please enter the username to authenticate with server
https://dav.jianguoyun.com/dav or hit enter for none.
  Username: xxx@qq.com
Please enter the password to authenticate user xxx@qq.com with server
https://dav.jianguoyun.com/dav or hit enter for none.
  Password:
/sbin/mount.davfs: mounting failed; the server does not support WebDAV
</pre>
<p>直接提示服务器不支持 WebDAV 这就比较奇怪的，在网上没找到解决办法，因此决定直接下载 davfs2 源代码进行分析，davfs2 现在的项目主页已从 SourceForge 迁移到：</p>
<p><a href="http://savannah.nongnu.org/projects/davfs2" target="_blank">http://savannah.nongnu.org/projects/davfs2</a></p>
<p>使用 cvs 检出源代码之后（还用这么古老的版本管理软件值得吐槽哈），需要先安装 WebDAV 支持库 libneon 才能正常编译。</p>
<p>很快就能找到出错的地方 webdav.c：</p>
<pre class="brush: cpp; title: davfs2/src/webdav.c; notranslate">
int
dav_init_connection(const char *path)
{
    char *spath = ne_path_escape(path);
    ne_server_capabilities caps = {0, 0, 0};
    int ret = ne_options(session, spath, &amp;caps);

    if (!ret) {
        initialized = 1;
        if (!caps.dav_class1 &amp;&amp; !ignore_dav_header) {
            if (have_terminal) {
                error(EXIT_FAILURE, 0,
                      _(&quot;mounting failed; the server does not support WebDAV&quot;));
            } else {
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR),
                       _(&quot;mounting failed; the server does not support WebDAV&quot;));
                ret = EINVAL;
            }
        }
</pre>
<p>简单分析上面的源代码可知 mount.davfs 命令在挂载时和 WebDAV 服务器建立连接，并通过 libneon 库的 ne_options 函数发送 HTTP OPTIONS 请求获取 WebDAV 服务器能力，虽然返回成功但判断坚果云 WebDAV 服务器不支持 Class 1 WebDAV，因此直接报错挂载失败。</p>
<p>看到这里解决办法也就简单了，davfs2 提供了通过配置文件禁用 WebDAV 头检测的功能，直接修改 <strong>/etc/davfs2/davfs2.conf</strong> 配置文件注释并改为：</p>
<p><strong>ignore_dav_header 1</strong></p>
<p>然后再重新使用 mount 或者 mount.davfs 命令挂载坚果云就可以成功了，后续拷贝文件之类的看起来也算正常，不过运行 df 命令看到 WebDAV 返回的容量信息还是不对：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ df -h /mnt
文件系统                        容量  已用  可用 已用% 挂载点
https://dav.jianguoyun.com/dav   26G   13G   13G   50% /mnt
</pre>
<p>不过这个就不用计较咯，祝玩的开心～～～</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/davfs2-nutstore/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>lighttpd的SQLite问题</title>
		<link>https://zohead.com/archives/lighty-sqlite-err/</link>
		<comments>https://zohead.com/archives/lighty-sqlite-err/#comments</comments>
		<pubDate>Sun, 07 Sep 2014 14:55:19 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[数据库]]></category>
		<category><![CDATA[lighttpd]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[sqlite3_prepare]]></category>
		<category><![CDATA[WebDAV]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=786</guid>
		<description><![CDATA[最近在使用 lighttpd 的 WebDAV 插件拷贝文件时遇到比较奇怪的 SQLite 库报错问题，WebDAV 插件需要使用 SQLite 数据库保存文件锁、文件属性等信息，报错信息如下： (mod_webdav.c.2182) sql-set failed: SQL logic error or missing database(mod_webdav.c.2182) sql-set failed: not an error(mod_webdav.c.2182) sql-set failed: not an error(mod_webdav.c.2182) sql-set failed: [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>最近在使用 lighttpd 的 WebDAV 插件拷贝文件时遇到比较奇怪的 SQLite 库报错问题，WebDAV 插件需要使用 SQLite 数据库保存文件锁、文件属性等信息，报错信息如下：</p>
<p>(mod_webdav.c.2182) sql-set failed: SQL logic error or missing database<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2511) remove lock: bind or column index out of range<br />(mod_webdav.c.2511) remove lock: bind or column index out of range<br />(mod_webdav.c.2511) remove lock: bind or column index out of range</p>
<p>我们先看看 mod_webdav.c 中对应的 SQLite 处理代码：</p>
<p><pre class="brush: cpp; highlight: [17,18,19,20,43]; title: mod_webdav.c; notranslate">
sqlite3_stmt *stmt;

stmt = (0 == xmlStrcmp(cmd-&gt;name, BAD_CAST &quot;remove&quot;)) ?
	p-&gt;conf.stmt_delete_prop : p-&gt;conf.stmt_update_prop;

for (props = cmd-&gt;children; props; props = props-&gt;next) {
	if (0 == xmlStrcmp(props-&gt;name, BAD_CAST &quot;prop&quot;)) {
		xmlNode *prop;
		int r;

		prop = props-&gt;children;

		sqlite3_reset(stmt);

		/* bind the values to the insert */

		sqlite3_bind_text(stmt, 1,
				  con-&gt;uri.path-&gt;ptr,
				  con-&gt;uri.path-&gt;used - 1,
				  SQLITE_TRANSIENT);
		sqlite3_bind_text(stmt, 2,
				  (char *)prop-&gt;name,
				  strlen((char *)prop-&gt;name),
				  SQLITE_TRANSIENT);
		if (prop-&gt;ns) {
			sqlite3_bind_text(stmt, 3,
					  (char *)prop-&gt;ns-&gt;href,
					  strlen((char *)prop-&gt;ns-&gt;href),
					  SQLITE_TRANSIENT);
		} else {
			sqlite3_bind_text(stmt, 3,
					  &quot;&quot;,
					  0,
					  SQLITE_TRANSIENT);
		}
		if (stmt == p-&gt;conf.stmt_update_prop) {
			sqlite3_bind_text(stmt, 4,
				  (char *)xmlNodeGetContent(prop),
				  strlen((char *)xmlNodeGetContent(prop)),
				  SQLITE_TRANSIENT);
		}

		if (SQLITE_DONE != (r = sqlite3_step(stmt))) {
			log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;,
					&quot;sql-set failed:&quot;, sqlite3_errmsg(p-&gt;conf.sql));
		}
	}
}
</pre>
</p>
<p>这里用到了 SQLite 的绑定数据 sqlite3_bind_text 和 sqlite3_step 执行函数。</p>
<p>同时我们可以看看 mod_webdav.c 中打开 SQLite 数据库以及 SQLite 变量绑定方式的 SQL 处理代码：</p>
<p><pre class="brush: cpp; highlight: [11,12,31,32,44,45,65,66]; title: mod_webdav.c; notranslate">
const char *next_stmt;
char *err;

if (SQLITE_OK != sqlite3_open(s-&gt;sqlite_db_name-&gt;ptr, &amp;(s-&gt;sql))) {
	log_error_write(srv, __FILE__, __LINE__, &quot;sbs&quot;, &quot;sqlite3_open failed for&quot;,
			s-&gt;sqlite_db_name,
			sqlite3_errmsg(s-&gt;sql));
	return HANDLER_ERROR;
}

if (SQLITE_OK != sqlite3_exec(s-&gt;sql,
		&quot;CREATE TABLE properties (&quot;
		&quot;  resource TEXT NOT NULL,&quot;
		&quot;  prop TEXT NOT NULL,&quot;
		&quot;  ns TEXT NOT NULL,&quot;
		&quot;  value TEXT NOT NULL,&quot;
		&quot;  PRIMARY KEY(resource, prop, ns))&quot;,
		NULL, NULL, &amp;err)) {

	if (0 != strcmp(err, &quot;table properties already exists&quot;)) {
		log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;can't open transaction:&quot;, err);
		sqlite3_free(err);

		return HANDLER_ERROR;
	}
	sqlite3_free(err);
}

...

if (SQLITE_OK != sqlite3_prepare(s-&gt;sql,
	CONST_STR_LEN(&quot;REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)&quot;),
	&amp;(s-&gt;stmt_update_prop), &amp;next_stmt)) {
	/* prepare failed */

	log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;sqlite3_prepare failed:&quot;, sqlite3_errmsg(s-&gt;sql));
	return HANDLER_ERROR;
}

...

/* LOCKS */

if (SQLITE_OK != sqlite3_exec(s-&gt;sql,
		&quot;CREATE TABLE locks (&quot;
		&quot;  locktoken TEXT NOT NULL,&quot;
		&quot;  resource TEXT NOT NULL,&quot;
		&quot;  lockscope TEXT NOT NULL,&quot;
		&quot;  locktype TEXT NOT NULL,&quot;
		&quot;  owner TEXT NOT NULL,&quot;
		&quot;  depth INT NOT NULL,&quot;
		&quot;  timeout TIMESTAMP NOT NULL,&quot;
		&quot;  PRIMARY KEY(locktoken))&quot;,
		NULL, NULL, &amp;err)) {

	if (0 != strcmp(err, &quot;table locks already exists&quot;)) {
		log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;can't open transaction:&quot;, err);
		sqlite3_free(err);

		return HANDLER_ERROR;
	}
	sqlite3_free(err);
}

if (SQLITE_OK != sqlite3_prepare(s-&gt;sql,
	CONST_STR_LEN(&quot;INSERT INTO locks (locktoken, resource, lockscope, locktype, owner, depth, timeout) VALUES (?,?,?,?,?,?, CURRENT_TIME + 600)&quot;),
	&amp;(s-&gt;stmt_create_lock), &amp;next_stmt)) {
	/* prepare failed */
	log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;sqlite3_prepare failed&quot;, sqlite3_errmsg(s-&gt;sql));

	return HANDLER_ERROR;
}

...
</pre>
</p>
<p>从上面的代码可以看出，lighttpd 打开 SQLite 数据库之后先创建 properties 表格，然后使用 sqlite3_prepare 函数进行 properties 表格的 SQL 语句预处理，接着创建 locks 表格，同时也使用 sqlite3_prepare 对 locks 表格进行预处理。</p>
<p>使用 sqlite3_prepare 函数而不直接使用 sqlite3_exec 函数的好处是可以直接将 SQL 语句转换为 SQLite 内部的字节码，后面使用时不需要再直接使用冗长的 SQL 语句，直接绑定数据就可以查询或者更新数据了。这里就比较奇怪了，stmt_update_prop 的 SQL 语句看起来也没有什么问题。首先把疑点放在 sqlite3_bind_text 上，怀疑是不是数据中有没有什么特殊字符导致 sqlite3_step 函数执行出错了，但我把 sqlite3_bind_text 的内容全部改成普通字符串还是一样的报错。</p>
<p>搜寻一番之后终于在 sqlite3.h 头文件中发现了端倪：</p>
<p><pre class="brush: cpp; title: sqlite3.h; notranslate">
** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
** recommended for all new programs. The two older interfaces are retained
** for backwards compatibility, but their use is discouraged.、
**
** In the &quot;v2&quot; interfaces, the prepared statement
** that is returned (the [sqlite3_stmt] object) contains a copy of the
** original SQL text. This causes the [sqlite3_step()] interface to
** behave differently in three ways:
**
** If the database schema changes, instead of returning [SQLITE_SCHEMA] as it
** always used to do, [sqlite3_step()] will automatically recompile the SQL
** statement and try to run it again. As many as [SQLITE_MAX_SCHEMA_RETRY]
** retries will occur before sqlite3_step() gives up and returns an error.
**
** When an error occurs, [sqlite3_step()] will return one of the detailed
** [error codes] or [extended error codes]. The legacy behavior was that
** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code
** and the application would have to make a second call to [sqlite3_reset()]
** in order to find the underlying cause of the problem. With the &quot;v2&quot; prepare
** interfaces, the underlying reason for the error is returned immediately.
</pre>
</p>
<p>sqlite3.h 中建议使用新的 sqlite3_prepare_v2 函数替代老的 sqlite3_prepare 函数，而且中间的注释中还提到使用 sqlite3_prepare 函数之后如果数据库的结构发生了变动，sqlite3_step 函数将会出错。这时我们看看 mod_webdav.c 中初始化数据库的代码可以发现 sqlite3_prepare 函数是在两个 sqlite3_exec 函数创建数据库之间运行的，这样 sqlite3_prepare 生成的字节码就不是正确的，才导致后续的 SQL 更新操作失败。</p>
<p>知道原因之后修改也比较简单了，我们可以修改 mod_webdav.c 中的数据库初始化代码，将两个 sqlite3_exec 创建数据库的操作放在最前面，将所有 sqlite3_prepare 替换为 sqlite3_prepare_v2 函数放在创建数据库之后，这样就不会出现 SQLite 数据库错误问题了。</p>
<p>本文为个人使用分析的结果，其中有任何问题欢迎提出指正，另外 lighttpd 最新版本库代码中这个问题似乎仍然没有修正。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/lighty-sqlite-err/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
