PostgreSQL 18:持续归档与时间点恢复(PITR)

PostgreSQL 18:持续归档与时间点恢复(PITR)

PostgreSQL 始终在数据库集群数据目录的 pg_wal/ 子目录中维护预写日志(WAL),记录对数据库数据文件的每一次修改。它主要用于保障崩溃安全:系统崩溃后,可以重放上次检查点以来的日志记录,使数据库恢复一致。

日志的存在也带来了第三种备份方法:把文件系统级备份与 WAL 文件备份结合。需要恢复时,先还原文件系统备份,再重放已备份的 WAL 文件,使系统恢复到较新的状态。它比前面介绍的两种方法更难管理,但有明显优点:

  • 起点不必是完全一致的文件系统备份;日志重放会修正备份内部的不一致,这与崩溃恢复的过程并无本质区别。因此不需要文件系统快照能力,使用 tar 或类似归档工具即可。
  • 可以重放任意长的 WAL 序列,因此持续归档 WAL 文件就能实现连续备份。这对不便频繁进行全量备份的大型数据库尤其有用。
  • 不必重放到日志末尾。可以在任意时刻停止重放,得到当时一致的数据库快照,因而支持时间点恢复:恢复到基础备份开始之后的某个时刻。
  • 把 WAL 序列持续传给已经装载同一基础备份的另一台机器,就能形成温备系统:随时启动第二台机器,即可得到接近当前状态的数据库副本。

注意:pg_dump 和 pg_dumpall 生成的是逻辑备份,不是文件系统级备份,缺少 WAL 重放所需信息,不能作为持续归档方案的一部分。

和普通文件系统备份一样,此方法只能恢复整个数据库集群,不能只恢复其中一部分。它也需要大量归档空间:基础备份可能很大,繁忙系统还会不断产生必须归档的 WAL 数据。尽管如此,在要求高可靠性的场景中,它通常是首选。

要使用持续归档(许多数据库厂商称其为“在线备份”)成功恢复,必须保留连续的 WAL 归档序列,至少追溯到备份开始。因此,第一次基础备份之前,应先设置并测试 WAL 归档流程。以下先介绍归档机制。

25.3.1 设置 WAL 归档

从抽象意义上说,运行中的 PostgreSQL 会产生无限长的 WAL 记录序列。物理上,它被划分为 WAL 段文件,通常每个 16MB;可在 initdb 时调整段大小。段文件的数字名称表示它在 WAL 序列中的位置。

不使用归档时,系统通常只建立少量段文件,随后把不再需要的段改名为更大的段号,循环使用。最后一个检查点之前的段通常被视为不再需要。

归档时,应在每个段写满后捕获其内容,并在循环使用前保存到其他位置。根据应用与硬件,保存方式可以是复制到另一台机器的 NFS 挂载目录、写入磁带(必须能识别每个文件的原始名称)、批量刻录到 CD,或其他办法。

为给管理员保留灵活性,PostgreSQL 不预设归档实现。管理员可以指定一个 shell 命令或归档库,把完成的段复制到目标位置;既可以用简单的 cp 命令,也可以调用复杂的 C 函数。

启用归档时,把 wal_level 设为 replica 或更高,把 archive_mode 设为 on,并在 archive_command 指定 shell 命令,或在 archive_library 指定归档库。这些设置通常放在 postgresql.conf。

archive_command 中的 %p 会替换为要归档文件的路径,%f 只替换为文件名。路径相对于当前工作目录,即集群数据目录。要写入真正的 % 字符,使用 %%。简单命令示例:

archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'  # Unix
archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"'  # Windows

这会把待归档的 WAL 段复制到 /mnt/server/archivedir。这只是示例,并非推荐方案,可能不适用于所有平台。替换参数后,实际执行的命令可能如下:

test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065

每个新归档文件都会生成类似命令。

归档命令以 PostgreSQL 服务器进程的同一用户身份运行。WAL 序列实际上包含数据库中的全部内容,因此应防止他人读取归档,例如把它放入组用户和其他用户均无读取权限的目录。

归档命令必须且只能在成功时返回退出状态零。收到零后,PostgreSQL 会认定文件已成功归档,随后删除或循环使用它。非零表示未归档,系统会定期重试直到成功。

也可以把自定义归档模块配置为 archive_library。模块用 C 编写,开发工作量可能大于 shell 命令,但性能可能更好,也能访问许多服务器资源。详见第 49 章。

如果归档命令被信号终止(服务器关闭时使用的 SIGTERM 除外),或 shell 出错且退出状态大于 125(如找不到命令),或归档函数发出 ERROR/FATAL,归档进程会退出并由 postmaster 重启。这类失败不会记录在 pg_stat_archiver 中。

归档命令和库通常应拒绝覆盖已存在的归档文件。这能在管理员误操作(例如两个服务器写入同一归档目录)时保护归档完整性。应测试拟采用的归档库,确认不会覆盖已有文件。

少数情况下,PostgreSQL 会重新归档已经归档的 WAL 文件。例如,在成功归档的持久记录落盘前发生崩溃,重启后只要仍启用归档,就会再次尝试。

遇到已有文件时,只有当 WAL 内容与其完全相同、且已有归档已完全持久写入存储,命令或库才应分别返回零或 true。内容不同时,必须分别返回非零或 false。

前面的 Unix 命令通过独立的 test 步骤避免覆盖。一些 Unix 平台的 cp -i 等选项也能缩短写法,但必须核实退出状态,不能直接依赖。尤其 GNU cp -i 在目标已存在时会返回零,这不是所需行为。

设计归档方案时,应考虑需要人工干预或归档空间耗尽导致持续失败的情况。例如,使用没有自动换带器的磁带设备,磁带写满后,换带前就无法继续归档。

错误与需要操作员介入的请求应适当报告,以便迅速解决。问题未解决时,pg_wal/ 会持续堆积段文件。其所在文件系统写满时,PostgreSQL 会以 PANIC 方式关闭;不会丢失已提交事务,但释放空间之前数据库会保持离线。

只要跟得上服务器平均产生 WAL 的速率,归档命令或库的速度并不重要。轻微落后不会中断正常运行;严重落后会增加灾难时可能丢失的数据,也会令未归档段大量占据 pg_wal/,最终可能耗尽空间。应监控归档进程,确认运行符合预期。

编写命令或库时,应假设文件名最长可达 64 个字符,可包含 ASCII 字母、数字和点。无需保留原始相对路径 %p,但必须保留文件名 %f。

WAL 归档可以还原 PostgreSQL 数据的修改,却不能还原 postgresql.conf、pg_hba.conf、pg_ident.conf 的修改,因为这些文件由人工编辑,而不是 SQL 操作。可把配置文件放入常规文件系统备份覆盖的位置;位置调整见19.2 节。

归档命令或函数只处理完成的段。WAL 流量很低,或某段时间较空闲时,事务完成到安全存入归档之间可能相隔很久。可设置 archive_timeout,强制服务器至少按指定间隔切换到新段,限制未归档数据的最老年龄。

强制切换而提前归档的文件仍与写满的文件一样大。因此不宜把 archive_timeout 设得很短,以免归档膨胀。约一分钟通常合理。

也可以手动调用 pg_switch_wal 切换段,使刚完成的事务尽快归档。其他 WAL 管理函数见表 9.97。

wal_level=minimal 时,某些 SQL 命令会通过避免 WAL 记录来优化,见14.4.7 节。这些语句执行期间若开启归档或流复制,WAL 不会含有归档恢复所需的全部信息,但崩溃恢复不受影响。因此,wal_level 只能在服务器启动时修改;archive_command 和 archive_library 则可通过重新加载配置修改。

使用 shell 归档时,若要暂时停止,可以把 archive_command 设为空字符串 ''。WAL 会在 pg_wal/ 中累积,直到恢复可用的命令。

25.3.2 创建基础备份

最简单的方式是使用 pg_basebackup,可生成普通文件或 tar 归档。如果需要比该工具更灵活的方式,也可采用下文的低级 API。

无需担心创建基础备份花费多少时间。不过,平时关闭 full_page_writes 的服务器在备份时可能变慢,因为备份模式实际上会强制开启它。

要使用备份,必须保留文件系统备份期间和之后产生的所有 WAL 段。基础备份流程会创建备份历史文件,并立即存入 WAL 归档区,帮助识别这些文件。历史文件名以恢复所需的第一个 WAL 段命名。例如首段为 0000000100001234000055CD,历史文件名可能为 0000000100001234000055CD.007C9330.backup。第二部分表示 WAL 内精确位置,通常可以忽略。

文件系统备份和历史文件指出的备份期间 WAL 段都已安全归档后,数字名称小于首段的归档 WAL 不再是恢复该备份所必需,可删除。不过应考虑保留多组备份,确保数据可恢复。

历史文件是小型文本文件,包含传给 pg_basebackup 的标签字符串,以及备份的开始、结束时间和 WAL 段。如果标签用于识别对应备份文件,那么历史文件就足以确定应还原哪份文件。

需要保留自最近基础备份以来的全部 WAL,因此基础备份间隔通常应根据愿意用于 WAL 归档的空间决定。还应考虑可接受的恢复时间:恢复必须重放全部相关段,离上次基础备份越久,重放可能越耗时。

25.3.3 创建增量备份

调用 pg_basebackup 并指定 --incremental 可以创建增量备份。该参数必须接收同一服务器上较早备份的备份清单文件。

结果中,非关系文件完整保存;一些关系文件则可能由更小的增量文件替代,仅保存前次备份以来修改的块,以及重建文件当前版本所需的元数据。

服务器通过 WAL 摘要判断要备份哪些块。摘要位于数据目录的 pg_wal/summaries。所需摘要缺失时,增量备份失败。此目录中的摘要必须覆盖前次备份起始 LSN 到本次备份起始 LSN 的整个区间。

服务器在确定当前备份起始 LSN 后立即查找摘要,因此所需文件可能还没落盘;它会等待缺失文件出现,这也有助于应对摘要进程落后。如果所需文件已被删除,或摘要进程未能及时赶上,备份仍会失败。

恢复增量备份时,不仅需要该增量备份,还需要能够提供其省略数据块的所有较早备份,详见 pg_combinebackup。集群校验和状态曾改变时,该工具的使用还有限制。

完整备份的全部要求也适用于增量备份:仍需文件系统备份期间与之后的所有 WAL 段、相关 WAL 历史文件,仍需建立 recovery.signal 或 standby.signal,并执行下文的恢复过程。

保留前置备份并使用 pg_combinebackup 是额外要求。PostgreSQL 没有内置机制判断哪些备份仍是后续增量恢复的基础;必须自行跟踪全量与增量备份的关系,不可删除未来恢复可能需要的较早备份。

增量备份通常只对大量数据不变或缓慢变化的大型数据库有意义。小数据库直接做全量备份更简单、管理更容易;大型数据库若全部数据都频繁修改,增量备份也不会比全量小很多。

增量备份只有在重放起点的检查点晚于它依赖的前次备份时才能建立。在主服务器上,每次备份触发新检查点,所以始终满足条件。在备用服务器上,重放从最近的重启点开始。因此前次备份后活动很少、未建立新重启点时,备用服务器的增量备份可能失败。

25.3.4 使用低级 API 创建基础备份

除了 pg_basebackup 的全量或增量备份,还可以用低级 API 创建基础备份。步骤较多,但相对简单。务必按顺序执行,并核实每一步成功后再继续。通过此 API 或 pg_basebackup 启动的多个备份可以并发运行。

  1. 确认 WAL 归档已启用且正常工作。
  2. 使用有权运行 pg_backup_start 的用户连接服务器(连接哪个数据库无关紧要):超级用户,或已被授予该函数 EXECUTE 权限的用户。执行:
SELECT pg_backup_start(label => 'label', fast => false);

label 可为任意唯一标识本次操作的字符串。调用 pg_backup_start 的连接必须保持到备份结束,否则备份会自动中止。

在线备份总在检查点开始处启动。默认情况下,pg_backup_start 等待下一个正常计划的检查点完成,可能耗时较长,参见 checkpoint_timeout 和 checkpoint_completion_target。这通常更好,因为能减少对运行中系统的影响。若希望尽快启动,把第二个参数设为 true,它会请求立即执行检查点,使用尽可能多的 I/O 尽快完成。

  1. 使用 tar、cpio 等文件系统备份工具执行备份,不能使用 pg_dump 或 pg_dumpall。此时既不必也不应该停止数据库正常运行。备份注意事项见下一小节。
  2. 在同一个连接执行:
SELECT * FROM pg_backup_stop(wait_for_archive => true);

这会结束备份模式。主服务器还会自动切换到下一个 WAL 段;备用服务器无法自动切换,可以在主服务器调用 pg_switch_wal 手动切换。这样可使备份区间最后写入的段准备好归档。

pg_backup_stop 返回一行三个值。第二个值必须写入备份根目录的 backup_label 文件;第三个值非空时,必须写入 tablespace_map 文件。这两个文件对备份有效性至关重要,必须逐字节原样写入,可能需要用二进制模式打开文件。

  1. 备份期间活动的 WAL 段全部归档后,备份完成。pg_backup_stop 第一个返回值指出构成完整备份所需的最后一段。主服务器启用 archive_mode 且 wait_for_archive=true 时,该函数会等最后一段归档后才返回。在备用服务器上,archive_mode 必须为 always 才会等待。

已配置 archive_command 或 archive_library,因此这些文件会自动归档。通常很快,但应监控归档系统,确认没有延迟。归档命令或库失败导致落后时,会持续重试,直到归档成功、备份完成。

若希望限制 pg_backup_stop 执行时间,可以设置合适的 statement_timeout;但若函数因此终止,备份可能无效。

如果备份进程自行监控并确保全部所需 WAL 段成功归档,可把默认 true 的 wait_for_archive 设为 false,使函数在停止备份记录写入 WAL 后立即返回。默认等待全部 WAL 归档可能耗时。必须谨慎:监控不正确会造成备份缺 WAL,因而不完整、无法恢复。

25.3.4.1 备份数据目录

部分文件系统备份工具在复制过程中源文件变化时会报告警告或错误。对运行中数据库的基础备份,这是正常情况,并非错误,但必须与真正的错误区分。例如某些 rsync 版本为“源文件消失”返回独立退出码,可在驱动脚本中将其视为非错误。

一些 GNU tar 版本在复制期间文件被截断时,会返回与致命错误无法区分的错误码。GNU tar 1.16 及以后,备份期间文件改变返回 1,其他错误返回 2;1.23 及以后可用 --warning=no-file-changed --warning=no-file-removed 隐藏相应警告。

必须包括集群目录(如 /usr/local/pgsql/data)中的所有文件。位于目录外的表空间也必须包括;符号链接应作为链接归档,否则恢复时会损坏表空间。

但应排除 pg_wal/ 中的文件,以降低恢复时出错的风险。如果它是指向集群目录之外的符号链接,就很容易排除;出于性能考虑,这本来就是常见配置。

也可排除 postmaster.pid 和 postmaster.opts:它们记录当前 postmaster,而不是将来使用此备份的进程,可能使 pg_ctl 混淆。

通常还应排除 pg_replslot/ 中的文件,避免把主服务器上的复制槽带入备份。否则用备份建立备用服务器时,可能令备用服务器无限保留 WAL;开启热备反馈时,也可能使主服务器膨胀,因为复制槽的客户端仍连接并更新主服务器的槽,而非备用服务器。即使备份只用于建立新主服务器,复制槽也通常无益,新主上线时其内容很可能已严重过时。

可以排除 pg_dynshmem/、pg_notify/、pg_serial/、pg_snapshots/、pg_stat_tmp/、pg_subtrans/ 的内容,但必须保留目录本身;postmaster 启动时会初始化这些内容。

任何以 pgsql_tmp 开头的文件或目录均可排除。启动时文件会删除,目录会按需重建。任何位置的 pg_internal.init 也可排除,它包含恢复时一定会重建的关系缓存。

备份标签文件包含传给 pg_backup_start 的标签、调用时间和起始 WAL 文件名,因此混淆时可查看它,确定备份文件来自哪个备份会话。表空间映射文件包含 pg_tblspc/ 下符号链接的名称及完整目标路径。这些文件不只是参考资料,其存在与内容对恢复正确运行至关重要。

也可以在服务器停止时备份。但此时不能使用 pg_backup_start/pg_backup_stop,必须自行记录每份备份身份,以及相关 WAL 应追溯多远。通常最好遵循上述持续归档流程。

25.3.5 使用持续归档备份恢复

发生严重故障、需要恢复时,按以下步骤进行:

  1. 若服务器仍运行,停止服务器。
  2. 空间允许时,把整个集群数据目录和所有表空间复制到临时位置,以备后用。此举需要足够空间容纳现有数据库的两份副本。空间不足时,至少保存 pg_wal 内容,其中可能有故障前未归档的 WAL。
  3. 删除集群数据目录以及各表空间根目录下的所有现有文件与子目录。
  4. 恢复全量备份时,可直接把数据库文件还原到目标目录。必须恢复正确所有者(数据库系统用户,不能是 root)和权限。使用表空间时,检查 pg_tblspc/ 中的符号链接正确恢复。
  5. 恢复增量备份时,先把该增量备份及其直接、间接依赖的所有较早备份还原到恢复机器的独立目录,不能放入最终运行服务器的目标目录。再用 pg_combinebackup 从全量及后续增量备份抽取数据,将合成的完整备份写入目标目录。同样核实权限与表空间链接。
  6. 删除 pg_wal/ 中现有文件,它们来自文件系统备份,很可能已经过时。若根本没备份该目录,则以正确权限重建;原来使用符号链接时,必须恢复链接。
  7. 若步骤 2 保存了未归档的段,将其复制到 pg_wal/。最好复制而不是移动,万一发生问题需要重来,仍保留未修改的原文件。
  8. 在 postgresql.conf 设置恢复配置,并在集群数据目录创建 recovery.signal。也可暂时修改 pg_hba.conf,在确认恢复成功前阻止普通用户连接。
  9. 启动服务器。服务器进入恢复模式,读取所需归档 WAL。外部错误中止恢复时,可直接重启服务器继续。恢复完成后,服务器删除 recovery.signal,避免日后意外再次进入恢复模式,然后开始正常数据库操作。
  10. 检查数据库,确认已恢复到所需状态;不符合时返回步骤 1。确认正常后,恢复 pg_hba.conf,允许用户连接。

关键是配置恢复方式和终点。必须指定 restore_command,告诉 PostgreSQL 如何取得归档 WAL 段。与归档命令一样,它是 shell 命令字符串,%f 替换为所需 WAL 文件名,%p 替换为复制目的路径。路径相对于集群数据目录,真正的 % 写作 %%。简单示例:

restore_command = 'cp /mnt/server/archivedir/%f %p'

此命令从 /mnt/server/archivedir 复制归档段,也可以使用复杂命令,例如要求操作员装载适当磁带的 shell 脚本。

命令失败时必须返回非零。恢复会请求归档中不存在的文件,遇到这种请求必须返回非零,这本身不是错误。但若命令被信号终止(服务器关闭用的 SIGTERM 除外),或 shell 出错(如找不到命令),恢复会中止,服务器无法启动。

请求的文件不一定都是 WAL 段,也会有 .history 文件。%p 路径的基本文件名与 %f 不同,不可假定可以互换。

归档中找不到的段会到 pg_wal/ 查找,因此能使用较新、尚未归档的段。不过,若归档中存在,优先使用归档文件。

通常恢复会处理所有可用段,把数据库恢复到当前时刻,或现有段允许的最接近时刻。因此正常恢复也会以“找不到文件”的消息结束,具体文字取决于 restore_command。恢复开始时,也可能出现类似 00000001.history 的文件错误。简单恢复情况下,这些都是正常现象,详见时间线章节。

需要恢复到较早时刻,例如初级 DBA 删除主要事务表之前,可指定恢复目标:日期时间、命名还原点,或特定事务 ID 完成后停止。在本版文档撰写时,日期时间和命名还原点更实用,因为没有工具帮助精确判断应使用哪个事务 ID。

注意:终点必须晚于基础备份结束,即 pg_backup_stop 的结束时间。不能使用某份基础备份恢复到该备份进行中的时刻;要恢复到那里,必须使用更早的基础备份再向前重放。

遇到损坏的 WAL 时,恢复在损坏点停止,服务器无法启动。可从头重试,并把目标设在损坏点之前,以便正常完成。因系统崩溃或归档不可访问等外部原因失败时,可直接重启恢复,几乎从失败位置继续。

恢复重启与正常检查点类似:服务器定期把全部状态强制写入磁盘,再更新 pg_control,表明已处理的 WAL 无需重新扫描。

25.3.6 时间线

恢复到过去会带来类似科幻故事中时间旅行和平行宇宙的复杂情况。例如,周二 17:15 删除了关键表,周三中午才发现。使用备份恢复到周二 17:14 后,系统重新运行,在这条历史中从未删除该表。

但后来又想回到原历史的周三上午,如果重新运行时覆盖了原来通往该时刻的 WAL 段,就无法回去。因此必须区分时间点恢复之后生成的 WAL 与原历史中的 WAL。

PostgreSQL 用“时间线”解决这个问题。每次归档恢复完成,都会建立新时间线,标识之后生成的 WAL。时间线 ID 是段文件名的一部分,避免覆盖旧时间线数据。例如 0000000100001234000055CD 开头的 00000001 是十六进制时间线 ID;服务器日志等其他场景通常用十进制显示。

可以归档多个时间线,这往往能挽救数据。例如尚不确定正确恢复时间,需要反复试验寻找最佳分支点;没有时间线会迅速变得难以管理。有时间线,就能恢复到任何较早状态,包括先前放弃的分支中的状态。

每次建立新时间线,PostgreSQL 都创建“时间线历史”文件,记录从哪条线、何时分支。恢复含多时间线的归档时,需要这些文件来选择正确的 WAL,因此它们也像段文件一样归档。

历史文件很小,长期保留成本低且合理;不像庞大的段文件。还可添加注释,记录创建原因和方式,试验导致大量分支时尤其有价值。

默认恢复到归档中的最新时间线。要恢复到基础备份时的时间线,或特定子时间线(即某次恢复尝试之后生成的状态),应把 recovery_target_timeline 设为 current 或目标 ID。不能恢复到在基础备份之前就已分支的时间线。

25.3.7 提示与示例

25.3.7.1 独立热备份

PostgreSQL 备份设施可以生成独立热备份。这类备份不能用于时间点恢复,但通常比 pg_dump 更快地备份和恢复;体积也更大,某些情况下可能抵消速度优势。

与基础备份一样,最简单的方法是 pg_basebackup。调用时加 -X,会自动包含使用该备份所需的全部 WAL,恢复无需特殊处理。

25.3.7.2 压缩归档日志

如果关注归档空间,可使用 gzip:

archive_command = 'gzip < %p > /mnt/server/archivedir/%f.gz'

恢复时则使用 gunzip:

restore_command = 'gunzip < /mnt/server/archivedir/%f.gz > %p'

25.3.7.3 archive_command 脚本

许多人用脚本定义归档命令,使配置保持简洁:

archive_command = 'local_backup_script.sh "%p" "%f"'

归档过程需要多个命令时,建议使用独立脚本,把复杂逻辑留在脚本中,可用 bash、perl 等常见语言。脚本可以处理:

  • 复制到安全的异地存储。
  • 批量传输 WAL,例如每三小时一次,而非逐个传输。
  • 对接其他备份恢复软件。
  • 对接监控软件,报告错误。

提示:使用归档脚本时,建议启用 logging_collector。脚本写入 stderr 的消息会出现在数据库服务器日志中,便于复杂配置失败时诊断。

25.3.8 注意事项

本版文档撰写时,持续归档仍有以下限制,未来版本可能修复:

  • 基础备份期间执行 CREATE DATABASE,随后在备份仍进行时修改该命令复制的模板数据库,恢复时可能把这些修改也传播到新建数据库。这并不理想;为避免风险,基础备份期间最好不要修改任何模板数据库。
  • CREATE TABLESPACE 在 WAL 中记录字面的绝对路径,重放时也使用同一路径创建表空间。在另一台机器重放时可能不合适;即使在同一台机器的新数据目录中重放,也可能危险,因为仍会覆盖原表空间内容。最佳做法是每次创建或删除表空间后,重新进行基础备份。

来源:PostgreSQL 18 官方文档 25.3;当前版本入口。本文翻译对应读取时的版本 18。示例命令保持原样,不代表已执行或适用于所有环境。

PostgreSQL License 版权及许可全文如下:

PostgreSQL Database Management System
(also known as Postgres, formerly as Postgres95)

Portions Copyright © 1996-2026, The PostgreSQL Global Development Group

Portions Copyright © 1994, The Regents of the University of California

Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.

IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容